Spring Data JPA/Hibernate:如何让实体及懒加载关联只读(不使用@Immutable)
解决Spring Data JPA/Hibernate只读实体及关联懒加载的只读问题
方案1:全局只读事务覆盖懒加载全过程
直接将只读服务的方法标记为只读事务,无需中途切换会话状态:
- 在只读服务方法上添加
@Transactional(readOnly = true),整个方法执行期间会话始终处于只读模式。 - 不管是主实体
MyEntity还是后续懒加载的otherEntities、MyOtherEntity的递归关联,所有操作都在只读会话中进行,自然会被持久化提供者标记为只读,不会产生非只读的关联实体。 - 示例代码:
@Service public class MyEntityReadOnlyService { private final MyEntityRepository repository; public MyEntityReadOnlyService(MyEntityRepository repository) { this.repository = repository; } @Transactional(readOnly = true) public MyEntity getReadOnlyEntity(Long id) { MyEntity entity = repository.findById(id).orElseThrow(); // 后续任意时机触发懒加载,都处于只读会话中 entity.getOtherEntities().forEach(other -> { // 即使递归懒加载MyOtherEntity的关联,也保持只读 other.getNestedEntities().size(); }); return entity; } }
方案2:自定义Hibernate拦截器强制实体只读
通过Hibernate拦截器拦截所有实体加载动作,自动标记只读:
- 实现
EmptyInterceptor,重写onLoad方法,检测当前会话是否为只读,若是则将加载的实体(包括懒加载关联)设为只读:
public class ReadOnlyEntityInterceptor extends EmptyInterceptor { private final EntityManagerFactory entityManagerFactory; public ReadOnlyEntityInterceptor(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } @Override public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { Session session = SessionFactoryUtils.getCurrentSession(entityManagerFactory); if (session.isReadOnly()) { session.setReadOnly(entity, true); } return super.onLoad(entity, id, state, propertyNames, types); } }
- 在Spring配置中将拦截器注册到EntityManagerFactory:
@Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, ReadOnlyEntityInterceptor interceptor) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.yourpackage.entity"); em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); Properties props = new Properties(); props.put("hibernate.ejb.interceptor", interceptor); em.setJpaProperties(props); return em; } @Bean public ReadOnlyEntityInterceptor readOnlyEntityInterceptor(EntityManagerFactory entityManagerFactory) { return new ReadOnlyEntityInterceptor(entityManagerFactory); }
- 此方案适用于需要在混合读写的事务中,指定部分实体及其关联保持只读的场景,无需预加载所有关联。
方案3:Spring Data JPA查询Hint强制只读
在只读仓储的查询方法上添加Hibernate只读Hint,直接控制实体及关联的只读状态:
- 在Spring Data JPA仓储接口的查询方法上添加
@QueryHint,指定READ_ONLY属性为true:
public interface MyEntityReadOnlyRepository extends JpaRepository<MyEntity, Long> { @QueryHints({ @QueryHint(name = org.hibernate.annotations.QueryHints.READ_ONLY, value = "true") }) Optional<MyEntity> findById(Long id); }
- 这个Hint会让Hibernate将查询返回的主实体,以及后续所有懒加载的关联实体都标记为只读状态,即使事务不是全局只读也能生效,无需预加载关联。
方案选择建议
- 若只读服务仅需执行查询操作,优先使用方案1,配置简单且性能最优。
- 若需要在读写混合的事务中控制实体只读状态,选择方案2。
- 若希望直接在仓储层灵活控制单查询的只读属性,选择方案3。
内容的提问来源于stack exchange,提问作者Pawel Os.
相关产品推荐
相关产品推荐

