Spring事务管理入门
事务概念
事务指逻辑上的一组操作,这组操作要么全部成功,要么全部失败.
eg: 假设有张三和李四2个人,张三要给李四转账1000元,那数据库里需要执行的操作是张三扣1000元,李四增加1000元,如果张三扣了1000元突然断电了,就导致“李四增加1000”的程序没有执行,那李四岂不是很亏?为了杜绝这种情况,引进了事务的概念,也就是要求“张三-1000元,李四+1000元”的操作必须全部完成或者全部失败(失败的话事务会回滚),不能一个完成一个失败。
事务特性
事务有四大特性称为ACID.
原子性好理解,和前面说的一个意思。
一致性是指事务处理前后数据的完整性必须保持一致.
隔离性指多用户并发访问数据库时,用户的事务不能被其它事务干扰,多个并发事务要相互隔离开.
持久性就是指一个事务一旦被提交,对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其任何影响.
事务管理接口
隔离级别
如果不考虑隔离性,会引发如下的安全问题: 1.脏读。 一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。 2.不可重复读。 在同一个事务中,多次读取同一数据返回的结果有所不同。 3.幻读。 一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。
详细可以参考 https://blog.****.net/starlh35/article/details/76445267
(MySQL底层默认使用REPEATABLE_READ级别,Oracle默认的事务隔离级别为READ_COMMITTED)
传播行为
事务一般都是在业务层(service) ,传播行为解决业务层不同方法间的调用问题。
比如业务层2个方法a()和b(),a()调用了b().
如果传播行为是PROPAGATION_REQUIRED,a()和b()都在一个事务中,
如果是PROPAGATION_REQUIRES_NEW,a()和b()在2个事务中,而且2个事务独立
如果是PROPAGATION_NESTED,则a()和b()在2个事务中,Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
详情参考 http://blog.sina.com.cn/s/blog_4b5bc0110100z7jr.html
事务状态
TransactionDefinition中定义了基本的事务属性,PlatformTransactionManager通过getTransaction()拿到事务定义,根据事务状态进行事务的管理。
(摘录自 https://www.cnblogs.com/ysocean/p/7617620.html#_label4)
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:
TransactionDefinition 接口方法如下:
实战案例
(事务管理分为编程式事务管理和声明式事务管理,具体见案例)
创建java web项目
在mydb数据库中创建数据表
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');
数据表记录了银行每个人编号,姓名,余额.
引入项目需要的包
写好spring配置文件。
properties文件:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.2.107:3306/mydb
jdbc.username=root
jdbc.password=root
applicationContext.xml(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"
xmlns:aop="http://www.springframework.org/schema/aop"
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"><!-- 引入jdbc.properties -->
<context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean><!-- 配置 Spring 的 dbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置DAO的类 -->
<bean id="accountDao" class="com.test.spring.demo1.AccountDaoImpl"></bean>
<!-- 配置业务层类 -->
<bean id="accountService" class="com.test.spring.demo1.AccountServiceImpl"></bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!-- 注解扫描 -->
<context:component-scan base-package="com.test.spring"/>
</beans>
dao层
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// out账户减少money元
@Override
public void outMoney(String out, Double money) {
String sql = "update account set money = money - ? where name = ? ";
jdbcTemplate.update(sql,money,out);
}// in 账户增加money元
@Override
public void inMoney(String in, Double money) {
String sql = "update account set money = money + ? where name = ? ";
jdbcTemplate.update(sql, money, in);
}}
Service层:
public class AccountServiceImpl implements AccountService {
// 注入事务管理的模板
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private AccountDao accountDao;
@Override
public void transfer(final String out, final String in, final Double money) {
// 编程式事务管理(实际开发很少使用)
// 备注: 匿名内部类要使用外部方法的形参必须用final修饰
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
accountDao.outMoney(out, money);
int i = 1 / 0; // 会导致出现异常,事务会回滚
accountDao.inMoney(in, money);
}
});
}}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
@Autowired
private AccountService accountService;
// aaa给bbb转账200
@Test
public void test() {
accountService.transfer("aaa", "bbb", 200d);
}
}
结果分析:
如果在service层没有事务管理,结果会导致aaa账户减少200元,bbb账户不变。配置了事务管理出现异常事务会回滚数据库不会有任何变化。
声明式事务管理:基于TransactionProxyFactoryBean的方式
将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"
xmlns:aop="http://www.springframework.org/schema/aop"
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"><!-- 引入db.properties -->
<context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置DAO的类 -->
<bean id="accountDao" class="com.test.spring.demo2.AccountDaoImpl"></bean>
<!-- 配置业务层类 -->
<bean id="accountService" class="com.test.spring.demo2.AccountServiceImpl"></bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池对象 -->
<property name="dataSource" ref="dataSource"></property>
</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 :事务的传播行为。
* ISOLATION :事务的隔离级别。
* readOnly :只读。
* -Exception :发生哪些异常回滚事务。
* +Exception :发生哪些异常事务不会滚。
-->
<!-- transfer开头的方法 -->
<prop key="transfer*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 注解扫描 -->
<context:component-scan base-package="com.test.spring"/>
</beans>
Service层改为
package cn.muke.spring.demo2;
/**
* 转账案例的业务成实现类
*/
public class AccountServiceImpl implements AccountService {
// 注入转账的DAO的类
private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}/**
* @param out :转出的账号
* @param in :转入的账号
* @param money :转账的金额
*/
public void transfer( String out, String in, Double money) {
accountDao.outMoney(out, money);
//int i =1/0;
accountDao.inMoney(in, money);
}}
Test改为:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo2 {
@Resource(name="accountServiceProxy")
private AccountService accountService;
@Test
public void test() {
accountService.transfer("aaa", "bbb", 200d);
}
}
(备注:此方法实际开发很少用)
声明式事务管理:基于AspectJ的XML方式(常用)
只需要把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"
xmlns:aop="http://www.springframework.org/schema/aop"
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"><!-- 引入db.properties -->
<context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置DAO的类 -->
<bean id="accountDao" class="com.test.spring.demo3.AccountDaoImpl"></bean>
<!-- 配置业务层类 -->
<bean id="accountService" class="com.test.spring.demo3.AccountServiceImpl"></bean>
<!-- 配置 Spring 的 jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知:(事务的增强) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation :事务传播行为
isolation :事务隔离级别
read-only :只读
rollback-for:发生哪些异常回滚
no-rollback-for:发生哪些异常不回滚
timeout :过期信息
-->
<tx:method name="transfer*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<!-- 第一个*:任意返回值,+:AccountService所有子类,*任意的方法,(..)任意的参数 -->
<aop:pointcut expression="execution(* com.test.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
<!-- 切面 -->
<!-- advisor只配置1个切点,aspect配置多切点 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
<!-- 注解扫描 -->
<context:component-scan base-package="com.test.spring"/>
</beans>
基于注解的事务管理(常用)
将配置文件改成
<?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:aop="http://www.springframework.org/schema/aop"
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"><!-- 引入db.properties -->
<context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置DAO的类 -->
<bean id="accountDao" class="com.test.spring.demo4.AccountDaoImpl"></bean>
<!-- 配置业务层类 -->
<bean id="accountService" class="com.test.spring.demo4.AccountServiceImpl"></bean>
<!-- 配置 Spring 的 jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注解扫描 -->
<context:component-scan base-package="com.test.spring.demo4"/>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
(主要是需要开启注解事务)
然后在需要事务管理的service层加上@Transactional注解,例如:
/**
*@Transational注解中的属性:
* propagation :事务的传播行为
* isolation :事务的隔离级别
* readOnly :只读
* rollbackFor :发生哪些异常回滚
* noRollbackFor:发生哪些异常不回滚
*/@Transactional(propagation=Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {@Autowired
private AccountDao accountDao;
@Override
public void transfer( String out, String in, Double money) {
// 声明式事务管理
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);}
}
这样就完成了spring的基于注解的事务管理.