DAY03_Spring—SpringAOPAOP切入点表达式AOP通知类型Spring事务管理

目录

  • 一 AOP
    • 1 AOP简介
      • 问题导入
      • 1.1 AOP简介和作用
      • 1.2 AOP中的核心概念
    • 2 AOP入门案例
      • 问题导入
      • 2.1 AOP入门案例思路分析
      • 2.2 AOP入门案例实现
        • 【第一步】导入aop相关坐标
        • 【第二步】定义dao接口与实现类
        • 【第三步】定义通知类,制作通知方法
        • 【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系)
        • 【第五步】在配置类中进行Spring注解包扫描和开启AOP功能
        • 测试类和运行结果
    • 3 AOP工作流程
      • 问题导入
      • 3.1 AOP工作流程
      • 3.2 AOP核心概念
      • 3.3 在测试类中验证代理对象
    • 4 AOP切入点表达式
      • 问题导入
      • 4.1 语法格式
      • 4.2 通配符
      • 4.3 书写技巧
    • 5 AOP通知类型
      • 问题导入
      • 5.1 AOP通知分类
      • 5.2 AOP通知详解
        • 5.2.1 前置通知
        • 5.2.2 后置通知
        • 5.2.3 返回后通知
        • 5.2.4 抛出异常后通知
        • 5.2.5 环绕通知
  • 二 AOP案例
    • 1 案例-测量业务层接口万次执行效率
      • 问题导入
      • 1.1 需求和分析
      • 1.2 代码实现
        • 【前置工作】环境准备
        • 【第一步】编写通知类
        • 【第二步】在SpringConfig配置类上开启AOP注解功能
        • 【第三步】运行测试类,查看结果
    • 2 AOP切入点数据获取
      • 问题导入
      • 2.1 获取参数
      • 2.2 获取返回值
      • 2.3 获取异常
    • 3 案例-百度网盘密码数据兼容处理
      • 问题导入
      • 3.1 需求和分析
      • 3.2 代码实现
        • 【前置工作】环境准备
        • 【第一步】编写通知类
        • 【第二步】在SpringConfig配置类上开启AOP注解功能
        • 【第三步】运行测试类,查看结果
    • 4 AOP开发总结
      • 4.1 AOP的核心概念
      • 4.2 切入点表达式语法
      • 4.3 五种通知类型
  • 三 Spring事务管理
    • 1 Spring事务简介
      • 问题导入
      • 1.1 Spring事务作用
      • 1.2 需求和分析
      • 1.3 代码实现
        • 【前置工作】环境准备
        • 【第一步】在业务层接口上添加Spring事务管理
        • 【第二步】设置事务管理器(将事务管理器添加到IOC容器中)
        • 【第三步】开启注解式事务驱动
        • 【第四步】运行测试类,查看结果
    • 2 Spring事务角色
      • 问题导入
      • 2.1 Spring事务角色
    • 3 Spring事务相关配置
      • 问题导入
      • 3.1 事务配置
      • 3.2 案例:转账业务追加日志
        • 需求和分析
        • 【准备工作】环境整备
        • 【第一步】在AccountServiceImpl中调用logService中添加日志的方法
        • 【第二步】在LogService的log()方法上设置事务的传播行为
        • 【第三步】运行测试类,查看结果
      • 3.3 事务传播行为

一 AOP

1 AOP简介

问题导入

问题1:AOP的作用是什么?
问题2:连接点和切入点有什么区别,二者谁的范围大?
问题3:请描述什么是切面?

1.1 AOP简介和作用

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
    • OOP(Object Oriented Programming)面向对象编程
  • 作用:在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。
  • Spring理念:无入侵式/无侵入式

1.2 AOP中的核心概念

在这里插入图片描述

  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 在SpringAOP中理解为方法的执行
      • 例如:update()、delete()、select()等都是连接点。
  • 切入点(Pointcut):匹配连接点的式子。进行功能增强了的方法,
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
        • 例如:update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点。
  • 通知(Advice):在切入点前后执行的操作,也就是增强的共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:通知方法所在的类叫做通知类
  • 切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。

2 AOP入门案例

问题导入

问题1:在通知方法中如何定义切入点表达式?
问题2:如何配置切面?
问题3:在配置类上如何开启AOP注解功能?

2.1 AOP入门案例思路分析

  • 案例设定:测定接口执行效率
  • 简化设定:在接口执行前输出当前系统时间
  • 开发模式:XML or 注解
  • 思路分析:
    1. 导入坐标(pom.xml)
    2. 制作连接点方法(原始操作,dao接口与实现类)
    3. 制作共性功能(通知类与通知)
    4. 定义切入点
    5. 绑定切入点与通知关系(切面)

2.2 AOP入门案例实现

【第一步】导入aop相关坐标

<dependencies><!--spring核心依赖,会将spring-aop传递进来--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.18</version></dependency><!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version></dependency>
</dependencies>

在这里插入图片描述

【第二步】定义dao接口与实现类

public interface BookDao {public void save();public void update();
}@Repository
public class BookDaoImpl implements BookDao {public void save() {System.out.println(System.currentTimeMillis());System.out.println("book dao save ...");}public void update(){System.out.println("book dao update ...");}
}

【第三步】定义通知类,制作通知方法

//通知类必须配置成Spring管理的bean
@Component
public class MyAdvice {public void method(){System.out.println(System.currentTimeMillis());}
}

【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系)

//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {//设置切入点,@Pointcut注解要求配置在方法上方@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}//设置在切入点pt()的前面运行当前操作(前置通知)@Before("pt()")public void method(){System.out.println(System.currentTimeMillis());}
}

在这里插入图片描述

【第五步】在配置类中进行Spring注解包扫描和开启AOP功能

@Configuration
@ComponentScan("com.itheima")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}

测试类和运行结果

public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = ctx.getBean(BookDao.class);bookDao.update();}
}

在这里插入图片描述

3 AOP工作流程

问题导入

什么是目标对象?什么是代理对象?

3.1 AOP工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建原始对象
    • 匹配成功,创建原始对象(目标对象)的代理对象
  4. 获取bean执行方法
    • 获取的bean是原始对象时,调用方法并执行,完成操作
    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

3.2 AOP核心概念

目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强。
代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象。

3.3 在测试类中验证代理对象

public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = ctx.getBean(BookDao.class);bookDao.update();//打印对象的类名System.out.println(bookDao.getClass());}
}

在这里插入图片描述

4 AOP切入点表达式

问题导入

在切入点表达式中如何简化包名和参数类型书写?

4.1 语法格式

  • 切入点:要进行增强的方法
  • 切入点表达式:要进行增强的方法的描述方式
    • 描述方式一:执行com.itheima.dao包下的BookDao接口中的无参数update方法
    execution(void com.itheima.dao.BookDao.update())
    
    • 描述方式二:执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法
    execution(void com.itheima.dao.impl.BookDaoImpl.update())
    
  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
    execution(public User com.itheima.service.UserService.findById(int))
    
    • 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
    • 访问修饰符:public,private等,可以省略
    • 返回值:写返回值类型
    • 包名:多级包使用点连接
    • 类/接口名:
    • 方法名:
    • 参数:直接写参数的类型,多个类型用逗号隔开
    • 异常名:方法定义中抛出指定异常,可以省略

4.2 通配符

可以使用通配符描述切入点,快速描述。

  • * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

execution(public * com.itheima.*.UserService.find*(*))
  • .. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

execution(public User com..UserService.findById(..))
  • +:专用于匹配子类类型
execution(* *..*Service+.*(..))

4.3 书写技巧

  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点 通常描述接口 ,而不描述实现类
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
  • 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用匹配,例如getById书写成getBy,selectAll书写成selectAll
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常 不使用异常 作为 匹配 规则

5 AOP通知类型

问题导入

请描述一下如何定义环绕通知方法?

5.1 AOP通知分类

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
  • AOP通知共分为5种类型
    • 前置通知:在切入点方法执行之前执行
    • 后置通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行。
    • 环绕通知(重点): 手动调用切入点方法并对其进行增强的通知方式。
    • 返回后通知(了解):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
    • 抛出异常后通知(了解):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。

5.2 AOP通知详解

5.2.1 前置通知

  • 名称:@Before
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
  • 范例:
@Before("pt()")
public void before() {System.out.println("before advice ...");
}

5.2.2 后置通知

  • 名称:@After
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
  • 范例:
@After("pt()")
public void after() {System.out.println("after advice ...");
}

5.2.3 返回后通知

  • 名称:@AfterReturning(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
  • 范例:
@AfterReturning("pt()")
public void afterReturning() {System.out.println("afterReturning advice ...");
}

5.2.4 抛出异常后通知

  • 名称:@AfterThrowing(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
  • 范例:
@AfterThrowing("pt()")
public void afterThrowing() {System.out.println("afterThrowing advice ...");
}

5.2.5 环绕通知

  • 名称:@Around(重点,常用)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
  • 范例::
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("around before advice ...");Object ret = pjp.proceed();System.out.println("around after advice ...");return ret;
}

环绕通知注意事项

  1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
  2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
  4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
  5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
  • 总结:
    • 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
    • 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。

二 AOP案例

1 案例-测量业务层接口万次执行效率

问题导入

能不能描述一下环绕通知里面的实现步骤?

1.1 需求和分析

  • 需求:
    • 任意业务层接口执行均可显示其执行效率(执行时长)
  • 分析:
    • 业务功能:业务层接口执行前后分别记录时间,求差值得到执行效率
    • 通知类型选择前后均可以增强的类型——环绕通知

1.2 代码实现

【前置工作】环境准备

  1. Spring整合mybatis对spring_db数据库中的Account进行CRUD操作
  2. Spring整合Junit测试CRUD是否OK。
  3. 在pom.xml中添加aspectjweaver切入点表达式依赖
  4. … …

【第一步】编写通知类

@Component
@Aspect
public class ProjectAdvice {//匹配业务层的所有方法@Pointcut("execution(* com.itheima.service.*Service.*(..))")private void servicePt(){}//设置环绕通知,在原始操作的运行前后记录执行时间@Around("ProjectAdvice.servicePt()") //本类类名可以省略不写public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {//获取执行的签名对象Signature signature = pjp.getSignature();//获取接口/类全限定名String className = signature.getDeclaringTypeName();//获取方法名String methodName = signature.getName();//记录开始时间long start = System.currentTimeMillis();//执行万次操作for (int i = 0; i < 10000; i++) {pjp.proceed();}//记录结束时间long end = System.currentTimeMillis();//打印执行结果System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");}
}

【第二步】在SpringConfig配置类上开启AOP注解功能

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableAspectJAutoProxy //开启AOP注解功能
public class SpringConfig {
}

【第三步】运行测试类,查看结果

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {@Autowiredprivate AccountService accountService;@Testpublic void testFindById(){Account account = accountService.findById(2);}@Testpublic void testFindAll(){List<Account> list = accountService.findAll();}
}

在这里插入图片描述

2 AOP切入点数据获取

问题导入

在环绕通知中可以获取到哪些数据?

2.1 获取参数

说明:在前置、后置、返回后、抛出异常后、环绕通知中都可以获取到连接点方法的参数们

  • JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()")
public void before(JoinPoint jp) {Object[] args = jp.getArgs(); //获取连接点方法的参数们System.out.println(Arrays.toString(args));
}
  • ProccedJointPoint是JoinPoint的子类
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {Object[] args = pjp.getArgs(); //获取连接点方法的参数们System.out.println(Arrays.toString(args));Object ret = pjp.proceed();return ret;
}

2.2 获取返回值

说明:在返回后通知和环绕通知中都可以获取到连接点方法的返回值

  • 返回后通知可以获取切入点方法中的返回值,使用形参可以接收对应的返回值
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(String ret) { //变量名要和returning="ret"的属性值一致System.out.println("afterReturning advice ..."+ret);
}
  • 环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {// 手动调用连接点方法,返回值就是连接点方法的返回值Object ret = pjp.proceed();return ret;
}

2.3 获取异常

说明:在抛出异常后通知和环绕通知中都可以获取到连接点方法中出现的异常

  • 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致System.out.println("afterThrowing advice ..."+ t);
}
  • 抛出异常后通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)  {Object ret = null;//此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常try {ret = pjp.proceed();} catch (Throwable t) {t.printStackTrace();}return ret;
}

3 案例-百度网盘密码数据兼容处理

问题导入

请说出我们该使用什么类型的通知来完成这个需求?

3.1 需求和分析

需求:对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理
在这里插入图片描述
分析:
①:在业务方法执行之前对所有的输入参数进行格式处理——trim()
②:使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用

3.2 代码实现

【前置工作】环境准备

//-------------service层代码-----------------------
public interface ResourcesService {public boolean openURL(String url ,String password);
}
@Service
public class ResourcesServiceImpl implements ResourcesService {@Autowiredprivate ResourcesDao resourcesDao;public boolean openURL(String url, String password) {return resourcesDao.readResources(url,password);}
}
//-------------dao层代码-----------------------
public interface ResourcesDao {boolean readResources(String url, String password);
}
@Repository
public class ResourcesDaoImpl implements ResourcesDao {public boolean readResources(String url, String password) {System.out.println(password.length());//模拟校验return password.equals("root");}
}

【第一步】编写通知类

@Component
@Aspect
public class DataAdvice {@Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")private void servicePt(){}@Around("DataAdvice.servicePt()")public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {Object[] args = pjp.getArgs();for (int i = 0; i < args.length; i++) {//判断参数是不是字符串if(args[i].getClass().equals(String.class)){args[i] = args[i].toString().trim();}}Object ret = pjp.proceed(args);return ret;}
}

【第二步】在SpringConfig配置类上开启AOP注解功能

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

【第三步】运行测试类,查看结果

public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);ResourcesService resourcesService = ctx.getBean(ResourcesService.class);boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", "root ");System.out.println(flag);}
}

4 AOP开发总结

4.1 AOP的核心概念

  • 概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式
  • 作用:在不惊动原始设计的基础上为方法进行功能增强
  • 核心概念
    • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
    • 连接点(JoinPoint): 在SpringAOP中,理解为任意方法的执行
    • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
    • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
    • 切面(Aspect):描述通知与切入点的对应关系
    • 目标对象(Target):被代理的原始对象成为目标对象

4.2 切入点表达式语法

  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
    • execution(* com.itheima.service.Service.(…))
  • 切入点表达式描述通配符:
    • 作用:用于快速描述,范围描述
      • *:匹配任意符号(常用)
      • .. :匹配多个连续的任意符号(常用)
      • +:匹配子类类型
  • 切入点表达式书写技巧
    • 按标准规范开发
    • 查询操作的返回值建议使用*匹配
    • 减少使用…的形式描述包
    • 对接口进行描述,使用*表示模块名,例如UserService的匹配描述为*Service
    • 方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy*
    • 参数根据实际情况灵活调整

4.3 五种通知类型

  • 前置通知
  • 后置通知
  • 环绕通知(重点)
    • 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
    • 环绕通知可以隔离原始方法的调用执行
    • 环绕通知返回值设置为Object类型
    • 环绕通知中可以对原始方法调用过程中出现的异常进行处理
  • 返回后通知
  • 抛出异常后通知

三 Spring事务管理

1 Spring事务简介

问题导入

Spring提供的事务管理是数据层的事务还是业务层的事务?

1.1 Spring事务作用

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或 业务层 保障一系列的数据库操作同成功同失败
    在这里插入图片描述

1.2 需求和分析

  • 需求:实现任意两个账户间转账操作
  • 需求微缩:A账户减钱,B账户加钱
  • 分析:
    • 数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
    • 业务层提供转账操作(transfer),调用减钱与加钱的操作
    • 提供2个账号和操作金额执行转账操作
    • 基于Spring整合MyBatis环境搭建上述操作
  • 结果分析:
    • 程序正常执行时,账户金额A减B加,没有问题
    • 程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

1.3 代码实现

【前置工作】环境准备

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>spring_24_case_transfer</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.18</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.20</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.6</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies>
</project>
  • Account实体类
import java.io.Serializable;public class Account implements Serializable {private Integer id;private String name;private Double money;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"id=" + id +", name='" + name + '\'' +", money=" + money +'}';}
}
  • AccountDao
public interface AccountDao {@Update("update tbl_account set money = money + #{money} where name = #{name}")void inMoney(@Param("name") String name, @Param("money") Double money);@Update("update tbl_account set money = money - #{money} where name = #{name}")void outMoney(@Param("name") String name, @Param("money") Double money);
}
  • AccountService
public interface AccountService {/*** 转账操作* @param out 传出方* @param in 转入方* @param money 金额*/public void transfer(String out,String in ,Double money) ;
}
  • AccountServiceImpl
@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;public void transfer(String out,String in ,Double money) {accountDao.outMoney(out,money);int i = 1/0;accountDao.inMoney(in,money);}
}
  • SpringConfig
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
  • MybatisConfig
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class MybatisConfig {@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();ssfb.setTypeAliasesPackage("com.itheima.domain");ssfb.setDataSource(dataSource);return ssfb;}@Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer msc = new MapperScannerConfigurer();msc.setBasePackage("com.itheima.dao");return msc;}
}
  • jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=root
  • JdbcConfig
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String userName;@Value("${jdbc.password}")private String password;@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(userName);ds.setPassword(password);return ds;}
}

【第一步】在业务层接口上添加Spring事务管理

public interface AccountService {//配置当前接口方法具有事务@Transactionalpublic void transfer(String out,String in ,Double money) ;
}
  • 注意事项
    • Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
    • 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

【第二步】设置事务管理器(将事务管理器添加到IOC容器中)

说明:可以在JdbcConfig中配置事务管理器

//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;
}

注意事项

  1. 事务管理器要根据实现技术进行选择
  2. MyBatis框架使用的是JDBC事务

【第三步】开启注解式事务驱动

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

【第四步】运行测试类,查看结果

import com.itheima.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.io.IOException;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {@Autowiredprivate AccountService accountService;@Testpublic void testTransfer() throws IOException {accountService.transfer("Tom","Jerry",100D);}
}

2 Spring事务角色

问题导入

什么是事务管理员,什么是事务协调员?

2.1 Spring事务角色

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
    在这里插入图片描述

3 Spring事务相关配置

问题导入

什么样的异常,Spring事务默认是不进行回滚的?

3.1 事务配置

在这里插入图片描述

说明:对于RuntimeException类型异常或者Error错误,Spring事务能够进行回滚操作。但是对于编译器异常,Spring事务是不进行回滚的,所以需要使用rollbackFor来设置要回滚的异常。

3.2 案例:转账业务追加日志

需求和分析

  • 需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
  • 需求微缩:A账户减钱,B账户加钱,数据库记录日志
  • 分析:
    • 基于转账操作案例添加日志模块,实现数据库中记录日志
    • 业务层转账操作(transfer),调用减钱、加钱与记录日志功能
  • 实现效果预期:
    • 无论转账操作是否成功,均进行转账操作的日志留痕
  • 存在的问题:
    • 日志的记录与转账操作隶属同一个事务,同成功同失败
  • 实现效果预期改进:
    • 无论转账操作是否成功,日志必须保留
  • 事务传播行为:事务协调员对事务管理员所携带事务的处理态度
    在这里插入图片描述

【准备工作】环境整备

USE spring_db;
CREATE TABLE tbl_log(id INT PRIMARY KEY AUTO_INCREMENT,info VARCHAR(255),createDate DATETIME
);
public interface LogService {//propagation设置事务属性:传播行为设置为当前操作需要新事务@Transactionalvoid log(String out, String in, Double money);
}@Service
public class LogServiceImpl implements LogService {@Autowiredprivate LogDao logDao;public void log(String out,String in,Double money ) {logDao.log("转账操作由"+out+"到"+in+",金额:"+money);}
}public interface LogDao {@Insert("insert into tbl_log (info,createDate) values(#{info},now())")void log(String info);
}

【第一步】在AccountServiceImpl中调用logService中添加日志的方法

@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Autowiredprivate LogService logService;public void transfer(String out,String in ,Double money) {try{accountDao.outMoney(out,money);int i = 1/0;accountDao.inMoney(in,money);}finally {logService.log(out,in,money);}}
}

【第二步】在LogService的log()方法上设置事务的传播行为

public interface LogService {//propagation设置事务属性:传播行为设置为当前操作需要新事务@Transactional(propagation = Propagation.REQUIRES_NEW)void log(String out, String in, Double money);
}

【第三步】运行测试类,查看结果

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {@Autowiredprivate AccountService accountService;@Testpublic void testTransfer() throws IOException {accountService.transfer("Tom","Jerry",50D);}
}

3.3 事务传播行为

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/29502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(八)穿越多媒体奇境:探索Streamlit的图像、音频与视频魔法

文章目录 1 前言2 st.image&#xff1a;嵌入图像内容2.1 图像展示与描述2.2 调整图像尺寸2.3 使用本地文件或URL 3 st.audio&#xff1a;嵌入音频内容3.1 播放音频文件3.2 生成音频数据播放 4 st.video&#xff1a;嵌入视频内容4.1 播放视频文件4.2 嵌入在线视频 5 结语&#x…

MySQL面试1

Mysql的面试突击1 Mysql的体系结构是什么样子的&#xff08;查询语句怎么进行执行的&#xff09; mysql的架构&#xff1a;单进程多线程的架构模式 CLient -----> Server架构 Mysql的链接方式有没有性能优化的点 2个点 查询缓存(Query Cache) MySQL 内部自带了一个缓存模…

Spring Boot集成EasyExcel实现excel导入导出操作

文章目录 Spring Boot集成EasyExcel实现excel导入导出操作0 简要说明简单使用读操作excel源文件实体类监听器业务代码 写操作*实体类*excel示例业务代码根据参数指定列导出指定哪几列导出复杂头导出 关于数值型&#xff0c;日期型&#xff0c;浮点型数据解决方案实体类接收字符…

算法通过村第二关-链表黄金笔记|K个一组反转

文章目录 前言链表反转|K个一组翻转链表解题方法&#xff1a;头插法处理&#xff1a;穿针引线法处理&#xff1a; 总结 前言 提示&#xff1a;没有人天生就喜欢一种气味而讨厌另一种气味。文明的暗示而已。 链表反转|K个一组翻转链表 给你链表的头节点 head &#xff0c;每 k…

【css】textarea-通过resize:none 禁止拖动设置大小

使用 resize 属性可防止调整 textareas 的大小&#xff08;禁用右下角的“抓取器”&#xff09;&#xff1a; 没有设置resize:none 代码&#xff1a; <!DOCTYPE html> <html> <head> <style> textarea {width: 100%;height: 150px;padding: 12px 20p…

源码分析——HashMap(JDK1.8)源码+底层数据结构分析

文章目录 HashMap 简介底层数据结构分析JDK1.8之前JDK1.8之后 HashMap源码分析构造方法put方法get方法resize方法 HashMap常用方法测试 HashMap 简介 HashMap 主要用来存放键值对&#xff0c;它基于哈希表的Map接口实现&#xff0c;是常用的Java集合之一。 JDK1.8 之前 HashM…

HBase-写流程

写流程顺序正如API编写顺序&#xff0c;首先创建HBase的重量级连接 &#xff08;1&#xff09;读取本地缓存中的Meta表信息&#xff1b;&#xff08;第一次启动客户端为空&#xff09; &#xff08;2&#xff09;向ZK发起读取Meta表所在位置的请求&#xff1b; &#xff08;…

python实现简单的爬虫功能

前言 Python是一种广泛应用于爬虫的高级编程语言&#xff0c;它提供了许多强大的库和框架&#xff0c;可以轻松地创建自己的爬虫程序。在本文中&#xff0c;我们将介绍如何使用Python实现简单的爬虫功能&#xff0c;并提供相关的代码实例。 如何实现简单的爬虫 1. 导入必要的…

1、如何实现两台电脑之间数据相互读写

一、确保两台电脑在同一个局域网中&#xff0c;可以使用网线【动态配置】进行两台电脑互连。 二、静态配置: 将IP地址和网关设为192.168.0.1&#xff0c;目的是让这台电脑做另一台电脑的网关&#xff0c;子网掩码一点击会自动添加。第二台电脑同样打开设置&#xff0c;此处IP地…

DT昆虫绑定学习(没蒙皮)

SelectEdgeLoopSp; ConvertSelectionToVertices;selectType -ocm -alc false; selectType -ocm -polymeshVertex true; CreateCluster; 连接到物体 global proc matchTrns() { string $mtr[] ls -sl; if (size($mtr) < 2){ warning "MUST select 2 objects!"…

SQL分类及通用语法数据类型(超详细版)

一、SQL分类 SQL是结构化查询语言&#xff08;Structured Query Language&#xff09;的缩写。它是一种用于管理和操作关系型数据库系统的标准化语言。SQL分类如下&#xff1a; DDL: 数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库、表、字段&#xff09;DML:…

Docker学习(二十四)报错速查手册

目录 一、This error may indicate that the docker daemon is not running 报错docker login 报错截图&#xff1a;原因分析&#xff1a;解决方案&#xff1a; 二、Get "https://harbor.xxx.cn/v2/": EOF 报错docker login 报错截图&#xff1a;原因分析&#xff1a…

DARPA TC-engagement5数据集解析为json格式输出到本地

关于这个数据集的一些基本信息就不赘述了&#xff0c;参考我之前的博客。DARPA TC-engagement5数据集官方工具可视化 官方给的工具是将解析的数据存到elasticsearch的&#xff0c;但是数据集的解压增长率非常恐怖&#xff0c;对空间要求很高。因此针对这个问题&#xff0c;我对…

安全防御(3)

1.总结当堂NAT与双机热备原理&#xff0c;形成思维导图 2.完成课堂nat与双机热备试验 引用IDS是指入侵检测系统&#xff0c;它可以在网络中检测和防御入侵行为。IDS的签名是指根据已知入侵行为的特征制定的规则&#xff0c;用于检测和警告可能存在的入侵行为。签名过滤器可以根…

计算机网络 ARP协议 IP地址简述

ARP只能在一个链路或一段网络上使用

玩机搞机---安卓新机型payload.bin刷写救砖 无需专用线刷包

目前的新机型官方卡刷包解包后都是payload.bin分区格式的卡刷固件。而有个别一些机型没有线刷包&#xff0c;当这些机型出现系统问题的时候有以下几种方法参考救砖。遇到类似故障的朋友可以借鉴参考下. 其中的不足和相关的资源可以参考这两个博文。任何教程的目的只是拓展你的…

LVS—DR集群的搭建

目录 lvs-dr模式工作原理&#xff1a; 搭建结构&#xff1a; 1、RS&#xff1a; 1&#xff09;两台RS准备好httpd环境和测试文件 2&#xff09;添加虚拟IP&#xff08;vip&#xff09;、添加访问本地vip的静态路由 并抑制ARP 2、DS&#xff1a; 1&#xff09;安装ipvasadm…

【C++】C++回调函数基本用法(详细讲解)

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

CommStudio for .NET Crack

CommStudio for .NET Crack CommStudio for.NET使您的应用程序可以轻松地使用串行端口和调制解调器进行通信。CommStudio for.NET是一套全面的组件和可视化调试工具&#xff0c;可将远程系统和设备与visual Studio 2005和visual Studio 2008集成。开发与遗留系统和外部设备集成…

成集云 | 畅捷通采购单同步至钉钉 | 解决方案

源系统成集云目标系统 介绍 畅捷通是一家专业的金融科技公司&#xff0c;致力于为投资者提供便捷、高效的金融服务。通过畅捷通T的交易方式&#xff0c;投资者可以更加灵活地进行买卖交易&#xff0c;并且在交易完成后即可获得结算款项&#xff0c;无需等待T1的结算周期。 钉…