Spring-data-jpa拦截器

在Spring Data JPA中,拦截器机制通常用于在执行某些数据库操作之前或之后进行一些处理。对于实体级别的拦截可以通过使用JPA的生命周期回调方法或者通过AOP来实现,常见的是使用Spring Data提供的Repository监听器或通过自定义的EntityListener来实现类似拦截器的功能。对于Session级别的拦截可以实现

实体生命周期

JPA(Java Persistence API)中的实体生命周期是指实体从创建到最终被移除的整个过程。在这个过程中,实体会经历不同的状态转换。以下是JPA中实体的四种生命周期状态:

  • 新建(New):当使用new关键字创建一个实体对象时,它处于新建状态。此时,该实体对象尚未与持久化上下文(Persistence Context)关联,也没有持久化标识(ID),即数据库中没有对应的记录。
  • 托管(Managed):当一个实体通过EntityManager的persist()方法被持久化或从数据库加载后,它进入了托管状态。
    在这个状态下,实体与持久化上下文建立了关联,并且它的任何更改都会在事务提交时自动同步到数据库中。
  • 游离(Detached):当一个托管实体脱离了持久化上下文(例如,事务结束、调用了clear()方法或者关闭了EntityManager),它变成了游离状态。游离状态下的实体虽然有持久化标识(ID),但是不再与持久化上下文关联,因此其变化不会自动同步到数据库。
  • 删除(Removed):当调用EntityManager的remove()方法并传入一个托管实体时,该实体进入删除状态。在这种状态下,实体仍然存在于持久化上下文中,但计划在事务提交时从数据库中删除。

生命周期事件

除了上述四种状态外,JPA还定义了一些生命周期事件,这些事件可以在实体的状态发生变化时触发回调方法。这些事件包括但不限于:

  • @PrePersist 和 @PostPersist:在实体被持久化之前和之后触发。
  • @PreRemove 和 @PostRemove:在实体被删除之前和之后触发。
  • @PreUpdate 和 @PostUpdate:在实体更新操作之前和之后触发。
  • @PostLoad:当实体从数据库加载完成后触发。

实体级别拦截实现

  • 实体监听器(Entity Listeners)
  • 监听Spring Data Repository事件

实体监听器(Entity Listeners)

可以使用实体监听器来拦截实体的生命周期事件, Lifecycle 定义了如下事件操作:

  • onSave
  • onUpdate
  • onDelete
  • onLoad

对应上面的事件,jakarta(以前的javax)实现了操作(注解实现):

  • PrePersist,

  • PreRemove,

  • PreUpdate,

  • PostLoad,

  • PostPersist,

  • PostRemove,

  • PostUpdate
    使用上面的注解我们可以针对具体的实体做一些操作。下面是一个具体的示例:

  • 定义一个实体监听器

1
2
3
4
5
6
7
8
9
10
11
12
public class UserEntityListener {

@PrePersist
public void prePersist(Object object) {
// 在持久化之前调用
}

@PreUpdate
public void preUpdate(Object object) {
// 在更新之前调用
}
}
  • 作用在实体上
1
2
3
4
5
@Entity
@EntityListeners(UserEntityListener.class)
public class User {
// 实体属性和方法
}

监听Spring Data Repository事件

Spring Data 提供了一种监听Repository事件(如AfterCreate, BeforeSave等)的机制,可以通过继承AbstractRepositoryEventListener 创建监听器类来监听这些事件:

1
2
3
4
5
6
7
8
9
10
11
12
public class CustomRepositoryEventListener extends AbstractRepositoryEventListener<Object> {

@Override
protected void onBeforeCreate(Object entity) {
// 在创建新实体之前调用
}

@Override
protected void onBeforeSave(Object entity) {
// 在保存实体之前调用
}
}

AbstractRepositoryEventListener定义的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected void onBeforeCreate(T entity) {
}

protected void onAfterCreate(T entity) {
}

protected void onBeforeSave(T entity) {
}

protected void onAfterSave(T entity) {
}

protected void onBeforeLinkSave(T parent, Object linked) {
}

protected void onAfterLinkSave(T parent, Object linked) {
}

protected void onBeforeLinkDelete(T parent, Object linked) {
}

protected void onAfterLinkDelete(T parent, Object linked) {
}

protected void onBeforeDelete(T entity) {
}

protected void onAfterDelete(T entity) {
}

EntityListener使用限制

EntityListener是JPA提供的一种机制,用于在实体生命周期的不同阶段执行特定的逻辑。尽管它是一个强大的工具,但在使用Spring Data JPA时,还是存在一些限制和注意事项:

  • 依赖注入限制:默认情况下,JPA的EntityListener不支持Spring的依赖注入(DI)。这是因为实体监听器不是由Spring容器管理的。如果需要在监听器中使用Spring管理的bean,可能需要手动获取应用上下文或使用其他方法来实现依赖注入。
  • 事务处理:虽然可以在实体监听器的方法中进行数据库操作,但需要注意的是这些操作不会自动参与到当前实体正在参与的事务中。这意味着你需要特别小心地处理事务边界,以避免数据一致性问题。
  • 异步执行:实体监听器中的方法默认是同步执行的。如果希望在监听器中执行的操作(例如发送邮件或者进行复杂的计算)不影响主事务的性能,那么需要自行实现异步调用的机制。
  • 跨实体监听:一个实体监听器只能直接应用于声明它的实体。如果想在一个实体上触发另一个实体的操作,则需要通过手动方式或其他设计模式来实现。
  • 生命周期事件的顺序:虽然JPA定义了实体生命周期事件的顺序,但是在复杂的场景下,理解并确保正确的执行顺序可能会变得复杂。例如,在嵌套事务或传播行为不同的事务中,监听器的执行顺序可能会导致意外的行为。
  • 移植性考虑:由于某些高级特性可能是特定于供应商的,因此使用过于复杂的EntityListener逻辑可能会影响应用程序的可移植性。尽量保持监听器逻辑简单,并遵循JPA规范,以提高代码的可移植性。

对于Session级别的拦截

  • Interceptor(hibernate)
  • StatementInspector(hibernate)
  • AOP实现

Interceptor

Hibernate Interceptor 可以用来在持久化生命周期的各个阶段(如保存、更新、删除等)执行自定义逻辑,可以实现访问到实体对象,并根据需要修改这些对象。Interceptor 接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public interface Interceptor {
default boolean onLoad(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)
throws CallbackException {
if (id==null || id instanceof Serializable) {
return onLoad(entity, (Serializable) id, state, propertyNames, types);
}
return false;
}
default boolean onSave(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)
throws CallbackException {
if (id==null || id instanceof Serializable) {
return onSave(entity, (Serializable) id, state, propertyNames, types);
}
return false;
}

default void onDelete(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)
throws CallbackException {
if (id==null || id instanceof Serializable) {
onDelete(entity, (Serializable) id, state, propertyNames, types);
}
}

default void preFlush(Iterator<Object> entities) throws CallbackException {}

default void postFlush(Iterator<Object> entities) throws CallbackException {}

default void afterTransactionBegin(Transaction tx) {}

default void beforeTransactionCompletion(Transaction tx) {}

default void afterTransactionCompletion(Transaction tx) {}
}

在实际使用中通常通过实现 org.hibernate.EmptyInterceptor 或扩展它,可以覆盖其中的方法来拦截特定事件。可以在 onSave() 方法中添加自定义逻辑,在实体被保存之前进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AuditInterceptor extends EmptyInterceptor {

private static final long serialVersionUID = 1L;

@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
System.out.println("Entity has been updated: " + entity.getClass().getSimpleName());
// 可以在这里添加审计日志或其他操作
return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
}

@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
System.out.println("Entity is being saved: " + entity.getClass().getSimpleName());
// 可以在这里添加审计日志或其他操作
return super.onSave(entity, id, state, propertyNames, types);
}

// 可以根据需要覆盖其他方法,如 onDelete, onLoad 等
}

配置:在Spring Boot 应用中通过设置 SessionFactory 来应用拦截器:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class HibernateConfig {

@Bean
public SessionFactory sessionFactory() {
Configuration configuration = new Configuration();
configuration.setInterceptor(new AuditInterceptor());
// 添加映射等配置
return configuration.buildSessionFactory();
}
}

StatementInspector

StatementInspector 是一个接口,通过实现StatementInspector可以检查甚至替换生成的 SQL 语句,它主要用于分析或修改 Hibernate 生成的 SQL。通过实现 org.hibernate.resource.jdbc.spi.StatementInspector 接口并将其配置给 SessionFactory来对SQL做一些特殊处理。下面代码实现了SQL的拦截,实现了对select, update, delete等SQL的限制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class SqlStatementInspector implements StatementInspector {

private final Logger logger = LoggerFactory.getLogger("sql");
private static final Pattern IGNORE_PATTERN = Pattern.compile(
".*(from|FROM)\\s+(hibernate_sequence|schema_version|flyway_schema_history).*",
Pattern.CASE_INSENSITIVE);

@Override
public String inspect(String sql) {
logger.info("sql:{}", sql);
if (sql == null || sql.trim().isEmpty() || IGNORE_PATTERN.matcher(sql).matches()) {
return sql;
}

String sqlUpperCase = sql.toUpperCase().trim();
try {
// 禁止TRUNCATE、DROP、ALTER、CREATE操作
if (sqlUpperCase.startsWith("TRUNCATE") ||
sqlUpperCase.startsWith("DROP") ||
sqlUpperCase.startsWith("ALTER") ||
sqlUpperCase.startsWith("CREATE")) {
throw new SqlCheckException("禁止执行TRUNCATE、DROP、ALTER、CREATE操作: " + sql);
}

// UPDATE必须带WHERE条件
if (sqlUpperCase.startsWith("UPDATE") && !sqlUpperCase.contains(" WHERE ")) {
throw new SqlCheckException("UPDATE操作必须包含WHERE条件: " + sql);
}

// DELETE必须带WHERE条件
if (sqlUpperCase.startsWith("DELETE") && !sqlUpperCase.contains(" WHERE ")) {
throw new SqlCheckException("DELETE操作必须包含WHERE条件: " + sql);
}

// SELECT必须带LIMIT
if (sqlUpperCase.startsWith("SELECT") &&
!sqlUpperCase.contains(" LIMIT ") &&
!isCountOrExistsQuery(sqlUpperCase) &&
!isDistinctQuery(sqlUpperCase)) {
throw new SqlCheckException("SELECT操作必须包含LIMIT条件: " + sql);
}

// INSERT必须带VALUES
if (sqlUpperCase.startsWith("INSERT") && !sqlUpperCase.contains("VALUES")) {
throw new SqlCheckException("INSERT操作必须包含VALUES: " + sql);
}

logger.debug("SQL验证通过: {}", sql);
return sql;
} catch (Exception e) {
logger.error("SQL验证失败: {}", e.getMessage());
throw e;
}
}

/**
* 判断是否为COUNT查询或EXISTS查询,这类查询可以不需要LIMIT
*/
private boolean isCountOrExistsQuery(String sql) {
return sql.contains("COUNT(") || sql.contains("EXISTS(") || sql.contains("SELECT 1");
}

private boolean isDistinctQuery(String sql) {
return sql.contains("DISTINCT");
}
}

yaml配置:

1
2
3
4
5
6
7
8
spring:
jpa:

properties:
hibernate:
session_factory:
statement_inspector: com.ares.domain.component.SqlStatementInspector

StatementInspector 适用于需要对生成的 SQL 进行分析或修改的场景,比如性能调优、动态SQL修改等。

AOP拦截

使用Spring AOP来创建拦截器逻辑更为灵活,可以针对特定的repository方法进行拦截,实现对数据访问层进行增强处理,比如日志记录、性能监控等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Aspect
@Component
public class JpaRepoAspect {

private static final Logger logger = LoggerFactory.getLogger(JpaRepoAspect.class);

// 拦截所有的Repository方法调用
@Around("execution(* com.ares.domain.repository.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();

Object proceed = joinPoint.proceed();

long executionTime = System.currentTimeMillis() - start;

logger.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}

小结

  • 作用范围:Hibernate Interceptor 和 StatementInspector 主要针对的是 Hibernate 内部的操作,而 AOP 拦截器可以应用于更广泛的应用层面,不仅限于数据库操作。
  • 灵活性:AOP 提供了更高的灵活性,可以很容易地应用于任何 Spring 管理的 Bean 上;Hibernate Interceptor 更专注于持久化相关的拦截;StatementInspector 则特别针对 SQL 语句的检查和修改。
  • 使用复杂度:StatementInspector 相对简单,主要集中在 SQL 层面的修改;Hibernate Interceptor 需要理解 Hibernate 的生命周期和事件模型;AOP 拦截器则需要对 Spring AOP 或 AspectJ 有一定的了解。

Spring-data-jpa拦截器
http://example.com/2025/06/11/spring-data-jpa拦截器/
作者
ares
发布于
2025年6月11日
许可协议