JavaWeb--SpringBoot--事务管理和AOP

90 阅读6分钟

1 事务回滚

1.1.** 概念:**

事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。

1.2. 操作:

- 开启事务(一组操作开始前,开启事务):start transaction/begin;
- 提交事务(这组操作全部成功后,提交事务):commit;
- 回滚事务(中间任何一个操作出现异常,回滚事务):rol1back;

2 Spring事务管理

2.1**@ 注解**

  • 注解:@Transactional
  • 位置:业务(service)层的方法上、类上、接口上
  • 作用:将当前方法交给spg进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务

三种方式

image.png

2.2 开启事务管理日志

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

3 事务的进阶

  • rollbackFor
  • propagation

3.1 事务属性-回滚

  • rollbackFor

默认情况下,只有出现RuntimeExceptio心才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务。

3.2 事务属性-传播行为

  • propagation

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制

image.png

逻辑层

@Transactional(rollbackFor = Exception.class) // springBoot 事务回滚
@Override
public void delete(Integer id) throws Exception {

    try {
        deptMapper.deleteById(id);  // 根据ID删除部门

        int i = 1/0;

   /*  if (true){
        throw new Exception("出错了!!!");
    } */

        empMapper.delectByDeptId(id); // 根据ID删除员工
    } finally {
        DeptLog deptLog = new DeptLog();
        deptLog.setCreateTime(LocalDateTime.now());
        deptLog.setDescription("执行啦解散部门的操作,此次解散的部门id"+id+"号部门");
        deptLogService.insert(deptLog);
    }
}

数据层

package com.itheima.mapper;

import com.itheima.pojo.DeptLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Mapper
public interface DeptLogMapper {

    @Insert("insert into deptlog (create_time, description) values (#{createTime}, #{description})")
    void insert(DeptLog deptLog);
}

image.png

3.3 通知类型

1.@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
2.@Before:前置通知,此注解标注的通知方法在目标方法前被执行
3.@Aftr:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
4.@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
5.@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

注意事项

  • @Around环绕通知需要自己调用Proceeding].oinPoint,proceed()来让原始方法执行,其他通知不需>要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。

3.4通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,
执行顺序
1.不同切面类中,默认按照切面类的类名字母排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
2.用 `@Order(数字)`加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行

4.4切入点表达式

  • 切入点表达式:描述切入点方法的一种表达式
  • 作用:主要用来决定项目中的哪些方法需要加入通知
  • 常见形式:
    • execution(…):根据方法的签名来匹配
    • @annotation():根据注解匹配

4.4.1 切入点表达式-execution

execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为: execution(访问修饰符?返回值包名.类名.?方法名(方法参数)throws异常?)

其中带的表示可以省略的部分

  • 访问修饰符:可省略(比如:public、protected)
  • 包名类名:可省略
  • throws异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
@Before("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
public void before(JoinPoint joinPoint){}
  • 可以使用通配符描述切入点

*:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

execution(*com.*.service.*.update*(*))

·:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

execution(*com.itheima..DeptService.*(..)

注意事项

  • 根据业务需要,可以使用且(&&)、或()、非(!)来组合比较复杂的切入点表达式。

书写建议

  • 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是find开头,更新类方法都是update?开头。

  • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。

  • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用,使用*匹配单个包。

实例


@Slf4j
@Aspect
@Component
public class MyAspcet5 {

    // @Pointcut("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
    // @Pointcut("execution(void delete(java.lang.Integer))")
    // @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
    // @Pointcut("execution(* com.*.service.impl.DeptServiceImpl.*(*))")
    // @Pointcut("execution(* com.itheima.service.*Service.delete(java.lang.Integer))")
    // @Pointcut("execution(* com.itheima.service.DeptService.*(..))")
    // @Pointcut("execution(* com..service.DeptService.*(..))")
    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.delete(*)) ||" +
            "execution(* com.itheima.service.impl.DeptServiceImpl.list(..))")
    private void pt(){}

    @Before("pt()")
    private void before() {
        log.info("before...5");
    }
}

4.4.2 切入点表达式-annotation

切入点表达式-@annotation

  • @annotation切入点表达式,用于匹配标识有特定注解的方法。
@annotation(com.itheima.anno.Log)
@Before ("@annotation(com.itheima.anno.Log)")
public void before(){
log.info ("before ....") 

实例

1、新建注解

@Retention(RetentionPolicy.RUNTIME) // 注解什么时候生效
@Target(ElementType.METHOD) // 注解什么时候执行
public @interface MyLog {
}

2、引用注解

@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptMapper deptMapper;


    @MyLog
    public List<Dept> list() {
        return deptMapper.list();
    }

    @MyLog
    public void delete(Integer id) {
        deptMapper.deleteById(id);  // 根据ID删除部门
    }
    }

3、使用

@Slf4j
@Aspect
@Component
public class MyAspcet6 {
    // @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.delete(*)) ||" +
    //         "execution(* com.itheima.service.impl.DeptServiceImpl.list(..))")

    @Pointcut("@annotation(com.itheima.aop.MyLog)")
    private void pt() {
    }

    @Before("pt()")
    private void before() {
        log.info("MyAspcet6...before");
    }
}

3.5连接点

在Spring中用]oinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

对于@Around通知,获取连接点信息只能使用Proceeding]oinPoint

对于其他四种通知,获取连接点信息只能使用JoinPoint,它是Proceeding]oinPoint的父类型

实例

@Slf4j
@Aspect
@Component
public class MyAspcet7 {

    @Pointcut("execution(* com.itheima.service.DeptService.*(..))")
    private void pt() {
    }

    @Before("pt()")
    private void before(JoinPoint joinPoint) {
        log.info("MyAspcet7...before");
    }

    @Around("pt()")
    private Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("MyAspcet7..around");

        // 1.获取目标对象的类名.
        String className = joinPoint.getClass().getName();
        log.info("获取目标对象的类名:{}", className);

        // 2.获取目标方法的方法名.
        String method = joinPoint.getSignature().getName();
        log.info("获取目标对象的方法名:{}", method);

        // 3.获取目标方法运行时传入的参数·
        Object[] args = joinPoint.getArgs();
        log.info("获取目标对象的参数:{}", Arrays.toString(args));

        // 4.获取目标方法执行.
        Object result = joinPoint.proceed(args);

        // 5.获取目标方法运行的返回值.
        log.info("获取目标对象的返回值:{}", result);

        return result;
    }
}

springboot原理

起步依赖

通过meavn的依赖传递

自动配置的原理

image.png

image.png

// @ComponentScan({"com.example","com.itheima"})


//@Import({TokenParser.class})
//@Import({HeaderConfig.class})
// @Import(MyImportSelector.class)
@EnableHeaderConfig
@SpringBootApplication
public class SpringbootWebConfig2Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }

}

conditional

image.png

@Configuration
public class HeaderConfig {

    @Bean
    // @ConditionalOnClass(name = "io.jsonwebtoken.Jwts") // 环境中存在指定的这个类,才会将该bean加入IOC容器中
    // @ConditionalOnMissingBean // 不存在该类型的bean,才会讲该bean加入IOC容器中 -- 指定类型(value属性) 或 名称(name属性)
    @ConditionalOnProperty(name = "name", havingValue = "itheima") // 配置文件中存在指定的属性和值,才会将该bean加入IOC容器中
    public HeaderParser headerParser() {
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator() {
        return new HeaderGenerator();
    }
}