10、声明式事务
事务分为声明式和编程式两种:
声明式事务:声明式事务是指通过注解的形式对事务的各种特性进行控制和管理。
编码式(编程式)事务:指的是通过编码的方式实现事务的声明。
11.1、编码方式实现事务:
11.2、声明式事务环境搭建
11.2.1、准备测试数据库
##创建tx数据库 drop database if exists `tx`; CREATE database `tx`; ##切换tx数据库 USE `tx`; ##删除用户表 DROP TABLE IF EXISTS `user`; ##创建用户表 CREATE TABLE `user` ( `id` int primary key auto_increment, `username` varchar(50) NOT NULL, `money` int(11) DEFAULT NULL ); ##插入数据 insert into `user`(`username`,`money`) values ('张三',1000),('李四',1000); ##删除图书表 drop table if exists `book`; ##创建图书表 create table `book`( `id` int primary key auto_increment, `name` varchar(500) not null, `stock` int ); ##插入数据 insert into book(`name`,`stock`) values('java编程思想',100),('C++编程思想',100); ##查看数据 select * from book; select * from user;
11.2.2、创建一个Java工程,导入Jar包
导入jar包:
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar commons-logging-1.1.3.jar druid-1.1.9.jar mysql-connector-java-5.1.37-bin.jar spring-aop-4.3.18.RELEASE.jar spring-beans-4.3.18.RELEASE.jar spring-context-4.3.18.RELEASE.jar spring-core-4.3.18.RELEASE.jar spring-expression-4.3.18.RELEASE.jar spring-jdbc-4.3.18.RELEASE.jar spring-orm-4.3.18.RELEASE.jar spring-test-4.3.18.RELEASE.jar spring-tx-4.3.18.RELEASE.jar
配置jdbc.properties属性配置文件:
url=jdbc:mysql://localhost:3306/tx user=root password=root driverClassName=com.mysql.jdbc.Driver initialSize=5 maxActive=10
配置Spring的配置文件:
<?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 http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 配置包扫描 --> <context:component-scan base-package="com"></context:component-scan> <!-- 加载jdbc.properties属性配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${user}"/> <property name="password" value="${password}"/> <property name="url" value="${url}"/> <property name="driverClassName" value="${driverClassName}"/> <property name="initialSize" value="${initialSize}" /> <property name="maxActive" value="${maxActive}"/> </bean> <!-- 配置jdbcTemplate工具类 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
11.3、测试Service的默认事务
实验1:测试service服务层的默认事务
@Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; public void updateBook() { jdbcTemplate.update("update book set name = '图书表被修改了'"); } } @Repository public class UserDao { @Autowired JdbcTemplate jdbcTemplate; public void updateUser() { jdbcTemplate.update("update user set username = '用户表被修改了'"); } } @Service public class TransactionService { @Autowired private UserDao userDao; @Autowired private BookDao bookDao; public void multiUpdate() { userDao.updateUser(); bookDao.updateBook(); } }
异常的演示
@Service public class TransactionService { @Autowired private UserDao userDao; @Autowired private BookDao bookDao; public void multiUpdate() { userDao.updateUser(); int i = 12 / 0 ; bookDao.updateBook(); } }
Spring事务引入的分析------PlatformTransactionManager类简单介绍
事务管理器实现类
由于我们使用的是数据库连接池访问数据库,所以事务管理器使用的是DataSourceTransactionManager。
11.4、使用Spring的注解声明事务管制
实验2:测试Spring的声明式事务
1、在需要事务的方法上加入注解。
/** * @Transactional 表示当前方法有事务 */ @Transactional public void multiUpdate() { userDao.updateUser(); int i = 12 / 0 ; bookDao.updateBook(); }
2、配置事务管理器驱动
<!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
3、配置事务注解驱动
<!-- <tx:annotation-driven/> 开启注解的事务驱动 启用代理 transaction-manager="transactionManager" 配置事务管理器 如果事务管理器的id值是transactionManager,则事务管理器属性可以省略 --> <tx:annotation-driven />
11.5、noRollbackFor和noRollbackForClassName测试不回滚的异常
实验3:noRollbackFor和noRollbackForClassName测试不回滚的异常
/** * @Transactional 表示当前方法有事务<br/> * 默认情况下。RuntimeException运行时异常和运行时子异常。都会回滚事务<br/> * noRollbackFor=java.lang.ArithmeticException.class 表示算术异常不回滚事务<br/> * noRollbackFor= {java.lang.ArithmeticException.class,java.lang.NullPointerException.class}<br/> * noRollbackForClassName="java.lang.NullPointerException" 设置哪些类名的异常不回滚事务<br/> */ @Transactional(noRollbackForClassName="java.lang.NullPointerException") public void multiUpdate() { userDao.updateUser(); // int i = 12 / 0 ; Object object = null; System.out.println( object.toString() ); bookDao.updateBook(); }
11.6、自定义设置回滚异常
实验5:rollbackFor和rollbackForClassName回滚的异常
/** * @throws FileNotFoundException * @Transactional 表示当前方法有事务<br/> * 默认情况下。RuntimeException运行时异常和运行时子异常。都会回滚事务<br/> * rollbackFor=FileNotFoundException.class 设置当抛出FileNotFoundException异常时,回滚事务<br/> * rollbackForClassName="java.io.FileNotFoundException" 设置当抛出FileNotFoundException异常时就会回滚事务<br/> */ @Transactional(rollbackForClassName="java.io.FileNotFoundException") public void multiUpdate() throws FileNotFoundException { userDao.updateUser(); int i = 12 ; if (i == 12) { throw new FileNotFoundException(); } bookDao.updateBook(); }
11.7、事务的只读属性
实验4:测试readOnly只读属性
/** * @throws FileNotFoundException * @Transactional 表示当前方法有事务<br/> * 默认情况下。RuntimeException运行时异常和运行时子异常。都会回滚事务<br/> * readOnly=false 表示当前方法可以执行读操作。也可以执行写操作(指的是sql语句)。<br/> * 修改、添加、删除、是写操作、select查询语句是读操作。<br/> * readOnly=true 表示当前方法只能执行查询操作<br/> */ @Transactional(readOnly=true) public void multiUpdate() throws FileNotFoundException { userDao.updateUser(); bookDao.updateBook(); }
只读的方法执行修改语句报错。
11.8、事务超时属性timeout(秒为单位)
/** * @throws FileNotFoundException * @Transactional 表示当前方法有事务<br/> * 默认情况下。RuntimeException运行时异常和运行时子异常。都会回滚事务<br/> * timeout=3 表示3秒之后就超时,不允许再执行sql语句 */ @Transactional(timeout=3) public void multiUpdate() throws FileNotFoundException { userDao.updateUser(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } bookDao.updateBook(); }
当执行时间超过指定的秒钟后就会超时:
11.10、事务的传播特性propagation
什么是事务的传播行为:
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
事务的传播特性,有以下几种类型:
11.11、注解演示事物传播特性
UserService
BookService
TransactionService
实验1:大小事务传播特性都是REQUIRED
@Transactional(propagation = Propagation.REQUIRED) public void multlTransaction() { @Transactional(propagation = Propagation.REQUIRED) public void updateBook() { @Transactional(propagation=Propagation.REQUIRED) public void updateUser() {
实验2:大小事务传播特性都是REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW) public void multiUpdate() @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateBook() @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUser()
实验3:大事务是REQUIRED,小事务都是REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED) public void multiUpdate() @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateBook() @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUser()
实验4:大事务是REQUIRED,小1REQUIRED,小2REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED) public void multiUpdate() @Transactional(propagation = Propagation.REQUIRED) public void updateBook() @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUser()
实验5:大事务是REQUIRED,小1REQUIRES_NEW,小2REQUIRED
@Transactional(propagation = Propagation.REQUIRED) public void multiUpdate() @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateBook() @Transactional(propagation = Propagation.REQUIRED) public void updateUser()
12、xml配置式事务声明
复制原来注解事务管理的工程。去掉里面所有@Transactional的注解。
<?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 http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!-- 配置包扫描 --> <context:component-scan base-package="com"></context:component-scan> <!-- 加载jdbc.properties属性配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${user}"/> <property name="password" value="${password}"/> <property name="url" value="${url}"/> <property name="driverClassName" value="${driverClassName}"/> <property name="initialSize" value="${initialSize}" /> <property name="maxActive" value="${maxActive}"/> </bean> <!-- 配置jdbcTemplate工具类 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置事务属性 --> <tx:advice id="tx_advice" transaction-manager="transactionManager"> <tx:attributes> <!-- tx:method 给某个方法配置事务属性 name就是方法的匹配规则(方法名) 精确匹配 --> <tx:method name="updateUser" propagation="REQUIRED"/> <!-- 给save*方法配置事务属性 name="save*" 表示给save字符串打头的方法配置事务属性 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRES_NEW"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="multlTransaction" propagation="REQUIRES_NEW"/> <!-- <tx:method name="*"/> 所有的方法 read-only="true" 剩下的方法都是查询操作。 --> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice> <aop:config> <!-- aop:advisor 配置通知 --> <aop:advisor advice-ref="tx_advice" pointcut="execution(public * com.service..*Service*.*(..))"/> </aop:config> </beans>