1、AOP
1.1、开启自动代理功能
- 在java配置类上添加注解@EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(value = "com.xyulu"
,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})})
public class AopConfig {
}
1.2、定义切面类
@Component
@Aspect
public class LogAspects {
}
1.3、定义切点
@Pointcut(value = "execution(* com.xyulu..*(..))")
public void pointCut() {
}
1.4、定义通知
- @Before:在目标方法执行前执行通知;
- @After:在目标方法执行后执行通知;
- @AfterReturning:在目标方法执行完成后执行通知;
- @AfterThrowing:在目标方法抛出异常后执行通知;
- @Around:可在目标方法执行前后自定义通知行为;
切点表达式
图片摘自https://blog.csdn.net/longyanchen/article/details/94486678
代码示例
public class LogAspect {
@Pointcut(value = "execution(* com.xyulu.service..*(..))")
public void pointcut() {
}
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("LogAspect.before,方法名:" + joinPoint.getSignature().getName() +
",参数列表:" + Arrays.toString(joinPoint.getArgs()));
}
@After(value = "pointcut()")
public void after(JoinPoint joinPoint) {
System.out.println("LogAspect.after,方法名:" + joinPoint.getSignature().getName());
}
@AfterReturning(value = "pointcut()", returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
System.out.println("LogAspect.afterReturn,方法名:" + joinPoint.getSignature().getName()
+ ",运行成功,返回值:" + result);
}
@AfterThrowing(value = "pointcut()", throwing = "exception")
public void exception(JoinPoint joinPoint, Exception exception) {
System.out.println("LogAspect.exception,方法名:" + joinPoint.getSignature().getName()
+ "出错,error is :" + exception);
}
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("LogAspect.around,环绕通知开始执行");
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
环绕通知和前置通知,后置通知有着很大的区别,主要有两个重要的区别:
-
目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知 是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。
-
环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用。
使用面向切面来处理一些问公共的问题,比如,权限管理,事务的委托,当程序发生异常时,重复提交请求,重复的次数是可以设定的
2、事务
示例参考
2.1、导入相关依赖
2.2、配置数据源,JdbcTemplate
2.3、给方法标注 @Transactional 注解
@Transactional
public int saveUser(User user) {
int row = userDao.insertUser(user);
int i = 1/0;
return row;
}
2.4、配置类上开启事务管理 @EnableTransactionManagement
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@ComponentScan(value = "com.xyulu")
public class TxConfig {
@Bean
public DataSource dataSource() {
return DatasourceUtil.getDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
}
2.5、配置事务管理器来控制事务
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@ComponentScan(value = "com.xyulu")
public class TxConfig {
@Bean
public DataSource dataSource() {
return DatasourceUtil.getDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
2.6、事务失效
实践例子
1、数据库没有按照期望回滚,同时出现非自定义的异常
org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public int com.xyulu.service.UserService.saveUser(com.xyulu.entity.User)
at org.springframework.aop.framework.CglibAopProxy.processReturnType(CglibAopProxy.java:395)
at org.springframework.aop.framework.CglibAopProxy.access$000(CglibAopProxy.java:85)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:694)
at com.xyulu.service.UserService$$EnhancerBySpringCGLIB$$78d36019.saveUser(<generated>)
at com.xyulu.TxTest.test(TxTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
、、、、、
- 此处是 int insertUser(User user);的返回值 int 导致的 无法将null类型转换成int类型
2、将返回值全部用Integer类型接收,重新执行
java.lang.NullPointerException
at com.xyulu.TxTest.test(TxTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at 、、、、tRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
- 任然报错
3、调试发现是AOP的环绕通知报错。
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("LogAspect.around,环绕通知开始执行");
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("LogAspect.around,环绕通知执行结束");
return result;
}
- 发现执行 joinPoint.proceed(); 出现异常,但是被代码捕获了,导致事务无法回滚
4、修改异常捕获为抛出
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("LogAspect.around,环绕通知开始执行");
Object result = null;
result = joinPoint.proceed();
System.out.println("LogAspect.around,环绕通知执行结束");
return result;
}
- 执行程序,正常回滚
2.7、事务失效的原因
查找了几种事务失效的情况
2.7.1、方法不是 public的:
- spring的事务注解@Transactional只能放在public修饰的方法上才起作用,如果放在其他非public方法上,虽然不报错,但是事务不起作用
2.7.2、数据源没有配置事务管理器
2.7.3、异常被捕获
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order;
}catch (Exception e){
//do something;
}
}
}
- 如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new RuntimeExecption异常,事务也不会回滚
2.7.4、异常类型错误或格式配置错误
- 默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下
@Transactional(rollbackFor = Exception.class)
2.7.5、自身调用问题
方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。
- 原因是在同一个类之中,方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的。
3、事务特性
属性 | 类型 | 描述 |
---|---|---|
value | String | 指定使用的事务管理器 |
propagation | Propagation【enum】 | 事务传播行为设置 |
isolation | Isolation【enum】 | 事务隔离级别设置 |
timeout | int | 事务超时时间设置 |
readOnly | boolean | 读写或者只读,默认只读 |
rollbackFor | Class<? extends Throwable>[] | 导致事务回滚的异常类数组 |
rollbackForClassName | String[] | 导致事务回滚的异常类名数组 |
noRollbackFor | Class<? extends Throwable>[] | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | String[] | 不会导致事务回滚的异常类名数组 |
3.1、propagation 属性
事务的传播行为,默认值为 Propagation.REQUIRED。
可选的值有:
-
Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
-
Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
-
Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
-
Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
-
Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
-
Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
-
Propagation.NESTED:和 Propagation.REQUIRED 效果一样。
3.2、Isolation 属性
隔离级别 | 含义 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
DEFAULT | 使用后端数据库默认的隔离级别 | |||
READ_UNCOMMITTED | 允许读取尚未提交的数据变更(最低的隔离级别) | 是 | 是 | 是 |
READ_COMMITTED | 允许读取并发事务已经提交的数据 | 否 | 是 | 是 |
REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改 | 否 | 否 | 是 |
SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 | 否 | 否 | 否 |