事务
通俗讲, AOP思想就是把中间的非主流程的代码(比如:验证、事务管理、缓存、日志记录等), 横向抽取出来放在一个公共的类中, 也就是切面.可以通过Spring框架来配置该切面
1:学习的东西是什么,解决什么问题
事务分为两种
编程式:通过手动编写代码的方式完成事务的管理
声明式:通过配置的方式完成事务的管理(底层采用aop技术, spring的事务都是通过AOP动态代理实现的,因此使用事务就要会aop )
aop(面向切面编程):aop原理为代理
(1)静态代理:【程序运行前就存在代理类的字节码文件】
(2)动态代理:JDK动态代理,CGLIB动态代理。【在程序运行期间由JVM根据反射等机制生成源码】
事务的四大特点(ACID):
-
atomictiy(原子性)
表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败。
-
consistency(一致性)
表示一个事务内有一个操作失败时,所有更改过的数据都必须回滚到修改前的状态。
-
isolation(隔离性)
事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的 数据,事务不会查看中间状态的数据
-
事务隔离级别从低到高(效率从高到低):
读取未提交(Read Uncommited)
读取已提交(Read commited)
复读(Repeatable Read)
序列化(serializable)(锁表)
-
durability(持久性)
持久性事务完成之后,它对于系统的影响是永久性的。
2:什么时候使用,什么场合适合用
可以用事务写性能日志和操作日志。
性能日志:查看性能的优异。
操作日志:防止他人甩锅给自己,说程序员代码写错,而不是他自己修改了数据
3:优势
编程式事务:手动管理事务,难写(很少使用),但易于程序员维护,更加灵活
声明式事务:代码入侵小,不用在业务逻辑层(service)编写事务管理的代码,可以用基于配置的xml(Aspectj方式)或只使用注解 来管理项目。 在没有修改源代码的情况下,就给原方法增加的新的功能,这就是AOP的魅力,
注意:使用注解特别简单,但不易理解底层代码,实现原理。
4:具体使用,怎么使用,使用步骤和注意事项
编程式事务(使用):
在xml中全都是bean(懒得写了,自己百度一下呗)
声明式事务(使用):
注意:在这里,我用的无注解方式写的性能日志。用注解方式写的操作日志
性能日志:效果(结束时间—开始时间=执行时间)
进行登陆操作时自动记入数据库
(1)配置xml:
<!-- AOP配置增强类 --> <aop:config proxy-target-class="true"> <!-- 配置切入点(业务层) --> <aop:pointcut expression="execution(* com.cws.service.impl.*.*(..))" id="pointcut"/> <!-- 配置切面 --> <aop:aspect id="myAspect" ref="myAspect"> <!-- method属性对应的是方法名字 --> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> <aop:around method="around" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> <!-- 设置事务的传播特征 --> <tx:advice id="advice" transaction-manager="dataSourceTransactionManager"> <tx:attributes> <!-- 需要事务的方法;事务是否只读?false:否;支持当前事务,如果当前没有事务,就新建一个事务,最常见的选择 --> <tx:method name="login" read-only="false" propagation="REQUIRED"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
(2)创建事务类:
package com.cws.aspect; import java.text.SimpleDateFormat; import java.util.Date; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.jdbc.core.JdbcTemplate; public class MyAspect { JdbcTemplate jdbcTemplate; //注入 public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } //前置方法,在目标方法执行前执行 private void before(JoinPoint jp) { //System.out.println(jp.getTarget()); } //加上这个ProceedingJoinPoint类型的参数(环绕通知) public Object around(ProceedingJoinPoint pjp) throws Throwable{ //开始时间 String begin =new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date())+""; //String begin=System.currentTimeMillis()+"";获取计算机时间 //启动目标方法 Object obj=pjp.proceed(); //结束时间 String end=new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date())+""; //执行方法名 String methdod_name=pjp.getSignature().getName(); String param=""; //获取参数 for ( Object object : pjp.getArgs()) { param=param+object+","; } Object objects[]={methdod_name,begin,end,param}; //直接在这里写sql语句,添加进数据库 String sql="insert into logri(methdod_name,begin,end,param) values(?,?,?,?)"; jdbcTemplate.update(sql, objects); return obj; } private void after(JoinPoint jp) { //System.out.println(jp.getTarget()); } }
操作日志:效果
每进行一次操作(增删改查),都会记录操作人,操作命令,地址ip,操作时间以及操作结果
(1)配置xml:
<aop:aspectj-autoproxy proxy-target-class="true"/> 等价于
(为true就是基于类,需要使用cglib库,为false就是基于接口,使用JDK动态代理)
JDK动态代理动态代理必须要有接口. 而CGLIB动态代理不管有没有接口都可以使用,因此CGLIB动态代理使用的更多
(2)创建事务类:
@Aspect (创建切面类{实则是个普通类})
@Component:泛指组件
@Aspect @Component public class MyAspect{ @Autowired LogServie loginService; //配置接入点 @Pointcut("execution(* com.cws.controller.*.*(..)) && !execution(* com.cws.controller.BaseController.*(..))") private void controllerAspect(){}//定义一个切入点 //在和aroundAdvice结合的时候,这个方法一定要加上这个ProceedingJoinPoint类型的参数 @Around(value = "controllerAspect()") public Object aroundTest(ProceedingJoinPoint pjp) throws Throwable { return this.show(pjp); } public Object show(ProceedingJoinPoint pjp) throws Throwable { //常见日志实体对象 LogEntity log = new LogEntity(); //获取登录用户账户,RequestContextHolder是使用spring获取serlvet的方法 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //这里的内容可不写,则获取登陆页面的登录名 log.setUserid("文"); //获取系统时间 String time = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date()); log.setDates(time); //获取系统ip String ip = IPUtil.getIPAddress(request); log.setIp(ip); //方法通知前获取时间 long start = System.currentTimeMillis(); // 拦截的实体类,就是当前正在执行的controller Object target = pjp.getTarget(); // 拦截的方法名称。当前正在执行的方法 String methodName = pjp.getSignature().getName(); // 拦截的方法参数 Object[] args = pjp.getArgs(); // 拦截的放参数类型 Signature sig = pjp.getSignature(); if (!(sig instanceof MethodSignature)) { throw new IllegalArgumentException("该注解只能用于方法"); } MethodSignature msig=(MethodSignature) sig; Class[] parameterTypes = msig.getMethod().getParameterTypes(); Object object = null; // 获得被拦截的方法 Method method = null; try { method = target.getClass().getMethod(methodName, parameterTypes); } catch (Exception e) { e.printStackTrace(); } if (null != method) { // 判断是否包含自定义的注解,说明一下这里的SysLog就是我自己自定义的注解 if (method.isAnnotationPresent(SysLog.class)) { SysLog sysLog = method.getAnnotation(SysLog.class); log.setModule(sysLog.module()); log.setMethod(sysLog.method()); try { object = pjp.proceed(); long end = System.currentTimeMillis(); //将计算好的时间保存在实体中 log.setResponse_date(""+(end-start)); log.setCommit("执行成功!"); //保存进数据库 loginService.saveLog(log); } catch (Throwable e) { long end = System.currentTimeMillis(); log.setResponse_date(""+(end-start)); log.setCommit("执行失败"); loginService.saveLog(log); } } else {//没有包含注解 object = pjp.proceed(); } } else { //不需要拦截直接执行 object = pjp.proceed(); } return object; } public MyAspect() { System.out.println("MyAspect --- 构造"); } }
注意:这里为了写操作日志,加入了自定义注解,至于自定义注解的内容,下次再写,下次一定!
实质就是:在方法执行前后进行拦截,然后再目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务.