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>
错误原因及修复方案
核心问题
- SessionFactory重复初始化:
GenericDAO接口中把SessionFactory定义为静态常量,每个实现该接口的DAO类都会触发一次SessionFactory初始化,生成多个实例。不同SessionFactory创建的Session属于不同上下文,同一个集合关联到不同Session时就会抛出异常。 - 关联对象的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
相关产品推荐
相关产品推荐

