工作是一场马拉松不是短跑。但是即便是马拉松,速度也很重要
本篇博文定位为为事务相关的其余博文的工具博文,属于Spring事务相关的基础类的打点、扫盲篇。
因为Spring的事务属于它非常非常重要的一块,因此内部的一些核心类、核心API有必要做个系统的了解,才能更好的了解到其深意。
TransactionAttribute
这个接口是在TransactionDefinition
的基础上的扩展
// 它继承自TransactionDefinition ,所有可以定义事务的基础属性
public interface TransactionAttribute extends TransactionDefinition {
// 返回与此事务属性关联的限定符值
//@since 3.0
@Nullable
String getQualifier();
// Should we roll back on the given exception?
boolean rollbackOn(Throwable ex);
}
它的主要实现类有:DefaultTransactionAttribute
、RuleBasedTransactionAttribute
、DelegatingTransactionAttribute
默认的事务属性实现,也是最常用的一个实现。
// 它继承自DefaultTransactionDefinition
public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {
@Nullable
private String qualifier;
@Nullable
private String descriptor;
// 你自己也可以自定义一个TransactionAttribute other 来替换掉一些默认行为
public DefaultTransactionAttribute() {
super();
}
public DefaultTransactionAttribute(TransactionAttribute other) {
super(other);
}
// @since 3.0
public void setQualifier(@Nullable String qualifier) {
this.qualifier = qualifier;
}
@Override
@Nullable
public String getQualifier() {
return this.qualifier;
}
...
// 可以清晰的看到:默认只回滚RuntimeException 或者 Error(比如OOM这种)
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
}
这个在基础实现的基础上扩展了一下,扩展了一些规则。
public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
/** Prefix for rollback-on-exception rules in description strings. */
public static final String PREFIX_ROLLBACK_RULE = "-";
/** Prefix for commit-on-exception rules in description strings. */
public static final String PREFIX_COMMIT_RULE = "+";
// RollbackRuleAttribute:它是个实体类,确定给定异常是否应导致回滚的规则
// 相当于封装了这个规则的一个实体,内部封装一个异常 提供一个实例变量: 这个变量相当于回滚规则为只回滚RuntimeException
// public static final RollbackRuleAttribute ROLLBACK_ON_RUNTIME_EXCEPTIONS = new RollbackRuleAttribute(RuntimeException.class);
// 所以此类最重要的一个属性,就是这个,它能维护多种回滚的规则~~~~
@Nullable
private List<RollbackRuleAttribute> rollbackRules;
...
public List<RollbackRuleAttribute> getRollbackRules() {
if (this.rollbackRules == null) {
this.rollbackRules = new LinkedList<>();
}
return this.rollbackRules;
}
// 核心逻辑输入,复写了父类的rollbackOn方法。也就是看看当前异常是否需要回滚呢???
@Override
public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
// 这里getDepth()就是去看看异常栈里面 该类型的异常处于啥位置。
// 这里用了Integer的最大值,基本相当于不管异常有多深,遇上此异常都应该回滚喽,也就是找到这个winnner了~~~~~
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
// User superclass behavior (rollback on unchecked) if no rule matches.
// 这句相当于:如果你没有指定回滚规则,那就交给父类吧(只回滚RuntimeException和Error类型)
if (winner == null) {
return super.rollbackOn(ex);
}
// 最终只要找到了,但是不是NoRollbackRuleAttribute类型就成`~~~~
return !(winner instanceof NoRollbackRuleAttribute);
}
}
很显然,它就是一个简单的代理,内部持有一个TransactionAttribute
的引用。自己也是个抽象类,没做啥事,此处略过。它也继承自:DelegatingTransactionDefinition
TransactionDefinition
事务的定义,上面已经介绍了一个重要分支:TransactionAttribute
。接下来继续介绍另外一个分支:
一样的也就是个代理抽象类,啥都木有做。内部持有一个TransactionDefinition targetDefinition
的引用而已,所有方法都是委托给targetDefinition
去做的
ResourceTransactionDefinition
这个子接口非常的新,是Spring5.1才提供的
// @since 5.1
// 指示资源事务,尤其是事务性资源是否准备好进行本地优化
public interface ResourceTransactionDefinition extends TransactionDefinition {
// 确定事务性资源是否准备好进行本地优化
// @see #isReadOnly()
boolean isLocalResource();
}
它和ResourceTransactionManager
的使用相关联。ResourceTransactionManager
是PlatformTransactionManager
的一个子接口。
我们最常用的事务管理器DataSourceTransactionManager
也实现了这个接口~~~~
目前Spring还未提供任何
ResourceTransactionDefinition
它的具体实现~
它位于包:org.springframework.transaction.interceptor
它有点类似于之前讲过的TargetSource
,它也是对TransactionAttribute
进行了一层包装~~
public interface TransactionAttributeSource {
// 通过Method和目标类,拿到事务属性~~~
// 比如我们的@Transaction是标注在方法上的,可议自定义方法级别的事务属性,用它就特别的方便~
@Nullable
TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass);
}
这里有很多人不明白了,为何都给了Method,为啥还要传入Class呢?难道Method还不知道它所属的类???
这里做如下解释:
method
– 目前正在进行的方法调用targetClass
– 真正
要调用的方法所在的类这里是有细微差别的:
method
的所属类不一样是targetClass
。比如:method是代理对象的方法,它的所属类是代理出来的类targetClass
一定会有一个方法和method
的方法签名一样通常情况下,
method
的所属类会是targetClass
的某个祖先类或者实现的某个接口。(动态代理)
TransactionAttributeSource
一般都是作为TransactionInterceptor
的一个属性被set进去,然后看看这个事务属性可以作用在不同的方法上面,实现不同方法的个性化定制~
(实际真正处理它的是父类TransactionAspectSupport
,它会做匹配~~~~) 具体的在详解TransactionInterceptor
的时候会讲述到
根据名字就能匹配,然后该事务属性就会作用在对应的方法上。比如下面例子:
// 自定义配置一个事务拦截器(@Transaction注解也会使用此拦截器进行拦截)
@Bean
public TransactionInterceptor transactionInterceptor(PlatformTransactionManager transactionManager) {
Map<String, TransactionAttribute> txMap = new HashMap<>();
// required事务 适用于觉得部分场景~
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(RuntimeException.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("delete*", requiredTx);
// 查询 使用只读事务
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
txMap.put("get*", readOnlyTx);
txMap.put("query*", readOnlyTx);
// 定义事务属性的source~~~ 此处使用它 也就是根据方法名进行匹配的~~~
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.setNameMap(txMap);
return new TransactionInterceptor(transactionManager, source);
}
这个在我们基于XML的配置事务的时候,原理就是这样的~~
注意此处的匹配模式也是基于简单匹配的:
PatternMatchUtils.simpleMatch
。而非强大的正则匹配。底层getTransactionAttribute()
时会根据不同的方法名,来返回不同的事务属性~~~
它的使用方式和NameMatchTransactionAttributeSource
基本相同,但是有一个不同在于:
如果使用NameMatchTransactionAttributeSource
配置属性源,比如get*配置为执行事务,那么所有的bean的get方法都会被加上事务,这可能不是我们想要的,因此对于自动代理,我们更好的选择是MethodMapTransactionAttributeSource
,它需要指定需要事务化的完整类名和方法名
若看前面AOP文章的详细讲解,讲述过它ComposablePointcut
:
【小家Spring】Spring AOP各个组件概述与总结【Pointcut、Advice、Advisor、Advised、TargetSource、AdvisorChainFactory…】
它代表一种组合模式。
// @since 2.0 它是Spring2.0后才推出来的
public class CompositeTransactionAttributeSource implements TransactionAttributeSource, Serializable {
private final TransactionAttributeSource[] transactionAttributeSources;
public CompositeTransactionAttributeSource(TransactionAttributeSource... transactionAttributeSources) {
Assert.notNull(transactionAttributeSources, "TransactionAttributeSource array must not be null");
this.transactionAttributeSources = transactionAttributeSources;
}
// 这个实现方法也很容易。多个TransactionAttributeSource放在一起,只要任意一个匹配上就成
// 备注:若匹配上多个,请注意先后顺序就成 这里面是数组 会保持和你放入的顺序一样~~~
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
for (TransactionAttributeSource source : this.transactionAttributeSources) {
TransactionAttribute attr = source.getTransactionAttribute(method, targetClass);
if (attr != null) {
return attr;
}
}
return null;
}
}
它是TransactionAttributeSource
的一个最简单的实现,每次调用,都是返回相同的TransactionAttribute
当然它是可议set一个
TransactionAttribute
作为通用的事务属性的实现的
AnnotationTransactionAttributeSource
这个就是重点了,它是基于注解驱动的事务管理的事务属性源,和@Transaction
相关,也是现在使用得最最多的方式。
它的基本作用为:它遇上比如@Transaction
标注的方法时,此类会分析此事务注解,最终组织形成一个TransactionAttribute
供随后的调用。
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable {
// 这个是“向下兼容”,JavaEE提供的其余两种注解~~
private static final boolean jta12Present; //JTA 1.2事务注解
private static final boolean ejb3Present; //EJB 3 事务注解是
static {
ClassLoader classLoader = AnnotationTransactionAttributeSource.class.getClassLoader();
jta12Present = ClassUtils.isPresent("javax.transaction.Transactional", classLoader);
ejb3Present = ClassUtils.isPresent("javax.ejb.TransactionAttribute", classLoader);
}
// true:只处理public方法(基于JDK的代理 显然就只会处理这种方法)
// false:private/protected等方法都会处理。 基于AspectJ代理得方式可议设置为false
// 默认情况下:会被赋值为true,表示只处理public的方法
private final boolean publicMethodsOnly;
// 保存用于分析事务注解的事务注解分析器 这个注解分析的解析器是重点
private final Set<TransactionAnnotationParser> annotationParsers;
// 构造函数, publicMethodsOnly 缺省使用 true
public AnnotationTransactionAttributeSource() {
this(true);
}
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
if (jta12Present || ejb3Present) {
this.annotationParsers = new LinkedHashSet<>(4);
this.annotationParsers.add(new SpringTransactionAnnotationParser());
if (jta12Present) {
this.annotationParsers.add(new JtaTransactionAnnotationParser());
}
if (ejb3Present) {
this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
}
}
// 默认情况下,只添加Spring自己的注解解析器(绝大部分情况都实这里)
else {
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
// 自己也可以指定一个TransactionAnnotationParser 或者多个也成
public AnnotationTransactionAttributeSource(TransactionAnnotationParser annotationParser) { ... }
public AnnotationTransactionAttributeSource(TransactionAnnotationParser... annotationParsers) { ... }
public AnnotationTransactionAttributeSource(Set<TransactionAnnotationParser> annotationParsers) { ... }
// 获取某个类/方法上的事务注解属性(属于 父类的抽象方法)
@Override
@Nullable
protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
return determineTransactionAttribute(clazz);
}
@Override
@Nullable
protected TransactionAttribute findTransactionAttribute(Method method) {
return determineTransactionAttribute(method);
}
// 具体实现如下:
// 分析获取某个被注解的元素(AnnotatedElement ),具体的来讲,指的是一个类或者一个方法上的事务注解属性。
// 实现会遍历自己属性annotationParsers中所包含的事务注解属性分析器试图获取事务注解属性 所以主要还是依赖于TransactionAnnotationParser 去解析的
@Nullable
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
TransactionAttribute attr = annotationParser.parseTransactionAnnotation(element);
if (attr != null) {
return attr;
}
}
return null;
}
/**
* By default, only public methods can be made transactional.
*/
@Override
protected boolean allowPublicMethodsOnly() {
return this.publicMethodsOnly;
}
...
}
从源码中可议知道,真正提供给调用的getTransactionAttribute
在父类中实现的:
AbstractFallbackTransactionAttributeSource
是接口TransactionAttributeSource
的抽象实现,也是上面提到的工具类AnnotationTransactionAttributeSource
的父类。
public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {
// 针对没有事务注解属性的方法进行事务注解属性缓存时使用的特殊值,用于标记该方法没有事务注解属性
// 从而不用在首次缓存在信息后,不用再次重复执行真正的分析 来提高查找的效率
// 标注了@Transaction注解的表示有事务属性的,才会最终加入事务。但是,但是此处需要注意的是,只要被事务的Advisor切中的,都会缓存起来 放置过度的查找~~~~ 因此才有这个常量的出现
private static final TransactionAttribute NULL_TRANSACTION_ATTRIBUTE = new DefaultTransactionAttribute() {
@Override
public String toString() {
return "null";
}
};
// 方法上的事务注解属性缓存,key使用目标类上的方法,使用类型MethodClassKey来表示
// 这个Map会比较大,会被事务相关的Advisor拦截下来的方法,最终都会缓存下来。关于事务相关的Advisor,后续也是会着重讲解的~~~
// 因为会有很多,所以我们才需要一个NULL_TRANSACTION_ATTRIBUTE常量来提高查找的效率~~~
private final Map<Object, TransactionAttribute> attributeCache = new ConcurrentHashMap<>(1024);
// 获取指定方法上的注解事务属性 如果方法上没有注解事务属性,则使用目标方法所属类上的注解事务属性
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// 如果目标方法是内置类Object上的方法,总是返回null,这些方法上不应用事务
if (method.getDeclaringClass() == Object.class) {
return null;
}
// 先看缓存里有木有,此处使用的非常经典的MethodClassKey作为Map的key
Object cacheKey = getCacheKey(method, targetClass);
TransactionAttribute cached = this.attributeCache.get(cacheKey);
if (cached != null) {
//目标方法上上并没有事务注解属性,但是已经被尝试分析过并且已经被缓存,
// 使用的值是 NULL_TRANSACTION_ATTRIBUTE,所以这里再次尝试获取其注解事务属性时,直接返回 null
if (cached == NULL_TRANSACTION_ATTRIBUTE) {
return null;
} else {
return cached;
}
}
// 缓存没有命中~~~~
else {
// 通过方法、目标Class 分析出此方法上的事务属性~~~~~
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
// 如果目标方法上并没有使用注解事务属性,也缓存该信息,只不过使用的值是一个特殊值:
if (txAttr == null) {
this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
}
// 存在目标属性~ 就put到里面去。
// 获取到methodIdentification 基本只为了输出日志~~~
else {
String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
if (txAttr instanceof DefaultTransactionAttribute) {
((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
}
if (logger.isTraceEnabled()) {
logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
}
this.attributeCache.put(cacheKey, txAttr);
}
return txAttr;
}
}
//查找目标方法上的事务注解属性 也是上面的核心方法
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// 如果事务注解属性分析仅仅针对public方法,而当前方法不是public,则直接返回null
// 如果是private,AOP是能切入,代理对象也会生成的 但就是事务不回生效的~~~~
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 上面说了,因为Method并不一样属于目标类。所以这个方法就是获取targetClass上的那个和method对应的方法 也就是最终要执行的方法
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// 第一步:去找直接标记在方法上的事务属性~~~ 如果方法上有就直接返回(不用再看类上的了)
// findTransactionAttribute这个方法其实就是子类去实现的
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// 然后尝试检查事务注解属性是否标记在目标方法 specificMethod(注意此处用不是Method) 所属类上
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
// 程序走到这里说明目标方法specificMethod,也就是实现类上的目标方法上没有标记事务注解属性(否则直接返回了嘛)
// 如果 specificMethod 和 method 不同,则说明 specificMethod 是具体实现类的方法method 是实现类所实现接口的方法
// 因此再次尝试从 method 上获取事务注解属性
// 这也就是为何我们的@Transaction标注在接口上或者接口的方法上都是好使的原因~~~~~~~
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
// 可议看到默认值是false 表示private的也是ok的
// 但是`AnnotationTransactionAttributeSource`复写了它 可以由开发者指定(默认是true了)
protected boolean allowPublicMethodsOnly() {
return false;
}
}
从上文可以看出,真正把@Transaction
注解变成一个TransactionAttribute
类的是TransactionAnnotationParser
,下面有必要来具体看看它
顾名思义,它是解析方法/类上事务注解的。
// @since 2.5
public interface TransactionAnnotationParser {
@Nullable
TransactionAttribute parseTransactionAnnotation(AnnotatedElement element);
}
它支持上面说的到三个注解,分别对应三个实现类:JtaTransactionAnnotationParser、Ejb3TransactionAnnotationParser、SpringTransactionAnnotationParser
。
因为现在基本是Spring的天下了,因此本文只讲述SpringTransactionAnnotationParser
,其它的雷同
它专门用于解析Class或者Method上的org.springframework.transaction.annotation.Transactional
注解的。
// @since 2.5 此类的实现相对来说还是比较简单的
public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {
// 此方法对外暴露,表示获取该方法/类上面的TransactionAttribute
@Override
@Nullable
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(element, Transactional.class, false, false);
if (attributes != null) {
// 此处注意,把这个注解的属性交给它,最终转换为事务的属性类~~~~
return parseTransactionAnnotation(attributes);
}
// 注解都木有,那就返回null
else {
return null;
}
}
// 顺便提供的一个重载方法,可以让你直接传入一个注解
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
}
// 这个简单的说:就是把注解的属性们 专门为事务属性们~~~~
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
// 此处用的 RuleBasedTransactionAttribute 因为它可议指定不需要回滚的类~~~~
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
// 事务的传播属性枚举:内部定义了7种事务传播行为~~~~~
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
// 事务的隔离级别枚举。一共是4中,枚举里提供一个默认值: 也就是上面我们说的TransactionDefinition.ISOLATION_DEFAULT
// 至于默认值是哪种隔离界别:这个具体的数据库有关~~~
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
// 设置事务的超时时间
rbta.setTimeout(attributes.getNumber("timeout").intValue());
// 是否是只读事务
rbta.setReadOnly(attributes.getBoolean("readOnly"));
// 这个属性,是指定事务管理器PlatformTransactionManager的BeanName的,若不指定,那就按照类型找了
// 若容器中存在多个事务管理器,但又没指定名字 那就报错啦~~~
rbta.setQualifier(attributes.getString("value"));
// rollbackFor可以指定需要回滚的异常,可议指定多个 若不指定默认为RuntimeException
// 此处使用的RollbackRuleAttribute包装~~~~ 它就是个POJO没有实现其余接口
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
// 全类名的方式~~
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
// 指定不需要回滚的异常类型们~~~
// 此处使用的NoRollbackRuleAttribute包装 它是RollbackRuleAttribute的子类
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
// 最后别忘了set进去
rbta.setRollbackRules(rollbackRules);
return rbta;
}
}
通过这个parser就可议把方法/类上的注解,转换为事务属性,然后缓存起来。
这样方法在调用的时候,直接根据Method
就能取到事务属性,从而执行不同的事务策略~~~
管理事务savepoint的编程式API接口。
JDBC定义了SavePoint接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以rollback到该保存点处的状态,而不是rollback整个事务。Connection接口的setSavepoint和releaseSavepoint方法可以设置和释放保存点。
// @since 1.1
public interface SavepointManager {
Object createSavepoint() throws TransactionException;
void rollbackToSavepoint(Object savepoint) throws TransactionException;
void releaseSavepoint(Object savepoint) throws TransactionException;
}
它的主要实现有如下:
TransactionStatus
这个分支很重要,后面有着重分析。这里先看看JdbcTransactionObjectSupport
这个实现
// @since 1.1 继承自SmartTransactionObject
public abstract class JdbcTransactionObjectSupport implements SavepointManager, SmartTransactionObject {
// 这是Spring定义的类,持有java.sql.Connection
// 所以最支不支持还原点、创建还原点其实都是委托给它来的~
@Nullable
private ConnectionHolder connectionHolder;
...
}
它也只是个抽象类,SmartTransactionObject
接口相关的方法都没有去实现~~~~但是它的子类DataSourceTransactionObject
有去实现的~
关于还原点的实现,整体上还是比较简单的,就是委托给Connection去做~
若群二维码失效,请加微信号(或者扫描下方二维码):fsx641385712。
并且备注:“java入群” 字样,会手动邀请入群