1 什么是事务
事务是指逻辑上的一组操作,要么全部成功,要么全部失败
2 事务的特性
- 原子性
- 原子性是指事务是一个不可分割 的工作单位,事务中的操作要么全部都发生,要么都不发生。
- 一致性
- 一致性是指事务前后的数据的完整性必须保持一致
- 隔离性
- 隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离
- 持久性
- 持久性是指一个事物一旦被提交,它对数据库中数据的改变就是永久性的,即使数据库发生故障也不应对其有任何影响
3 Spring事务管理的API
Spring事务管理高层抽象主要包括3个接口
- PlatformTransactionManager:事务管理器
- TransactionDefinition:事务定义信息(隔离,传播,超时,只读)
- TransactionStatus:事务具体运行状态
3.1 PlatformTransactionManager接口
3.1.1 Spring为不同的框架提供了不同的PlatformTransactionManager接口实现
事务 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC或iBatis进行持久化数据时使用 |
org.springframework.jdbc.orm.hibernate3.HibernateTransactionManager | 使用Hibernate3.0版本进行持久化数据时使用 |
org.springframework.orm.jpa.JpaTransactionManager | 使用JPA进行持久化时的使用 |
org.springframework.jdo.JdaoTransactionManager | 当持久化机制是Jdo时使用 |
org.springframework.transaction.jta.JtaTransactionManager | 使用一个JTA实现管理事务,在一个事务跨越多个资源时必须使用 |
3.2 TransactionDefinition接口
3.2.1 几个概念:
- 脏读:
- 一个事务读取了另一个事务改写但是还未提交的数据,如果这些数据被回滚,则读到的数据是无效的
- 不可重复读:
- 在同一个事务中,多次读取同一数据返回的结果有所不同
- 幻读:
- 一个事务读取了几行记录后,另一个事务插入一些记录。幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录
3.2.2 事务隔离级别
隔离级别 | 含义 |
---|---|
DEFAULT | 使用后端数据库默认的隔离级别(Spring中的选择项) |
READ_UNCOMMITTED | 允许你读取还未提交的但是改变了的数据。可能是脏读,幻读,不可重复读 |
READ_COMMITED | 允许在并发事务已经提交后读取。可防止脏读,但是幻读,不可重复读仍可发生 |
REPEATABLE_READ | 对于相同的字段的多次读取是一致的,除非数据被事务本身改变。可防止脏读,幻读,不可重复读 |
SERIALIZABLE | 完全服从ACID的隔离级别,确保不会发生脏,幻,不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的 |
3.2.3 事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果不存在就新建一个 |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在,就不使用事务 |
PROPAGATION_MANDATORY | 支持当前事务,如果不存在,就抛出异常 |
PROPAGATION_REQUIRED_NEW | 如果有事务存在,挂起当期事务,创建一个新的事务 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果有事务存在,挂起当前事务 |
PROPAGATION_NEVER | 以非事务方式运行,如果有事务存在,抛出异常 |
PROPAGATION_NESTED | 如果当前事务存在,则嵌套事务执行 |
实例应用
银行转账问题的Demo
eg.:
当A账户向B账户转账时,应该保证A的钱转出去后,B一定能收到,如果任意一方出错,则整个转账过程就是不成功的。确保双方账户的安全性。这就好比一个事务一样,要么全部成功,要么全部失败。
初始状态,没做处理
- 先创建数据库springdemo和表格account并插入测试数据
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', 'aaa', '1000');
INSERT INTO `account` VALUES ('2', 'bbb', '1000');
INSERT INTO `account` VALUES ('3', 'ccc', '1000');
测试前数据内容
+----+------+-------+
| id | name | money |
+----+------+-------+
| 1 | aaa | 1000 |
| 2 | bbb | 1000 |
| 3 | ccc | 1000 |
+----+------+-------+
- 创建两个接口
- AccountDao:实现对数据库的操作
package com.bart.dao;
public interface AccountDao {
/**
* @param in
* @param money
*/
public void inMoney(String in,Double money);
/**
* @param out
* @param money
*/
public void outMoney(String out,Double money);
}
====================分割线============================
package com.bart.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
/**
* @param in
* @param money
*/
public void inMoney(String in, Double money) {
String sql = "update account set money = money+? where name = ? ";
this.getJdbcTemplate().update(sql,money,in);
}
/**
* @param out
* @param money
*/
public void outMoney(String out, Double money) {
String sql = "update account set money = money-? where name = ? ";
this.getJdbcTemplate().update(sql,money,out);
}
}
- AccountService:实现业务操作,例如把aaa账户欠款打到bbb账户
package com.bart.demo;
public interface AccountService {
/**
* @param in
* @param out
* @param money
*/
public void transfer(String in,String out,Double money);
}
==========================分割线==============================
package com.bart.demo;
import org.springframework.transaction.support.TransactionTemplate;
import com.bart.dao.AccountDao;
import com.sun.istack.internal.FinalArrayList;
public class AccountServiceImpl implements AccountService {
//注入accountDaoImpl
private AccountDao accountDao;
/**
* @param in
* @param out
* @param money
*/
public void transfer(String in, String out, Double money) {
accountDao.outMoney(out, money);
accountDao.inMoney(in, money);
}
//注入必须配置set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
- 配置数据库属性
jdbc.properties
jdbc.Driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/springdemo
jdbc.password=
jdbc.username=root
- 配置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:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.Driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置业务类 -->
<bean id="accountService" class="com.bart.demo.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO类 -->
<bean id="accountDao" class="com.bart.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- 测试
@Test
public void demo1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
accountService=(AccountService) context.getBean("accountService");
accountService.transfer("aaa", "bbb", 200.00);
}
- 测试结果
+----+------+-------+
| id | name | money |
+----+------+-------+
| 1 | aaa | 1200 |
| 2 | bbb | 800 |
| 3 | ccc | 1000 |
+----+------+-------+
可以看出开,正常情况下通过测试aaa账户的200元确实转账到了bbb账户里,但是如果在转账过程中发生了异常会导致什么情况呢?下面测试一下。
首先把所有的钱全部恢复默认值,都是1000元。修改AccountServiceImpl类的transfer方法,添加一个异常。
/**
* @param in
* @param out
* @param money
*/
public void transfer(String in, String out, Double money) {
accountDao.outMoney(out, money);
int i=1/0;
accountDao.inMoney(in, money);
}
再次进行测试。发现钱“转丢了”。。。
+----+------+-------+
| id | name | money |
+----+------+-------+
| 1 | aaa | 1000 |
| 2 | bbb | 800 |
| 3 | ccc | 1000 |
+----+------+-------+
bbb的钱转出去了200元,但是由于手动添加了一个异常aaa并没有收到bbb转过来的200元。这种情况是不允许出现的。为了解决这个问题,需要用到事务处理,Spring框架给我们提供了几种方法。
编程式事务管理
编程式事务管理使用spring框架提供的org.springframework.transaction.support.TransactionTemplate模板来进行事务的管理。
- 首先重新配置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:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.Driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置业务类 -->
<bean id="accountService" class="com.bart.demo.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
<!-- 配置DAO类 -->
<bean id="accountDao" class="com.bart.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
- 修改AccountServiceImpl
package com.bart.demo;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.bart.dao.AccountDao;
public class AccountServiceImpl implements AccountService {
//注入accountDaoImpl
private AccountDao accountDao;
//注入一个transactionTemplate模板,基于编程的事务管理
private TransactionTemplate transactionTemplate;
/**
* 1. 编程式的事务管理
* */
public void transfer(final String in, final String out, final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.outMoney(out, money);
//设置异常,测试转账中断
int i=1/0;
accountDao.inMoney(in, money);
}
});
}
//注入必须配置set方法
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
//注入必须配置set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
- 测试
@Test
public void demo1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
accountService=(AccountService) context.getBean("accountService");
accountService.transfer("aaa", "bbb", 200.00);
}
- 测试结果:
+----+------+-------+
| id | name | money |
+----+------+-------+
| 1 | aaa | 1000 |
| 2 | bbb | 1000 |
| 3 | ccc | 1000 |
+----+------+-------+
在有异常的情况下,不会出现转丢失的情况,两个动作绑在了一起,成为一个事务,所以一起成功,一起失败。
声明式事务管理
三种方式:
- 基于TransactionProxyFactoryBean的方式
- 基于AspectJ的XML方式
- 基于注解的方式
基于TransactionProxyFactoryBean的方式
类似Spring的Aop里面配置事务的方法。
1. 修改后的AccountServiceImpl
package com.bart.demo;
import org.springframework.transaction.support.TransactionTemplate;
import com.bart.dao.AccountDao;
public class AccountServiceImpl implements AccountService {
//注入accountDaoImpl
private AccountDao accountDao;
/**
* 2. 基于AOP思想的声明式的事务管理
* */
public void transfer(String in, String out, Double money) {
accountDao.outMoney(out, money);
//int i=1/0;
accountDao.inMoney(in, money);
}
//注入必须配置set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
- 配置applicationContextAop.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:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.Driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置业务类 -->
<bean id="accountService" class="com.bart.demo.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO类 -->
<bean id="accountDao" class="com.bart.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置业务层的代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目标对象 -->
<property name="target" ref="accountService"/>
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 注入事务属性 -->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事务的传播行为
* ISOLATIN :事务的传播行为
* readOnly :只读
* -Exception :发生哪些异常时回滚事务
* +Exception :发生哪些异常时事务不回滚
-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
- 测试
@Test
public void dedom2() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContextAop.xml");
accountService=(AccountService) context.getBean("accountServiceProxy"); accountService.transfer("aaa", "bbb", 200.00);
}
- 测试结果
此时不管在AccountServiceImpl的transfer方法中如果有异常,则转账失败,不会出现钱转丢的情况
+----+------+-------+
| id | name | money |
+----+------+-------+
| 1 | aaa | 1000 |
| 2 | bbb | 800 |
| 3 | ccc | 1000 |
+----+------+-------+
基于AspectJ的XML方式
- 配置ApplicationContextAspectJ.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:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.Driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置业务类 -->
<bean id="accountService" class="com.bart.demo.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO类 -->
<bean id="accountDao" class="com.bart.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的增强(Advice通知) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation :事务传播行为
isolation :事务隔离级别
read-only :只读
rollback-for:发生哪些异常时候回滚
no-rollback-for:发生哪些异常不回滚
-->
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 第一个*表示任意类型返回值,第二个+表示该类的所有子类,第三个*表示所有的方法(..)表示所有参数 -->
<aop:pointcut id="pointcut" expression="execution(* com.bart.demo.AccountServiceImpl+.*(..))"/>
<!-- 配置通知 -->
<aop:advisor pointcut-ref="pointcut" advice-ref="txAdvice" />
</aop:config>
</beans>
- AccountServiceImpl不做改变, 测试
@Test
public void dedom3() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContextAspectJ.xml");
accountService=(AccountService) context.getBean("accountService");
accountService.transfer("aaa", "bbb", 200.00);
}
- 测试结果
和基于TransactionProxyFactoryBean的方式效果相同
基于注解的方式
- 修改AccountServiceImpl:
package com.bart.demo;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.bart.dao.AccountDao;
import com.sun.istack.internal.FinalArrayList;
/** Transaction:注解事务管理
* propagation :事务的传播行为
* isolation :事务的隔离级别
* readOnly :只读
* rollbackFor :发生哪些异常时回滚
* notRollbackFor :发生哪些异常时不回滚
**/
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)
public class AccountServiceImpl implements AccountService {
//注入accountDaoImpl
private AccountDao accountDao;
/**
* 2. 基于AOP思想的声明式的事务管理
* */
public void transfer(String in, String out, Double money) {
accountDao.outMoney(out, money);
//int i=1/0;
accountDao.inMoney(in, money);
}
//注入必须配置set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
- 配置applicationContextAnnotation.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:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.Driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置业务类 -->
<bean id="accountService" class="com.bart.demo.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO类 -->
<bean id="accountDao" class="com.bart.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
- 测试
效果和前两个一样
@Test
public void demo4() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContextAnnotation.xml");
accountService = (AccountService) context.getBean("accountService");
accountService.transfer("aaa", "bbb", 200d);
}
总结
Spring将事务管理分成了两类:
- 编程式事务管理:
- 手动编写代码进行事务管理。(很少使用)
- 声明式事务管理:
- 基于TransactionProxyFactoryBean的方式。(很少使用)
-需要为每个进行事务管理 - 基于AspectJ的XML配置方式。(经常使用)
- 一旦配置好之后,类上不需要添加任何东西
- 基于注解方式。(经常使用)
- 配置简单,需要在业务层上添加一个@transactional的注解
- 基于TransactionProxyFactoryBean的方式。(很少使用)