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="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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"
"http://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"
"http://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"
"http://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="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--
把数据库的配置信息写在一个独立的文件中,编译修改数据库的配置内容
让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="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"
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">
<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="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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="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">
<!--
把数据库的配置信息写在一个独立的文件中,编译修改数据库的配置内容
让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:指定哪些哪类要创建代理
-------------------------------------------
你闻讯而来,我大喜过望,我在这等你,你又在哪呢?喜欢的话加一个“关注”呗!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!