一、Spring的事务管理
事务是一些SQL语句作为一个整体执行的集合。事务原本是数据库中的概念,在Dao层。但一般情况下,需要将事务提升到业务层,即Service层。这样做是为了能够使用事务的特性来管理具体的业务。
在Spring中通常可以用一下两种方式实现事务的管理:
1)使用Spring的事务注解管理事务
2)使用AspectJ的AOP配置事务管理
二、Spring事务管理API
Spring 的事务管理,主要用到两个事务相关的接口:事务管理器接口PlatformTransactionManager和事务定义接口TransactionDefinition
1、事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。使用Spring的事务管理器,管理不同数据库访问技术的事务处理,开发人员只需要掌握Spring的事务处理一个方案,就可以实现使用不同数据库访问技术的事务管理。事务管理器有很多实现类:一种数据库的访问技术有一个实现类,由实现类具体完成事务的提交,回滚意味着:Hibernate或者Mybatis访问数据库都有自己的事务管理器实现类
1)常用的两个实现类
PlatformTransactionManager 接口有两个常用的实现类:
DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
2)事务提交和回滚的时机
当你的业务方法正常执行时,没有异常,事务是提交的。如果你的业务方法抛出了运行时异常,事务是回滚的。Spring 事务的默认回滚方式是:发生运行时异常和 Error时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
异常分类:
Error:严重错误,回滚事务
比如 OutOfMemoryError、ThreadDeath、NoSuchMethodError
Exception:异常类,可以抛出来的异常情况
1)运行时异常:是 RuntimeException 类或其子类
例如:NullPointerException、ArrayIndexOutOfBoundsException
2)受查异常:编写Java代码的时候,必须要抛出来的异常
例如:SQLException,ClassNotFoundException,IOException
2、事务定义接口
TransactionDefinition接口,定义了事务描述相关的三类常量,定义了有关事务控制的属性:1)事务隔离级别;2)事务传播行为;3)事务超时时限。Serivice业务方法说明的事务属性,和ACID不一样。
1)隔离级别
控制事务之间影响的程度,隔离级别定义了五个值,但只有四个隔离级别
DEFAULT | 采用DB默认的事务隔离级别。 MySQL默认:REPEATABLE_READ ;Oracle 默认为 READ_COMMITTED |
READ_UNCOMMITED | 读未提交。未解决任何并发值问题 |
READ_COMMITTED | 读已提交。解决脏读,存在不可重复读与幻读 |
REPEATABLE_READ | 可重复度。解决脏读,不可重复度,存在幻读 |
SERIALIZABLE | 串行化,不存在并发问题,效率低 |
2)超时时间
表示一个业务方法最长的执行时间,没有达到时间没有执行完毕,Spring回滚事务;常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。
注意:事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。超时时间,以秒为单位,整数值,默认为-1
3)传播行为
传播行为:业务方法在调用时,事务在方法之间的,传递和使用。所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法doOther(),在调用执行期间事务的维护情况,就称为事务传播行为,事务传播行为是加在方法上的
A、PROPAGATION_REQUIRED
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是Spring 默认的事务传播行为。如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则doOther()方法会创建一个事务,并在其中执行。
B、PROPAGATION_REQUIRES_NEW
方法总是需要一个新事物。如果调用方法是,存在一个事务,则原来的事务暂停,直到新事物执行完毕。如果方法调用时,没有事务,则新建一个事务,在新事务里面执行
C、PROPAGATION_SUPPORTS
指定的方法支持当前事务,没有事务也可以正常执行;有事务就用当前事务,没事务就没有
三、环境准备
1、建表
数据准备:
2、POM依赖
<dependencies>
<!--lomok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!--单一测试依赖-->
<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>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<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>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3、实体类
4、DAO接口
5、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">
<!--注意 namespace 后面 跟自己的包路径-->
<mapper namespace="com.dgs.dao.GoodsDao">
<select id="selectById" resultType="com.dgs.entity.Goods">
select id ,name,amount,price from goods where id = #{id}
</select>
<update id="updateGoods" parameterType="com.dgs.entity.Goods">
update goods set amount = amount - #{amount} where id = #{id}
</update>
</mapper>
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">
<!--注意 namespace 后面 跟自己的包路径-->
<mapper namespace="com.dgs.dao.SaleDao">
<insert id="insertSale">
insert into sale (gid,nums) values (#{gid},#{nums});
</insert>
</mapper>
6、自定义异常类
7、定义Service接口和接口实现类
public interface BuyGoodsService {
void buy(Integer goodsId,Integer num);
}
@Service
public class BuyGoodsServiceImpl implements BuyGoodsService {
@Resource
private SaleDao saleDao;
@Resource
private GoodsDao goodsDao;
public void buy(Integer goodsId, Integer num) {
// 生成销售记录
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(num);
saleDao.insertSale(sale);
// 更新库存
Goods goods = goodsDao.selectById(goodsId);
if (goods == null) {
throw new NullPointerException("无此商品");
}else if(goods.getAmount() < num){
throw new NotEnougthException("库存不足");
}
//更新库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(num);
goodsDao.updateGoods(buyGoods);
}
}
8、配置文件
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">
<!--声明数据源-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/dgs_study" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<!--声明SqlSessionFactory-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--声明MapperScannerConfiguration-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"/>
<property name="basePackage" value="com.dgs.dao"/>
</bean>
<!--包扫描-->
<context:component-scan base-package="com.dgs.service" />
</beans>
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>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--别名-->
<typeAliases>
<package name="com.dgs.entity"/>
</typeAliases>
<!-- 指定其他mapper文件的位置-->
<mappers>
<package name="com.dgs.dao"/>
</mappers>
</configuration>
9、测试类
public class AppTest {
@Test
public void test() {
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
BuyGoodsService bean = (BuyGoodsService)ctx.getBean("buyGoodsServiceImpl");
bean.buy(1001,1);
}
}
四、Spring框架使用自己的注解@Transaction控制事务
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
1、注解属性:
位置:1)在业务方法的上面,是在public方法的上面 2)在类的上面 | |
1、propagation | 事务的传播行为,他使用的Propagation类的枚举值。例如Propagation.REQUIRED |
2、isolation | 表示隔离级别,使用isolation类的枚举值,表示隔离界别,默认是Isolation.DEFAULT |
3、readOnly | boolean类型的值,表示数据库操作是不是只读的,默认是false |
4、timeout | 事务超时,默认是-1,整数值,单位是秒。例如timeout=20 |
5、roolbackFor | 表示回滚的异常类列表,他的值是一个数组 |
6、roolbackForClassName | 表示回滚的异常类列表,他的值是异常类名称,是String类型的值 |
7、noRollbackFor | 不需要回滚的异常类列表。是class类型的 |
8、noRollbackForClassNAme | 不需要回滚的异常;类列表,是String类型的值 |
2、注解的使用步骤:
1)在配置文件: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">
<!--声明数据源-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/dgs_study" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<!--声明SqlSessionFactory-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--声明MapperScannerConfiguration-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"/>
<property name="basePackage" value="com.dgs.dao"/>
</bean>
<!--包扫描-->
<context:component-scan base-package="com.dgs.service" />
<!-- ____________声明事务的控制_____________ -->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="myDataSource" />
</bean>
<!--开启事务注解驱动,声明框架使用注解管理事务 transaction-manager:指定事务管理器的id-->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
2)在类的源代码中,加入@Transaction
@Service
public class BuyGoodsServiceImpl implements BuyGoodsService {
@Resource
private SaleDao saleDao;
@Resource
private GoodsDao goodsDao;
@Override
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
timeout = 20,
rollbackFor = {NullPointerException.class,NotEnougthException.class}
)
public void buy(Integer goodsId, Integer num) {
// 生成销售记录
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(num);
saleDao.insertSale(sale);
// 更新库存
Goods goods = goodsDao.selectById(goodsId);
if (goods == null) {
throw new NullPointerException("无此商品");
}else if(goods.getAmount() < num){
throw new NotEnougthException("无库存");
}
//更新库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(num);
goodsDao.updateGoods(buyGoods);
}
}
五、使用 AspectJ 的 AOP 配置管理事务
使用aspect的aop,声明事务控制叫做声明式事务
使用步骤:
1、pom.xml加入 apeing-aspect的依赖
2、在Spring的配置文件声明事务的内容
1)声明事务管理器
2)声明业务方法需要的业务属性
3)声明切入点表达式
修改配置文件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">
<!--声明数据源-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/dgs_study" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<!--声明SqlSessionFactory-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--声明MapperScannerConfiguration-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"/>
<property name="basePackage" value="com.dgs.dao"/>
</bean>
<!--包扫描-->
<context:component-scan base-package="com.dgs.service" />
<!-- ____________声明事务的控制_____________ -->
<!--1、声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="myDataSource" />
</bean>
<!--2、声明业务方法的事务属性(隔离级别,传播行为,超时)-->
<!--
id:给业务方法配置事务段代码起个名称,唯一值
transaction-manager:事务管理器的id
-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<!--
给具体的业务方法增加事务的说明
name:业务方法名称,配置name的值 1:业务方法的名称 2:带有部分通配符的方法名称 3:使用*
propagation:指定传播行为的值
isolation:是否只读,默认false
timeout:超时时间
rollback-for:指定回滚的异常类列表,使用的异常全限定类名称
-->
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED"
isolation="DEFAULT" read-only="false" timeout="20" rollback-for="java.lang.NullPointerException,com.dgs.excetion.NotEnougthException"/>
<tx:method name="add*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!--声明切入点表达式,表示那些包中的类,类中的方法参与方法与事务-->
<aop:config>
<!--声明切入点表达式
expression:切入点表达式,表示那些类和类中的方法参与事务
id:切入点表达式的名称:唯一值
-->
<aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))"/>
<!--管理切入点表达式和事务通知-->
<aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
</beans>