日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

面试官:请列举 Spring 的事务会失效的场景

發(fā)布時(shí)間:2024/1/11 javascript 60 coder
生活随笔 收集整理的這篇文章主要介紹了 面试官:请列举 Spring 的事务会失效的场景 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在日常工作中,如果對(duì) Spring 的事務(wù)管理功能使用不當(dāng),則會(huì)造成 Spring 事務(wù)不生效的問題。而針對(duì) Spring 事務(wù)不生效的問題,也是在跳槽面試中被問的比較頻繁的一個(gè)問題。
今天,我們就一起梳理下有哪些場(chǎng)景會(huì)導(dǎo)致 Spring 事務(wù)失效。

Spring 事務(wù)失效的8中場(chǎng)景

下面就舉例說明這8種失效場(chǎng)景及解決方法

1.使用不支持事務(wù)的存儲(chǔ)引擎

Spring 事務(wù)生效的前提是所連接的數(shù)據(jù)庫要支持事務(wù),如果底層的數(shù)據(jù)庫都不支持事務(wù),則 Spring 的事務(wù)肯定會(huì)失效。例如,如果使用的數(shù)據(jù)庫為 MySQL,并且選用了 MyISAM 存儲(chǔ)引擎,則 Spring 的事務(wù)就會(huì)失效。

解決方法:使用MySQL中的InnoDB存儲(chǔ)引擎就支持事務(wù)

2.拋出檢查異常導(dǎo)致事務(wù)不能正確回滾

以下是一個(gè)示例,演示了拋出檢查異常導(dǎo)致事務(wù)不能正確回滾的情況:

public class UserService {  
  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void saveUser(User user) {  
        try {  
            jdbcTemplate.update("INSERT INTO users (name, age) VALUES (?, ?)", user.getName(), user.getAge());  
            // 拋出檢查異常,事務(wù)將不會(huì)回滾  
            throw new Exception("模擬檢查異常");  
        } catch (Exception e) {  
            // 異常處理邏輯  
            e.printStackTrace();  
        }  
    }  
}

在上面的示例中,saveUser()方法被標(biāo)記為@Transactional,并指定了propagation = Propagation.REQUIRES_NEW傳播行為。這意味著該方法必須在一個(gè)新的事務(wù)中運(yùn)行。如果在執(zhí)行插入操作后拋出了檢查異常(Exception),事務(wù)將不會(huì)回滾。這是因?yàn)闄z查異常是開發(fā)者可以預(yù)見的異常,并且開發(fā)者通過捕獲并處理這些異常來控制程序的流程。因此,事務(wù)管理器不會(huì)回滾事務(wù),以保持?jǐn)?shù)據(jù)庫的一致性。

解決方法:使用運(yùn)行時(shí)異常:在Spring框架中,建議使用RuntimeException或其子類作為事務(wù)方法中拋出的異常。RuntimeException是未檢查異常的子類,因此不會(huì)導(dǎo)致事務(wù)回滾。相反,檢查異常(即那些直接或間接繼承自Exception的異常)會(huì)導(dǎo)致事務(wù)回滾。

3.業(yè)務(wù)方法內(nèi)自己 try-catch導(dǎo)致事務(wù)不能正確回滾


public class UserService {  
  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void saveUser(User user) {  
        try {  
            jdbcTemplate.update("INSERT INTO users (name, age) VALUES (?, ?)", user.getName(), user.getAge());  
            // 模擬拋出異常,但被try-catch捕獲并靜默處理  
            throw new Exception("模擬異常");  
        } catch (Exception e) {  
            // 異常被捕獲并靜默處理,事務(wù)不會(huì)回滾  
            e.printStackTrace();  
        }  
    }  
}

在上面的示例中,saveUser()方法被標(biāo)記為@Transactional,并指定了propagation = Propagation.REQUIRES_NEW傳播行為。這意味著該方法必須在一個(gè)新的事務(wù)中運(yùn)行。在try塊中,我們執(zhí)行了一個(gè)插入操作,然后模擬拋出了一個(gè)異常。這個(gè)異常被catch塊捕獲,并靜默處理(只是打印堆棧跟蹤)。由于異常被靜默處理,事務(wù)不會(huì)回滾。

解決方法: 要避免這種情況,你應(yīng)該確保在事務(wù)方法中捕獲的異常被適當(dāng)?shù)叵蛲鈷伋觯员鉙pring的事務(wù)管理器可以檢測(cè)到異常并回滾事務(wù)。你可以選擇拋出運(yùn)行時(shí)異常或檢查異常,但重要的是要確保異常被正確地傳遞給調(diào)用者,以便于調(diào)試和錯(cuò)誤處理。

4.非public方法導(dǎo)致的事務(wù)時(shí)效

當(dāng)事務(wù)方法被標(biāo)記為非public時(shí),會(huì)導(dǎo)致事務(wù)失效。這是因?yàn)樵赟pring的聲明式事務(wù)管理機(jī)制中,代理類只能代理public方法。如果方法被聲明為非public,代理類無法訪問該方法,從而導(dǎo)致事務(wù)失效。
以下是一個(gè)示例,演示了非public方法導(dǎo)致的事務(wù)失效場(chǎng)景:


public class UserService {  
  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
  
    @Transactional  
    private void saveUser(User user) {  
        jdbcTemplate.update("INSERT INTO users (name, age) VALUES (?, ?)",  user.getName(), user.getAge());  
    }  
}

在上面的示例中,saveUser()方法被聲明為private,導(dǎo)致Spring的代理類無法訪問該方法。因此,事務(wù)失效,并且無法正確地回滾事務(wù)。要解決這個(gè)問題,你可以將方法聲明為public,以確保Spring的代理類可以訪問該方法并正確地管理事務(wù)。
解決方法: 使用public方法

5.@Transactional沒有保證原子行為

當(dāng)事務(wù)方法中存在SELECT方法時(shí),Spring的@Transactional注解無法保證原子性。這是因?yàn)镾ELECT方法不會(huì)阻塞,事務(wù)的原子性僅僅涵蓋INSERT、UPDATE、DELETE、SELECT...FOR UPDATE語句。
以下是一個(gè)示例,演示了@Transactional沒有保證原子行為導(dǎo)致的事務(wù)失效場(chǎng)景:


public class UserService {  
  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
  
    @Transactional  
    public void updateUser(User user) {  
        // SELECT方法不會(huì)阻塞,事務(wù)的原子性無法保證  
        User existingUser = jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", user.getId());  
        // 更新操作  
        jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", user.getName(), user.getId());  
    }  
}

在上面的示例中,事務(wù)方法中包含了一個(gè)SELECT方法,用于查詢用戶信息。然后執(zhí)行了一個(gè)更新操作。由于SELECT方法不會(huì)阻塞,事務(wù)的原子性無法得到保證。如果其他線程在SELECT和UPDATE之間修改了數(shù)據(jù),可能會(huì)出現(xiàn)數(shù)據(jù)不一致的情況。要解決這個(gè)問題,你可以考慮使用其他方式來保證原子性,例如使用數(shù)據(jù)庫鎖或使用Spring的事務(wù)傳播行為。
解決方法:

  1. 使用數(shù)據(jù)庫鎖:通過數(shù)據(jù)庫鎖來保證多個(gè)操作在一個(gè)事務(wù)中的原子性。你可以使用數(shù)據(jù)庫提供的鎖機(jī)制,例如行鎖或表鎖,來確保在事務(wù)中的操作不會(huì)被其他線程干擾。
  2. 修改存儲(chǔ)引擎:將數(shù)據(jù)庫的存儲(chǔ)引擎改為InnoDB,而不是默認(rèn)的MyISAM。InnoDB引擎支持事務(wù),并提供了行級(jí)鎖定和外鍵約束等特性,可以更好地保證數(shù)據(jù)的一致性和完整性。
  3. 使用Spring的事務(wù)傳播行為:通過設(shè)置@Transactional注解的propagation屬性,你可以指定事務(wù)的傳播行為。例如,你可以設(shè)置propagation = Propagation.REQUIRES_NEW,這樣每個(gè)事務(wù)方法都會(huì)運(yùn)行在一個(gè)新的事務(wù)中,確保其原子性。
  4. 修改SELECT語句:將SELECT語句替換為SELECT...FOR UPDATE語句。這樣,在查詢時(shí)會(huì)對(duì)選定的行加鎖,直到事務(wù)結(jié)束時(shí)才會(huì)釋放鎖,從而避免了其他線程的干擾。
  5. 使用同步機(jī)制:在事務(wù)方法中使用同步機(jī)制,確保同一時(shí)間只有一個(gè)線程可以執(zhí)行該方法。這樣可以避免并發(fā)爭(zhēng)搶資源的情況,保證原子性。

6.AOP切面順序?qū)е率聞?wù)不能正確回滾

以下是一個(gè)例子,展示了由于Spring AOP切面順序?qū)е率聞?wù)不能正確回滾的場(chǎng)景:
假設(shè)你有一個(gè)服務(wù)層方法,使用@Transactional注解進(jìn)行事務(wù)管理。在調(diào)用該方法之前,你希望先進(jìn)行日志記錄,以便記錄方法的調(diào)用信息和參數(shù)。因此,你使用了AOP切面來實(shí)現(xiàn)日志記錄功能。

@Service  
public class UserService {  
    @Transactional  
    public void createUser(User user) {  
        // 業(yè)務(wù)邏輯代碼  
    }  
}
@Aspect  
@Component  
public class LoggingAspect {  
    // 定義日志切面  
}  
@Aspect  
@Component  
public class TransactionAspect {  
    // 定義事務(wù)切面  
}

在上述示例中,createUser()方法被標(biāo)記為@Transactional,用于管理事務(wù)。同時(shí),你定義了兩個(gè)切面:日志切面和事務(wù)切面。

日志切面:用于記錄方法的調(diào)用信息和參數(shù)。
事務(wù)切面:用于管理事務(wù)的開始和回滾。

如果在Spring配置中,日志切面在事務(wù)切面前執(zhí)行,那么當(dāng)createUser()方法拋出異常時(shí),日志切面可能會(huì)先捕獲到異常并記錄日志,而事務(wù)切面可能還沒有開始事務(wù)。這樣,事務(wù)切面無法正確地回滾事務(wù),導(dǎo)致數(shù)據(jù)不一致和其他潛在問題。

解決方法: 為了解決這個(gè)問題,你可以在Spring配置中明確指定切面的順序,確保事務(wù)切面在日志切面前執(zhí)行。你可以使用@Order注解或通過XML配置來定義切面的順序。例如:

@Aspect  
@Component  
@Order(1) // 定義日志切面的順序?yàn)?  
public class LoggingAspect {  
    // 定義日志切面邏輯  
}  
  
@Aspect  
@Component  
@Order(2) // 定義事務(wù)切面的順序?yàn)?  
public class TransactionAspect {  
    // 定義事務(wù)切面邏輯  
}

7.調(diào)用本類方法導(dǎo)致傳播行為失效

在Spring事務(wù)中,當(dāng)一個(gè)事務(wù)方法調(diào)用了本類(同一個(gè)類)的其他方法時(shí),可能會(huì)導(dǎo)致事務(wù)的傳播行為失效。
下面是一個(gè)示例場(chǎng)景,展示了由于調(diào)用本類方法導(dǎo)致事務(wù)傳播行為失效的問題:

假設(shè)你有一個(gè)服務(wù)類UserService,其中包含兩個(gè)方法:createUser()updateUser()createUser()方法被標(biāo)記為@Transactional,用于管理事務(wù)。

@Service  
public class UserService {  
  
    @Transactional  
    public void createUser(User user) {  
        // 調(diào)用updateUser()方法  
        updateUser(user);  
    }  
  
    public void updateUser(User user) {  
        // 更新用戶信息的邏輯代碼  
    }  
}

在上述示例中,createUser()方法被標(biāo)記為@Transactional,并調(diào)用了本類的updateUser()方法。這意味著,當(dāng)createUser()方法執(zhí)行時(shí),它應(yīng)該在一個(gè)事務(wù)的上下文中運(yùn)行。

然而,由于updateUser()方法沒有被標(biāo)記為@Transactional,它不會(huì)在事務(wù)的上下文中執(zhí)行。這意味著,如果在updateUser()方法中發(fā)生了異常,事務(wù)不會(huì)回滾,因?yàn)槭聞?wù)的傳播行為失效了。

解決辦法:你可以將需要事務(wù)管理的所有方法都標(biāo)記為@Transactional,或者使用Spring的事務(wù)傳播行為來指定事務(wù)的傳播行為。例如,你可以將@Transactional注解的propagation屬性設(shè)置為Propagation.REQUIRES_NEW,這樣每個(gè)事務(wù)方法都會(huì)運(yùn)行在一個(gè)新的事務(wù)中。

@Service  
public class UserService {  
  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void createUser(User user) {  
        // 調(diào)用updateUser()方法  
        updateUser(user);  
    }  
  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void updateUser(User user) {  
        // 更新用戶信息的邏輯代碼  
    }  
}

8.@Transactional方法導(dǎo)致的synchronized失效

當(dāng)你在Spring中使用@Transactional注解時(shí),Spring會(huì)為你自動(dòng)管理事務(wù)。但是,@Transactional注解并不會(huì)將方法同步化,也就是說,它不會(huì)將方法標(biāo)記為synchronized

以下是一個(gè)示例場(chǎng)景,展示了由于@Transactional方法導(dǎo)致的synchronized失效的場(chǎng)景:

假設(shè)你有兩個(gè)服務(wù)類UserServiceAUserServiceB,它們都包含一個(gè)名為updateUser()的方法,該方法使用synchronized關(guān)鍵字進(jìn)行同步。

@Service  
public class UserServiceA {  
  
    @Transactional  
    public synchronized void updateUser(User user) {  
        // 更新用戶信息的邏輯代碼  
    }  
}  
  
@Service  
public class UserServiceB {  
  
    @Transactional  
    public synchronized void updateUser(User user) {  
        // 更新用戶信息的邏輯代碼  
    }  
}

在上述示例中,updateUser()方法被標(biāo)記為@Transactionalsynchronized。這意味著在多線程環(huán)境中,同一時(shí)間只能有一個(gè)線程調(diào)用該方法。

然而,由于@Transactional注解的存在,Spring會(huì)為每個(gè)事務(wù)創(chuàng)建一個(gè)新的事務(wù)代理對(duì)象。這意味著,當(dāng)兩個(gè)線程同時(shí)調(diào)用UserServiceA.updateUser()UserServiceB.updateUser()方法時(shí),它們實(shí)際上是兩個(gè)不同的方法,而不是同一個(gè)方法的兩個(gè)實(shí)例。因此,盡管方法被標(biāo)記為synchronized,但由于事務(wù)代理的存在,這兩個(gè)方法的同步性失效了。

解決方法:updateUser()方法被標(biāo)記為@Transactional。為了確保同一時(shí)間只有一個(gè)線程執(zhí)行該方法的同步代碼塊,我們使用了一個(gè)同步代碼塊,將this對(duì)象作為鎖對(duì)象。這樣,當(dāng)一個(gè)線程進(jìn)入同步代碼塊時(shí),其他線程將會(huì)被阻塞,直到第一個(gè)線程退出同步代碼塊。

import org.springframework.transaction.annotation.Transactional;  
  
@Service  
public class UserService {  
  
    @Transactional  
    public void updateUser(User user) {  
        // 同步代碼塊,確保同一時(shí)間只有一個(gè)線程執(zhí)行  
        synchronized (this) {  
            // 更新用戶信息的邏輯代碼  
        }  
    }  
}

通過使用同步代碼塊,你可以確保同一時(shí)間只有一個(gè)線程能夠訪問共享資源,即使事務(wù)代理存在,也不會(huì)導(dǎo)致synchronized失效。這種方法適用于簡(jiǎn)單的同步需求,如果你的應(yīng)用有更復(fù)雜的并發(fā)控制需求,可能需要考慮其他同步機(jī)制或數(shù)據(jù)庫鎖等更高級(jí)的解決方案。

總結(jié)

以上是生活随笔為你收集整理的面试官:请列举 Spring 的事务会失效的场景的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。