1. 程式人生 > >SpringBoot----SQL資料庫事務處理

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屬性