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

Hibernate多对多保存报错:集合关联两个打开会话问题排查

Hibernate保存对象时出现“Illegal attempt to associate a collection with two open sessions”异常排查

问题场景

执行对象保存操作的代码:

Session session = FACTORY.getCurrentSession();
Transaction trans=session.beginTransaction();
session.save(objeto);
trans.commit();

抛出异常:

org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection : [es.ddmain.entidades.Juego.juegosEnStore#1]

相关代码及配置

实体类

@Entity
@Table(name = "whislist")
public class Juego implements Serializable {

    private static final long serialVersionUID = -4151736911313824434L;
    
    @Id
    @Column(name="idWhislist")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long idJuego;
    
    @Column(nullable = false)
    private String nombre;
    
    @Column(nullable = false)
    private int posicion;

    @Column(nullable = true)
    private Integer nivel;
    
    @Column(nullable = true)
    private Date fechaLanzamiento;
    
    @OneToMany(mappedBy = "juego")
    private Set<JuegoEnStore> juegosEnStore;
    
    // GETTERS && SETTERS

}


@Entity
@Table(name = "whislistinstore")
@IdClass(JuegoEnStoreId.class)
public class JuegoEnStore implements Serializable{

    private static final long serialVersionUID = -4141985115157735120L;
    
    @Id
    @ManyToOne
    @JoinColumn(name = "idWhislist", referencedColumnName = "idWhislist")
    private Juego juego;
    
    @Id
    @ManyToOne
    @JoinColumn(name = "idStore", referencedColumnName = "idStore")
    private Store store;
    
    @Column(name="precioOriginal")
    private BigDecimal precioOriginal;
    
    @Column(name="precioActual")
    private BigDecimal precioActual;
    
    @Column(name="precioMinimo")
    private BigDecimal precioMinimo;
    
    @Column(name="dlcs", columnDefinition = "TINYINT")
    @Type(type = "org.hibernate.type.NumericBooleanType")
    private boolean dlcs;
    
    @Column(name="precioDLCs")
    private BigDecimal precioDLCs;
    
    @Column(name="precioCompleto")
    private BigDecimal precioCompleto;
    
    @Column(name="drm", columnDefinition = "TINYINT")
    @Type(type = "org.hibernate.type.NumericBooleanType")
    private boolean drm;
    
    @Column(name="nombreDRM")
    private String nombreDRM;
    
    @Column(name="comentarios")
    private String comentarios;
    
    @Column(name="earlyAccess", columnDefinition = "TINYINT")
    @Type(type = "org.hibernate.type.NumericBooleanType")
    private boolean earlyAccess;
    
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "idIdioma")
    private Idioma idioma;
    
    @Formula(value = "round((precioOriginal - precioMinimo) / precioOriginal * 100, 2)")
    private BigDecimal descuento;

    // SETTERS Y GETTERS
}


@Entity
@Table(name = "store")
public class Store implements Serializable{

    private static final long serialVersionUID = -3859365948690038372L;
    
    @Id
    @Column(name="idStore")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long idStore;
    
    @Column(nullable = false)
    private String nombre;
    
    @Column(nullable = false)
    private String logo;
    
    @OneToMany(mappedBy = "store")
    private Set<JuegoEnStore> juegosEnStore;
    
    // GETTERS Y SETTERS
}


@Entity
@Table(name = "idioma")
public class Idioma implements Serializable{

    private static final long serialVersionUID = -3005625482252394246L;
    
    @Id
    @Column(name="idIdioma")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long idIdioma;
    
    @Column(nullable = false)
    private String nombre;
    
    @Column(nullable = false)
    private String bandera;

    @Column(name="defecto", columnDefinition = "TINYINT", nullable = false)
    @Type(type = "org.hibernate.type.NumericBooleanType")
    private boolean defecto;
    
    // SETTERS Y GETTERS
}

DAO层代码

public class JuegoDAOImpl implements JuegoDAO {

    private static final Logger logger = Logger.getLogger(JuegoDAOImpl.class);
    
    @Override
    public void guardar(Juego objeto) {
        Session session = FACTORY.getCurrentSession();
        Transaction trans=session.beginTransaction();
        session.save(objeto);
        trans.commit();
    }
}

public interface JuegoDAO extends GenericDAO<Juego> {
    
    public List<Juego> obtenerJuegos(FiltroJuegos filtro);

}


public interface GenericDAO<T> {
    
    public final static StandardServiceRegistry SSR = new StandardServiceRegistryBuilder().configure("hibernate.cfg.xml").build();
    public final static Metadata META = new MetadataSources(SSR).getMetadataBuilder().build();
    public final static SessionFactory FACTORY = META.getSessionFactoryBuilder().build();
    
    public List<T> obtenerTodos();
    public void guardar(T objeto);
    public void actualizar(T objeto);
    public void borrar(T objeto);
    

}

业务类代码

public class FachadaImpl implements Fachada {

    private ServicioJuego servicioJuego;
    
    @Override
    public void guardar(Juego objeto) {
        servicioJuego.guardar(objeto);
    }
}

public class ServicioJuegoImpl implements ServicioJuego {

    private JuegoDAO juegoDAO;
        
    @Override
    public void guardar(Juego objeto) {
        juegoDAO.guardar(objeto);
    }
}

Hibernate配置文件

<hibernate-configuration>

    <session-factory>

        <property name="connection.datasource">java:/vpmDS</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>

    <property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
    <property name="current_session_context_class">thread</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        
        <mapping class="es.ddmain.entidades.Juego" />
        <mapping class="es.ddmain.entidades.Store" />
        <mapping class="es.ddmain.entidades.JuegoEnStore" />
        <mapping class="es.ddmain.entidades.Idioma" />

    </session-factory>

</hibernate-configuration>

错误原因及修复方案

核心问题

  1. SessionFactory重复初始化:GenericDAO接口中把SessionFactory定义为静态常量,每个实现该接口的DAO类都会触发一次SessionFactory初始化,生成多个实例。不同SessionFactory创建的Session属于不同上下文,同一个集合关联到不同Session时就会抛出异常。
  2. 关联对象的Session上下文冲突:如果Juego对象的juegosEnStore集合中,Store、Idioma等关联对象来自其他Session,也会导致集合被绑定到多个Session。

修复步骤

1. 全局统一SessionFactory实例

将SessionFactory初始化逻辑抽离到工具类,确保全局唯一:

public class HibernateUtil {
    private static final SessionFactory FACTORY;

    static {
        try {
            StandardServiceRegistry SSR = new StandardServiceRegistryBuilder().configure("hibernate.cfg.xml").build();
            Metadata META = new MetadataSources(SSR).getMetadataBuilder().build();
            FACTORY = META.getSessionFactoryBuilder().build();
        } catch (Throwable ex) {
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return FACTORY;
    }
}

修改GenericDAO接口,移除静态常量:

public interface GenericDAO<T> {
    default SessionFactory getSessionFactory() {
        return HibernateUtil.getSessionFactory();
    }

    public List<T> obtenerTodos();
    public void guardar(T objeto);
    public void actualizar(T objeto);
    public void borrar(T objeto);
}

更新JuegoDAOImpl的Session获取逻辑:

public class JuegoDAOImpl implements JuegoDAO {
    private static final Logger logger = Logger.getLogger(JuegoDAOImpl.class);
    
    @Override
    public void guardar(Juego objeto) {
        Session session = getSessionFactory().getCurrentSession();
        Transaction trans=session.beginTransaction();
        session.save(objeto);
        trans.commit();
    }
}

2. 统一关联对象的Session上下文

如果关联对象(如Store、Idioma)来自其他Session,需要先合并到当前Session:

@Override
public void guardar(Juego objeto) {
    Session session = getSessionFactory().getCurrentSession();
    Transaction trans=session.beginTransaction();
    // 合并集合中的关联对象到当前Session
    for(JuegoEnStore jes : objeto.getJuegosEnStore()){
        jes.setStore((Store) session.merge(jes.getStore()));
        jes.setIdioma((Idioma) session.merge(jes.getIdioma()));
        jes.setJuego(objeto); // 维护双向关联
    }
    session.save(objeto);
    trans.commit();
}

3. 确保事务边界唯一

整个保存流程中,所有操作都使用getCurrentSession()获取线程绑定的Session,避免手动创建新Session,保证同一事务内只有一个Session在工作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 20:27:18