Apong's Blog

当你快坚持不住的时候,困难也快坚持不住了

0%

Transactional注解、Java工作路径、Paths类

@Transactional 事务代理失效问题

在Spring中是通过aop增强对@Transactional注解标注的方法进行事务代理的。

但是如果通过 this 调用该方法,那么这个注解就会失效,也就是事务代理失效。

原因:

aop的实现原理是在管理的bean中匹配切入点,然后对bean执行的方法进行增强。

而通过 this 调用,这个 this 是不被 Spring 管理的,如何找到它并且匹配切入点呢??

简而言之,只有当该方法是被 Spring 的代理对象执行时,aop增强才会生效。

事务加入问题

1
2
3
4
5
6
7
8
9
10
11
12
13
public Class Test {
@Transactional
public void proxyFunc() {
this.dbOperation1();
this.dbOperation2();
}

// 数据库操作1
public void dbOperation1() {...}

// 数据库操作2
public void dbOperation2() {...}
}

代码中存在一个被代理对象调用并标注 @Transactional 注解的 proxyFunc 方法,其内部通过 this 指针调用了两个数据库操作。

这里的两个数据库操作会加入 proxyFunc 的事务吗?

答案:会。

原因:因为 proxyFunc 已经被 @Transactional 增强了,开启了事务,那么其内部所有的数据库操作都会加入这个事务。

事务冒泡问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Class Test {
@Transactional
public void proxyFunc() {
this.dbOperation1();
this.dbOperation2();
}

// 数据库操作1
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void dbOperation1() {...}

// 数据库操作2
public void dbOperation2() {...}
}

还是一样的代码,不过这次给数据库操作1标注了 @Transactional(propagation = Propagation.NOT_SUPPORTED) 不支持事务的注解,也表示不会加入其他事务。

那么这个注解会生效吗?

答案:不会

原因:因为数据库操作1是通过 this 调用的,而不是 proxy 代理对象,不会被aop“增强”,也就是注解失效!

不是最外层是 proxy 代理的,内部就都是它代理,可以回想一下 aop 增强的实现代码,本质是在目标方法的外层再包装一层逻辑,所以这个 proxy 只会包装最外层的注解增强代码,无法处理内部。

那事务冒泡配置注解什么时候使用呢?

可以在其他 service 的代码中使用,通过注入其代理对象,然后使用目标方法,这时候由于是 proxy 代理的,注解就会正常生效。

1
2
3
4
5
public Class OtherService {
// 数据库操作1
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void dbOperation1() {...}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Class Test {
@Resource
private OtherService otherService;

@Transactional
public void proxyFunc() {
// 不支持事务注解正常生效
otherService.dbOperation1();
this.dbOperation2();
}

// 数据库操作2
public void dbOperation2() {...}
}

如果需要分离方法体单独封装,又不能通过 this 调用,该如何保证目标方法事务生效呢

答案:手动获取代理对象,然后通过代理对象调用目标方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
public Class TestService {
// 调用入口
public void calledFunc() {
// 需要引入 aspectjweaver aop依赖,并且开启 exposeProxy
(TestService) proxy = (TestService) AopContext.currentProxy();
proxy.sealedDBOperation();
}

// 封装数据库操作
@Transactional
public void sealedDBOperation() {...}

}

获取工作目录根路径

通过 System 方法获取环境属性。

1
String rootWorkPath = System.getProperty("user.dir")

拼接其他路径

使用 java.nio.file 下的 Paths 工具类

1
String path = Paths.get(rootWorkPath, "path1", "path2 ...")

线程安全

不可变对象也是线程安全的。

如 List、String 等。

但是引用提升至 List 的 ArrayList 实例同样是线程不安全的。

异步下,对 ArrayList 的插入操作,会导致 length 和 列表元素 出现错乱。

为什么大部分类都是线程不安全的?

因为线程安全会影响性能,应交由开发者自由控制。