一、spring的事务管理
1.事物的概念:
事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性。
事务就是一系列的动作, 它们被当作一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。2.事务的属性
事务的4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。原子性(atomicity):
事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用。
一致性(consistency):
一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中。
隔离性(isolation):
可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏。
持久性(durability):
一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中。
二、事务的分类
编程式事务和生命式事务1.编程式事务管理:
将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。可以清楚的定义事务的边界,可以实现细粒度的事务控制,开发者可以通过程序代码来控制你的事务何时开始,何时结束。如:JDBC的事务处理。但是事务的控制与业务代码耦合在一起,不能共用,而且开发效率低,不便于维护。在代码中 通过 TransactionTemplate 手动进行事务管理 ,在实际开发中很少被用到。
2.声明式事务:
将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理。 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化。 Spring 通过 Spring AOP 框架支持声明式事务管理。如果需要修改或者删除事务,移除配置即可,解除了系统服务(事务)与业务代码的耦合,而且方便管理。在实际开发中,一般提倡用声明式事务管理。
三中实现方式:
1.用xml配置文件实现事务管理
2.用注解的方式实现事务管理
3.用AOP的方式实现事务管理
三、spring中的事务管理器
1. spring的事务API
Spring 的核心事务管理抽象是
PlatformTransactionManager 平台事务管理器
* void commit(TransactionStatus status)
提交事务
* TransactionStatus getTransaction(TransactionDefinition definition)
根据事务定义信息,获得当前状态
* void rollback(TransactionStatus status)
回滚事务
TransactionDefinition 事务定义信息 (配置信息 来自xml配置文件 和 注解)
包括 (隔离、传播、超时、只读)
* ISOLATION_xxx 事务隔离级别
* PROPAGATION_xxx 事务传播行为
* int getTimeout() 获得超时信息(了解)
* boolean isReadOnly() 判断事务是否只读(了解)
TransactionStatus 事务具体运行状态
* 每一个时刻点 事务具体状态信息
管理封装了一组独立于技术的方法。 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的。
关系:
PlatformTransactionManager 根据 TransactionDefinition 进行事务管理, 管理过程中事务存在多种状态, 每个状态信息通过 TransactionStatus 表示
PlatformTransactionManager 接口详解
不同平台事务管理器实现1) 使用Spring JDBC或myBatis 进行持久化数据时使用
2) 使用Hibernate5.0版本进行持久化数据时使用
3)使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用(了解)
四、事务的实现
1.基于xml的实现
首先创建一个IUserDao接口
package com.dqsy.spring.dao;
import java.math.BigDecimal;
import com.dqsy.spring.entity.User;
/**
* 用户账户操作接口
* @author lenovo
*
*/
public interface IUserDao {
/**
* 添加金额
*/
public boolean addMoney(int userId, BigDecimal money);
/**
* 减少金额
*/
public boolean subMoney(int userId, BigDecimal money);
/**
* 添加用户
*/
public boolean addUser(User user);
/**
* 转出账户的金额
*/
public BigDecimal getAccount(int fromUserId);
}
实现这个接口
package com.dqsy.spring.dao.impl;
import java.math.BigDecimal;
import org.springframework.jdbc.core.JdbcTemplate;
import com.dqsy.spring.dao.IUserDao;
import com.dqsy.spring.entity.User;
public class IUserDaoImpl implements IUserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public boolean addMoney(int userId, BigDecimal money) {
// TODO Auto-generated method stub
boolean flag = false;
String sql = "update user set account = account + ? where id=?";
int rows = this.jdbcTemplate.update(sql, money, userId);
if(rows > 0){
flag = true;
}
return flag;
}
@Override
public boolean subMoney(int userId, BigDecimal money) {
// TODO Auto-generated method stub
boolean flag = false;
String sql = "update user set account = account - ? where id=?";
int rows = this.jdbcTemplate.update(sql, money, userId);
if(rows > 0){
flag = true;
}
return flag;
}
@Override
public boolean addUser(User user) {
// TODO Auto-generated method stub
boolean flag = false;
String sql = "insert into user(name, account) values(?, ?)";
int rows = this.jdbcTemplate.update(sql, user.getName(), user.getAccount());
if(rows > 0){
flag = true;
}
return flag;
}
@Override
public BigDecimal getAccount(int fromUserId) {
// TODO Auto-generated method stub
String sql = "select account from user where id=?";
BigDecimal account = this.jdbcTemplate
.queryForObject(sql, BigDecimal.class, fromUserId);
return account;
}
}
建立User实体类
package com.dqsy.spring.entity;
import java.math.BigDecimal;
public class User {
private int id;
private String name;
private BigDecimal account;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getAccount() {
return account;
}
public void setAccount(BigDecimal account) {
this.account = account;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", account=" + account + "]";
}
}
建立IUserService接口
package com.dqsy.spring.service;
import java.math.BigDecimal;
/**
* 用户转账的服务
* @author Administrator
*
*/
public interface IUserService {
public void operateAccount(int fromUserId, int toUserId, BigDecimal money);
public void operateMulAccount(int fromUserId, int toUserId, BigDecimal money);
}
实现这个接口
package com.dqsy.spring.service.impl;
import java.math.BigDecimal;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.dqsy.spring.dao.IUserDao;
import com.dqsy.spring.exception.UserAccountException;
import com.dqsy.spring.service.IUserService;
/**
* 转账服务业务层
*
* @author Administrator
*
*/
public class IUserServiceImpl implements IUserService {
private IUserDao userDao;
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
/**
* 实现转账 money : 转账金额
*/
@Override
public void operateAccount(int fromUserId, int toUserId, BigDecimal money) {
// TODO Auto-generated method stub
System.out.println("....开始转账.....");
System.out.println("转出金额 : " + money);
// 1.转出账户的金额
try {
BigDecimal fromAccount = userDao.getAccount(fromUserId);
/**
* 当fromAccount < money 是等于-1 当fromAccount = money 是等于0 当fromAccount
* > money 是等于 1
*/
if (!(fromAccount.compareTo(money) == -1)) {
// 2.从转出账户中扣除金额
userDao.subMoney(fromUserId, money);
//int a = 1/0;
// 3.在转入账户上加上转入金额
userDao.addMoney(toUserId, money);
} else {
throw new UserAccountException("余额不足");
}
} catch (UserAccountException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("....结束转账.....");
}
@Override
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
public void operateMulAccount(int fromUserId, int toUserId, BigDecimal money) {
// TODO Auto-generated method stub
System.out.println("....开始转账.....");
BigDecimal fromAccount = userDao.getAccount(fromUserId);
if (!(fromAccount.compareTo(money) == -1)) {
userDao.subMoney(fromUserId, money);
//int a = 1/0;
userDao.addMoney(toUserId, money);
}
System.out.println("....结束转账.....");
}
}
因为第一个方法operateAccount()方法是没有用事务的测试,在这我也付上代码
异常处理:
package com.dqsy.spring.exception;
public class UserAccountException extends Exception {
public UserAccountException() {
super();
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public UserAccountException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public UserAccountException(String string) {
// TODO Auto-generated constructor stub
super(string);
}
/**
*
*/
private static final long serialVersionUID = 1L;
}
配置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"
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/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<bean id="userService" class="com.dqsy.spring.service.impl.IUserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.dqsy.spring.dao.impl.IUserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 注册SpringJDBC查询模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 导入外部资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 注册Spring自带的管理数据库连接的数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 数据库连接的四要素 -->
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 注册事务管理器 -->
<bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注册事务代理 -->
<bean id="transProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="txMgr"></property>
<property name="target" ref="userService"></property>
<property name="transactionAttributes">
<props>
<prop key="oper*">ISOLATION_DEFAULT, PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
测试:
package com.dqsy.spring.test;
import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.dqsy.spring.service.IUserService;
public class AccountTransTest {
private ApplicationContext ctx = null;
@Before
public void init(){
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void test(){
IUserService userService = (IUserService) ctx.getBean("transProxy");
userService.operateMulAccount(10,11, new BigDecimal(100));
}
}
结果:
是可以转的,然后在IUserService中的operatorMul方法中手动添加一个异常int a=1/0;那么结果为?
查看数据库,没有变化,说明事务起作用了.
2.基于AspectJ实现事务管理
把注册事务代理的那部分代码改为如下:
<!-- 注册通知 -->
<tx:advice id="txAdvice" transaction-manager="txMgr">
<tx:attributes>
<tx:method name="oper*" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--切入点与通知配置 -->
<aop:config>
<aop:pointcut expression="execution(* *..IUserService.*(..))" id="userPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="userPointCut"/>
</aop:config>
测试类:
package com.dqsy.spring.test;
import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.dqsy.spring.service.IUserService;
public class AccountAopTest {
private ApplicationContext ctx = null;
@Before
public void init(){
ctx = new ClassPathXmlApplicationContext("applicationContext-aspectj.xml");
}
@Test
public void test(){
IUserService userService = (IUserService) ctx.getBean("userService");
userService.operateMulAccount(10,11, new BigDecimal(100));
}
}
结果同上
3.基于注解实现事务(非常重要)
Spring 还允许简单地用 @Transactional 注解来标注事务方法。
为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解。根据 Spring AOP 基于代理机制, 只能标注公有方法。
可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的。
在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了。
如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器。
把注册事务代理的那部分代码改为如下:
<!-- 事务管理的注解 -->
<tx:annotation-driven transaction-manager="txMgr"/>
IUserServiceImpl中添加注解:
这是得到的结果同样是相同的
五、事务传播属性
当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为.
常用的是二种:
* PROPAGATION_REQUIRED :如果存在一个事务,则支持当前事务,如果没有事务则开启事务
* PROPAGATION_REQUIED_NEW :总是开启一个新事务
REQUIRED 传播行为
从起始点到终止点只有一个事务。要么行,要么不行。REQUIRES_NEW 传播行为
它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它。简单的理解就是把一个大的事务管理分成若干个小事务,即使,在几个小事务后面有异常,但是前面的几个事务都提交了
事务的隔离级别
从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行。
在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.
事务的隔离级别可以通过隔离事务属性指定。
事务并发产生的问题:
脏读:一个事务读取到了另外一个事务没有提交的数据
事务1:更新一条数据
------------->事务2:读取事务1更新的记录
事务1:调用commit进行提交
***此时事务2读取到的数据是保存在数据库内存中的数据,称为脏读。
***读到的数据为脏数据
不可重复读:在同一事务中,两次读取同一数据,得到内容不同
事务1:查询一条记录
-------------->事务2:更新事务1查询的记录
-------------->事务2:调用commit进行提交
事务1:再次查询上次的记录
***此时事务1对同一数据查询了两次,可得到的内容不同,称为不可重复读。
幻读:同一事务中,用同样的操作读取两次,得到的记录数不相同
事务1:查询表中所有记录
-------------->事务2:插入一条记录
-------------->事务2:调用commit进行提交
事务1:再次查询表中所有记录
***此时事务1两次查询到的记录是不一样的,称为幻读
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持。
MySQL支持四种。前四种。
Oracle支持二种。READ_COMMITED , SERIALIZABLE
总结:
声明式事务管理:
基于TransactionFactoryProxyBean(很少使用,需要为每一个进行事务管理的类,配置一个增强生成代理。)
基于AspectJ的xml方式(常用,配置比xml复杂,但在配置文件中可以清晰看到增强的位置,并且类上不需要做任何修改。)
基于注解方式(常用,配置简单,但是需要修改业务层的类,为它们加上@Transactional注解。)