Spring的事務管理
事務是什麼
是用戶定義的一組數據庫操作的集合,要麼全成功,要麼全失敗
事務特徵
特徵 | 含義 |
---|---|
原子性 | 事務是不可分割的一組操作要麼全做,要麼全不做 |
一致性 | 數據庫中數據從一個一致的狀態轉爲另一個一致的狀態 |
隔離性 | 不同的事務之間互不影響 |
持久性 | 事務一旦提交對數據的影響是持久的 |
事務隔離級別
查看MySQL默認隔離級別
SHOW VARIABLES LIKE ‘%tx_isolation%’
級別類型
類型 | 概念 |
---|---|
讀未提交 | 事務A和B,事務A未提交的數據,事務B可以讀到。讀取到的數據是髒數據 |
讀已提交 | 事務A提交的數據,事務B才能讀取到。避免了髒讀 |
可重複讀 | 直到事務A結束前,一直能讀到相同的數據。避免了髒讀和不可重複讀(MySQL默認隔離級別) |
串行化 | 不允許讀寫併發操作,是最高隔離級別,寫時讀必須等待,效率低。避免幻讀 |
事務併發問題
前提
不考慮隔離級別引發的安全問題
併發問題
併發問題 | 含義 |
---|---|
髒讀 | A事務讀取到B事務未提交的數據(只要沒提交就是髒讀),因爲A事務可能回滾 |
不可重複讀 | A事務多次讀取某數據,期間B事務對該數據做了更改並已提交,導致多次讀的數據不一致UPDATE、DELETE |
幻讀 | 事務A讀取到了另外一個事務已經提交的數據,導致多次查詢結果不一致INSERT |
Spring事務管理
- 編程式事務:將事務管理代碼嵌入到業務方法中來控制事務提交和回滾
- 聲明式事務:將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理(思想是AOP)
事務管理的核心接口
- Spring針對不同的持久層框架,提供接口不同的實現類。
- 事務管理器:PlatformTransactionManager
– getTranscation:獲取事務狀態
– commit:提交事務
– rollback:回滾事務 - Spring並不是直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委託給了Mybatis或者其他框架來實現事務管理
Spring中的聲明式事務管理方式
基於XML的事務管理
步驟
- 配置事務管理器DataSourceTransactionManager,配置數據源
- 配置事務增強(設置事務操作的方法匹配)
- 配置切面(配置切入點,配置切面advice-ref,pointcut-ref)
通過在配置文件中配置事務相關的規則
AccountMapper.java
public interface AccountMapper {
// 轉出
int outMoney(@Param("username")String username,@Param("money")int money);
// 轉入
int inMoney(@Param("username")String username,@Param("money")int money);
}
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hpe.mapper.AccountMapper">
<!-- 轉出 -->
<update id="outMoney">
UPDATE account SET money = money-#{money} WHERE username=#{username}
</update>
<!-- 轉入 -->
<update id="inMoney">
UPDATE account SET money = money+#{money} WHERE username=#{username}
</update>
</mapper>
AccountService.java
public interface AccountService {
/**
* 轉賬的方法
* @param out 轉出人
* @param in 轉入人
* @param money 轉賬金額
*/
void updateAccount(String out,String in,int money);
}
AccountServiceImpl.java
package com.hpe.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hpe.mapper.AccountMapper;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
/* 實現轉賬的操作:
* 1.編程式事務
* 2.聲明式事務
*/
@Autowired
private AccountMapper accountMapper;
@Override
public void updateAccount(String out, String in, int money) {
// 轉出
accountMapper.outMoney(out, money);
// 模擬異常(算數異常)
//int i = 10/0;
// 轉入
accountMapper.inMoney(in, money);
}
}
applicationContext.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 開啓註解掃描 -->
<context:component-scan base-package="com.hpe"></context:component-scan>
<!-- 加載外部資源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 通過Spring管理SqlSessionFactory mapper接口 -->
<!-- 配置SqlSessionFactory爲了使用 Spring來管理SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置mybatis核心配置文件的路徑 -->
<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
<!-- 指定數據源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 指定批量創建別名的包 -->
<property name="typeAliasesPackage" value="com.hpe.po"></property>
</bean>
<!-- 批量創建Mapper接口實現類的Bean,可以不指定id
默認Bean是由id的,是Mapper接口的名稱且首字母小寫-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定需要創建實現類的Mapper接口所在的包
可以指定多個包,使用逗號隔開即可-->
<property name="basePackage" value="com.hpe.mapper"></property>
<!-- 需要指定SqlSessionFactory,使用sqlSessionFactoryBeanName
通過這種方式就可以等到Spring初始化完成後,再去構造SqlSessionFactory,否則無法連接數據庫 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- Spring聲明式事務管理:XML方式 -->
<!-- 1.配置事務管理器(XML及註解方式均需配置) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事務增強:需要編寫切入點及細節 -->
<!-- propagation:事務的傳播行爲
REQUIRED:必須的,如果有事務則使用,沒有則創建
SUPPORTS:可選的,如果有事務則使用,沒有不使用
isolation:事務的隔離級別
DEFAULT:mysql默認隔離級別(可重複讀)
讀未提交
讀已提交
可串行化:可避免幻讀
rollback-for:回滾(對哪寫異常回滾)
no-rollback-for:不回滾(對哪些異常不回滾)
注意: 如果不設置默認的話,是對運行時異常進行回滾
read-only:如果將select設置爲true,則告訴數據庫引擎查詢是隻讀的。
timeout=-1:過期時間(設置多長時間不回滾)
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 設置進行事務操作方法匹配的規則(規範程序員的命名規則) -->
<tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 3.配置切面 -->
<aop:config>
<!-- 配置切入點 -->
<aop:pointcut expression="execution(* com.hpe.service.*.*(..))" id="pointcut1"/>
<!-- 配置切面(應用增強)
advice-ref:使用哪個增強
pointcut-ref:將增強應用到哪個切入點上面(也可以使用pointcut指定相應的表達式即可)
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
</beans>
Test.java
public class SpringTest {
// 使用事務模擬轉賬
@Test
public void test1(){
// 1.加載Spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.創建UserService對象
AccountService accountService = context.getBean(AccountService.class);
// 3.調用mapper中的方法
accountService.updateAccount("張三", "李四", 100);
}
}
基於註解的事務管理
步驟
- 配置事務管理器DataSourceTransactionManager,配置數據源
- 配置事務相關的註解
- 在使用事務的方法或者類上面添加註解
代碼實戰
AccountMapper.java、AccountMapper.xml、AccountService.java相比於上面並未改動(此處不再貼重複代碼)
AccountServiceImpl2.java
package com.hpe.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.hpe.mapper.AccountMapper;
@Service("accountService2")
// @Transactional:表示事務的設置對這個類的所有方法都起作用
//@Transactional
public class AccountServiceImpl2 implements AccountService {
@Autowired
private AccountMapper accountMapper;
// @Transactional:表示事務的設置只對這個方法起作用
@Transactional
@Override
public void updateAccount(String out, String in, int money) {
// 轉出
accountMapper.outMoney(out, money);
// 模擬異常(算數異常)
//int i = 10/0;
// 轉入
accountMapper.inMoney(in, money);
}
}
applicationContext2.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 開啓註解掃描 -->
<context:component-scan base-package="com.hpe"></context:component-scan>
<!-- 加載外部資源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 通過Spring管理SqlSessionFactory mapper接口 -->
<!-- 配置SqlSessionFactory爲了使用 Spring來管理SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置mybatis核心配置文件的路徑 -->
<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
<!-- 指定數據源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 指定批量創建別名的包 -->
<property name="typeAliasesPackage" value="com.hpe.po"></property>
</bean>
<!-- 批量創建Mapper接口實現類的Bean,可以不指定id
默認Bean是由id的,是Mapper接口的名稱且首字母小寫-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定需要創建實現類的Mapper接口所在的包
可以指定多個包,使用逗號隔開即可-->
<property name="basePackage" value="com.hpe.mapper"></property>
<!-- 需要指定SqlSessionFactory,使用sqlSessionFactoryBeanName
通過這種方式就可以等到Spring初始化完成後,再去構造SqlSessionFactory,否則無法連接數據庫 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- Spring聲明式事務管理:註解方式 -->
<!-- 1.配置事務管理器(XML及註解方式均需配置) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.註冊事務註解驅動
transaction-manager:自動檢測事務處理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
測試方法
@Test
public void test2(){
// 1.加載Spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
// 2.創建UserService對象
AccountService accountService = context.getBean("accountService2",AccountService.class);
// 3.調用mapper中的方法
accountService.updateAccount("張三", "李四", 100);
}