-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 
Author Message
 Post subject: 2nd level cache broken for (inv) bidirectional one-to-many
PostPosted: Tue Jan 09, 2007 10:22 am 
Newbie

Joined: Sun May 08, 2005 12:26 pm
Posts: 10
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.java
Code:
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.java
Code:
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.xml
Code:
<?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


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 09, 2007 10:41 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
This looks suspicious, please submit with a runnable testcase to JIRA.

_________________
JAVA PERSISTENCE WITH HIBERNATE
http://jpwh.org
Get the book, training, and consulting for your Hibernate team.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 09, 2007 10:45 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
It would also be better if you can reproduce this by modifying an existing Hibernate testcase. Also, remove the tree structure: if it's a problem with a non-inverse indexed collection, you don't need the tree to trigger any potential issue. This just complicates the scenario.

_________________
JAVA PERSISTENCE WITH HIBERNATE
http://jpwh.org
Get the book, training, and consulting for your Hibernate team.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 09, 2007 1:18 pm 
Newbie

Joined: Sun May 08, 2005 12:26 pm
Posts: 10
Submitted to JIRA: HHH-2350.

christian wrote:
It would also be better if you can reproduce this by modifying an existing Hibernate testcase.


Done.

christian wrote:
Also, remove the tree structure: if it's a problem with a non-inverse indexed collection, you don't need the tree to trigger any potential issue. This just complicates the scenario.


I'm not sure if I understand - the problem only occurs when using inverse bidirectional x-to-many relations (with or without indexing). And I think I do need some kind of tree-structure... (moreover right now I don't have the time to simplify the testcase... ;-)

Regards
Burkhard


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.