10、声明式事务
事务分为声明式和编程式两种:
声明式事务:声明式事务是指通过注解的形式对事务的各种特性进行控制和管理。
编码式(编程式)事务:指的是通过编码的方式实现事务的声明。

11.1、编码方式实现事务:

Java事务跟数据库连接的关系_bc


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包

Java事务跟数据库连接的关系_spring_02


导入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类简单介绍

事务管理器实现类

Java事务跟数据库连接的关系_jar_03

由于我们使用的是数据库连接池访问数据库,所以事务管理器使用的是DataSourceTransactionManager。

Java事务跟数据库连接的关系_bc_04

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();
}

只读的方法执行修改语句报错。

Java事务跟数据库连接的关系_Java事务跟数据库连接的关系_05


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();
}

当执行时间超过指定的秒钟后就会超时:

Java事务跟数据库连接的关系_Java事务跟数据库连接的关系_06


11.10、事务的传播特性propagation

什么是事务的传播行为:

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。事务的传播特性,有以下几种类型:

Java事务跟数据库连接的关系_jar_07

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() {

Java事务跟数据库连接的关系_jar_08


实验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()

Java事务跟数据库连接的关系_jar_09

实验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()

Java事务跟数据库连接的关系_bc_10

实验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()

Java事务跟数据库连接的关系_jar_11

实验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()

Java事务跟数据库连接的关系_spring_12


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>