天天看點

spring的聲明式事務spring的聲明式事務

spring的聲明式事務

什麼是事務?

一組操作,形成一個業務,那麼這組操作要麼都成功,要麼都失敗。保證業務操作完整性一種操作。

示例:比如轉賬,張三賬戶扣錢,和李四賬戶加錢,這兩個操作一定要同時成功。

Spring JdbcTemplate

​ 在spring中為了更加友善的操作JDBC,在JDBC的基礎之上定義了一個抽象層,此設計的目的是為不同類型的JDBC操作提供模闆方法,每個模闆方法都能控制整個過程,并允許覆寫過程中的特定任務,通過這種方式,可以盡可能保留靈活性,将資料庫存取的工作量降到最低。

引入依賴

<!--spring整合第三方ORM架構的包,這個依賴還會同時引入spring-jdbc和spring-tx(事務)的包還有springIoc的基礎jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

<!--        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.6.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.6.RELEASE</version>
            <scope>compile</scope>
        </dependency>-->
           

驅動和資料庫版本對應關系:https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-versions.html

配置連接配接池和JdbcTemplate對象

<!--配置掃描-->
    <context:component-scan base-package="com.blog"/>
    <!--引入外部配置檔案-->
    <context:property-placeholder location="db.properties"/>
    <!--配置連接配接池對象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="driverClassName" value="${mysql.driverClassName}"/>
    </bean>
    <!--配置jdbc-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--具名參數jdbc處理類-->
    <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
        <constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
    </bean>
           

測試方法展示

ClassPathXmlApplicationContext context;

@Before
public void before(){
    context = new ClassPathXmlApplicationContext("classpath:spring-ioc.xml");
}
@Test
public void test1(){
    DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
    System.out.println(dataSource);
}
/*
* jdbcTemplate連接配接測試
* */
@Test
public void test2(){
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    Integer integer = jdbcTemplate.queryForObject("select  count(1) from user", Integer.class);
    System.out.println(integer);
}

/*
 * jdbcTemplate連接配接測試
 * */
@Test
public void test3(){
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    /*
    * 如果和資料庫字段一緻
    * */
    //        User user = jdbcTemplate.queryForObject("select  count(1) from User", new BeanPropertyRowMapper<>(User.class));
    User o = jdbcTemplate.queryForObject("select  * from user where id=1",
            (resultSet, i) -> {
                /*從結果集中擷取資料*/
                User user = new User();
                user.setBalance(resultSet.getInt("BALANCE"));
                user.setId(resultSet.getInt("ID"));
                user.setCardno(resultSet.getString("CARD_NO"));
                return user;
            });
    System.out.println(o);
}

/*
 * 查詢實體list
 * */
@Test
public void test4(){
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);

    List<User> userList = jdbcTemplate.query("select  * from user", new RowMapper<User>() {
        @Override
        public User mapRow(ResultSet resultSet, int i) throws SQLException {
            /*從結果集中擷取資料*/
            User user = new User();
            user.setBalance(resultSet.getInt("BALANCE"));
            user.setId(resultSet.getInt("ID"));
            user.setCardno(resultSet.getString("CARD_NO"));
            return user;
        }
    });
    System.out.println(userList);
}

/*
 * 新增
 * */
@Test
public void test5(){
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    int i = jdbcTemplate.update("insert into user (REAL_NAME, CARD_NO, BALANCE) VALUES (?,?,?)", "李四", "133", "546");
    System.out.println(i);
}
/*
 * 修改
 * */
@Test
public void test6(){
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    int i = jdbcTemplate.update("update user set BALANCE = ? where ID = 3",  "546");
    System.out.println(i);
}


/*
 * 删除
 * */
@Test
public void test7(){
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    int i = jdbcTemplate.update("delete from user where ID =?",  3);
    System.out.println(i);
}

/**
 * 具名參數處理NamedParameterJdbcTemplate
 */
@Test
public void test08(){
    NamedParameterJdbcTemplate jdbcTemplate = context.getBean(NamedParameterJdbcTemplate.class);

    Map<String,Object> map=new HashMap<>();
    map.put("id",2);

    // 修改類同
    User user = jdbcTemplate.queryForObject("select * from user where ID = :id", map, new RowMapper<User>() {
        @Override
        public User mapRow(ResultSet resultSet, int i) throws SQLException {

            /*從結果集中擷取資料*/
            User user = new User();
            // 如果查詢到的資料為0條,傳回null
            if(i == 0){
                return null;
            }
            user.setBalance(resultSet.getInt("BALANCE"));
            user.setId(resultSet.getInt("ID"));
            user.setCardno(resultSet.getString("CARD_NO"));
            return user;
        }
    });

    System.out.println(user);
}
           

dao層使用示例

@Repository
public class UserDaoImpl implements UserDao {
    private JdbcTemplate jdbcTemplate;
    /*
    * JdbcTemplate線程安全的,這以為你可以在多個dao中使用同一個JdbcTemplate的執行個體,當然也可以在同一個dao中定義為私有屬性
    * 官方:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbc-JdbcTemplate-idioms
    * 這個是官方推薦用法。一個dao對應一個JdbcTemplate
    * */
    @Autowired
    public UserDaoImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    }
           

事務四大特性ACID

ACID 四大特性

  • A (Atomicity)原子性:原子性指的是 在一組業務操作下 要麼都成功 要麼都失敗在一組增删改查的業務下 要麼都送出 要麼都復原
  • C(Consistency) 一緻性:事務前後的資料要保證資料的一緻性。由一個一緻性狀态變為另一個一緻性狀态。比如張三給李四轉1000元,但是張三李四總的金額保持不變。
  • I (Isolation)隔離性:多線程情況下,一個事務的執行不應該被另一個事務影響。
  • D (Durability)持久性:事務送出後對資料的改變時永久性的。

總結:在事務控制方面,主要有兩個分類:

程式設計式事務

在代碼中直接加入處理事務的邏輯,可能需要在代碼中顯式調用開啟事務,送出,復原,例、beginTransaction()、commit()、rollback()等事務管理相關的方法

connetion.autoCommit(false);

­­­­­­

­­­­

­­­

connction.commint()

catch(){

connction.rollback();

}

聲明式事務

​ 在方法的外部添加注解或者直接在配置檔案中定義,将事務管理代碼從業務方法中分離出來,以聲明的方式來實作事務管理。spring的AOP恰好可以完成此功能:事務管理代碼的固定模式作為一種橫切關注點,通過AOP方法子產品化,進而實作聲明式事務

spring聲明式事務使用

​ 配置事務管理器和開啟基于注解的事務控制模式

<!--配置事務管理器,由于事務底層操作都是通過連接配接來進行事務開啟,送出復原等操作,是以需要配置資料源-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--開啟基于注解的事務控制模式,基于tx命名空間
xmlns:tx="http://www.springframework.org/schema/tx
如果注解的配置和xml都配置,注解優先

@EnableTransactionManagement 加在配置類上
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
           

​ 在需要的方法上面添加注解 @Transactional

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public User getUser(){
        return userDao.getUser();
    }
    /*
    * 可以标記在類上面(表示所有方法都加上這個注解),也可以标記在方法上面,如果同時有這個注解,以方法上的為準
    * 建議寫在方法上,力度更細
    * 建議下載下傳業務邏輯層。
    * */
    @Override
    @Transactional
    public void trans() {
        userDao.sub();
        System.out.println("張三扣錢完成");
        int i = 1/0;
        userDao.add();

    }
}
           

@Transactional事務屬性配置

isolation:設定事務的隔離級别
    propagation:事務的傳播行為
    noRollbackFor:那些異常事務可以不復原
    noRollbackForClassName:填寫的參數是全類名
    rollbackFor:哪些異常事務需要復原
    rollbackForClassName:填寫的參數是全類名
    readOnly:設定事務是否為隻讀事務  
    timeout:事務超出指定執行時長後自動終止并復原,機關是秒
           

isolation設定事務的隔離級别

​ 用來處理并發事務下的一些問題

/*
    *Isolation設定事務的隔離級别
    * Isolation.DEFAULT 使用資料庫預設的資料庫隔離級别--預設
    * Isolation.READ_UNCOMMITTED   讀未送出
    * Isolation.READ_COMMITTED    讀已送出(不可重複讀)
    * Isolation.REPEATABLE_READ 可重複讀
    * Isolation.SERIALIZABLE 串行話
    * */
    @Override
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void trans() {
    	//修改,删除等多個操作
    }
           
事務隔離級别
事務隔離級别 髒讀 不可重複讀 幻讀
讀未送出(read-uncommitted)
讀已送出(read-committed)
可重複讀(repeatable-read)
串行化(serializable)

髒讀:一個事務有a和b兩個操作,a操作修改了資料庫,這個時候另一個事務讀取到了a操作修改的資料,然後b操作執行失敗,事務復原。

不可重複讀: 事務a讀取資料庫資料,事務b在此過程中修改了資料庫的資料,造成事務a兩次讀取事務結果不一緻。而且不一緻後還可以修改。

可重複讀:事務a讀取資料後,事務b修改事務a讀取的資料後,是以事務a再次擷取資料還是一樣的。

串行話:a和b兩個事務不在同時執行,而是強行控制先後執行

查詢資料庫的預設隔離級别

select @@tx_isolation

程式測試-可重複讀–幻讀:

參考:

https://blog.csdn.net/sanyuesan0000/article/details/90235335?utm_term=mysql%E5%B9%BB%E8%AF%BB%E7%9A%84%E5%BD%B1%E5%93%8D&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allsobaiduweb~default-0-90235335&spm=3001.4430

https://blog.csdn.net/qq_31930499/article/details/110393988

可重複讀下出現幻讀情況測試

mvcc -->多版本并發控制

在《高性能MySQL》中對MVCC的解釋如下

InnoDB的mvcc,是通過在每行記錄後面儲存兩個隐藏的列來實作的。這兩個列,一個儲存了行的建立時間,一個儲存行的過期時間(或删除時間)。當然存儲的并不是實際的時間值,而是系統版本号(system version number),每開始一個新的事務,系統版本号都會自動遞增。事務開始時刻的系統版本号會作為事務的版本号,用來和查詢到的每行記錄的版本号進行比較。下面看一下在REPEATABLE READ隔離級别下, MVCC具體是如何操作的。

SELECT

InnoDB會根據以下兩個條件檢查每行記錄:

a. InnoDB隻查找版本早于目前事務版本的資料行(也就是,行的系統版本号小于或等于事務的系統版本号),這樣可以確定事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的。

b.行的删除版本要麼未定義,要麼大于目前事務版本号。這可以確定事務讀取到的行,在事務開始之前未被删除。隻有符合上述兩個條件的記錄,才能傳回作為查詢結果。

INSERT

InnoDB為新插入的每一行儲存目前系統版本号作為行版本号。

DELETE

InnoDB為删除的每一行儲存目前系統版本号作為行删除辨別。

UPDATE

InnoDB為插入一行新記錄,儲存目前系統版本号作為行版本号,同時儲存目前系紡版本号到原來的行作為行删除辨別。

一下是測試過程

事務a 事務b
T1 select * from user
T2 update user set balance=balance-200
T3 update user set balance=balance-200 insert into user (REAL_NAME, CARD_NO, BALANCE) VALUES (?,?,?)", “李四”, “133”, “546”
T4 commit
T5 select * from user
T6 commit
T7
spring的聲明式事務spring的聲明式事務

測試結果了解:事務a查詢後執行更新操作,由于和事務b更新的是相同的資料,且事務b先執行,是以此時事務a因為鎖的問題陷入等待,是以此時事務b送出後才可以修改,此時事務b修改和新增的資料會标記上對應的版本号,此時事務a修改資料,由于修改時目前讀的操作,會擷取到事務b已經送出的資料,事務b再進行修改後再次更新了所有資料的版本号,包括事務b剛新增的那一條,是以事務a再次查詢會多查出來一條。

propagation事務的傳播特性

​ 事務的傳播特性指的是當一個事務方法被另一個事務方法調用時,這個事務方法

應該如何進行?

希望如果外部存在事務就用外部的, 外部不存在就自己開啟事務

a上開啟的事務叫目前事務,相對于b和c也叫外部事務

a(){
b();
c();
}
           
事務傳播行為 外部不存在事務 外部存在事務 使用場景
REQUIRED(預設) 開啟新的事務 融合到外部事務中 @Transactional(propagation = Propagation.REQUIRED)适用增删改查
SUPPORTS 不開啟新的事務 融合到外部事務中 @Transactional(propagation = Propagation.SUPPORTS)适用查詢
REQUIRES_NEW 開啟新的事務 挂起外部事務,建立新的事務 @Transactional(propagation = Propagation.REQUIRES_NEW)适用内部事務和外部事務不存在業務關聯情況,如日志
NOT_SUPPORTED 不開啟新的事務 挂起外部事務 @Transactional(propagation =Propagation.NOT_SUPPORTED)不常用
NEVER 不開啟新的事務 抛出異常 @Transactional(propagation = Propagation.NEVER )不常用
MANDATORY 抛出異常 融合到外部事務中 @Transactional(propagation = Propagation.MANDATORY)不常用
spring的聲明式事務spring的聲明式事務

timeout事務執行逾時時間

指定事務等待的最長時間(秒)

目前事務通路資料時,有可能通路的資料被别的資料進行加鎖的處理,那麼此時事務就必須等待,如果等待時間過長給使用者造成的體驗感差。

設定事務隻讀(readOnly)

readonly:隻會設定在查詢的業務方法中

connection.setReadOnly(true) 通知資料庫,目前資料庫操作是隻讀,資料庫就會對目前隻讀做相應優化

使用場景:

如果你一次執行單條查詢語句,則沒有必要啟用事務支援,資料庫默

認支援SQL執行期間的讀一緻性;

如果你一次執行多條查詢語句,例如統計查詢,報表查詢,在這種場

景下,多條查詢SQL必須保證整體的讀一緻性,否則,在前條SQL查詢之後,後

條SQL查詢之前,資料被其他使用者改變,則該次整體的統計查詢将會出現讀資料

不一緻的狀态,此時,應該啟用事務支援(如:設定不可重複度、幻影讀級

别)。

異常屬性

設定 目前事務出現的那些異常就進行復原或者送出。

預設對于RuntimeException 及其子類 采用的是復原的政策。

預設對于Exception 及其子類 采用的是送出的政策。

1、設定哪些異常不復原(noRollbackFor)

2、設定哪些異常復原(rollbackFor )

@Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class})

在實戰中事務的使用方式

如果目前業務方法是一組 增、改、删 可以這樣設定事務

@Transactional

如果目前業務方法是一組 查詢 可以這樣設定事務

@Transactionl(readOnly=true)

如果目前業務方法是單個 查詢 可以這樣設定事務

@Transactionl(propagation=propagation.SUPPORTS ,readOnly=true)

基于xml的使用方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置掃描-->
    <context:component-scan base-package="com.blog"/>
    <!--引入外部配置檔案-->
    <context:property-placeholder location="db.properties"/>
    <!--配置連接配接池對象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="driverClassName" value="${mysql.driverClassName}"/>
    </bean>
    <!--配置jdbc-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--具名參數jdbc處理類-->
    <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
        <constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
    </bean>
    <!--配置事務管理器,由于事務底層操作都是通過連接配接來進行事務開啟,送出復原等操作,是以需要配置資料源-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--開啟基于注解的事務控制模式,基于tx命名空間
    xmlns:tx="http://www.springframework.org/schema/tx -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

    <!--聲明式事務通過aop實作,在方法的不同位置,通過連接配接對象開啟,復原,送出事務 -->
    <aop:config>
        <!--比對業務實作層所有類和方法-->
        <aop:pointcut id="transactionCut" expression="execution(* com.blog.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="myAdvice" pointcut-ref="transactionCut"/>
    </aop:config>

    <!--明确切點比對到的方法那些要聲明事務-->
    <tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <!-- 通配符-->
            <tx:method name="update*"/>
            <tx:method name="delete*"/>
            <tx:method name="add*"/>
            <!--配置get開頭的方法為隻讀,且當目前事務不存在時不開啟事務,存在時融入目前事務-->
            <tx:method name="get" read-only="true" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

</beans>
           

繼續閱讀