SpringBoot----SQL資料庫事務處理
一、事務有四個特性:ACID 原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要麼全部完成, 要麼完全不起作用。 一致性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀 態,而不會是部分完成部分失敗。在現實中的資料不應該被破壞。 隔離性(Isolation):可能有許多事務會同時處理相同的資料,因此每個事務都應該與其他事務隔離開來, 防止資料損壞。 永續性(Durability):一旦事務完成,無論發生什麼系統錯誤,它的結果都不應該受到影響,這樣就能從 任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化儲存器中。 二、傳播行為 當事務方法被另一個事務方法呼叫時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運 行,也可能開啟一個新事務,並在自己的事務中執行。 Spring 定義了七種傳播行為: PROPAGATION_REQUIRED
表示當前方法必須執行在事務中。如果當前事務存在,方法將會在該事務中運 行。否則,會啟動一個新的事務,Spring 預設使用 PROPAGATION_SUPPORTS 表示當前方法不需要事務上下文,但是如果存在當前事務的話,那麼該方法會 在這個事務中執行 PROPAGATION_MANDATORY 表示該方法必須在事務中執行,如果當前事務不存在,則會丟擲一個異常 PROPAGATION_REQUIRED_NEW 表示當前方法必須執行在它自己的事務中。一個新的事務將被啟動。如果 存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用 JTATransactionManager 的話,則需要 訪問 TransactionManager PROPAGATION_NOT_SUPPORTED表示該方法不應該執行在事務中。如果存在當前事務,在該方法執行期 間,當前事務將被掛起。如果使用 JTATransactionManager 的話,則需要訪問 TransactionManager PROPAGATION_NEVER 表示當前方法不應該執行在事務上下文中。如果當前正有一個事務在執行,則會拋 出異常 PROPAGATION_NESTED 表示如果當前已經存在一個事務,那麼該方法將會在巢狀事務中執行。巢狀的事務 可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那麼其行為與PROPAGATION_REQUIRED 一樣。注意各廠商對這種傳播行為的支援是有所差異的。可以參考資源管理器的 文件來確認它們是否支援巢狀事務 三、隔離級別 隔離級別定義了一個事務可能受其他併發事務影響的程度。 ISOLATION_DEFAULT使用後端資料庫預設的隔離級別,Spring 預設使用,mysql 預設的隔離級別為: Repeatable Read(可重複讀) ISOLATION_READ_UNCOMMITTED 讀未提交,最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致 髒讀、幻讀或不可重複讀 ISOLATION_READ_COMMITTED 讀已提交,允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀 或不可重複讀仍有可能發生 ISOLATION_REPEATABLE_READ 可重複讀,對同一欄位的多次讀取結果都是一致的,除非資料是被本身事 務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生 ISOLATION_SERIALIZABLE 可序列化,最高的隔離級別,完全服從 ACID 的隔離級別,確保阻止髒讀、不可 重複讀以及幻讀,也是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的資料庫表來實現的 髒讀(Dirty reads)——髒讀發生在一個事務讀取了另一個事務改寫但尚未提交的資料時。如果改寫再 稍後被回滾了,那麼第一個事務獲取的資料就是無效的。 不可重複讀(Nonrepeatable read)——不可重複讀發生在一個事務執行相同的查詢兩次或兩次以上,但 是每次都得到不同的資料時。這通常是因為另一個併發事務在兩次查詢期間進行了更新。 幻讀(Phantom read)——幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行資料,接著另一 個併發事務(T2)插入了一些資料時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在 的記錄。 四、屬性說明 @Transactional a、isolation:用於指定事務的隔離級別。預設為底層事務的隔離級別。 b、noRollbackFor:指定遇到指定異常時強制不回滾事務。 c、noRollbackForClassName:指定遇到指定多個異常時強制不回滾事務。該屬性可以指定多個異常類 名。 d、propagation:指定事務的傳播屬性。 e、readOnly:指定事務是否只讀。表示這個事務只讀取資料但不更新資料,這樣可以幫助資料庫引擎優 化事務。若真的是一個只讀取的資料庫應設定 readOnly=true f、rollbackFor:指定遇到指定異常時強制回滾事務。 g、rollbackForClassName:指定遇到指定多個異常時強制回滾事務。該屬性可以指定多個異常類名。 h、timeout:指定事務的超時時長。 注意: 1.mysql 為例,儲存引擎不能使用 MyISAM,應該使用 InnoDB
dao介面層:
package com.nyist.transation.dao; import com.nyist.transation.model.RoncooUser; public interface RoncooUserDao { int insert(RoncooUser roncooUser); int deleteById(int id); int updateById(RoncooUser roncooUser); RoncooUser selectById(int id); }
package com.nyist.transation.dao; import com.nyist.transation.model.RoncooUserLog; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; public interface RoncooUserLogDao extends JpaRepository<RoncooUserLog, Integer> { /** * @param string * @return */ @Query(value = "select u from RoncooUserLog u where u.userName=?1") List<RoncooUserLog> findByUserName(String userName); /** * @param string * @param string2 * @return */ List<RoncooUserLog> findByUserNameAndUserIp(String string, String string2); /** * @param exampl * @param pageable * @return */ Page<RoncooUserLog> findByUserName(String userName, Pageable pageable); }
daoImpl 實現類:
package com.nyist.transation.dao.Impl; import com.nyist.transation.dao.RoncooUserDao; import com.nyist.transation.model.RoncooUser; import com.nyist.transation.util.base.JdbcDaoImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class RoncooUserDaoImpl extends JdbcDaoImpl implements RoncooUserDao { //JdbcTemplate 是 SpringBoot 特有的一個包 支援對資料庫的原生開發 @Autowired private JdbcTemplate jdbcTemplate; @Override public int insert(RoncooUser roncooUser) { String sql = "insert into roncoo_user(name, create_time) values (?, ?)"; return jdbcTemplate.update(sql, roncooUser.getName(),roncooUser.getCreateTime()); } @Override public int deleteById(int id) { String sql = "delete from roncoo_user where id=?"; return jdbcTemplate.update(sql, id); } @Override public int updateById(RoncooUser roncooUser) { String sql = "update roncoo_user set name=?, create_time=? where id=?"; return jdbcTemplate.update(sql, roncooUser.getName(), roncooUser.getCreateTime(), roncooUser.getId()); } @Override public RoncooUser selectById(int id) { String sql = "select * from roncoo_user where id=?"; /*RoncooUser roncooUser = jdbcTemplate.queryForObject(sql, new RowMapper<RoncooUser>() { @Override public RoncooUser mapRow(ResultSet rs, int rowNum) throws SQLException { RoncooUser roncooUser = new RoncooUser(); roncooUser.setId(rs.getInt("id")); roncooUser.setName(rs.getString("name")); roncooUser.setCreateTime(rs.getDate("create_time")); return roncooUser; } }, id);*/ return queryForObject(sql,RoncooUser.class,id); } }
測試類:
package com.nyist.transation; import com.nyist.transation.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class TransationApplicationTests { @Autowired private UserService userService; @Test public void register() { String result = userService.register("無境", "192.168.1.1"); System.out.println(result); } }
Service 業務層:
package com.nyist.transation.service; import com.nyist.transation.dao.RoncooUserDao; import com.nyist.transation.dao.RoncooUserLogDao; import com.nyist.transation.model.RoncooUser; import com.nyist.transation.model.RoncooUserLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; @Service public class UserService { @Autowired private RoncooUserDao roncooUserDao; @Autowired private RoncooUserLogDao roncooUserLogDao; /** * 使用者註冊 * * @return*/ /** * * Transactional 開始事務的註解 */ @Transactional public String register(String name, String ip) { // 1.新增使用者 RoncooUser roncooUser = new RoncooUser(); roncooUser.setName(name); roncooUser.setCreateTime(new Date()); roncooUserDao.insert(roncooUser); // 測試使用 boolean flag = true; if (flag) { throw new RuntimeException(); } // 2.添加註冊日誌 RoncooUserLog roncooUserLog = new RoncooUserLog(); roncooUserLog.setUserName(name); roncooUserLog.setUserIp(ip); roncooUserLog.setCreateTime(new Date()); roncooUserLogDao.save(roncooUserLog); return "success"; } }
主體架構圖:
在執行之前我們先看一下資料庫中表的資料:
測試類測的資料程式程式,執行結果如下:
之所以儲存是應為我們在Service 業務層派出了一個new RuntimeException()異常,我們看一下資料庫中的資料是否有變動,結果如下:
資料沒有變化,說明我們使用@Transactional 事務註解起作用了,保準了資料的原子性。
我們將自己拋異常的地方註釋掉,然後再次執行檢視資料庫中表的結果,如下:
可以看到資料已經插入到表中去了。
我們將@TranTransaction 註解註釋掉,主動拋一個new RuntimeException()異常,檢視結果如下:
2018-11-05 20:34:32.994 |-DEBUG [main] org.springframework.jdbc.core.JdbcTemplate [860] -| Executing prepared SQL update 2018-11-05 20:34:32.995 DEBUG 40924 --- [ main] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [insert into roncoo_user(name, create_time) values (?, ?)] 2018-11-05 20:34:32.995 |-DEBUG [main] org.springframework.jdbc.core.JdbcTemplate [609] -| Executing prepared SQL statement [insert into roncoo_user(name, create_time) values (?, ?)] java.lang.RuntimeException at com.nyist.transation.service.UserService.register(UserService.java:36) at com.nyist.transation.TransationApplicationTests.register(TransationApplicationTests.java:19) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74) at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
檢視資料表中的結果:
可以看出第一個表RoncooUser表中插入了一條資料,而RoncooUserLog表中沒有插入新得資料,這足夠說明@Transaction 可以新增事務 保證事務的ACID屬性