Hibernate version: 3.2.1.ga
Database: hsqldb, mysql
Hi, I think I found a serious problem:
When using "inverse" one-to-many relations as described in
2.4.6.2.3._Bidirectional association with indexed collections hibernate is wrong when activating 2nd level cache.
I'll provide a simple example, which transforms a simple structure:
Code:
___ ___
| | | |
-> 1 -> 1
| -transform-> / \
2 2 3
|
3
After the transformation node1 is the parent of node3. But with 2nd level cache enabled hibernate states that node2 is still the parent of node3.
I guess that the cache is not updated properly.
Node.javaCode:
package de.idon;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.IndexColumn;
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Node implements Serializable {
private Integer id;
private Node parentNode;
private List<Node> subNodes = new ArrayList<Node>();
public Node() {
}
public Node(int id) {
setId(new Integer(id));
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="node_id", insertable=false, updatable=false, nullable=false)
public Node getParentNode() {
return parentNode;
}
public void setParentNode(Node parentNode) {
this.parentNode = parentNode;
}
@OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.LAZY)
@JoinColumn(name="node_id", nullable=false)
@IndexColumn(name = "idx")
public List<Node> getSubNodes() {
return subNodes;
}
public void setSubNodes(List<Node> subNodes) {
this.subNodes = subNodes;
}
/**
* Set the parentNode and modifies its subnodes.
*/
@Transient
public void setParent(Node parentNode) {
setParentNode(parentNode);
parentNode.getSubNodes().add(this);
}
}
Test.javaCode:
package de.idon;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
public class Test {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static void main( String[] args )
{
setup();
transform();
test();
sessionFactory.close();
}
static void setup() {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Node node1 = new Node(1);
node1.setParent(node1);
Node node2 = new Node(2);
node2.setParent(node1);
Node node3 = new Node(3);
node3.setParent(node2);
session.save(node1);
session.getTransaction().commit();
}
static void transform() {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Node node3 = (Node) session.load(Node.class, new Integer(3));
Node node2 = node3.getParentNode();
node2.getSubNodes().remove(node3);
node3.setParent(node2.getParentNode());
session.getTransaction().commit();
}
static void test() {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Node node3 = (Node) session.load(Node.class, new Integer(3));
System.out.println("node3.getParentNode: " + node3.getParentNode().getId() + " (should be 1)");
session.getTransaction().commit();
}
}
hibernate.cfg.xmlCode:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:mem:test</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Enable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<property name="jdbc.batch_size">0</property>
<mapping class="de.idon.Node"/>
</session-factory>
</hibernate-configuration>
Commenting out
Code:
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
in
Node.java produces the correct result.
When using "normal" bidirectional one-to-many relations (i.e. the one side is the owner of the relation) the problem does not occur.
Any ideas? Should I post it to JIRA?
Regards
Burkhard