今天来和大家分享一下spring 的事务管理机制。也是很重要的一个概念。简单的说,spring的事务可以分为声明式事务和编程式事务。
首先先来说明一下事务的概念:
事务(Transcation),一般指我们要做的事情。是恢复和并发控制的基本单位。具备四个属性:
- 原子性(atomicity):一个事务中包括的操作要么都做,要不都不做。
- 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
也是成为事务的ACID属性。
二者的区别
编程式事务是需要我们手动去实现的。而声明式事务是基于springAOP来实现的。其本质就是对方法前后实现拦截来实现的。用官方的话说就是,编程式事务的耦合度太高,入侵性比较强的。而声明式恰好弥补了前者的缺点。当然,spring在支持编程式事务时,现在也提出了TransactionTemplate模班。也是spring比较推荐的。
举个简单的例子:
转账:这是在合适不过的例子了。小博要给小硕转2000元。那这个问题应该怎么解决。
首先这应该是一个方法级来实现的。看下面
/****
* 转账方法
* sourceName:转出账户
* targerName :转入账户
* money:钱
****/
public boolean transferOfAccount(String sourceName,String targeName,Float money){
//根据转出账户名查转出账户是否存在
Account source = accountDao.findAccountByName(sourceName);
//根据转入账户查询转入账户是否存在
Account targer = accountDao.findAccountByName(targeName);
//转出账户减钱
source.setMoney(source.getMoney()-money);
//转入账户加钱
targer.setMoney(targer.getMoney()+money);
//更新转出账户
accountDao.updateAccount(source);
//更新转入账户
accountDao.updateAccount(targer);
}
我们普通的方法应该就是这么实现的。但是有一个问题,在上面这个方法里面的任何一个环节出现问题,那么应该是转账失败的。而我们传统的这种写法是没有事务支持的。导致这几个步骤是独立的。每一个步骤是一个独立的事务,执行完即提交。所以,如果在生活中直接用这样的方法来实现转账,那么,天下大乱。
那怎么办?
好,我们先来看一下spring的事务机制实现:
POM文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tff.demo</groupId>
<artifactId>spring_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<!-- 解析切点表达式的 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
</dependencies>
</project>
dao层(利用springJdbc来完成对数据库的操作)
package com.springtx.demo.dao;
import com.springtx.demo.domain.Account;
import java.util.List;
import java.util.Map;
public interface AccountDao {
/**
* 根据 id 查询账户信息
*
* @param id
* @return
*/
Account findAccountById(Integer id);
/**
* 根据名称查询账户信息
*
* @return }
*/
Account findAccountByName(String name);
/**
* 更新账户信息
*
* @param account
*/
void updateAccount(Account account);
//查询所有的信息
List<Account> findAccountAll();
}
package com.springtx.demo.dao.impl;
import com.springtx.demo.dao.AccountDao;
import com.springtx.demo.dao.rowmapper.AccountRowMapper;
import com.springtx.demo.domain.Account;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
public Account findAccountById(Integer id) {
//方法里面第二参数,是我们封装的一个类,这个类实现了RowMapper<T>
List<Account> list = getJdbcTemplate().query("select * from account1 where id = ?", new AccountRowMapper(), id);
return list.isEmpty() ? null : list.get(0);
}
public Account findAccountByName(String name) {
List<Account> list = getJdbcTemplate().query("select * from account1 where name = ?", new AccountRowMapper(), name);
if (list.isEmpty()) {
return null;
}
if (list.size() > 1) {
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
System.out.println("转账方法已经执行完毕=====================================");
return list.get(0);
}
public void updateAccount(Account account) {
getJdbcTemplate().update("update account1 set name = ?,money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
}
public List<Account> findAccountAll() {
List<Account> list = getJdbcTemplate().query("select * from account1 ", new RowMapper<Account>() {
public Account mapRow(ResultSet rs, int i) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
});
return list;
}
}
封装的参数类(也可以写成内部类的形式)
package com.springtx.demo.dao.rowmapper;
import com.springtx.demo.domain.Account;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class AccountRowMapper implements RowMapper<Account> {
/**
* 封装账户的返回数据,也可以写成内部类的形式
* @param rs
* @return
* @throws SQLException
*/
public Account mapRow(ResultSet rs, int rownum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
service层
package com.springtx.demo.service;
import com.springtx.demo.domain.Account;
import java.util.List;
public interface AccountService {
/**
* 根据 id 查询账户信息
* @param id
* @return
*/
Account findAccountById(Integer id);//查
/**
* 转账
* @param sourceName 转出账户名称
* @param targeName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName,String targeName,Float money);//增删改
//查询所有的信息
List<Account> findAccountAll();
}
package com.springtx.demo.service.impl;
import com.springtx.demo.dao.AccountDao;
import com.springtx.demo.domain.Account;
import com.springtx.demo.service.AccountService;
import java.util.List;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
public void transfer(String sourceName, String targeName, Float money) {
Account source = accountDao.findAccountByName(sourceName);
Account targer = accountDao.findAccountByName(targeName);
source.setMoney(source.getMoney()-money);
targer.setMoney(targer.getMoney()+money);
accountDao.updateAccount(source);
//int i = 5 / 0;
accountDao.updateAccount(targer);
}
public List<Account> findAccountAll() {
return accountDao.findAccountAll();
}
}
实体类:
package com.springtx.demo.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
日志类(只是为了给大家说明一下springAOP面向切面的编程)
package com.springtx.demo.log;
import java.util.Date;
public class Log {
public void logBefore(){
System.out.println("之前日志 === Log日志打印"+new Date());
}
public void logAfterReturning(){
System.out.println("之后日志 === Log日志打印"+new Date());
}
public void logThrow(){
System.out.println("异常日志 === Log日志打印"+new Date());
}
public void logAfter(){
System.out.println("最终 === Log日志打印"+new Date());
}
public void loground(){
System.out.println("环绕日志 === Log日志打印"+new Date());
}
}
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: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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service层 -->
<bean id="accountService" class="com.springtx.demo.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置dao层 -->
<bean id="accountDao" class="com.springtx.demo.dao.impl.AccountDaoImpl">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.10.6.45:3396/TEST"></property>
<property name="username" value="root"></property>
<property name="password" value="Az.123456"></property>
</bean>
<!-- 配置一个事务管理器 (springJDBC提供的事务机制,一会在后面详细讲解) -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="log" class="com.springtx.demo.log.Log"></bean>
<!-- 事务的配置(曾强的配置) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 配置事务的属性 相当于编程时中显示的调用事务 -->
<tx:attributes>
<!-- 指定方法名称:是业务核心方法
read-only:是否是只读事务。默认 false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回
滚。没有默认值,任何异常都回滚。
-->
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut id="pt1" expression="execution(* com.springtx.demo.service.impl.*.*(..))"></aop:pointcut>
<!-- 实现springJdBC的事务机制 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
<!-- 为了演示spring的面向切面编程 -->
<aop:aspect id="logP" ref="log">
<aop:before method="logBefore" pointcut-ref="pt1"></aop:before>
<aop:after-throwing method="logThrow" pointcut-ref="pt1"></aop:after-throwing>
<aop:after-returning method="logAfterReturning" pointcut-ref="pt1"></aop:after-returning>
<aop:after method="logAfter" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
运行结果:
我们看到,存续已经正常运行出来了。
可能就会有人问,这不是一样的吗?看代码,好像是一样的。但是,这已经是实现了事务机制的。那个转账方法“transOfAccount”里面的那几个步骤会当成一个事务,还记得我们上面讲的事务的特性吗?其中第一条,原子性。也就是,这个转账方法,要么全部执行完毕,要不就不执行。多用在恢复和事务并发的场合。就已经很能说能问题了。如果,我们在转账过程中,遇到异常或者网络问题转账失败时,事务会进行恢复。要满足一致性的特点。
注解实现方式
很多地方都一样,只需要改动很小的一部分
第一步:配置事务管理器并注入数据源
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
第二步:在业务层使用@Transactional注解
@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
@Override
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
public void transfer(String sourceName, String targeName, Float money) {
//1.根据名称查询两个账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targeName);
//2.修改两个账户的金额
source.setMoney(source.getMoney()-money);//转出账户减钱
target.setMoney(target.getMoney()+money);//转入账户加钱
//3.更新两个账户
accountDao.updateAccount(source);
//int i=1/0;
accountDao.updateAccount(target);
}
}
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
第三步:在配置文件中开启Spring对注解事务的支持
<!-- 开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
备注:如果不适用XML来实现第三步,要使用注解的话如下:
@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器。在之前的步骤已经写过了。
}
接下来我们看一张图:
这是spring对事务管理及涉及的接口:
而我们上面的demo正是用的springJDBC提供的事务支持。所以,我们在配置文件中肯定要注入DataSourceTransactionManager这个类。
spring事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.transaction;
public interface PlatformTransactionManager {
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。
- JDBC事务
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- Hibernate
如果应用程序的持久化是通过Hibernate实现的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
- Java持久化API(JPA)
Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。
4. Java 原生的API
如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。
对spring声明式事务的讲解就到这里了。欢迎各路大神批评指正!