一、AOP讲解
- 创建普通对象UserService
@Component
public class UserService{
@Autowired
private OrderService orderService;
public void test(){
System.out.println(orderService);
}
}
- 创建代理对象UserServiceProxy,对test()方法进行切面编程
@Aspect
@Component
public class ZhouyuAspect{
@Before("execution(public void com.zhouyu.service.UserService.test())")
public void zhouyuBefore(JoinPoint joinPoint){
System.out.println("zhouyuBefore");
}
}
- 上述代码的伪代码相当于
class UserServiceProxy extends UserService{
执行切面逻辑代码;
super.test(); //执行父类方法
}
- 此时装入单例池Map中的Bean对象就是代理对象UserServiceProxy,所以我们拿出来的bean对象是UserServiceProxy代理对象
- 现在来思考一个问题,在普通对象userService的属性orderService,有@Autowired进行依赖注入,那么orderService是有值的。但是在初始化后,进行了AOP生成了代理对象userServiceProxy,userServiceProxy的属性orderService并没有注入依赖,是null,这就来问题了。
- 其实第3点的伪代码应该改成如下,这样就解决了null值得问题了,下面说说为什么。
- 试想一下,生成普通对象userService,并为普通对象userService的属性orderService依赖注入,那么为什么生成代理对象userServiceProxy,不为代理对象userServiceProxy的属性orderService进行依赖注入呢?Spring认为是没必要的,就像下面的伪代码一样,除了执行AOP切面编程的代码之外,还是用回普通对象userService属性和方法
- 所以一句话说完:UserServiceProxy代理对象做Bean对象,除了切面编程@Before会执行代理对象,其他的方法和属性可以认为是在执行普通对象
class UserServiceProxy extends UserService{
UserService target;
执行切面逻辑
target.test(); //执行父类方法
}
二、Spring事务
Spring事务是基于AOP实现的,下面以SQL语句为例子。
1. 使用@Transactional为test方法做AOP
@Transactional //回滚——Spring事务
public void test(){
jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");
throw new NullPointerException();
}
- @Transactional对应的伪代码
public class UserServiceProxy extends UserService{
UserService target;
//不难看出来1.2.3.4.6都是对test做一个切面,由UserServiceProxy代理对象执行;
//test方法由普通对象执行
public void test(){
1. 事务逻辑
2. 开启事务
3. 事务管理器去创建数据库连接conn并放入了ThreadLocal
4. conn.autocommit = false
5. target.test(); //UserService普通对象.test() jdbcTemplate conn sql语句
6. SQL没问题就conn.commit(),否则就conn.rollback();
}
}
- autocommit = true对应的功能
- 如果数据库由jdbcTemplate连接,autocommit默认为true,则执行完SQL语句之后就会自动提交
- 如果数据库由Spring连接,那么Spring就会把autocommit改成false,则执行完SQL语句之后还不会自动提交
- 所以数据库创建必须由Spring来进行,但是如果由Spring创建数据库,那么Spring一启动就要连接数据库,万一我们不用数据库呢?不用就不用呗,哈哈哈哈!
2. 总结Spring事务
- 普通对象执行的是UserService.test();
- 代理对象执行的是UserServiceProxy.test()= @Transactional + UserService.test();
3. 特殊场景
@Transactional
public void test(){
jdbcTemplate.execute("insert into t1 values (1,1,1,1,'1')");
a();
}
//功能:出现事务就出现异常报告
@Transactional(propagation = Propagation.NEVER)
public void a(){
jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");
}
//测试方法
userServiceProxy.test();
测试结果:Process finished with exit code 0(没有任何信息)
我们根据上面两点来分析这个结果
- 代理对象userServiceProxy先执行了事务@Transactional,然后再由普通对象执行test方法体,当执行到a方法的时候,仍然由普通对象执行,那么这时候a方法的@Transactional(propagation = Propagation.NEVER) 事务就不会被识别到。所以测试结果没有报错:Process finished with exit code 0(没有任何信息)
那么如何让a方法执行@Transactional(propagation = Propagation.NEVER)这个事务
public class OrderService{
@Transaction(propagation = Propagation.NEVER)
public void a(){
jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");
}
}
public class UserService{
//1. 因为这里注入的Bean是代理对象
@Autowired
private OrderService orderService;
@Transaction
public void test(){
jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");
//2. 由代理对象执行a方法
orderService.a();
}
}
测试:Existing transaction found for transaction marked with propagation…
- 分析:因为这一次的a()方法是代理对象UserServiceProxy的orderService属性的方法,所以a()方法的执行是在代理对象UserServiceProxy下执行的,可以执行事务,即可以存在事务,所以@Transactional(propagation = Propagation.NEVER)会被识别到。
4. 多线程属于同一个事务吗?
public class OrderService{
@Transactional
public void test(){
jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");
}
}
public class UserService{
@Autowired
OrderService orderService;
//1. 事务管理器去创建数据库连接conn并放入了ThreadLocal,执行的时候再拿出来
orderService.test();
//2. 这里执行test会去ThreadLocal寻找数据库二连接conn,但是发现找不到,就会自己创建数据库连接
new Thread(new Runnable(){
@Override
public void run(){
orderService.test();
}
}).start();
//1和2都通过不同数据库链接来连接数据库,所以肯定不在同一个事务中
}
5. 方法修饰词private和事务
public class UserService{
@Transaction
private void test(){
System.out.println("测试数据库回滚事务");
}
}
执行结果:报错
分析:
- 事务的实质是AOP,AOP的实质是继承,所以事务的实质是继承
- 父类private修饰的方法,子类无法访问重写
- 所以父类private修饰的方法,无法进行AOP,也无法进行事务
- 那么@Transaction就会报错