在Spring Boot應用程序中,事務註解是最常用的註解之一。它可以幫助程序員更好地管理資料庫事務,並提高代碼的可維護性和可測試性。本文將從多個方面對Spring Boot事務註解進行詳細的闡述,包括事務註解回滾、事務註解不生效、事務註解原理、事務註解失效問題、事務註解參數、核心註解等內容。
一、Spring Boot事務註解回滾
Spring Boot事務註解中最常用到的一個功能就是回滾。在默認情況下,當代碼中的一個方法出現異常時,Spring會自動回滾所有對資料庫的修改。例如:
@Service public class UserService { @Autowired private UserRepository userRepository; @Transactional public void saveAll(List userList) { for (User user : userList) { userRepository.save(user); } throw new RuntimeException("出現異常,事務回滾"); } }
上面的代碼中,當saveAll方法被調用時,如果保存到資料庫時出現異常,Spring將自動回滾所有對資料庫的修改。這是因為saveAll方法上被@Transactional註解所標註。
同時,也可以使用@Transactional註解的rollbackFor屬性來指定哪些異常可以觸發回滾:
@Transactional(rollbackFor = Exception.class) public void saveAll(List userList) { for (User user : userList) { userRepository.save(user); } }
上面的代碼中,指定了rollbackFor屬性為Exception.class,表示只要代碼中出現Exception及其子類的異常,都會觸發事務回滾。
二、Spring Boot事物註解不生效
事務註解不生效可能是讓程序員最為頭疼的問題之一。以下列出一些事務註解不生效的原因:
1. 不是public方法
Spring事務默認只對public方法有效,因為只有public方法才能被外部調用。如果使用事務註解來註解一個非public方法,那麼這個事務註解將不會生效。
@Component public class UserService { @Autowired private UserDao userDao; @Transactional protected void save(User user) { userDao.save(user); } }
上面的代碼中,save方法是被protected修飾的。因此,save方法上加的@Transactional註解將無效。
2. 方法內部調用
如果在方法內部調用了另一個帶有@Transactional註解的方法,事務註解將不會生效。這是因為Spring事務是基於AOP來實現的。在方法內部調用另一個方法,會繞過AOP代理。因此如果要使事務註解生效,必須在外部調用方法。
@Service public class UserService { @Autowired private UserDao userDao; @Transactional public void saveAndDelete(User userToDelete, User userToSave) { userDao.delete(userToDelete); save(userToSave);//save方法上有@Transactional註解 } @Transactional public void save(User user) { userDao.save(user); } }
上面的代碼中,如果調用saveAndDelete方法,只有saveAndDelete方法上的@Transactional註解會生效,而save方法上的@Transactional註解將無效。
3. 異常被捕獲
如果在事務方法內部捕獲了異常,並處理了異常,那麼事務將正常提交,不會回滾。
@Autowired private UserDao userDao; @Transactional public void save(User user) { try { userDao.save(user); throw new RuntimeException("出現異常,但已被處理"); } catch (Exception e) { //異常處理 } }
上面的代碼中,出現異常會被捕獲並進行了處理。因此,@Transactional註解將不會回滾事務。
三、Spring Boot事務註解原理
在Spring Boot中,事務註解的原理是基於AOP(面向切面編程)的。也就是說,對於帶有@Transactional註解的方法,在運行時會被動態地生成一個代理類,並將該方法被代理。當執行代理方法時,代理方法會判斷是否有事務正在進行,如果沒有,則開啟一個新事務;如果有,則將該方法的操作放入當前事務中。最後,執行完代理方法時,會判斷是否有異常發生,如果有,則回滾事務,否則提交事務。
四、Spring Boot事務註解失效問題
除了上面提到的事務註解不生效的原因之外,下面列舉一些其他的事務註解失效問題:
1. 異常不聲明拋出類型
聲明拋出的異常類型不能夠衝突,否則事務註解將失效。
@Transactional public void save(User user) throws Exception { userDao.save(user); try { throw new Exception("業務異常"); } catch (Exception e) { throw new Exception("異常"); } }
上面的代碼中,save方法聲明拋出Exception異常。但是,在try-catch中,又拋出了另一個Exception異常。這將導致布滿Spring默認的事務註解失效。
2. Redis註解與事務註解同時使用
在Spring Boot應用程序中,有時會使用Redis來緩存數據。如果在同一個方法中同時使用Redis註解和事務註解,可能會導致事務註解失效。
@Transactional public void saveAndCache(User user) { userDao.save(user); redisTemplate.opsForValue().set(user.getId(), user); }
上面的代碼中,在同一個方法中使用了@Transactional註解和Redis操作,這將導致事務註解失效。
為了解決這個問題,可以使用Spring提供的專門的Redis事務註解。將Redis操作放到一個單獨的方法中,並使用@RedisTransaction註解標註該方法,然後在需要訪問緩存的其他方法中調用該方法:
@Transactional public void saveAndCache(User user) { userDao.save(user); cache(user); } @RedisTransaction public void cache(User user) { redisTemplate.opsForValue().set(user.getId(), user); }
五、Spring Boot事務註解加了沒用
如果我們在代碼中添加了@Transactional註解,但是並沒有看到任何事務被開啟或者提交,我們需要檢查以下幾個事項:
1. 開啟事務的方法名是否正確
在使用註解的時候是通過方法進行控制的,因此,事務註解加在的方法是一定要正確的。如果註解加在了一個不執行的方法上,那麼註解就是毫無作用的。
2. 檢查所在的類是否被代理成功
如果事務代理並沒有對一個類進行代理,那麼在這個類中加上@Transactional也是沒有卵用的。
3. 檢查事務是否已經被提交或回滾
如果事務已經被提交或回滾,那麼為了保證代碼的正確性,事務代理就不會執行任何操作了。在這種情況下,我們可以在日誌中查看是否有相關的記錄,以確定事務是否被提交或回滾。
六、Spring Boot事物註解失效場景
下面列舉一些常見的場景,可能導致Spring Boot事務註解失效:
1. 使用JdbcTemplate訪問資料庫
如果在使用Spring Boot JdbcTemplate訪問資料庫時,沒有使用Spring Boot提供的JdbcTemplate事務註解,可能會導致事務註解失效。
@Autowired private JdbcTemplate jdbcTemplate; @Transactional public void save(User user) { jdbcTemplate.update("insert into user(username, password) values(?, ?)", user.getUsername(), user.getPassword()); }
上面的代碼中,雖然save方法上標註了@Transactional註解,但是jdbcTemplate是通過注入的方式獲得的,沒有使用JdbcTemplate事務註解,因此,事務註解將失效。
2. 多數據源情況下
在多數據源情況下,鐵線事務註解可能會失效。因為事務註解是基於AOP實現的,必須確保所有的操作都在同一個事務中進行。
@Bean(name = "primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "primaryJdbcTemplate") public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean(name = "secondaryJdbcTemplate") public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } @Transactional public void save(User user) { primaryJdbcTemplate.update("insert into user(username, password) values(?, ?)", user.getUsername(), user.getPassword()); secondaryJdbcTemplate.update("insert into user(username, password) values(?, ?)", user.getUsername(), user.getPassword()); }
上面的代碼中,save方法中同時使用了兩個數據源的JdbcTemplate進行訪問。由於每個JdbcTemplate都有一個事務,因此@Transactional註解將失效。
七、Spring Boot事務註解參數
Spring Boot事務註解提供了一些參數,可以用來進一步控制事務的行為:
1. isolation
表示事務的隔離級別。常用的有READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。其中,SERIALIZABLE級別可以最大程度地保證數據的一致性,但是對資料庫性能的影響最大。
@Transactional(isolation = Isolation.READ_COMMITTED) public void updateCUser(User user1, User user2) { //... }
2. timeout
表示事務的超時時間,單位是秒。如果事務在指定的時間內沒有執行完,就會被強制回滾。
@Transactional(timeout = 10) public void save(User user) { //... }
3. readOnly
表示事務是否為只讀。如果為只讀,那麼事務中只能進行查詢操作,不能進行修改操作。這可以提高查詢效率,減少鎖定時間。
@Transactional(readOnly = true) public List getAllUser() { //... }
4. propagation
表示事務的傳播行為。當一個事務方法調用另一個事務方法時,就會使用到傳播行為。常用的有REQUIRED、REQUIRES_NEW、NESTED等。其中,REQUIRED是默認值。REQUIRES_NEW表示如果當前方法已有事務,則會掛起當前事務並重新開啟一個新事務執行;而NESTED則表示如果當前方法已有事務,則嵌套在當前事務中執行。
@Transactional(propagation = Propagation.REQUIRED) public void saveUserAndRole(User user, Role role) { saveUser(user); saveRole(role); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveUser(User user) { //... } @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveRole(Role role) { //... }
八、SpringBoot核心註解
最後,本文列舉了一
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/183739.html