💎AOP切面编程
📄AOP概念与原理
✂AOP编程的概念
AOP切面编程能帮助我们在不修改现有代码的情况下,对程序的功能进行扩展,符合程序的“开闭原则”(对扩展开放,对修改封闭),往往用于实现日志处理,权限控制,性能检测,事务控制。
📝AOP的原理
AOP的实现原理就是利用动态代理;
在有接口的情况下,使用JDK动态代理;
在没有接口的情况下,使用cglib动态代理
🧾AOP的常用术语
1、连接点 Joint point
类中可以被增强的方法,称为连接点
2、切入点 Pointcut
实际被增强的方法
3、通知 Advice
实际增强的功能(逻辑部分)称为通知
4、目标对象 Target
被增强功能的对象
5、切面 Aspect
就是把功能相关的一些advice方法放在一起声明成一个类,这个类叫切面
6、织入 Weaving
创建代理对象并实现功能增强的声明并运行的过程
📄使用注解方式实现AOP
-
导入依赖包(实现AOP的联盟包)
<dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency>
🖋切入点表达式:
通过表达式来确定AOP要增强的是哪个或者那些方法
语法结构:execution(权限修饰符 返回值类型 类的全路径名 方法名(参数 列表) )
execution(* com.jdl.dao.UserDaoImpl.add(…)) //指定切点为UserDaoImpl.add方法
execution(* com.jdl.dao.UserDaoImpl.(…)) //指定切点为UserDaoImpl.所有的方法
execution( com.jdl.dao..(…)) //指定切点为dao包下所有的类中的所有的方法
execution(* com.jdl.dao..add(…)) // 指定切点为dao包下所有的类中的add的方法
execution( com.jdl.dao..add(…)) // 指定切点为dao包下所有的类中的add开头的方法
-
在xml配置文件中配置
<!--spring 包扫描 --> <context:component-scan base-package="com.jdl"/> <!--aop autoProxy 自动生成代理对象 --> <aop:aspectj-autoproxy/>
-
主要代码:
@Component @Aspect public class DaoAspect { //定义公共切点 @Pointcut("execution(* com.jdl.dao.*.add*(..))") public void addPointCut(){} /* * 前置通知: 切点方法执行之前先执行的功能 * 参数列表可以用JoinPoint接收切点对象 * 可以获取方法执行的参数 * */ @Before("addPointCut()") public void methodBefore(JoinPoint joinPoint){ System.out.println("Before invoked"); } /* * 后置通知:方法执行之后要增强的功能 * 无论切点方法是否出现异常都会执行的方法 * 参数列表可以用JoinPoint接收切点对象 * */ @After("addPointCut()") public void methodAfter(JoinPoint joinPoint){ System.out.println("After invoked"); } /* * 返回通知:切点方法正常运行结束后增强的功能 * 如果方法运行过程中出现异常,则该功能不运行 * 参数列表可以用 JoinPoint joinPoint接收切点对象 * 可以用Object res接收方法返回值,需要用returning指定返回值名称 * */ @AfterReturning( value = "addPointCut()",returning = "res") public void methodAfterReturning(JoinPoint joinPoint,Object res){ System.out.println("AfterReturning invoked"); } /* * 异常通知:切点方法出现异常时运行的增强功能 * 如果方法运行没有出现异常,则该功能不运行 * 参数列表可以用Exception ex接收异常对象 需要通过throwing指定异常名称 * */ @AfterThrowing( value = "addPointCut()",throwing = "ex") public void methodAfterThrowing(Exception ex){ System.out.println("AfterThrowing invoked"); } /*环绕通知:在切点方法之前和之后都进行功能的增强 * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能 * 方法列表可以通过ProceedingJoinPoint获取执行的切点 * 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置 * proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理 * 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值 * */ @Around("addPointCut()") public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("aroundA invoked"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("aroundB invoked"); return proceed; } }
📄JDBCTemplate的使用
-
导入相关依赖(MySQL驱动,SpringJDBC包,Spring事务控制包,德鲁伊连接池等)
-
创建JDBC.properties文件
-
配置xml文件
<!--spring 注解扫描--> <context:component-scan base-package="com.msb"/> <!--读取jdbc配置文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置德鲁伊连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc_username}"></property> <property name="password" value="${jdbc_password}"></property> <property name="url" value="${jdbc_url}"></property> <property name="driverClassName" value="${jdbc_driver}"></property> </bean> <!--配置JDBCTemplate对象,并向里面注入DataSource--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--通过set方法注入连接池--> <property name="dataSource" ref="dataSource"></property> </bean>
-
主要代码:
@Repository public class EmpDaoImpl implements EmpDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public int findEmpCount() { /*查询员工个数 * queryForObject 两个参数 * 1 SQL语句 * */ Integer empCount = jdbcTemplate.queryForObject("select count(1) from emp", Integer.class); return empCount; } @Override public Emp findByEmpno(int empno) { /* * 查询单个员工对象 * queryForObject三个参数 * 1 SQL语句 * 2 RowMapper接口的实现类对象,用于执行返回的结果用哪个类来进行封装 ,实现类为BeanPropertyRowMapper * 3 SQL语句中需要的参数 (可变参数) * */ BeanPropertyRowMapper<Emp> rowMapper =new BeanPropertyRowMapper<>(Emp.class); Emp emp = jdbcTemplate.queryForObject("select * from emp where empno =?", rowMapper, empno); return emp; } @Override public List<Emp> findByDeptno(int deptno) { /* * 查询单个员工对象 * query三个参数 * 1 SQL语句 * 2 RowMapper接口的实现类对象,用于执行返回的结果用哪个类来进行封装 ,实现类为BeanPropertyRowMapper * 3 SQL语句中需要的参数 (可变参数) * */ BeanPropertyRowMapper<Emp> rowMapper =new BeanPropertyRowMapper<>(Emp.class); List<Emp> emps = jdbcTemplate.query("select * from emp where deptno =?", rowMapper, deptno); return emps; } @Override public int addEmp(Emp emp) { /*增删改 * 统统用update方法 两个参数 * 1 SQL语句 * 2 SQL语句需要的参数 (可变参数) * * */ String sql ="insert into emp values(DEFAULT ,?,?,?,?,?,?,?)"; Object[] args ={emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),emp.getComm(),emp.getDeptno()}; return jdbcTemplate.update(sql,args); }
💎处理事务
📄什么是事务
事务(Transaction)是指一个操作序列,该操作序列中的多个操作要么都做,要么就都不做,是个不可分割的工作单位。
🗒事务的特性,也称ACID特性
原子性(Atomicity):原子是最小颗粒,不可再分的特性
一致性(Consistency):事务执行的结果必须使数据库从一个一致性状态编导另一个一致性状态。
隔离性(Isolation):各个事务执行互不干扰,互不影响。
持久性(Durability)事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,就算出现故障也能恢复。
🗒事务的并发问题
1、脏读(Dirty read)
当一个事务正在访问数据并且对数据进行了修改,而这修改还没提交带数据库中。这时另一个事务也访问了该数据库,然后使用了这个数据,因为该数据还没有提交数据,那么另外的事务读到的数据是脏数据,操作可能不正确。
2、不可重复读(Unrepeatableread)
指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
3、幻读(Phantom read)
它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读的重点是修改,幻读的重点在于新增或者删除。
📄使用注解管理事务
-
在xml配置文件中配置事务管理器
<!--配置一个事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--将数据源注入事务管理器--> <property name="dataSource" ref="dataSource"></property> </bean> <!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"/>
-
事务管理注解
@Transactional
放到类上,代表类中的所有方法都添加了 事务管理
放在方法上,表示只有该方法增加了事务管理
-
事务传播行为
多个事务方法之间调用,事务是如何管理的
PROPAGATION_REQUIRED
如果a方法有事务,那么b和c就加入到a方法里的事务;
如果a方法没有事务,那么就新建一个事务,b和c就加入到a方法里的事务
PROPAGATION_REQUIRES_NEW
无论a方法有没有事务,都新建一个事务,把所有的方法都添加到新事务中,原来的不要了
没有山穷水尽,哪来柳暗花明