@Override
public void delete(Integer id) {
deptMapper.deleteById(id); //根据id删除部门数据
/*如果这里出现异常,即时异常抛出,下面代码也不会执行,员工归属部门会出现问题
* 需要把这个整体代码封装到一个事务当中,来解决,使用 @Transactional 注解 */
empMapper.deleteByDeptId(id); //根据部门id删除该部门下的员工
}
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
@Transactional //spring 事务管理
@Override
public void delete(Integer id) {
deptMapper.deleteById(id); //根据id删除部门数据
empMapper.deleteByDeptId(id); //根据部门id删除该部门下的员工
}
rollbackFor 异常回滚属性
默认情况下,只有出现 RuntimeException(运行时异常) 才回滚异常。rollbackFor 属性用于控制出现何种异常类型,回滚事务。
@Transactional(rollbackFor = Exception.class) //所有异常都会进行回滚操作
propagation 事务传播行为
属性值 | 含义 |
---|---|
REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 如果调用b方法时有事务,就加入到这个事务中,如果调用b方法时没事务,就再现有的方法中运行 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 如果调用 b 方法的时候已经发现存在事务了,会把事务先挂起再执行b方法 |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须没事务,否则抛异常 |
… |
@Transactional
public void a() {
// ...
userService.b(); //如果b方法支持事务(有@Transactional注解)
// ...
}
@Transactional(propagation = Propagtion.REQUIRED)
public void b(){
// ...
}
案例:
需求:解散部门时,无论是成功还是失败,都要记录操作日志
@Transactional(rollbackFor = Exception.class) //spring 事务管理
@Override
public void delete(Integer id) {
try {
deptMapper.deleteById(id); //根据id删除部门数据
int i = 1/0;
empMapper.deleteByDeptId(id); //根据部门id删除该部门下的员工
} finally {
DeptLog log = new DeptLog();
log.setCreateTime(LocalDateTime.now());
log.Descrtption("执行了解散部门操作,此次解散的是" + id + "号的部门");
deptLogService.insert(log); //记录操作日志
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Overide
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
//这个类里面的所有方法
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> list() {
List<Dept> deptList = deptMapper.list();
return deptList;
}
@Override
public void delete(Integer id) {
deptMapper.delete(id);
}
@Override
public void save(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.save(dept);
}
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
/*1. 记录开始时间*/
long begin = System.currentTimeMillis();
/*2. 调用原始方法运行*/
Object result = joinPoint.proceed();
/*3. 记录结束时间, 计算方法执行耗时*/
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"方法执行耗时: {}ms", end-begin);
//joinPoint.getSignature()拿到方法的签名
return result;
}
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))"
//切面
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") //切入点表达式
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"方法执行耗时: {}ms", end-begin);
return result;
}
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
用方法运行结束时间 —— 方法运行开始时间 ==== 运行原始方法时间
<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Slf4j
@Component //将当前类交给ioc容器管理
@Aspect //AOP类
public class TimeAspect {
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") //切入点表达式
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
/*1. 记录开始时间*/
long begin = System.currentTimeMillis();
/*2. 调用原始方法运行*/
Object result = joinPoint.proceed();
/*3. 记录结束时间, 计算方法执行耗时*/
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"方法执行耗时: {}ms", end-begin);
//joinPoint.getSignature()拿到方法的签名
return result;
}
}
@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt(){}
@Before("pt()")
public void before(){
log.info("before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
log.info("around after ...");
return result;
}
@After("pt()")
public void after(){
log.info("after ...");
}
@AfterReturning("pt()")
public void afterReturning(){
log.info("afterReturning ...");
}
@AfterThrowing("pt()")
public void afterThrowing(){
log.info("afterThrowing ...");
}
}
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt(){}
//public:在其他外部的切面类中也可以引用该表达式
//privite:仅能在当前切面类中引用该表达式
@Before("pt()")
public void before(){
log.info("before ...");
}
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
@Order(1)
@Slf4j
@Component
@Aspect
public class MyAspect4 {
@Before("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void before(){
log.info("before ...4");
}
@After("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void after(){
log.info("after ...4");
}
}
切入点表达式——execution
切入点表达式——@annotation
就是可以被 AOP 控制的方法
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("MyAspect8 around before ...");
//1. 获取 目标对象的类名 .
String className = joinPoint.getTarget().getClass().getName();
log.info("目标对象的类名:{}", className);
//2. 获取 目标方法的方法名 .
String methodName = joinPoint.getSignature().getName();
log.info("目标方法的方法名: {}",methodName);
//3. 获取 目标方法运行时传入的参数 .
Object[] args = joinPoint.getArgs();
log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));
//4. 放行 目标方法执行 .
Object result = joinPoint.proceed();
//5. 获取 目标方法运行的返回值 .
log.info("目标方法运行的返回值: {}",result); //前置通知拿不到返回值
log.info("MyAspect8 around after ...");
return result;
}
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("MyAspect8 ... before ...");
}
将案例中 增、删、改 相关接口的操作日志记录到数据库表中
@Slf4j
@Component
@Aspect //切面类
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.itheima.anno.Log)") //匹配方法上加有 Log 注解的方法
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
/*操作人 ID——当前登录人的ID*/
//获取请求头的 jwt 令牌,解析令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
/*操作时间*/
LocalDateTime operateTime = LocalDateTime.now();
/*操作类类名*/
// 目标对象 类对象 目标类类名
String className = joinPoint.getTarget().getClass().getName();
/*操作方法名*/
// 方法签名 方法名
String methodName = joinPoint.getSignature().getName();
/*操作方法参数*/
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
long begin = System.currentTimeMillis();
/*调用原始目标方法运行*/
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
/*方法返回值*/
String returnValue = JSONObject.toJSONString(result);
/*操作耗时*/
long costTime = end - begin;
/*记录操作日志*/
OperateLog operateLog = new OperateLog(null, operateUser, operateTime, className, methodName, methodParams, returnValue, costTime);
operateLogMapper.insert(operateLog);
log.info("AOP记录操作日志:{}", operateLog);
return result;
}
}
配置:
SpringBoot 除了支持配置文件属性配置,还支持 Java系统属性 和 命令行参数 的方式进行属性配置
//获取bean对象
@Test
public void testGetBean(){
//根据bean的名称获取
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");//类名首字母小写
System.out.println(bean1);
//根据bean的类型获取
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);
//根据bean的名称 及 类型获取
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(bean3);
}
作用域 | 说明 |
---|---|
singleton | 容器内同 名称 的 bean 只有一个实例(单例)(默认) |
prototype | 每次使用该 bean 时会创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web环境中) |
session | 每个请求范围内会创建新的实例(web环境中) |
application | 每个请求范围内会创建新的实例(web环境中) |
@SpringBootApplication
public class SpringbootWebConfig2Application {
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
public SAXReader saxReader(){
return new SAXReader();
}
}
//写在启动类,不建议
@Configuration //配置类
public class CommonConfig {
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
//通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
public SAXReader reader(DeptService deptService){
System.out.println(deptService);
return new SAXReader();
}
}
maven 的依赖传递
自动配置原理
@ComponentScan
组件扫描@Import
导入(使用@Import导入的类会被Spring加载到IOC容器中)@ComponentScan
组件扫描
@Import
导入,使用@Import
导入的类会被 Spring 加载到 IOC 容器中
@Import
导入普通类:
@Import
导入配置类:
@Import
导入ImportSelector
接口实现类:
ImportSelector
接口实现类
@EnableXxxxx
注解
@EnableXxxxx
注解即可
源码跟踪
@SpringBootApplication
该注解标识在 SpringBoot 工程引导类上,是 SpringBoot 中最重要的注解,该注解由三个部分组成:
@SpringBootConfiguration
:该注解与@Configuration
注解作用相同,用来声明当前也是一个配置类@EnableAutoConfiguration
:SpringBoot 实现自动化配置的核心注解@ComponentScan
:组件扫描,默认扫描当前引导类所在包及其子包自动配置原理:
@Conditional
@Conditional
本身是一个父注解,派生出大量的子注解
@ConditionalOnClass
:判断环境中是否有对应字节码文件,才注册 bean 到 IOC 容器@ConditionalOnMissingBean
:判断环境中没有对应的 bean (类型 或 名称),才注册 bean 到 IOC 容器@ConditionalOnProperty
:判断配置文件中有对应属性和值,才注册 bean 到 IOC 容器SpringBoot 会根据 @Conditional 注解条件装配
案例(自定义 stater)
<parent> ... </parent>
<dependencyManagement>
来统一管理依赖版本<dependencyManagement>
<dependencies>
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
自定义属性 / 引用属性
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<lombok.version>1.18.24</lombok.version>
<jjwt.version>0.9.1</jjwt.version>
<aliyun.oss.version>3.15.1</aliyun.oss.version>
<jaxb.version>2.3.1</jaxb.version>
<activation.version>1.1.1</activation.version>
<jaxb.runtime.version>2.3.3</jaxb.runtime.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
<!--统一管理依赖版本-->
<dependencyManagement>
<dependencies>
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!--阿里云OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>${activation.version}</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>${jaxb.runtime.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
在 maven 中,可以在父工程的 pom 文件中通过<dependencyManagement>
来统一管理依赖版本,在子工程中就不用指定 version 版本号了
</dependencies>
:是直接依赖,在父工程配置了依赖,子工程会直接继承下来<dependencyManagement>
:是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)<modules>
设置当前聚合工程所包含的子模块名称 <!--聚合其他模块-->
<modules>
<module>../tlias-pojo</module> <!--要和模块名平级-->
<module>../tlias-utils</module>
<module>../tlias-web-management</module>
</modules>
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。