Hi,
I have discovered that when I have a many-to-many association that there are circumstances where association is deleted when hibernate gets the collection. For example I have a Car object that has many Owners. When hibernate retrieves the Owners associated with the Car it deletes the association.
I have simplified this example so that it is easy to run and see the behaviour. It should be possible to copy and paste the code to create a running example.
Thanks in advance,
Simon
Hibernate version:2.1.3
Mapping documents:
car.hbm.xml
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="Car" table="CAR">
<id name="id" column="CAR_ID" type="integer">
<generator class="increment"/>
</id>
<set name="owners" table="OWNER_CAR">
<key column="CAR_ID"/>
<many-to-many column="OWNER_ID" class="Owner"/>
</set>
</class>
</hibernate-mapping>
owner.hbm.xml
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="Owner" table="OWNER">
<id name="id" column="OWNER_ID" type="integer">
<generator class="increment"/>
</id>
<set name="cars" table="OWNER_CAR" inverse="true">
<key column="OWNER_ID"/>
<many-to-many column="CAR_ID" class="Car"/>
</set>
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():This code creates a car, several owners, and then associates the car with the owners. I am using the standard HibernateUtil implementation. Note that this behaviour occurs when I execute the load in a new session. When executed in the same session hibernate still deletes the association but then creates it again.
Code:
Session session;
session = HibernateUtil.currentSession();
Transaction tx;
tx = session.beginTransaction();
Car car = new Car();
Serializable id = session.save(car);
car = (Car) session.load(Car.class, id);
for(int i = 0; i < 10; i++) {
Owner owner = new Owner();
session.save(owner);
car.getOwners().add(owner);
}
tx.commit();
HibernateUtil.closeSession();
session = HibernateUtil.currentSession();
tx = session.beginTransaction();
car = (Car) session.load(Car.class, id);
System.out.println("Size = " + car.getOwners().size());
tx.commit();
HibernateUtil.closeSession();
Classes and other files:Car.java
Code:
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
public class Car {
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
private Integer id = null;
public boolean equals(Object obj) {
if (obj != null && this.id != null && obj instanceof Car) {
return this.id.equals(((Car)obj).id);
}
return super.equals(obj);
}
public int hashCode() {
if (id != null) {
return id.hashCode();
}
return super.hashCode();
}
private Set owners = new HashSet();
public Set getOwners() {
return owners;
}
public void setOwners(Set set) {
this.owners = owners;
}
}
Owner.java
Code:
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
public class Owner {
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
private Integer id = null;
public boolean equals(Object obj) {
if (obj != null && this.id != null && obj instanceof Owner) {
return this.id.equals(((Owner)obj).id);
}
return super.equals(obj);
}
public int hashCode() {
if (id != null) {
return id.hashCode();
}
return super.hashCode();
}
private Set cars = new HashSet();
public Set getCars() {
return cars;
}
public void setCars(Set set) {
this.cars = set;
}
}
HibernateUtil.java
Code:
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Session;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory
sessionFactory = new Configuration()
.addClass(Car.class)
.addClass(Owner.class)
.buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// Open a new Session, if this Thread has none yet
if (s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
Main.java
Code:
import net.sf.hibernate.Session;
import net.sf.hibernate.Transaction;
import java.io.Serializable;
public class Main {
public static void main(String args[]) throws Exception {
Session session;
session = HibernateUtil.currentSession();
Transaction tx;
tx = session.beginTransaction();
Car car = new Car();
Serializable id = session.save(car);
car = (Car) session.load(Car.class, id);
for(int i = 0; i < 10; i++) {
Owner owner = new Owner();
session.save(owner);
car.getOwners().add(owner);
}
tx.commit();
HibernateUtil.closeSession();
session = HibernateUtil.currentSession();
tx = session.beginTransaction();
car = (Car) session.load(Car.class, id);
System.out.println("Size = " + car.getOwners().size());
tx.commit();
HibernateUtil.closeSession();
}
}
hibernate.properties
Code:
hibernate.connection.driver_class=net.sourceforge.jtds.jdbc.Driver
hibernate.connection.url=jdbc:jtds:sqlserver://localhost:1433/master
hibernate.connection.username=sa
hibernate.connection.password=
hibernate.connection.pool_size=1
hibernate.dialect=net.sf.hibernate.dialect.SQLServerDialect
hibernate.show_sql=false
Name and version of the database you are using:I have tried this with both SQL Server 8 and Postgres 7.4 and get the same behaviour.
The generated SQL (show_sql=true):As you can see from the sql below the last sql statement executed deletes the association even though the code clearly only inserts and retrieves and object.
Hibernate: insert into CAR (CAR_ID) values (?)
Hibernate: insert into OWNER (OWNER_ID) values (?)
Hibernate: insert into OWNER_CAR (CAR_ID, OWNER_ID) values (?, ?)
Hibernate: select car0_.CAR_ID as CAR_ID0_ from CAR car0_ where car0_.CAR_ID=?
Hibernate: select owners0_.OWNER_ID as OWNER_ID__, owners0_.CAR_ID as CAR_ID__, owner1_.OWNER_ID as OWNER_ID0_ from OWNER_CAR owners0_ inner join OWNER owner1_ on owners0_.OWNER_ID=owner1_.OWNER_ID where owners0_.CAR_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Hibernate: select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
Size = 0
[i]Hibernate: delete from OWNER_CAR where CAR_ID=?[b][i]
[b]Debug level Hibernate log excerpt:I'm not sure if this log is much use but here it is anyway.
14:09:46,839 INFO Environment:462 - Hibernate 2.1.3
14:09:46,855 INFO Environment:496 - loaded properties from resource hibernate.properties: {hibernate.connection.username=sa, hibernate.connection.password=, hibernate.cglib.use_reflection_optimizer=true, hibernate.dialect=net.sf.hibernate.dialect.SQLServerDialect, hibernate.connection.pool_size=1, hibernate.show_sql=false, hibernate.connection.url=jdbc:jtds:sqlserver://simontpc:1433/webshop/lastUpdateCount=true, hibernate.connection.driver_class=net.sourceforge.jtds.jdbc.Driver}
14:09:46,855 INFO Environment:519 - using CGLIB reflection optimizer
14:09:46,886 INFO Configuration:347 - Mapping resource: Car.hbm.xml
14:09:47,074 INFO Binder:229 - Mapping class: Car -> CAR
14:09:47,199 INFO Binder:560 - Mapping collection: Car.owners -> OWNER_CAR
14:09:47,214 INFO Configuration:347 - Mapping resource: Owner.hbm.xml
14:09:47,246 INFO Binder:229 - Mapping class: Owner -> OWNER
14:09:47,246 INFO Binder:560 - Mapping collection: Owner.cars -> OWNER_CAR
14:09:47,246 INFO Configuration:613 - processing one-to-many association mappings
14:09:47,246 INFO Configuration:622 - processing one-to-one association property references
14:09:47,246 INFO Configuration:647 - processing foreign key constraints
14:09:47,277 INFO Dialect:82 - Using dialect: net.sf.hibernate.dialect.SQLServerDialect
14:09:47,277 INFO SettingsFactory:62 - Use outer join fetching: true
14:09:47,277 INFO DriverManagerConnectionProvider:42 - Using Hibernate built-in connection pool (not for production use!)
14:09:47,292 INFO DriverManagerConnectionProvider:43 - Hibernate connection pool size: 1
14:09:47,292 INFO DriverManagerConnectionProvider:77 - using driver: net.sourceforge.jtds.jdbc.Driver at URL: jdbc:jtds:sqlserver://simontpc:1433/webshop/lastUpdateCount=true
14:09:47,292 INFO DriverManagerConnectionProvider:78 - connection properties: {user=sa, password=}
14:09:47,308 INFO TransactionManagerLookupFactory:33 - No TransactionManagerLookup configured (in JTA environment, use of process level read-write cache is not recommended)
14:09:47,308 DEBUG DriverManagerConnectionProvider:84 - total checked-out connections: 0
14:09:47,324 DEBUG DriverManagerConnectionProvider:100 - opening new JDBC connection
14:09:47,527 DEBUG DriverManagerConnectionProvider:106 - created connection to: jdbc:jtds:sqlserver://simontpc:1433/webshop/lastUpdateCount=true, Isolation Level: 2
14:09:47,527 DEBUG DriverManagerConnectionProvider:120 - returning connection to pool, pool size: 1
14:09:47,527 INFO SettingsFactory:102 - Use scrollable result sets: true
14:09:47,527 INFO SettingsFactory:105 - Use JDBC3 getGeneratedKeys(): true
14:09:47,527 INFO SettingsFactory:108 - Optimize cache for minimal puts: false
14:09:47,527 INFO SettingsFactory:117 - Query language substitutions: {}
14:09:47,527 INFO SettingsFactory:128 - cache provider: net.sf.ehcache.hibernate.Provider
14:09:47,542 INFO Configuration:1093 - instantiating and configuring caches
14:09:47,683 INFO SessionFactoryImpl:119 - building session factory
14:09:48,152 INFO SessionFactoryObjectFactory:82 - no JNDI name configured
14:09:48,214 DEBUG DriverManagerConnectionProvider:84 - total checked-out connections: 0
14:09:48,214 DEBUG DriverManagerConnectionProvider:90 - using pooled JDBC connection, pool size: 0
14:09:48,246 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,246 DEBUG SQL:237 - insert into CAR (CAR_ID) values (?)
14:09:48,261 DEBUG BatcherImpl:241 - preparing statement
14:09:48,261 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,261 DEBUG BatcherImpl:261 - closing statement
14:09:48,261 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,261 DEBUG SQL:237 - insert into OWNER (OWNER_ID) values (?)
14:09:48,261 DEBUG BatcherImpl:241 - preparing statement
14:09:48,261 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,277 DEBUG BatcherImpl:261 - closing statement
14:09:48,277 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,277 DEBUG SQL:237 - insert into OWNER_CAR (CAR_ID, OWNER_ID) values (?, ?)
14:09:48,277 DEBUG BatcherImpl:241 - preparing statement
14:09:48,277 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,277 DEBUG BatcherImpl:261 - closing statement
14:09:48,293 DEBUG DriverManagerConnectionProvider:120 - returning connection to pool, pool size: 1
14:09:48,293 DEBUG DriverManagerConnectionProvider:84 - total checked-out connections: 0
14:09:48,293 DEBUG DriverManagerConnectionProvider:90 - using pooled JDBC connection, pool size: 0
14:09:48,308 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,308 DEBUG SQL:237 - select car0_.CAR_ID as CAR_ID0_ from CAR car0_ where car0_.CAR_ID=?
14:09:48,308 DEBUG BatcherImpl:241 - preparing statement
14:09:48,308 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,308 DEBUG BatcherImpl:261 - closing statement
14:09:48,308 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,324 DEBUG SQL:237 - select owners0_.OWNER_ID as OWNER_ID__, owners0_.CAR_ID as CAR_ID__, owner1_.OWNER_ID as OWNER_ID0_ from OWNER_CAR owners0_ inner join OWNER owner1_ on owners0_.OWNER_ID=owner1_.OWNER_ID where owners0_.CAR_ID=?
14:09:48,324 DEBUG BatcherImpl:241 - preparing statement
14:09:48,339 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,339 DEBUG BatcherImpl:261 - closing statement
14:09:48,339 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,339 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,339 DEBUG BatcherImpl:241 - preparing statement
14:09:48,339 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,339 DEBUG BatcherImpl:261 - closing statement
14:09:48,355 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,355 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,355 DEBUG BatcherImpl:241 - preparing statement
14:09:48,355 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,355 DEBUG BatcherImpl:261 - closing statement
14:09:48,371 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,371 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,371 DEBUG BatcherImpl:241 - preparing statement
14:09:48,371 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,371 DEBUG BatcherImpl:261 - closing statement
14:09:48,371 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,371 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,371 DEBUG BatcherImpl:241 - preparing statement
14:09:48,371 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,386 DEBUG BatcherImpl:261 - closing statement
14:09:48,386 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,386 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,386 DEBUG BatcherImpl:241 - preparing statement
14:09:48,386 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,386 DEBUG BatcherImpl:261 - closing statement
14:09:48,386 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,386 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,386 DEBUG BatcherImpl:241 - preparing statement
14:09:48,402 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,402 DEBUG BatcherImpl:261 - closing statement
14:09:48,402 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,402 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,402 DEBUG BatcherImpl:241 - preparing statement
14:09:48,418 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,418 DEBUG BatcherImpl:261 - closing statement
14:09:48,418 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,418 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,418 DEBUG BatcherImpl:241 - preparing statement
14:09:48,418 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,418 DEBUG BatcherImpl:261 - closing statement
14:09:48,418 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,418 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,418 DEBUG BatcherImpl:241 - preparing statement
14:09:48,418 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,433 DEBUG BatcherImpl:261 - closing statement
14:09:48,433 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,433 DEBUG SQL:237 - select cars0_.CAR_ID as CAR_ID__, cars0_.OWNER_ID as OWNER_ID__ from OWNER_CAR cars0_ where cars0_.OWNER_ID=?
14:09:48,433 DEBUG BatcherImpl:241 - preparing statement
14:09:48,433 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,433 DEBUG BatcherImpl:261 - closing statement
Size = 0
14:09:48,449 DEBUG BatcherImpl:196 - about to open: 0 open PreparedStatements, 0 open ResultSets
14:09:48,449 DEBUG SQL:237 - delete from OWNER_CAR where CAR_ID=?
14:09:48,449 DEBUG BatcherImpl:241 - preparing statement
14:09:48,449 DEBUG BatcherImpl:203 - done closing: 0 open PreparedStatements, 0 open ResultSets
14:09:48,449 DEBUG BatcherImpl:261 - closing statement
14:09:48,449 DEBUG DriverManagerConnectionProvider:120 - returning connection to pool, pool size: 1[/code]