数据准确性是每个后端开端必备要求之一。而关系数据库提供的事务《mysql事务》保证了这一点。spring框架也提供了spring-tx针对事务设计,让开发者自由接入。
引入spring-tx包,利用《spring-aop 扩展代理》中介绍的FactoryBean模式进业务对得事务代理。
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="username" value="root">property>
<property name="password" value="123456">property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql://localhost/test">property>
bean>
<bean id="userService" class="mapper.UserSevice">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionManager" class="utils.CustomTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="userService"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticExceptionprop>
props>
property>
bean>
//业务类
public class UserSevice {
private DataSource dataSource;
public void transfer() throws Exception {
Object object = TransactionSynchronizationManager.getResource(dataSource);
ConnectionHolder connectionHolder = (ConnectionHolder) object;
Connection conn = connectionHolder.getConnection();
Statement stat = conn.createStatement();
int c = stat.executeUpdate("update users set type = 0 where id='65'");
throw new RuntimeException("yoyo");
}
}
public class ConnectionHolder extends ResourceHolderSupport {
private final Connection connection;
public ConnectionHolder(Connection connection) {
this.connection = connection;
}
public Connection getConnection() {
return this.connection;
}
}
public class CustomTransactionObject {
private ConnectionHolder connectionHolder;
public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) {
this.connectionHolder = connectionHolder;
}
public ConnectionHolder getConnectionHolder(){
return this.connectionHolder;
}
}
//自定义事务管理器
public class CustomTransactionManager extends AbstractPlatformTransactionManager {
private DataSource dataSource;
protected Object doGetTransaction() throws TransactionException {
CustomTransactionObject txObject = new CustomTransactionObject();
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder);
return txObject;
}
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
{
CustomTransactionObject txObject = (CustomTransactionObject) transaction;
Connection con = null;
try {
if (txObject.getConnectionHolder() ==null ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon));
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
con.setAutoCommit(false);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
} catch (Throwable ex) {
con.close();
}
}
}
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
CustomTransactionObject txObject = (CustomTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
con.commit();
}
}
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
CustomTransactionObject txObject = (CustomTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
con.rollback();
}
protected void doCleanupAfterCompletion(Object transaction) {
CustomTransactionObject txObject = (CustomTransactionObject) transaction;
TransactionSynchronizationManager.unbindResource(obtainDataSource());
try {
txObject.getConnectionHolder().getConnection().close();
} catch (SQLException e) {
e.printStackTrace();
}
txObject.getConnectionHolder().clear();
}
public DataSource obtainDataSource() {
return dataSource;
}
}
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-tx.xml");
UserSevice userService = (UserSevice) context.getBean("accountServiceProxy");
userService.transfer();
为了看懂以上的Demo,我们先了解下几个核心类
事务的基本属性
public interface TransactionDefinition {
//未提交读,值为1
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
//提交读,值为2
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
//可重复读,值为4
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
//可串行化,值为8
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
//不设置,走数据库默认级别
int ISOLATION_DEFAULT = -1;
//返回事务隔离级别
int getIsolationLevel();
//是否只读事务
boolean isReadOnly();
/**------以下是spring对事务扩展的概念--------**/
//若当前存在事务,则加入该事务,若不存在事务,则新建一个事务
int PROPAGATION_REQUIRED = 0;
//支持当前事务,若当前不存在事务,以非事务的方式执行。
int PROPAGATION_SUPPORTS = 1;
//强制事务执行,若当前不存在事务,则抛出异常
int PROPAGATION_MANDATORY = 2;
//若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务
int PROPAGATION_REQUIRES_NEW = 3;
//以非事务的方式执行,若当前存在事务,则把当前事务挂起。
int PROPAGATION_NOT_SUPPORTED = 4;
//以非事务的方式执行,如果当前存在事务,则抛出异常。
int PROPAGATION_NEVER = 5;
//如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。mysql 使用savePoint来完成
int PROPAGATION_NESTED = 6;
//返回事务的传输级别 0-6
int getPropagationBehavior();
//默认为无超时时间
int TIMEOUT_DEFAULT = -1;
//返回默认事务超时时间
int getTimeout();
//事务名称,用于监听器标识事务的来源应用,无具体作用
String getName();
}
对TransactionDefinition接口的基本实现
public class DefaultTransactionDefinition {
//通过反射,把TransactionDefinition定义的值转为Map类型
static final Constants constants = new Constants(TransactionDefinition.class);
//设置默认的传播级别
private int propagationBehavior = PROPAGATION_REQUIRED;
//设置默认事务隔离级别
private int isolationLevel = ISOLATION_DEFAULT;
//事务超时时间
private int timeout = TIMEOUT_DEFAULT;
//是否只读事务
private boolean readOnly = false;
//事务名称,用于监听器标识事务的来源应用
private String name;
}
扩展了TransactionDefinition,增加了spring事务的功能点
public interface TransactionAttribute {
//用于spring多manager,可根据qualifier值去容器中找到对应值
String getQualifier();
//回滚异常判断,有些异常可以不回滚直接提交
boolean rollbackOn(Throwable ex);
}
对TransactionAttribute进行实现,并对应的业务方法进行映射
public class DefaultTransactionAttribute {
//对应事务manager名称
private String qualifier;
//对应事务所在方法的标识,实现业务方法于配置的映射
//@see AbstractFallbackTransactionAttributeSource.getTransactionAttribute
private String descriptor;
}
对rollbackOn的异常进行黑白名单的配置。如果二者都名单就以那个异常层级高为胜者
public class RuleBasedTransactionAttribute {
//白名单,以-为前缀的异常直接回滚
//@see RollbackRuleAttribute
public static final String PREFIX_ROLLBACK_RULE = "-";
//黑名单,以+为异常的名字不回滚提交
//@see NoRollbackRuleAttribute
public static final String PREFIX_COMMIT_RULE = "+";
// 回滚策略集合
//@see TransactionAttributeEditor.setAsText
private List<RollbackRuleAttribute> rollbackRules;
}
主要实现切点与配置的映射。
public interface TransactionAttributeSource {
//根据class-method获取方法上事务的配置
//根据不同的代理方式,其映射实现不同
TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass);
}
FactoryBean方式采用的切点映射实现
public class NameMatchTransactionAttributeSource{
// 方法class-method与配置的Map
private Map<String, TransactionAttribute> nameMap = new HashMap<>();
// 参数对应Demo中的transactionAttributes配置
public void setProperties(Properties transactionAttributes) {
TransactionAttributeEditor tae = new TransactionAttributeEditor();
Enumeration<?> propNames = transactionAttributes.propertyNames();
while (propNames.hasMoreElements()) {
//demo中key就是切点
String methodName = (String) propNames.nextElement();
//value就是配置行
String value = transactionAttributes.getProperty(methodName);
//通过TransactionAttributeEditor解析配置行
tae.setAsText(value);
TransactionAttribute attr = (TransactionAttribute) tae.getValue();
//将解析好的配置缓存到nameMap中
addTransactionalMethod(methodName, attr);
}
}
}
mysql自带的savapoint功能, 实现 PROPAGATION_NESTED 嵌套事务
// @see java.sql.Savepoint
public interface SavepointManager {
//创建建savepoint
Object createSavepoint() throws TransactionException;
//回滚事务到savepoint
void rollbackToSavepoint(Object savepoint) throws TransactionException;
//释放savepoint,相当于提交
void releaseSavepoint(Object savepoint) throws TransactionException;
}
事务状态,每个pointcut对应一个状态
public interface TransactionStatus {
//是否针对这切面为新创事务
boolean isNewTransaction();
//是否有 savapoint
boolean hasSavepoint();
// 设置回滚标记
void setRollbackOnly();
// 当回滚标记为true, commit失效全部rollback
// @see AbstractPlatformTransactionManager.commit
boolean isRollbackOnly();
// 把事务中内存修改的数据提交到数据库,一些orm框架有用到
// 例如: Hibernate/JPA sessions
void flush();
// 事务是否已完成, commit/rollback 后
boolean isCompleted();
}
对TransactionStatus的部分实现
public abstract class AbstractTransactionStatus {
// 本地事务回滚标记
private boolean rollbackOnly = false;
// 事务是否已完成
private boolean completed = false;
private Object savepoint;
}
对TransactionStatus的实现
public class DefaultTransactionStatus {
//真实的事务对象
private final Object transaction;
// 是否新建事务标识
private final boolean newTransaction;
// 是否事务状态同步, 相当于缓存
// @see TransactionSynchronizationManager
private final boolean newSynchronization;
// 是否只读
private final boolean readOnly;
// 暂存事务状态, 用于2事务交互
// 每个cutpoint,如果有外部事务,会把部事务挂在此对象上
// @see SuspendedResourcesHolder
private final Object suspendedResources;
}
public abstract class AbstractPlatformTransactionManager {
// 一直保持事务同步,不管事务是否为空, PROPAGATION_SUPPORTS
// @see newSynchronization
public static final int SYNCHRONIZATION_ALWAYS = 0;
// 如果事务为空,则不同步。PROPAGATION_SUPPORTS
// @see newSynchronization
public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;
// 一直不同步,不管有没有事务
// @see newSynchronization
public static final int SYNCHRONIZATION_NEVER = 2;
// 默认为地直保持事务同步
private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
// 把AbstractPlatformTransactionManager的常量,变成map形式
private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class);
// 默认为事务不超时
private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT;
// 是否允许嵌套事务
private boolean nestedTransactionAllowed = false;
// 是否开启事务验证,如果外部事务和内部事务不兼容, PROPAGATION_REQUIRED or PROPAGATION_SUPPORTS
// 如果为 false, 则直接忽略内部事务配置
private boolean validateExistingTransaction = false;
// 事务全局否标记回滚,和本地事务回滚相互作用
// @see rollbackOnly
private boolean globalRollbackOnParticipationFailure = true;
// 如果提交成功被打 rollback = true 导致抛出UnexpectedRollbackException异常
// 默认为false不抛出
private boolean failEarlyOnGlobalRollbackOnly = false;
// 提交失败是否回滚,否则触发 TransactionSynchronization 通知事务
private boolean rollbackOnCommitFailure = false;
// 根据配置属性获取事务状态
// 判断是否使用已有事务
// 如果已有事务
// PROPAGATION_NEVER 报错
// PROPAGATION_NOT_SUPPORTED 挂起已有事务,创建空事务
// PROPAGATION_REQUIRES_NEW 挂起已有事务, 创建事务
// PROPAGATION_NESTED 创建savepoint
// PROPAGATION_REQUIRED || PROPAGATION_SUPPORTS || PROPAGATION_MANDATORY 继续使用当前事务
// 如果没有事务
// PROPAGATION_MANDATORY 报错
// PROPAGATION_REQUIRED || PROPAGATION_REQUIRES_NEW || PROPAGATION_NESTED 创建新事务状态
// PROPAGATION_SUPPORTS || PROPAGATION_NOT_SUPPORTED || PROPAGATION_NEVER 创建空事务
// @see suspend 挂起事务
// @see prepareSynchronization 同步事务状态到全局
public final TransactionStatus getTransaction(TransactionDefinition definition);
// 根据当前的事务状态提交事务
// 如果本地标记了rollback, 则调用回滚方法
// 触发事件 BeforeCommit || BeforeCompletion
// 如果是savepoint 释放 savepoint
// 如果是普通事务, 则手动提交
// 触发事件 AfterCommit AfterCompletion
// 如果有外部事务,则恢复
// @see resume 恢复事务
// @see trigger***
void commit(TransactionStatus status) throws TransactionException;
// 根据当前的事务状态回滚事务
// 触发事件 BeforeCompletion
// 如果是savepoint 回退到 savepoint
// 如果是普通事务, 则手动回滚
// 触发事件 AfterCompletion
// 如果有外部事务,则恢复
// @see resume 恢复事务
// @see trigger***
void rollback(TransactionStatus status) throws TransactionException;
}
标识事务连接conn的状态
public abstract class ResourceHolderSupport implements ResourceHolder {
// 是否已关系到事务对象
private boolean synchronizedWithTransaction = false;
// 是否rollback
private boolean rollbackOnly = false;
// 事务timeout时间
private Date deadline;
// 连接调用次数
private int referenceCount = 0;
// 是否事务连接失效
private boolean isVoid = false;
}
事务全局状态管理器
public abstract class TransactionSynchronizationManager {
// key:线程+连接池 value: 事务连接ResourceHolder
// 相当于一个事务下可以搞多个连接池的连接, 同时开启,同时提交,同时回滚,但还有很小可能造成数据不一致
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// key: 线程 value: 监听事务发的事件
// 其生命周期跟着事务。
// 在prepareSynchronization初始化之后,可调用registerSynchronization进行注册
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
// 当前线程正在执行的事务配置名
// @see TransactionDefinition.getName
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
// 当前线程正在执行事务是否只读事务
// @see TransactionDefinition.isReadOnly
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
// 当前线程正在执行事务隔离级别
// @see TransactionDefinition.getIsolationLevel
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
// 当前线程正在执行事务是否活跃
// @see DefaultTransactionStatus.hasTransaction
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
}
通过AbstractPlatformTransactionManager类的分析,发现spring-tx只是定义一个一个事务的流转过程,并不约束具体实现。其实大致可以分为以下三种:
上述三种都需要对接第三方包,其中DataSourceTransactionManager在spring-jdbc包中实现
引入spring-jdbc包,demo进阶为
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
public class UserSevice {
private DataSource dataSource;
public void transfer() throws Exception {
Connection conn = DataSourceUtils.getConnection(dataSource);
Statement stat = conn.createStatement();
int c = stat.executeUpdate("update users set type = 0 where id='38'");
throw new RuntimeException("yoyo");
}
}
- DataSourceTransactionManager的原理和我们的CustomTransactionManager类似
- DataSourceUtils 也是辅助在业务代码中更方便的获取conn
另外jdbc包可引入JdbcTemplate把一些获取conn,stat的操作给隐藏掉
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<bean id="userService" class="mapper.UserSevice">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
bean>
public class UserSevice {
private JdbcTemplate jdbcTemplate;
public void transfer() throws Exception {
int c =jdbcTemplate.update("update users set type = 0 where id='38'");
throw new RuntimeException("yoyo");
}
}
通过《spring-aop 扩展代理》的分析,其代码方式主要有2种。
通过分析TransactionProxyFactoryBean类
// TransactionProxyFactoryBean实现了InitializingBean
// 初始化时触发afterPropertiesSet方法
public void afterPropertiesSet() {
//创建代理工厂类
ProxyFactory proxyFactory = new ProxyFactory();
// TransactionInterceptor 做为Advisor
// TransactionAttributeSourcePointcut 做为pointcut
transactionAttributes 属性做判断
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor()));
// 设置代理目标对象
TargetSource targetSource = createTargetSource(this.target);
proxyFactory.setTargetSource(targetSource);
// 创建代理对象,在getObject返回
// 调用方法,通过TransactionAttributeSourcePointcut的matches方法判断是否代理,matches主要判断是根据transactionAttributes属性来
// 如果方法合理,通过TransactionInterceptor的父类 TransactionAspectSupport.invokeWithinTransaction开始事务的判断
this.proxy = proxyFactory.getProxy(this.proxyClassLoader);
}
通过
- 注册BeanFactoryTransactionAttributeSourceAdvisor Bean
- 设置1的transactionAttributeSource属性为AnnotationTransactionAttributeSource Bean
- 设置1的adviceBeanName属性为 TransactionInterceptor Bean 的名字
- 根据spring-context原理,spring会先加载Advisor
- 根据spring-aop原理,spring加载Bean时会取容器中Advisor进行pointcut判断matchs
- BeanFactoryTransactionAttributeSourceAdvisor 用的 pointcut 为 TransactionAttributeSourcePointcut,其matchs逻辑依赖 AnnotationTransactionAttributeSource
- AnnotationTransactionAttributeSource 是专门用来识别 @Transactional
- 如果matchs通过,则通过TransactionInterceptor的父类 TransactionAspectSupport.invokeWithinTransaction开始事务的判断
<tx:annotation-driven transaction-manager="transactionManager" />
public class UserSevice {
private JdbcTemplate jdbcTemplate;
@Transactional
public void transfer() throws Exception {
int c =jdbcTemplate.update("update users set type = 0 where id='38'");
throw new RuntimeException("yoyo");
}
}
《Spring事务实现原理及源码分析》
《Spring-tx模块分析》