I've been struggling to find an way to populate many-to-many relationships using Hibernate stateless sessions or using HSQL. I'm using Hibernate 3.2.4ga and Hibernate Annotations 3.3.0.
Up to now, we have performed bulk data loads using standard Hibernate persistance, but this is starting to perform very badly under big imports. This is mostly due to the interactions with the second-level cache used with some of the objects causing the memory usage to grow and grow.
I have investigated using stateless sessions which works fine for all the standard entities and for one-to-many relationships (using mappedBy), but completely breaks down as collections within an entity are not persisted and there seems to be no way to persist just the collection. I've tried exploring the API thoroughly and reading chapter 13 over and over, but cannot for the life of we see how I'm supposed to do it.
I've even tried resorting to HQL for this, but all my attempts at constructing appropriate HQL were met with invalid SQL being passed to the database or various errors thrown by Hibernate. Again, none of the examples in Chapter 14 seem to help.
Ultimately, in the example below I really want to generate the SQL: insert into a_b (a_id, m1_id) values (?,?)
So, is this possible to achieve without using the standard Hibernate ORM model?
Here is my example, modelling just a simple many-to-many relationship:
Code:
@javax.persistence.Entity
@Table(name = "a")
public class A {
@Id
@GeneratedValue(generator = "generator")
@GenericGenerator(name = "generator", strategy = "com.isolutions.poc.hibernate.ralf.Generator")
public Long id;
public String name;
@ManyToMany(fetch = FetchType.LAZY, targetEntity = B.class)
@JoinTable(name = "a_b")
@Cascade(value = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.SAVE_UPDATE, CascadeType.REFRESH})
public Set<B> bs;
}
Code:
@javax.persistence.Entity
@Table(name = "b")
public class B {
@Id
@GeneratedValue(generator = "generator")
@GenericGenerator(name = "generator", strategy = "com.isolutions.poc.hibernate.ralf.Generator")
public Long id;
public String name;
@ManyToMany(fetch = FetchType.LAZY, targetEntity = A.class, mappedBy = "bs")
@JoinTable(name = "a_b")
@Cascade(value = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.SAVE_UPDATE, CascadeType.REFRESH})
public Set<B> as;
}
Code:
public class Generator implements IdentifierGenerator {
static long base = System.currentTimeMillis();
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
return base++;
}
}
Code:
public class SimpleTest {
private static final Logger log = Logger.getLogger(SimpleTest.class);
ListFactoryBean factoryBean = new ListFactoryBean();
private SessionFactory sessionFactory;
@Before
public final void setUp() {
AnnotationConfiguration configuration = new AnnotationConfiguration();
configuration
.setProperty("hibernate.dialect", "com.isolutions.hibernate.dialect.MySQLDialect")
.setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver")
.setProperty("hibernate.connection.url", "jdbc:mysql://termite/v4ralf")
.setProperty("hibernate.connection.username", "v4ralf")
.setProperty("hibernate.connection.password", "v4ralf")
.setProperty("hibernate.connection.pool_size", "20")
.setProperty("hibernate.order_updates", "true")
.setProperty("hibernate.show_sql", "true")
.setProperty("hibernate.hbm2ddl.auto", "update");
configuration.addAnnotatedClass(A.class);
configuration.addAnnotatedClass(B.class);
sessionFactory = configuration.buildSessionFactory();
}
@After
public final void tearDown() {
sessionFactory.close();
}
protected final SessionFactory getSessionFactory() {
return sessionFactory;
}
@Test
public void testCreate() {
StatelessSession session = getSessionFactory().openStatelessSession();
Transaction transaction = session.beginTransaction();
B b1 = new B();
b1.name = "B 1";
Long b1fp = (Long) session.insert(b1);
B b2 = new B();
b2.name = "B 2";
Long b2fp = (Long) session.insert(b2);
B b3 = new B();
b3.name = "B 3";
Long b3fp = (Long) session.insert(b3);
A a1 = new A();
a1.name = "A 1";
Long a1fp = (Long) session.insert(a1);
A a2 = new A();
a2.name = "A 2";
Long a2fp = (Long) session.insert(a2);
HashSet<Long> members = new HashSet<Long>();
members.add(b1fp);
members.add(b2fp);
members.add(b3fp);
Query fetchQuery = session.
createQuery("from B where id in (:members)").
setParameterList("members", members);
for (Object o : fetchQuery.list()) {
log.debug(o);
}
/**/
Query query = session.
createQuery("update A a set a.bs=(from B b where b.id in (:members)) where a.id = :fp").
setParameterList("members", members).
setLong("fp", a1fp);
log.debug("Update returned " + query.executeUpdate());
/**/
transaction.commit();
session.close();
}
}
If I run this with the code between the two /**/ markers removed, the 5 database objects are created and the fetch query works fine. With the code as-is, Hibernate generates the following SQL:
Code:
2007-08-01 16:09:53,998 DEBUG [main] org.hibernate.SQL - update a, set .=(select b3_.id from b b3_ where b3_.id in (? , ? , ?)) where id=?
Hibernate: update a, set .=(select b3_.id from b b3_ where b3_.id in (? , ? , ?)) where id=?
I'm really struggling to understand how I should proceed further with this. At this stage, I'm open to pretty much any solution that doesn't involve using a standard Hibernate session (for the aforementioned level 2 cache problem causing memory exhaustion before the database commit).