Spring非Boot项目AspectJ配置引发的事务与EntityManager异常问题
我有一个非Spring Boot的Spring项目,启用<aop:aspectj-autoproxy />后,@PersistenceContext和@Transactional注解出现异常行为:
1. 配置<aop:aspectj-autoproxy proxy-target-class="true" />时的问题
外部调用delete方法,其内部调用find方法时EntityManager为null,但外部直接调用find方法正常。通用类代码如下:
@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public class GenericServiceImpl<T extends IEntity<PK>, PK extends Serializable> implements IGenericService<T, PK> { private final Class<T> type; public GenericServiceImpl(final Class<T> type) { this.type = type; } private EntityManager em; @PersistenceContext public final void setEntityManager(final EntityManager em) { this.em = em; } public final EntityManager getEntityManager() { return em; } public final void delete(final PK pk) { // 外部调用此方法 T o = find(pk); // 内部调用失败,em为null if (o != null) { o.setDeleted(true); save(o); } } @Transactional(readOnly = true) // 外部直接调用此方法正常 public final T find(final PK pk) { if (pk == null) { return null; } T o = em.find(type, pk); // 从delete内部调用时em为null if (o != null && o.isDeleted()) { return null; } else { return o; } } }
2. 仅使用<aop:aspectj-autoproxy />时的问题
- 切面中无法获取参数名(已通过自动生成参数名临时解决);
- 解决EntityManager为空问题后,调用
delete方法删除实体后,调用find方法仍返回旧状态,疑似实体被持久化上下文缓存。
环境信息
- Java版本:8
- 核心依赖:
<spring.version>4.3.30.RELEASE</spring.version> .... <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.19</version> </dependency>
针对问题1:CGLIB代理下内部调用EntityManager为null的问题
原因
设置proxy-target-class="true"时Spring使用CGLIB生成子类代理,但你的setEntityManager、delete、find均为final方法——CGLIB无法代理final方法,导致Spring的依赖注入和事务切面无法生效到这些方法上。内部调用时this指向目标对象而非代理对象,目标对象的em未被正确注入,因此出现null。
解决方法
方法1:移除方法的final修饰符
让CGLIB可以代理这些方法,内部调用时会通过代理对象访问,确保em正确注入:
@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public class GenericServiceImpl<T extends IEntity<PK>, PK extends Serializable> implements IGenericService<T, PK> { private final Class<T> type; public GenericServiceImpl(final Class<T> type) { this.type = type; } private EntityManager em; @PersistenceContext public void setEntityManager(final EntityManager em) { // 移除final this.em = em; } public EntityManager getEntityManager() { // 移除final return em; } public void delete(final PK pk) { // 移除final T o = find(pk); if (o != null) { o.setDeleted(true); save(o); } } @Transactional(readOnly = true) public T find(final PK pk) { // 移除final if (pk == null) { return null; } T o = em.find(type, pk); if (o != null && o.isDeleted()) { return null; } else { return o; } } }
方法2:通过Spring上下文获取代理对象(耦合性较高)
如果无法修改方法修饰符,可在内部调用时获取代理对象:
@Autowired private ApplicationContext applicationContext; public final void delete(final PK pk) { IGenericService<T, PK> proxy = applicationContext.getBean(getClass()); T o = proxy.find(pk); if (o != null) { o.setDeleted(true); proxy.save(o); } }
针对问题2:JDK动态代理下的两个问题
问题2.1:切面无法获取参数名
原因
JDK动态代理依赖接口,Spring默认依赖Java 8编译时保留的参数名信息。如果编译时未添加-parameters参数,Spring无法直接获取参数名。
解决方法
在Maven编译插件中添加-parameters参数,确保编译时保留参数名:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin>
问题2.2:delete后find返回旧状态(持久化上下文缓存)
原因
逻辑删除后,EntityManager的一级缓存仍保留实体旧状态,find方法优先从缓存读取,而非查询数据库。
解决方法
方法1:手动管理缓存
在save后同步数据库并清除该实体的缓存:
public void delete(final PK pk) { T o = find(pk); if (o != null) { o.setDeleted(true); save(o); em.flush(); // 同步变更到数据库 em.detach(o); // 从缓存移除该实体 } }
方法2:使用JPQL查询绕过缓存
修改find方法为JPQL查询,直接从数据库获取最新状态:
@Transactional(readOnly = true) public T find(final PK pk) { if (pk == null) { return null; } TypedQuery<T> query = em.createQuery( "SELECT t FROM " + type.getSimpleName() + " t WHERE t.id = :id AND t.deleted = false", type ); query.setParameter("id", pk); List<T> result = query.getResultList(); return result.isEmpty() ? null : result.get(0); }
方法3:调整事务传播行为
将find方法设置为开启新事务,新事务的EntityManager缓存是干净的:
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) public T find(final PK pk) { // 原有逻辑 }
额外建议
- 优先使用JDK动态代理(不设置
proxy-target-class=true),Spring对接口代理的支持更成熟,避免CGLIB代理的final方法限制; - 所有需要AOP增强(事务、自定义切面)的方法,不要设置为final。
内容的提问来源于stack exchange,提问作者Volodymyr Bilovus

