Spring事務
一、事務簡介
具體的看資料庫中關於事務的知識點,這裡做一個大概
1. 什麼是事務?
講mysql的時候,提出了事務。 事務是指一組sql語句的集合, 集合中有多條sql語句可能是insert , update ,select ,delete, 我們希望這些多個sql語句都能成功,或者都失敗, 這些sql語句的執行是一致的,作為一個整體執行。
2. 在什麼時候想到使用事務
當我的操作,涉及得到多個表,或者是多個sql語句的insert,update,delete。需要保證這些語句都是成功才能完成我的功能,或者都失敗,保證操作是符合要求的
在java程式碼中寫程式,控制事務,此時事務應該放在那裡呢?
service類的業務方法上,因為業務方法會調用多個dao方法,執行多個sql語句
3. 通常使用JDBC訪問資料庫, 還是mybatis訪問資料庫怎麼處理事務
jdbc訪問資料庫,處理事務 Connection conn ; conn.commit(); conn.rollback();
mybatis訪問資料庫,處理事務, SqlSession.commit(); SqlSession.rollback();
hibernate訪問資料庫,處理事務, Session.commit(); Session.rollback();
4. 問題中事務的處理方式,有什麼不足
- 不同的資料庫訪問技術,處理事務的對象,方法不同,需要了解不同資料庫訪問技術使用事務的原理
- 掌握多種資料庫中事務的處理邏輯。什麼時候提交事務,什麼時候回顧事務
- 處理事務的多種方法。
總結: 就是多種資料庫的訪問技術,有不同的事務處理的機制,對象,方法
5. 怎麼解決不足
spring提供一種處理事務的統一模型, 能使用統一步驟,方式完成多種不同資料庫訪問技術的事務處理。
使用spring的事務處理機制,可以完成mybatis訪問資料庫的事務處理
使用spring的事務處理機制,可以完成hibernate訪問資料庫的事務處理。
6. 處理事務,需要怎麼做,做什麼
spring處理事務的模型,使用的步驟都是固定的。把事務使用的資訊提供給spring就可以了
-
事務內部提交,回滾事務,使用的事務管理器對象,代替你完成commit,rollback事務管理器是一個介面和他的眾多實現類。
介面:PlatformTransactionManager ,定義了事務重要方法 commit ,rollback
實現類:spring把每一種資料庫訪問技術對應的事務處理類都創建好了。
mybatis訪問資料庫—spring創建好的是DataSourceTransactionManager
hibernate訪問資料庫—-spring創建的是HibernateTransactionManager怎麼使用:你需要告訴spring 你用是那種資料庫的訪問技術,怎麼告訴spring呢?
聲明資料庫訪問技術對於的事務管理器實現類, 在spring的配置文件中使用聲明就可以了
例如,你要使用mybatis訪問資料庫,你應該在xml配置文件中
<bean id=「xxx” class=”…DataSourceTransactionManager”> -
你的業務方法需要什麼樣的事務,說明需要事務的類型。
1. 說明方法需要的事務:
事務的隔離級別:有4個值。
DEFAULT:採用 DB 默認的事務隔離級別。MySql 的默認為 REPEATABLE_READ; Oracle默認為 READ_COMMITTED。
➢ READ_UNCOMMITTED:讀未提交。未解決任何並發問題。
➢ READ_COMMITTED:讀已提交。解決臟讀,存在不可重複讀與幻讀。
➢ REPEATABLE_READ:可重複讀。解決臟讀、不可重複讀,存在幻讀
➢ SERIALIZABLE:串列化。不存在並發問題。- 事務的超時時間: 表示一個方法最長的執行時間,如果方法執行時超過了時間,事務就回滾
- 單位是秒, 整數值, 默認是 -1.
- 事務的傳播行為 : 控制業務方法是不是有事務的, 是什麼樣的事務的, 7個傳播行為,表示你的業務方法調用時,事務在方法之間是如果使用的,記得前三個就行
- PROPAGATION_REQUIRED
- PROPAGATION_REQUIRES_NEW
- PROPAGATION_SUPPORTS
- PROPAGATION_MANDATORY
- PROPAGATION_NESTED
- PROPAGATION_NEVER
- PROPAGATION_NOT_SUPPORTED
- 事務提交事務,回滾事務的時機
- 事務的超時時間: 表示一個方法最長的執行時間,如果方法執行時超過了時間,事務就回滾
1. 當你的業務方法,執行成功,沒有異常拋出,當方法執行完畢,spring在方法執行後提交事務。事務管理器commit
2. 當你的業務方法拋出運行時異常或ERROR, spring執行回滾,調用事務管理器的rollback
運行時異常的定義: RuntimeException 和他的子類都是運行時異常, 例如NullPointException , NumberFormatException
3. 當你的業務方法拋出非運行時異常, 主要是受查異常時,提交事務
受查異常:在你寫程式碼中,必須處理的異常。例如IOException, SQLException
7. 總結spring的事務
- 管理事務的是 事務管理和他的實現類
- spring的事務是一個統一模型
-
指定要使用的事務管理器實現類,使用<bean>
-
指定哪些類,哪些方法需要加入事務的功能
-
指定方法需要的隔離級別,傳播行為,超時
你需要告訴spring,你的項目中類資訊,方法的名稱,方法的事務傳播行為
二、程式舉例環境搭建
購買商品,用戶下單,向銷售表中添加銷售記錄,從商品表中減少數據
1. 創建數據表
創建數據表sale(銷售表)和goods(商品表)
sale:id自增,方便後面測試
goods:id不自增,
注意,平時開發price用decimal類型
向goods表示適當的添加兩天數據,用於測試
2. maven依賴pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.md</groupId>
<artifactId>07-spring-trans</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- 單元測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--spring核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring事務用到的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis的-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis和spring集成的-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--德魯伊,資料庫連接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<build>
<!--目的是把src/main/java目錄中的xml文件包含到輸出結果中,也就是輸出到classes目錄中-->
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目錄-->
<includes><!--包括目錄下的.properties,.xml 文件都會掃描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
3. 創建實體類
package com.md.domain;
/**
* @author MD
* @create 2020-08-11 9:20
*/
public class Sale {
private Integer id;
private Integer gid;
private Integer nums;
public Sale() {
}
public Sale(Integer id, Integer gid, Integer nums) {
this.id = id;
this.gid = gid;
this.nums = nums;
}
public void setId(Integer id) {
this.id = id;
}
public void setGid(Integer gid) {
this.gid = gid;
}
public void setNums(Integer nums) {
this.nums = nums;
}
public Integer getId() {
return id;
}
public Integer getGid() {
return gid;
}
public Integer getNums() {
return nums;
}
@Override
public String toString() {
return "SaleDao{" +
"id=" + id +
", gid=" + gid +
", nums=" + nums +
'}';
}
}
//-----------------
package com.md.domain;
/**
* @author MD
* @create 2020-08-11 9:21
*/
public class Goods {
private Integer id;
private String name;
private Integer amount;
//實際開發中不用float
private Float price;
public Goods() {
}
public Goods(Integer id, String name, Integer amount, Float price) {
this.id = id;
this.name = name;
this.amount = amount;
this.price = price;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"id=" + id +
", name='" + name + '\'' +
", amount=" + amount +
", price=" + price +
'}';
}
}
//--------------------------------
4. 定義dao介面
package com.md.dao;
import com.md.domain.Sale;
/**
* @author MD
* @create 2020-08-11 9:24
*/
public interface SaleDao {
// 增加銷售記錄
int insertSale(Sale sale);
}
//----------------------
package com.md.dao;
import com.md.domain.Goods;
/**
* @author MD
* @create 2020-08-11 9:30
*/
public interface GoodsDao {
// 更新庫存
int updateGoods(Goods goods);
// 查詢商品的資訊,根據id
Goods selectGoods(Integer id);
}
5. 定義dao介面對應的sql映射文件
SaleDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.md.dao.SaleDao">
<insert id="insertSale">
insert into sale(gid,nums) values(#{gid},#{nums})
</insert>
</mapper>
GoodsDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.md.dao.GoodsDao">
<select id="selectGoods" resultType="com.md.domain.Goods">
select id , name , amount , price from goods where id=#{id}
</select>
<!-- -->
<update id="updateGoods">
update goods set amount = amount - #{amount} where id=#{id}
</update>
</mapper>
6. 定義異常類
定義service層可能拋出的異常類
package com.md.excep;
/**
* @author MD
* @create 2020-08-11 9:49
*/
// 自定義的運行時異常
public class NotEnoughException extends RuntimeException {
public NotEnoughException() {
super();
}
public NotEnoughException(String message) {
super(message);
}
}
7. 定義service介面及實現類
package com.md.service;
/**
* @author MD
* @create 2020-08-11 9:43
*/
public interface BuyGoodsService {
// 購買商品,goodsId:購買商品的編號,nums:購買的數量
void buy(Integer goodsId , Integer nums);
}
//-----------------------------------
package com.md.service.impl;
import com.md.dao.GoodsDao;
import com.md.dao.SaleDao;
import com.md.domain.Goods;
import com.md.domain.Sale;
import com.md.excep.NotEnoughException;
import com.md.service.BuyGoodsService;
/**
* @author MD
* @create 2020-08-11 9:45
*/
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=========buy方法開始===========");
// 記錄銷售記錄,向sale表中添加數據
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
// 先查詢該商品
Goods goods = goodsDao.selectGoods(goodsId);
if (goods == null){
// 商品不存在
throw new NullPointerException("編號:"+goodsId+" 的商品不存在");
}else if (goods.getAmount() < nums){
// 商品庫存不足
throw new NotEnoughException("編號:"+goodsId+" 的商品不足,只能購買: "+goods.getAmount()+" 個");
}
// 更新庫存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=========buy方法結束===========");
}
}
8. mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"//mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--settings:控制mybatis全局行為-->
<settings>
<!--設置mybatis輸出日誌-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--設置別名-->
<typeAliases>
<!--name:實體類所在的包名-->
<package name="com.md.domain"/>
</typeAliases>
<!-- sql映射文件的位置 -->
<mappers>
<!--name是包名,這個包中所有mapper.xml一次載入-->
<package name="com.md.dao"/>
</mappers>
</configuration>
9. Spring配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:context="//www.springframework.org/schema/context"
xsi:schemaLocation="//www.springframework.org/schema/beans
//www.springframework.org/schema/beans/spring-beans.xsd //www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd">
<!--
把資料庫的配置資訊寫在一個獨立的文件中,編譯修改資料庫的配置內容
讓spring知道jdbc.properties文件的位置
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--聲明數據源DataSource,作用是連接資料庫-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--set注入提供連接資料庫資訊-->
<!--<property name="url" value="jdbc:mysql://localhost:3306/ssm" />-->
<!--<property name="username" value="root" />-->
<!--<property name="password" value="123456" />-->
<!--<property name="maxActive" value="20" />-->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${jdbc.maxActive}" />
</bean>
<!--SqlSessionFactory-->
<!--聲明的是mybatis中提供的SqlSessionFactoryBean類,這個類內部創建SqlSessionFactory-->
<bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--set注入,把資料庫連接池付給dataSource屬性-->
<property name="dataSource" ref="myDataSource"/>
<!--mybatis主配置文件的位置
configLocation屬性是Resource類型,讀取配置文件
它的賦值使用的是value , 指定文件的路徑,使用的是classpath:表示文件的位置
-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--創建 dao對象
使用SqlSession的getMapper(StudentDao.class)
MapperScannerConfigurer在內部調用getMapper()生成每個dao介面的代理對象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定的是SqlSessionFactory對象的id-->
<property name="sqlSessionFactoryBeanName" value="SqlSessionFactory"/>
<!--指定包名,包名是dao介面所在的包名
MapperScannerConfigurer會掃描這個包中的所有介面,把每個介面都執行
一次getMapper()方法,得到每個介面的dao對象
創建好的dao對象放入到spring的容器中
dao默認對象的名稱:是介面名字的首字母小寫
-->
<property name="basePackage" value="com.md.dao"/>
<!--多個包-->
<!--<property name="basePackage" value="com.md.dao,com.md.dao2"/>-->
</bean>
<!--上面的這個是一個模板,只有最後dao對象的這個包名的value的值是根據自己創建寫的-->
<!--下面的就是自己定義的service-->
<!--聲明service-->
<bean id="buyGoodsService" class="com.md.service.impl.BuyGoodsServiceImpl">
<!--就是上面通過創建的dao對象,在service介面的實現類中使用-->
<property name="saleDao" ref="saleDao"/>
<property name="goodsDao" ref="goodsDao"/>
</bean>
</beans>
10. 測試
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 從容器中獲取service,你聲明service時候的id
BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
buyGoodsService.buy(1001,101);
}
此時程式一切正常
三、使用 Spring 的事務註解管理事務
通過@Transactional 註解方式,可將事務織入到相應 public 方法中,實現事務管理
主要記住前三個就行了
@Transactional 的所有可選屬性如下所示:
- propagation:用於設置事務傳播屬性。該屬性類型為 Propagation 枚舉,默認值為Propagation.REQUIRED
- isolation:用於設置事務的隔離級別。該屬性類型為 Isolation 枚舉,默認值為Isolation.DEFAULT。
- readOnly:用於設置該方法對資料庫的操作是否是只讀的。該屬性為 boolean,默認值為 false。
- timeout:用於設置本操作與資料庫連接的超時時限。單位為秒,類型為 int,默認值為-1,即沒有時限。
- rollbackFor:指定需要回滾的異常類。類型為 Class[],默認值為空數組。當然,若只有一個異常類時,可以不使用數組。
- rollbackForClassName:指定需要回滾的異常類類名。類型為 String[],默認值為空數組。當然,若只有一個異常類時,可以不使用數組
- noRollbackFor:指定不需要回滾的異常類。類型為 Class[],默認值為空數組。當然,若只有一個異常類時,可以不使用數組。
- noRollbackForClassName:指定不需要回滾的異常類類名。類型為 String[],默認值為空數組。當然,若只有一個異常類時,可以不使用數組。
需要注意的是,
@Transactional 若用在方法上,只能用於 public 方法上
對於其他非 public方法,如果加上了註解@Transactional,雖然 Spring 不會報錯,但不會將指定事務織入到該方法中。因為 Spring 會忽略掉所有非 public 方法上的@Transaction 註解。
若@Transaction 註解在類上,則表示該類上所有的方法均將在執行時織入事務。
實現註解的事務步驟:
把上面寫好的程式重新複製一份,內容不變
1. 聲明事務管理器
還是在Spring的配置文件中加入
<!--1. 聲明事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 連接資料庫,指定數據源-->
<property name="dataSource" ref="myDataSource"/>
</bean>
2. 開啟註解驅動
<!--2. 開啟事務註解驅動,告訴spring使用註解管理事務,創建代理對象 -->
<!--注意:選擇結尾是tx的driven-->
<!--transaction-manager:事務管理器對象的id-->
<tx:annotation-driven transaction-manager="transactionManager"/>
3. 完整Spring配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:context="//www.springframework.org/schema/context" xmlns:tx="//www.springframework.org/schema/tx"
xsi:schemaLocation="//www.springframework.org/schema/beans
//www.springframework.org/schema/beans/spring-beans.xsd //www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd //www.springframework.org/schema/tx //www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--聲明數據源DataSource,作用是連接資料庫-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${jdbc.maxActive}" />
</bean>
<!--SqlSessionFactory-->
<bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--創建 dao對象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="SqlSessionFactory"/>
<property name="basePackage" value="com.md.dao"/>
</bean>
<!--聲明service-->
<bean id="buyGoodsService" class="com.md.service.impl.BuyGoodsServiceImpl">
<!--就是上面通過創建的dao對象-->
<property name="saleDao" ref="saleDao"/>
<property name="goodsDao" ref="goodsDao"/>
</bean>
<!--
使用spring的事務處理
-->
<!--1. 聲明事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 連接資料庫,指定數據源-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--2. 開啟事務註解驅動,告訴spring使用註解管理事務,創建代理對象 -->
<!--注意:選擇結尾是tx的driven-->
<!--transaction-manager:事務管理器對象的id-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 3. 在需要事務方法上加註解 -->
</beans>
4. 業務層 public 方法加入事務屬性
在方法上面加@Transactional
這裡也就是在service介面的實現類里的方法上,全部程式碼如下:
package com.md.service.impl;
import com.md.dao.GoodsDao;
import com.md.dao.SaleDao;
import com.md.domain.Goods;
import com.md.domain.Sale;
import com.md.excep.NotEnoughException;
import com.md.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author MD
* @create 2020-08-11 9:45
*/
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
/**
*rollbackFor:表示發生指定的異常一定回滾
*
*/
// @Transactional(
// propagation = Propagation.REQUIRED,
// isolation = Isolation.DEFAULT,
// readOnly = false,
// rollbackFor = {
// NullPointerException.class,NotEnoughException.class
// }
// )
// 都使用默認值也是可以的,默認的傳播行為是REQUIRED,默認的隔離級別是DEFAULT
// 默認拋出運行時異常,回滾事務
@Transactional
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=========buy方法開始===========");
// 記錄銷售記錄,向sale表中添加數據
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
// 先查詢該商品
Goods goods = goodsDao.selectGoods(goodsId);
if (goods == null){
// 商品不存在
throw new NullPointerException("編號:"+goodsId+" 的商品不存在");
}else if (goods.getAmount() < nums){
// 商品庫存不足
throw new NotEnoughException("編號:"+goodsId+" 的商品不足,只能購買: "+goods.getAmount()+" 個");
}
// 更新庫存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=========buy方法結束===========");
}
}
5. 測試
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 從容器中獲取service,你聲明service時候的id
BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
// jdk動態代理對象com.sun.proxy.$Proxy16
//System.out.println(buyGoodsService.getClass().getName());
buyGoodsService.buy(1003,100);
}
假設此時的1003號商品不存在,即使上面已經向sale表中添加數據了,但是由於還沒有執行到更新庫存方法的時候出現了異常,此時由於添加了事務,這個時候就會回滾,你查看sale的數據表沒有數據,但是你之後買一個存在的商品,發現id號已經不是連續的了,如圖所示:
由於sale的id設置的自增,就是因為回滾的原因,id不連續
四、使用 AspectJ 的 AOP 配置管理事務
使用 XML 配置事務代理的方式的不足是,每個目標類都需要配置事務代理。當目標類較多,配置文件會變得非常臃腫。
使用 XML 配置顧問方式可以自動為每個符合切入點表達式的類生成事務代理。其用法很簡單,只需將前面程式碼中關於事務代理的配置刪除,再替換為如下內容即可
還是使用上面舉例的程式,複製一份
1. maven依賴pom.xml
還是直接把完整的pom.xml放這,主要就是添加了一個aspectj的
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.md</groupId>
<artifactId>09-spring-trans-aspectj</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- 單元測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--aspectj依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring事務用到的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis的-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis和spring集成的-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--德魯伊,資料庫連接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<build>
<!--目的是把src/main/java目錄中的xml文件包含到輸出結果中,也就是輸出到classes目錄中-->
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目錄-->
<includes><!--包括目錄下的.properties,.xml 文件都會掃描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
2. 在容器中添加事務管理器
還是在Spring的配置文件中加入
<!--1. 聲明事務管理器對象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
3. 配置事務通知
為事務通知設置相關屬性。用於指定要將事務以什麼方式織入給哪些方法。
例如,應用到 buy 方法上的事務要求是必須的,且當 buy 方法發生異常後要回滾業務
<!--2. 聲明業務方法它的事務屬性
id:自定義名稱,表示<tx:advice>和</tx:advice>之間的配置內容
transaction-manager:事務管理器對象的id
-->
<!--注意:advice選擇tx結尾的-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!-- tx:attributes :配置事務的屬性-->
<tx:attributes>
<!-- tx:method:給具體的方法配置事務屬性,可以有多個,分別給不同的方法設置事務
name:方法名稱
1. 完整的方法名稱,不帶包和類
2. 方法可以使用通配符,*表示任意字元
propagation:傳播行為
isolation:隔離級別
rollback-for:你指定的異常類名,全限定類名,發生異常一定回滾
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException , com.md.excep.NotEnoughException"/>
<!--當方法多的時候,使用通配符-->
<!--表示add開頭的方法,其他都是默認-->
<tx:method name="add*" />
<tx:method name="modify*"/>
</tx:attributes>
</tx:advice>
4. 配置增強器
指定將配置好的事務通知,織入給誰
<!--3. 配置aop-->
<aop:config>
<!--配置切入點表達式:指定那些包中的類,要使用事務
id:切入點表達式名稱,唯一
expression:切入點表達式,指定那些類要使用事務,aspectj會創建代理對象
這裡寫的表示所有service類中的所有方法
-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--配置增強器:關聯advice和pointcut
advice-ref:通知,上面tx:advice裡面的配置
pointcut-ref:切入點表達式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
5. 完整Spring配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:context="//www.springframework.org/schema/context" xmlns:tx="//www.springframework.org/schema/tx"
xmlns:aop="//www.springframework.org/schema/aop"
xsi:schemaLocation="//www.springframework.org/schema/beans
//www.springframework.org/schema/beans/spring-beans.xsd //www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd //www.springframework.org/schema/tx //www.springframework.org/schema/tx/spring-tx.xsd //www.springframework.org/schema/aop //www.springframework.org/schema/aop/spring-aop.xsd">
<!--
把資料庫的配置資訊寫在一個獨立的文件中,編譯修改資料庫的配置內容
讓spring知道jdbc.properties文件的位置
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--聲明數據源DataSource,作用是連接資料庫-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${jdbc.maxActive}" />
</bean>
<!--SqlSessionFactory-->
<bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--創建 dao對象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="SqlSessionFactory"/>
<property name="basePackage" value="com.md.dao"/>
</bean>
<!--下面的就是自己定義的service-->
<!--聲明service-->
<bean id="buyGoodsService" class="com.md.service.impl.BuyGoodsServiceImpl">
<!--就是上面通過創建的dao對象-->
<property name="saleDao" ref="saleDao"/>
<property name="goodsDao" ref="goodsDao"/>
</bean>
<!--
聲明式事務處理:和源程式碼完全分離
-->
<!--1. 聲明事務管理器對象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--2. 聲明業務方法它的事務屬性
id:自定義名稱,表示<tx:advice>和</tx:advice>之間的配置內容
transaction-manager:事務管理器對象的id
-->
<!--注意:advice選擇tx結尾的-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!-- tx:attributes :配置事務的屬性-->
<tx:attributes>
<!-- tx:method:給具體的方法配置事務屬性,可以有多個,分別給不同的方法設置事務
name:方法名稱
1. 完整的方法名稱,不帶包和類
2. 方法可以使用通配符,*表示任意字元
propagation:傳播行為
isolation:隔離級別
rollback-for:你指定的異常類名,全限定類名,發生異常一定回滾
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException , com.md.excep.NotEnoughException"/>
<!--當方法多的時候,使用通配符-->
<!--表示add開頭的方法,其他都是默認-->
<tx:method name="add*" />
<tx:method name="modify*"/>
</tx:attributes>
</tx:advice>
<!--3. 配置aop-->
<aop:config>
<!--配置切入點表達式:指定那些包中的類,要使用事務
id:切入點表達式名稱,唯一
expression:切入點表達式,指定那些類要使用事務,aspectj會創建代理對象
這裡寫的表示所有service類中的所有方法
-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--配置增強器:關聯advice和pointcut
advice-ref:通知,上面tx:advice裡面的配置
pointcut-ref:切入點表達式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
</beans>
6. 測試類
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 從容器中獲取service,你聲明service時候的id
BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
buyGoodsService.buy(1001,10);
}
五、總結
1. Spring 的事務註解管理事務
適合中小項目使用的註解方案
spring框架自己用aop實現給業務方法增加事務的功能, 使用@Transactional註解增加事務。
@Transactional註解是spring框架自己註解,放在public方法的上面,表示當前方法具有事務。
可以給註解的屬性賦值,表示具體的隔離級別,傳播行為,異常資訊等等
使用@Transactional的步驟:
-
需要聲明事務管理器對象
- <bean id=”xx” class=”DataSourceTransactionManager”>
-
開啟事務註解驅動, 告訴spring框架,我要使用註解的方式管理事務
- spring使用aop機制,創建@Transactional所在的類代理對象,給方法加入事務的功能
- spring給業務方法加入事務:在你的業務方法執行之前,先開啟事務,在業務方法之後提交或回滾事務,使用aop的環繞通知
@Around("你要增加的事務功能的業務方法名稱") Object myAround(){ 開啟事務,spring給你開啟 try{ buy(1001,10); spring的事務管理器.commit(); }catch(Exception e){ spring的事務管理器.rollback(); } }
-
在你的方法的上面加入@Trancational
2. AspectJ 的 AOP 配置管理事務
適合大型項目,有很多的類,方法,需要大量的配置事務,使用aspectj框架功能,在spring配置文件中聲明類,方法需要的事務
這種方式業務方法和事務配置完全分離
實現步驟: 都是在xml配置文件中實現
-
要使用的是aspectj框架,需要加入依賴
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
聲明事務管理器對象
<bean id=”xx” class=”DataSourceTransactionManager”>
-
聲明方法需要的事務類型(配置方法的事務屬性【隔離級別,傳播行為,超時】)
-
配置aop:指定哪些哪類要創建代理