一、AOP
1.问题提出
在实际开发中我们可能同时需要编写业务核心代码、事务处理代码和日志记录代码,通常的做法, 例如在dao层除了在执行数据库操作,还需其他一些操作,如 日志等。 卸载dao层 将业务代码与非业务代码 交织在一块,不利于代码维护。
public void insertAdmin(int id,int num){
//核心业务代码
System.out.println("保存管理");
}
public void saveLog(){
//入日志代码
System.out.println("");
}
2.AOP概述
oop是面向对象编程,是对整个程序的全局的一种架构设计。
aop是oop的补充,将程序中的非业务代码进行抽取,将非业务代码与业务代码相隔离,通过一个代理对象在业务代码中调用非业务代码,提供业务灵活性 提高开发效率
aop这一思想,核心就是使用一个代理对象,在中间帮忙调用,为程序添加功能。
aop思想不是spring特有的,是java中的一种代理思想。
3.AOP基本概念
连接点(JoinPoint):类中可以被增强的方法,就成为连接点,如方法A 方法B
切入点(pointcut):类中有许多方法可以被增强,但实际中只用 方法A被增强了,那么方法A就被称为切入点**(实际实现的连接点)**
通知(Advice):通知是指一个切面在特定的连接点要做的事情**(增强的功能)**,通知分为 前置通知、后置通知、最终通知、异常通知、环绕通知
切面(Aspect):把通知添加到切入点的整个过程称为切面
目标(target):代理的目标对象, 连接点 切入点所在的类,真正做这件事的类
代理(Proxy):向目标对象应用通知时,创建的代理对象–帮我们使用通知的类
二、springAOP实现
前置通知:在方法执行之前 被调用
后置通知:在方法正常的执行完成后 被调用
最终通知:无论是否出现异常,都会被调用
异常通知:方法中 出现异常时 才调用
环绕通知:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。
1.aspectj xml配置实现
1.1下载AOP相关jar
<!-- aspectj -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
1.2 xml文件中配置
<!--aop配置-->
<bean id="commonUtil" class="com.ffyc.springpro.util.CommonUtil"></bean>
<!-- 配置通知和切入点之间的关系 -->
<aop:config>
<!-- *表示返回值任意 ..表示任意参数-->
<aop:pointcut id="insertAdmin" expression="execution(* com.ffyc.springpro.dao.*.*(..))"/>
<aop:aspect ref="commonUtil">
<!-- 前置通知,在方法执行之前 被调用-->
<!--<aop:before method="saveLog" pointcut-ref="insertAdmin"></aop:before>-->
<!-- 后置通知 在方法正常的执行完成后 被调用-->
<!--aop:after-returning method="saveLog" pointcut-ref="insertAdmin"></aop:after-returning>-->
<!-- 最终通知 无论是否出现异常,都会被调用-->
<!-- <aop:after method="saveLog" pointcut-ref="insertAdmin"></aop:after> -->
<!--异常通知 方法中 出现异常时 才调用-->
<!--<aop:after-throwing method="saveLog" pointcut-ref="insertAdmin" throwing="e"></aop:after-throwing>-->
<!-- 环绕通知 -->
<aop:around method="saveLog" pointcut-ref="insertAdmin"></aop:around>
</aop:aspect>
</aop:config>
2.注解方式实现
2.1 启动AspectJ支持
在spring.xml中
<!-- //启动 AspectJ 支持:-->
<aop:aspectj-autoproxy />
2.2定义通知
@Component
@Aspect
public class CommonUtil {
//@Before("execution(* com.ffyc.springpro.dao.*.*(..))")
//@After("execution(* com.ffyc.springpro.dao.*.*(..))")
//@AfterReturning("execution(* com.ffyc.springpro.dao.*.*(..))")
// @AfterThrowing(value ="execution(* com.ffyc.springpro.dao.*.*(..))",throwing = "e")
// public void saveLog(JoinPoint joinPoint,Throwable e){
/*public void saveLog(JoinPoint joinPoint,Throwable e){
Object []objects=joinPoint.getArgs();
System.out.println(Arrays.toString(objects));
System.out.println("时间------->"+new Date());
}*/
//------------------环绕通知------------------------
@Around("execution(* com.ffyc.springpro.dao.*.*(..))")
public void saveLog(ProceedingJoinPoint proceedingJoinPoint){
try {
System.out.println("前置通知");
proceedingJoinPoint.proceed();
System.out.println("后置通知");
}catch (Throwable e){
System.out.println("异常通知"+e.getMessage());
}
System.out.println("最终通知");
}
}
3.测试
@Test
public void test3(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml");
AdminService adminService=applicationContext.getBean("adminService",AdminService.class);
adminService.saveAdmin();
adminService.updateAdmin();
}
前置通知
保存管理
后置通知
最终通知
前置通知
修改管理
后置通知
最终通知
4.面向切片编程
将程序中的 非业务代码进行抽取,在不修改业务代码的前提下,为其添加功能。
面向切片编程思想底层时为目标类,创建一个代理对象,让代理对象调用目标类中方法,咋爱代理对象调用时,可以额外调用其他方法(增强的方法,通知)。
提高代码复用性,灵活性,提高开发效率, 降低业务代码与非业务代码耦合性。
案例: 保存日志、 事务管理、权限认证、同一处理异常
三、事务管理
1.什么是事务
数据库事务,是属于数据库中的功能
事务可以看做由对数据库若干操作组成的一个操作单元。
一次操作执行多条sql,要保证原子性,要么都执行成功,要么都执行失败;
事务是一系列动作,他们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,事务就会回滚到最开始的状态,从而保证数据满足一致性的要求;
经典案例: 银行转账
首先A的银行卡减去500元钱;然后B的银行卡多500元钱。这两个步骤必须是要么都执行要么都不执行。不管哪一个步骤失败了以后,整个转账过程都能回滚。
A:-500
B:+500
2.spring事务管理
编程式事务管理
在项目中很少使用,这种方式需要注入一个事务管理对象 TransactionTemplate ,需要在代码中手动提交和回滚。
声明式事务管理
管理建立在 AOP 基础上,本质是对方法前后进行拦截,所以声明式
事务是方法级别的。只需要在事务管理中,运行的方法 添加注解标签声明即可
3.注解实现
开启事务管理
<!-- 配置 spring 事务管理类, 并注入数据源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在需要事务管理的方法或者类(把注解加到类上面,该类中的所有方法都添加事务管理)上面添加 @Transactional注解即可
@Service(value = "adminService")
@Transactional
public class AdminService {
@Autowired
AdminDao adminDao;
public void zhuanzhang(){
adminDao.sub();
System.out.println(10/0);
adminDao.add();
}
dao层两台哦sql,我们将注解标签添加在service层,这样在遇到异常不会进行事务提交, 如果在dao层,sql语句不会同时执行,会导致一整个事务不完全提交, 转账只转了,收不到。
public void add(){
//spring jdbc默认自动提交事务
jdbcTemplate.update("update admin set money=money-500 where id= 1");
}
public void sub(){
jdbcTemplate.update("update admin set money=money+500 where id=2");
}
4.声明式事务失效
1.@Transactional注解 作用在非 public修饰的方法
@Transactional
void zhuanzhang(){
adminDao.sub();
System.out.println(10/0);
adminDao.add();
}
2.异常被catch捕获,代理对象认为方法没有异常,都会执行
public void zhuanzhang(){
adminDao.sub();
try {
System.out.println(10/0);
}catch (Exception e){
}
adminDao.add();
}
// 出现异常 两部操作都会执行
3.默认情况下方法出现编译期异常 是失效的,我们可以修改配置 将注解标签中的值改为:
@Transactional(rollbackFor = Exception.class)
4.@Transactional 事务传播行为设置错误
5.数据库引擎 不支持事务
6.同一个类中,使用非代理对象调用其他的方法
四、事务传播行为
1.什么是事务传播行为?
传播,即至少有两个东西,单体不存在传播行为。
事务传播行为 指的是当一个事务方法被另一个事务方法调用时,这个事务方法如何进行。 事务传播行为我spring框架独有的事务增强特性,它不属于事务提供方数据库行为。
例如: 方法A 事务方法 调用 方法B, 方法B是继续在 方法A的事务中运行,还是开启一个新事物运行, 就是由方法B的事务传播决定。
2.spring 事务传播
propagation = Propagation.REQUIRED
A方法没有事务,调用B方法, B会自己创建一个事务,在事务中运行,B方法出现异常,不影响A方法
A方法有十五,调用B方法, B 方法会加入A方法, 两者合二为一,任意一方异常,会影响双方
propagation = Propagation.REQUIRES_NEW
A方法没有事务,调用B方法, B会自己创建一个事务,在事务中运行,B方法出现异常,不影响A方法
A方法有事务,调用B方法, B方法会自己创建一个事务,将A方法挂起,A方法出现异常 不影响B
3.测试事务传播
@Test
public void test(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml");
AdminService adminService=applicationContext.getBean("adminService",AdminService.class);
adminService.saveAdmin();
}
dao
public void saveLog(){
jdbcTemplate.update("insert into log(opertime) values(now())");
}
public void insertAdmin(){
//核心业务代码
jdbcTemplate.update("insert into admin(account,password)values('bbb','666')");
}
service
@Transactional(propagation = Propagation.REQUIRED)
public void savelogg(){
logDao.saveLog();
System.out.println(10/0);
}
public void saveAdmin(){
adminDao.insertAdmin();
logService.savelogg();
}
在调用的方法中添加了异常, adminDao.insertAdmin()执行不受影响。
当为 admin中添加事务后,都不会执行。
五、spring集成mybatis
1.导入mybatis jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
2.配置 sqlSessionFactory
<!--配置sqsSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis.xml"></property>
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml">
</property>
</bean>
3.指定生成接口代理
<!-- 配置为接口生成代理对象-->
<bean id="mapperFactory" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ffyc.ssm.dao"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory">
</property>
</bean>
正常使用mybatis即可
</bean>
## 3.指定生成接口代理
```xml
<!-- 配置为接口生成代理对象-->
<bean id="mapperFactory" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ffyc.ssm.dao"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory">
</property>
</bean>
正常使用mybatis即可