事物管理
事务管理是指对一系列数据库操作进行管理,确保这些操作要么全部成功执行,要么在遇到错误时全部回滚,以维护数据的一致性和完整性。在多用户并发操作和大数据处理的现代软件开发领域中,事务管理已成为确保数据一致性和完整性的关键技术之一。
基本概念
- 定义:事务是由N步数据库操作序列组成的逻辑执行单元,这系列操作要么全执行,要么全放弃执行。
- 四大特性(ACID):事务需要满足原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)四个基本属性。
Spring事务管理
Spring事务管理是Spring框架提供的一个重要功能,用于确保在应用程序中多个数据库操作能够作为一个整体进行管理,要么所有操作全部成功,要么在出现错误时全部回滚。
Spring事务管理通过提供一个统一的编程模型,可以整合各种事务API(如JTA、JDBC、Hibernate等),使得事务管理更加简便和统一。以下是Spring事务管理的详细解析:
Spring事务管理器
- 事务管理器接口:Spring事务管理器的核心接口是
PlatformTransactionManager
,它定义了事务管理的基本操作,包括获取事务状态、提交事务和回滚事务等。 - 具体实现类:针对不同的持久化技术,Spring提供了多个实现类,例如
DataSourceTransactionManager
用于JDBC事务管理,HibernateTransactionManager
用于Hibernate事务管理,以及JpaTransactionManager
用于Java Persistence API (JPA)事务管理。
Spring事务传播行为
- 传播行为定义:事务传播行为定义了事务方法之间的调用关系。例如,当一个标记为
@Transactional
的方法A调用另一个标记为@Transactional
的方法B时,方法B是运行在方法A的事务内部,还是独立运行在自己的事务中,这由传播行为决定。 - 常见传播行为:Spring支持多种传播行为,包括
REQUIRED
(支持当前事务,如果没有则创建新事务)、REQUIRES_NEW
(无论是否存在事务,都创建新事务并暂停当前事务)、NESTED
(如果存在当前事务,则嵌套在其中;否则与REQUIRED
类似)等。
Spring事务隔离级别
- 隔离级别定义:事务隔离级别用于解决多个事务并发执行时可能产生的问题,如脏读、不可重复读和幻读等。
- 常用隔离级别:Spring支持多种隔离级别,包括
DEFAULT
(使用底层数据库默认隔离级别)、READ_COMMITTED
(提交读,防止脏读,但不能防止不可重复读和幻读)、SERIALIZABLE
(串行化,最高隔离级别,防止所有并发问题,但性能开销大)等。
Spring事务注解用法
- @Transactional注解:该注解用于声明事务管理,可以标注在类或方法上。通过该注解,开发人员可以轻松地开启事务管理,而不必像传统JDBC那样手动管理事务边界。
- 注解属性:
@Transactional
注解有多个属性,如propagation
(指定传播行为)、isolation
(指定隔离级别)、readOnly
(指定事务是否只读)、timeout
(指定事务超时时间)等。
Spring事务回滚规则
- 遇到运行时异常回滚:Spring默认设置是在遇到运行时异常(
RuntimeException
)和错误(Error
)时回滚事务。 - 遇到检查异常不回滚:如果方法抛出检查异常(
Checked Exception
),事务将不会回滚。如果需要在这些情况下回滚,可以通过在@Transactional
注解中设置rollbackFor
属性来指定异常类型。
Spring事务模板
- 编程式事务管理:除了声明式事务管理,Spring还支持编程式事务管理。这允许通过代码更灵活地控制事务的开始和结束。
- 使用事务模板:
TransactionTemplate
是Spring提供的一个工具类,用于编程式事务管理。通过这个模板,可以使用execute
方法执行具有事务保护的操作。
代码示例:
启动类加注释@EnableTransactionManagement
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching//开启spring cache 操作redis
public class Demo {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public void createUser(String name, String email) {
// 创建用户对象
User user = new User();
user.setName(name);
user.setEmail(email);
// 保存用户到数据库
userRepository.save(user);
// 模拟异常,触发事务回滚
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email cannot be empty");
}
}
}
在这个示例中,我们定义了一个UserService
类,其中包含一个createUser
方法用于创建用户。该方法使用@Transactional
注解标记为事务方法,表示该方法需要在一个事务中执行。在方法内部,我们首先创建一个User
对象并设置其属性,然后调用userRepository.save()
方法将用户保存到数据库中。最后,我们模拟了一个异常情况,当传入的电子邮件为空时抛出IllegalArgumentException
异常,这将导致事务回滚。
需要注意的是,@Transactional
注解可以应用于类或方法级别。在上述示例中,我们将其应用于方法级别,这意味着只有该方法内的代码会在一个事务中执行。如果将其应用于类级别,则该类中的所有方法都将在一个事务中执行。
AOP
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,通过对程序的横切关注点进行模块化,强化程序的可重用性和开发效率。下面将细致解析AOP的概念、原理、应用场景以及在Spring框架中的具体实现:
AOP的概念
- 定义:AOP是面向对象编程(OOP)的延续,旨在将散落在系统各个部分的交叉业务逻辑(如日志记录、事务管理等)封装起来,从而将应用中的业务逻辑与系统服务(如事务管理)分离。
- 切面(Aspect):切面是AOP的核心,它将横切关注点如日志、事务等通用功能模块化。
- 连接点(Joint point)和切点(Pointcut):连接点指程序中明确定义的点,如方法调用或异常处理;切点则是一组连接点,通过逻辑关系或正则表达式集中,定义增强(Advice)何时何地发生。
- 增强(Advice):增强定义了切点处要执行的操作,可以在切点之前、之后或代替原操作执行。
AOP的原理
- 代理模式:AOP基于代理模式,动态或静态地创建一个对象的代理,代理对象包含了原对象的核心业务逻辑以及横切关注点的增强逻辑。
- 织入(Weaving):织入是将切面与目标对象结合的过程,可以在编译期、类加载期或运行期完成。例如,Spring AOP通常在运行期通过动态代理实现。
- 通知(Advice)分类:AOP中的通知可以分为前置通知(Before advice)、后置通知(After returning advice)、环绕通知(Around advice)等,每种通知都有特定执行时机。
AOP的应用场景
- 日志记录:通过AOP可以统一处理日志记录,无需在每个方法内部手动添加日志代码,提高代码的可维护性。
- 事务管理:AOP能够自然地处理事务管理,增强方法的安全性和可靠性。在方法执行前后自动开启和提交事务,出现异常时回滚。
- 权限验证:通过AOP切入权限检查逻辑,确保只有具备相应权限的方法调用才能执行。
- 性能监测:AOP还可以用于性能监测,通过增强方法记录方法执行时间和异常情况,帮助开发者优化性能。
Spring框架中的AOP
- Spring AOP的实现:Spring通过动态代理实现AOP,主要支持方法类型的连接点。如果需要对字段访问进行增强,则需要使用AspectJ等工具。
- 注解和配置:在Spring中,可以通过
@Aspect
注解定义切面,并通过@Pointcut
定义切点,最后使用@Before
、@After
、@Around
等注解定义具体通知逻辑。 - Spring AOP的局限:Spring AOP主要是动态代理机制,基于接口的动态代理(JDK动态代理),或者基于类的动态代理(CGLib代理)。对于没有实现接口的类,Spring AOP使用CGLib来实现代理。
AOP快速入门
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
切面类
@Component
@Aspect //当前类为切面类
@Slf4j
public class TimeAspect {
@Around("execution(* com.aop.service.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//记录方法执行开始时间
long begin = System.currentTimeMillis();
//执行原始方法
Object result = pjp.proceed();
//记录方法执行结束时间
long end = System.currentTimeMillis();
//计算方法执行耗时
log.info(pjp.getSignature()+"执行耗时: {}毫秒",end-begin);
return result;
}
}
通知类型
Spring AOP中共有五种类型的通知,这些通知定义了切面在何时以及如何与目标对象的方法进行交互。以下是这五种通知的详细介绍及其具体应用场景:
前置通知(Before advice)
- 定义:前置通知在目标方法执行之前运行,可以对方法的输入参数进行检查或修改。
- 使用场景:常用于权限验证、日志记录等。例如,可以在方法执行前检查用户是否具备相应操作权限。
- 示例:
@Before("execution(* com.example.service.*.*(..))") public void beforeMethod(JoinPoint joinPoint) { System.out.println("前置通知: " + joinPoint.getSignature().getName() + " 方法开始执行"); }
后置返回通知(After returning advice)
- 定义:后置返回通知在目标方法成功完成并返回结果后执行。
- 使用场景:适用于记录操作成功的日志、统计方法的执行时间等。
- 示例:
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result") public void afterReturningAdvice(Object result) { System.out.println("方法执行结束,返回值:" + result); }
后置异常通知(After throwing advice)
- 定义:当目标方法抛出异常时,后置异常通知会被执行。
- 使用场景:用于处理异常情况,如记录错误日志、清理资源等。
- 示例:
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex") public void afterThrowingAdvice(Exception ex) { System.out.println("方法执行异常,异常信息:" + ex.getMessage()); }
后置最终通知(After (finally) advice)
- 定义:无论目标方法是正常结束还是抛出异常,后置最终通知都会在方法完成后执行。
- 使用场景:常用于释放资源、记录方法的退出等。
- 示例:
@After("execution(* com.example.service.*.*(..))") public void afterMethod(JoinPoint joinPoint) { System.out.println("最终通知: " + joinPoint.getSignature().getName() + " 方法结束执行"); }
环绕通知(Around advice)
- 定义:环绕通知在目标方法执行前后都可以执行自定义行为,并且可以选择是否继续执行目标方法。
- 使用场景:最强大的通知类型,可以替代其他所有类型的通知,适用于事务管理、性能监控等复杂场景。
- 示例:
@Around("execution(* com.example.service.*.*(..))") public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知 - 方法开始执行"); Object result = proceedingJoinPoint.proceed(); System.out.println("环绕通知 - 方法结束执行"); return result; }
以上是Spring AOP提供的五种通知类型及其详细解释和示例代码。每种通知都有其特定的用途,可以根据具体需求选择合适的通知类型来实现AOP功能。
通知顺序
在Spring AOP中,如果有多个切面类同时作用于一个方法,它们的执行顺序是由切面类的优先级决定的。具体来说,有以下几种方式可以配置切面的执行顺序:
通过实现Ordered
接口
- 切面类可以实现
org.springframework.core.Ordered
接口,并重写getOrder()
方法来指定切面的优先级。数值越小,优先级越高,越先执行。例如:@Component @Aspect public class FirstAspect implements Ordered { public int getOrder() { return 1; // 优先级最高 } // 切面逻辑... }
使用@Order注解
- 在切面类上添加
@org.springframework.core.annotation.Order
注解,并指定一个整数值来表示切面的执行顺序。同样,数值越小,优先级越高。例如:@Component @Aspect @Order(2) public class SecondAspect { // 切面逻辑... }
通过配置文件
- 在Spring配置文件中,可以使用
<aop:config>
元素,并通过order
属性为每个切面指定执行顺序。例如:<aop:config> <aop:aspect ref="firstAspect" order="1"/> <aop:aspect ref="secondAspect" order="2"/> <!-- 其他切面配置 --> </aop:config>
当多个切面类联合作用于同一个方法时,它们的执行顺序遵循以下规则:
- 环绕通知(Around Advice)首先执行,因为它可以控制目标方法的执行时机。
- 紧接着是前置通知(Before Advice),然后是后置返回通知(After Returning Advice)或后置异常通知(After Throwing Advice)。
- 最后执行的是后置最终通知(After (Finally) Advice),它无论目标方法是正常结束还是异常结束都会被执行。
在切面内部,通知按照其类型依次执行,但在多个切面之间,其执行顺序则依据前述的优先级配置。如果未显式指定优先级,则默认按照切面类的名称字母顺序执行,字母越小越靠前。
切入点表达式
切入点表达式是Spring AOP中用于定义通知执行范围的表达式。下面将详细解析切入点表达式的各个组成部分以及如何有效应用它们:
概述
- 切入点表达式(Pointcut Expression)是Spring AOP实现中非常关键的部分,它定义了哪些方法应该被切面(Aspect)影响。切点表达式可以精确地指定哪些包、类、方法以及它们的参数会被应用通知(Advice)。
类型
execution
: 匹配方法执行点,是最常用和最复杂的切点表达式。例如,execution(* com.xyz.service.AccountService.*(..))
表示所有com.xyz.service.AccountService
接口或类中的方法都会被匹配。within
: 限定某个类型内部的方法。如within(com.xyz.service.*)
将会匹配com.xyz.service
包下所有类的方法。this
: 当代理对象可以被强制转型为给定的类型时,满足切点。例如this(com.xyz.service.AccountService)
会拦截所有AccountService
类型的Bean方法。target
: 当被代理的对象可以被强制转型为给定的类型时,满足切点。例如target(com.xyz.service.AccountDAO)
会拦截所有AccountDAO
类型的Bean方法。args
: 根据方法参数的类型来匹配切点。如args(java.lang.String)
将会匹配所有第一个参数为String
类型的方法。@annotation
: 当存在特定注解时匹配切点。例如@annotation(org.springframework.web.bind.annotation.RequestMapping)
会匹配所有使用@RequestMapping
注解的方法。
组合
- 在定义切面时,可以使用逻辑运算符如
&&
、||
和!
来组合多个切点表达式,以满足复杂业务场景的需求。例如execution(* *(..)) && this(com.xyz.service.AccountService)
仅匹配AccountService
接口或类中的所有方法。
语法细节
modifier
: 可选,用于匹配方法的访问修饰符(如public、protected等)。ret-type
: 必选,用于匹配方法的返回类型。使用*
表示任意类型。declaring-type
: 可选,用于匹配类的全路径类型。使用*
表示任意类型,使用..
表示当前包及其子包中的类。name-pattern
: 必选,用于匹配方法名称。使用*
作为通配符。param-pattern
: 必选,用于匹配方法参数。()
表示无参数,(..)
表示有任意数量参数,(*)
表示有一个任意类型的参数。
公共配置
- 可以在任意类中定义一个公共方法,并使用
@Pointcut
注解来指定表达式。例如,在一个专门配置切点的类中定义公共切点方法,并在切面类中引用这些切点方法。v
execution
在Spring AOP中,execution切入点表达式是用于定义哪些方法应该被切面(Aspect)影响的表达式。下面将详细解析execution
表达式的各个组成部分,并给出具体的应用示例:
概述
execution
表达式是Spring AOP中最常用且最复杂的切入点表达式,它允许开发者精确地匹配方法执行的连接点(Join point)。开发者可以通过该表达式指定包括修饰符、返回类型、类名、方法名和参数类型等多个元素来过滤需要拦截的方法。
语法格式
execution
表达式的基础语法结构为:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
。其中,返回类型模式(ret-type-pattern)、方法名模式(name-pattern)和参数模式(param-pattern)是必须的部分,而其他部分如修饰符模式(modifiers-pattern)和异常模式(throws-pattern)是可选的。- 例如,
execution(* com.xyz.service.AccountService.*(..))
表示匹配com.xyz.service.AccountService
接口或类中的所有方法。
关键元素详解
modifiers-pattern
: 可选,匹配方法的访问修饰符,如public、private等。ret-type-pattern
: 必选,指定方法的返回类型,使用*
表示任意类型。declaring-type-pattern
: 可选,指定类的全路径类型或包路径。使用*
表示任意类型,使用..
表示当前包及其子包中的类。name-pattern
: 必选,指定方法名称,使用*
作为通配符。param-pattern
: 必选,指定方法参数的类型,()
表示无参数,(..)
表示有任意数量参数,(*)
表示有一个任意类型的参数。
具体示例
execution(* *(..))
: 匹配所有返回任意类型且接受任意数量参数的方法。execution(public * *(..))
: 仅匹配所有public方法。execution(* com.taotao.Waiter.*(..))
: 匹配com.taotao.Waiter
接口中的所有方法。execution(* com.taotao.Waiter+.*(..))
: 匹配com.taotao.Waiter
接口及其所有实现类中的方法。execution(* com.taotao..*(..))
: 匹配com.taotao
包及其子孙包下所有类的所有方法。
高级组合
- 使用逻辑运算符如
&&
、||
和!
可以组合多个切点表达式。例如,execution(* *(..)) && this(com.xyz.service.AccountService)
仅匹配AccountService
接口或类中的所有方法。 - 可以在
@Pointcut
注解中使用这些组合表达式来定义公共切点方法,并在切面类中引用这些切点方法。
注意事项
- 在使用
execution
表达式时,确保返回类型、方法名和参数模式正确无误,否则可能导致预期外的拦截行为。 - 尽量避免使用过于宽泛的匹配模式,以免影响系统性能。合理规划切点表达式可以提高AOP的效率与准确性。
@annotation
@annotation 切入点表达式是Spring AOP中用于匹配带有特定注解的方法的表达式。下面将详细解析@annotation
表达式的使用方法以及如何有效应用它们:
概述
@annotation
表达式允许开发者在切面中匹配使用特定注解的方法。这种类型的切入点表达式非常适用于当开发者想要对标记了特定注解的方法应用特定的切面逻辑时。
语法格式
@annotation(annotationName)
:其中annotationName
是需要匹配的注解名称。例如,@Before("@annotation(com.xyz.MyAnnotation)")
表示在方法上使用com.xyz.MyAnnotation
注解的所有方法都会被这个前置通知拦截。
关键元素详解
- 该表达式会匹配使用了指定注解的方法。它不会匹配接口方法上的注解,因为Spring AOP只支持对Spring Bean的方法进行切入。
- 开发者可以在通知方法的参数列表中直接引用这些注解类型,从而在通知方法内部方便地访问这些注解的属性和方法。
具体示例
- 假设有一个自定义注解
MethodLog
,可以使用@Before("@annotation(methodLog)")
来拦截所有使用MethodLog
注解的方法,并在通知方法中处理这个注解及其属性。 - 如要在环绕通知中使用
@annotation
,可以这样写:@Around("@annotation(methodLog)")
,则methodLog
参数可以直接在通知方法中使用。
高级组合
- 开发者可以使用逻辑运算符如
&&
、||
和!
来组合多个切点表达式,以实现更复杂的匹配逻辑。例如,@Before("execution(* *(..)) && @annotation(com.xyz.MyAnnotation)")
表示拦截所有方法,并附加特定注解的处理逻辑。
注意事项
- 在使用
@annotation
表达式时,确保提供正确的注解全路径名,并且只在方法上使用该注解,而不是在类的其他地方使用。 - 合理规划切点表达式可以提高AOP的效率与准确性,避免因过于宽泛的匹配模式而影响系统性能。
案例:
案例背景
假设我们有一个需求,希望记录所有处理时间超过指定阈值的方法调用。为了实现这个功能,我们可以创建一个自定义注解TimeThreshold
,并用它来标记那些需要被监控的方法。
步骤一:创建自定义注解
首先,我们需要定义一个自定义注解TimeThreshold
,该注解可以包含一个表示时间阈值的属性:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 注解应用于方法
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效
public @interface TimeThreshold {
long value() default 1000; // 默认阈值为1000毫秒
}
步骤二:创建切面类
接下来,我们创建一个切面类MethodMonitorAspect
,在这个切面中,我们会使用@Before
和@Around
通知来拦截带有TimeThreshold
注解的方法:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MethodMonitorAspect {
// 前置通知,用于记录方法开始时间
@Before("@annotation(timeThreshold)")
public void logStartTime(TimeThreshold timeThreshold) {
System.out.println("Method " + timeThreshold + " started");
}
// 环绕通知,用于测量方法和记录超出阈值的方法
@Around("@annotation(timeThreshold)")
public Object monitorMethodExecutionTime(ProceedingJoinPoint joinPoint, TimeThreshold timeThreshold) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long elapsed = System.currentTimeMillis() - start;
if (elapsed > timeThreshold.value()) {
System.out.println("Warning: Method " + joinPoint.getSignature() + " exceeded time threshold of " + timeThreshold.value() + " ms");
} else {
System.out.println("Method " + joinPoint.getSignature() + " executed within time threshold");
}
return result;
}
}
步骤三:应用注解
现在,我们可以在任何需要被监控的方法上应用TimeThreshold
注解:
import com.example.demo.aop.annotation.TimeThreshold;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@TimeThreshold(2000) // 设置时间阈值为2000毫秒
public void slowMethod() {
// 模拟慢方法
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void fastMethod() {
// 模拟快方法
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
标签:事务,Spring,切面,AOP,注解,方法,public
From: https://blog.csdn.net/weixin_74521994/article/details/139783539