You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate Reactive:如何为@OneToMany字段保持Session开启?

问题

使用Hibernate Reactive时,实体类Usuari中原本注释掉@OneToMany关联的permisos字段时,Repository的查询功能正常;取消该字段注释后,调用findOne方法查询时抛出Session/EntityManager is closed异常,如何为每个查询保持Session开启?

实体类代码

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
@Entity
@Table(name = "GITTBUSUARIS")
public class Usuari {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USUARIS_ID")
    private Integer id;
    
    @Column(name = "USUARIS_CODI")
    private String codi;

    @Column(name = "USUARIS_NOM")
    private String nom;

    // 取消注释后出现异常
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "id.usuari", fetch = FetchType.EAGER)
    private Set<PermisUsuari> permisos;
    
}

Repository实现代码

@RequiredArgsConstructor
public class ReactiveJpaSpecificationExecutorBase<T> implements ReactiveJpaSpecificationExecutor<T> {

    private final Mutiny.SessionFactory sessionFactory;

    @Override
    public Mono<T> findOne(Specification<T> spec) {
        CriteriaQuery<T> criteriaQuery = this.buildQuery(spec, Sort.unsorted());

        return this.sessionFactory
            .withSession(session -> session.createQuery(criteriaQuery).getSingleResultOrNull())
            .convert().with(UniReactorConverters.toMono());
    }

}

异常信息

Failed to execute statement [select rol0_.ROLS_ID as rols_id1_12_0_, rol0_.ROLS_CODI as rols_cod2_12_0_, rol0_.ROLS_DENOMINACIO as rols_den3_12_0_ from GITTBROLS rol0_ where rol0_.ROLS_ID=@P1]: could not load an entity: [cat.gencat.clt.git.backend.model.persistency.Rol#1]

java.util.concurrent.CompletionException: java.lang.IllegalStateException: Session/EntityManager is closed

控制器调用代码

@RestController
@RequestMapping(value = "/qdcf")
@RequiredArgsConstructor
@Timed
public class QdCFController {

    private final QdCFService qdcfService;
    private final UsuariRepository usuariRepository;

    @GetMapping("/user")
    public Mono<Usuari> user() {
        return this.usuariRepository.findOne(UsuariSpecs.hasCodi("11111111A"));
    }
}
解决方案

原因分析

问题出在FetchType.EAGER的使用上:当前代码中withSession的作用域仅覆盖到getSingleResultOrNull()执行完成,之后Session就会关闭。但EAGER加载的关联实体可能在主实体被序列化(比如返回给前端)时才触发实际查询,此时Session已经关闭,就会抛出异常。

具体解决方法

方法1:改用LAZY加载+Fetch Join主动关联查询

这是最推荐的方案,既避免Session生命周期问题,又能控制查询时机:

  1. 修改实体类的关联加载策略:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "id.usuari", fetch = FetchType.LAZY)
private Set<PermisUsuari> permisos;
  1. 在Specification中添加Fetch Join,确保在Session生命周期内完成关联查询:
public static Specification<Usuari> hasCodi(String codi) {
    return (root, query, cb) -> {
        // 主动加载permisos关联
        root.fetch("permisos", JoinType.LEFT);
        return cb.equal(root.get("codi"), codi);
    };
}

方法2:延长Session作用域到整个响应流

调整Repository的findOne方法,确保Session在Mono流完全处理完成后才关闭:

@Override
public Mono<T> findOne(Specification<T> spec) {
    CriteriaQuery<T> criteriaQuery = this.buildQuery(spec, Sort.unsorted());

    return Mono.fromCompletionStage(() -> 
        sessionFactory.withSession(session -> 
            session.createQuery(criteriaQuery).getSingleResultOrNull()
        ).subscribeAsCompletionStage()
    );
}

这种方式通过正确的Mutiny与Reactor转换,让Session的生命周期绑定到Mono的整个执行过程。

方法3:启用Open Session in View模式(不推荐高并发场景)

如果是Spring Boot应用,可以通过配置让Session在整个请求周期内保持打开,但这种方式会延长数据库连接占用时间,可能导致连接池耗尽:
在application.properties中添加:

spring.hibernate.reactive.open-in-view=true

内容的提问来源于stack exchange,提问作者Jordi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 11:00:56