-->
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: OneToOne and ManyToOne associations on same column
PostPosted: Fri Jan 25, 2008 3:53 pm 
Newbie

Joined: Mon Mar 27, 2006 8:21 pm
Posts: 4
This problem sounds similiar to
http://forum.hibernate.org/viewtopic.php?t=983185
but I'm creating a separate post in case its not.

I have a legacy database with the a parent/child relationship where one of the children is the "default" child.

Code:
+--------+         1..n +-------+
| Parent |/\____________| Child |
|        |\/            |       |
|        |--------------|       |
+--------+            1 +-------+

create database testdb;
create table testdb.PARENT_TABLE (
   PARENT_ID decimal(15,1) NOT NULL,
   DEFAULT_CHILD_ID decimal(15,1) NOT NULL,
   NAME varchar(20) default NULL
);
create table testdb.CHILD_TABLE (
   CHILD_ID decimal(15,1) NOT NULL,
   PARENT_ID decimal(15,1) NOT NULL,
   NAME varchar(20) default NULL
);

To complicate matters I have my own PersistentIdentifierGenerator to generate ID the same way our legacy code does it. However, this doesn't appear to be the problem (at least not yet).

My problem has to do with the NOT NULL contraints in these relationships. My little test program creates a Parent and a Child, sets the relationships and persists them but when I commit the transaction, I get errors about

Column 'DEFAULT_CHILD_ID' cannot be null

When I inspect these two objects with a debugger before calling commit they look fine. The parent's default child is set to a non-null value. If I remove the NON NULL constraint from DEFAULT_CHILD_ID the records are written and DEFAULT_CHILD_ID is non-null value that does indeed reference the child record.

I've played around with @OneOnOne(mappedBy="CHILD_ID") in the parent and @ManyToOne(cascade=CascadeType.ALL) but in the child. This changes what errors I get and when I get them (during EntityManagerFactory creation instead of during the transaction commit). I can't seem to find in the write combination of settings to make my little test program work. Any suggestions will be greatly appreciated.

Hibernate version: 3.2.5 annotations 3.3.0

Mapping documents:

Code between sessionFactory.openSession() and session.close():
test program
Code:
package jpa.prototype.model;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.Persistence;

public class CreateData {
    private static final EntityManagerFactory entityManagerFactory;
    static {
        entityManagerFactory = Persistence.createEntityManagerFactory("hpidm");
    }
   
    public static void main(String[] args) {
        EntityManager entMgr = entityManagerFactory.createEntityManager();
        entMgr.setFlushMode(FlushModeType.COMMIT); // just to be sure
        EntityTransaction t = entMgr.getTransaction();
        t.begin();
       
        Parent p = new Parent();
        p.setName("parent");
        Child defaultChild = new Child();
        p.setDefaultChild(defaultChild);
        defaultChild.setParent(p);
        defaultChild.setName("default Child");
       
        entMgr.persist(defaultChild);
        entMgr.persist(p);

        t.commit();
        entMgr.close();
        entityManagerFactory.close();
    }

}


Parent pojo
Code:
package jpa.prototype.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.GenericGenerator;

@Entity
@GenericGenerator(name="LegacyIdGenerator",
                  strategy="jpa.prototype.model.LegacyIdGenerator")
@Table(name="PARENT_TABLE", catalog="testdb", uniqueConstraints = @UniqueConstraint(columnNames = "NAME"))
public class Parent {
    private Long id;
    private Child defaultChild;
    private String name;
   
    public Parent() { /* do nothing */ }
   
    public Parent(Long aId, Child aDefaultChild, String aName) {
        id = aId;
        defaultChild = aDefaultChild;
        name = aName;
    }
   
    @Id
    @GeneratedValue(generator = "LegacyIdGenerator")
    @Column(name = "PARENT_ID", unique = true, nullable = false)
    public Long getId() { return id; }
   
    public void setId(Long aId) { id = aId; }
   
    @OneToOne/* (mappedBy="CHILD_ID", optional=false) */
    @JoinColumn(name = "DEFAULT_CHILD_ID")
    public Child getDefaultChild() { return defaultChild; }
   
    public void setDefaultChild(Child aDfltChild) {
        aDfltChild.setParent(this);
        defaultChild = aDfltChild;
    }
   
    @Column(name = "NAME")
    public String getName() { return name; }
   
    public void setName(String aName) { name = aName; }
}




Child pojo
Code:
package jpa.prototype.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.GenericGenerator;

@Entity
@GenericGenerator(name="LegacyIdGenerator",
                  strategy="jpa.prototype.model.LegacyIdGenerator")
@Table(name="CHILD_TABLE", catalog="testdb", uniqueConstraints = @UniqueConstraint(columnNames = "NAME"))
public class Child {
    private Long id;
    private Parent parent;
    private String name;
   
    public Child() { /* do nothing */ }
   
    public Child(Long aId, Parent aParent, String aName) {
        id = aId;
        parent = aParent;
        name = aName;
    }
   
    @Id
    @GeneratedValue(generator="LegacyIdGenerator")
    @Column(name = "CHILD_ID", unique = true, nullable = false)
    public Long getId() {
        return id;
    }

    void setId(Long aId) {
        id = aId;
    }

    @ManyToOne(cascade=CascadeType.ALL)
    @JoinColumn(name = "PARENT_ID")
    public Parent getParent() {
        return parent;
    }
   
    public void setParent(Parent aParent) {
        parent = aParent;
    }
   
    @Column(name = "NAME")
    public String getName() {
        return name;
    }
   
    public void setName(String aName) {
        name = aName;
    }
}



Simplified legacy Identifier generator
Code:
package jpa.prototype.model;

import java.io.Serializable;

import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.PersistentIdentifierGenerator;
// Simplified version of legacy ID generator.
public class LegacyIdGenerator implements PersistentIdentifierGenerator {
    private static long seed = System.currentTimeMillis();
   
    private static synchronized long generateId() {
        return ++seed;
    }
   
    public Object generatorKey() {
        return  generateId();
    }

    public String[] sqlCreateStrings(Dialect aDialect)
            throws HibernateException {
        return null;
    }

    public String[] sqlDropStrings(Dialect aDialect) throws HibernateException {
        return null;
    }

    public Serializable generate(SessionImplementor aSession, Object aObject)
            throws HibernateException {
        return generateId();
    }

}


Full stack trace of any exception that occurs:

Name and version of the database you are using: MySQL Serve 5.0

The generated SQL (show_sql=true):

Debug level Hibernate log excerpt:

Problems with Session and transaction handling?

Read this: http://hibernate.org/42.html


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 25, 2008 7:24 pm 
Expert
Expert

Joined: Mon Nov 26, 2007 2:29 pm
Posts: 443
I know this will sound like one of those open answers that don't really solve you problem, but let me suggest 2 things.

- The "Default Child", although meaningful for your business, doesn't seem to be relevant in regard to the object tree, which is what Hibernate is about. Maybe you should simply not try to map in in any way. A foreign key to ensure that the child exists on the database side is good enough.

- It is you who need to decide if the Default Child property can be null or not, according to your business needs. It should not be dictated by Hibernate or configuration problems.

_________________
Gonzalo Díaz


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 25, 2008 7:56 pm 
Newbie

Joined: Mon Mar 27, 2006 8:21 pm
Posts: 4
Gonzalo,

Thanks for the prompt reply.

I'm trying avoid modifying the existing schema but I might be able to get away with removing the NOT NULL constraint for the "default child" and still co-exist with all our legacy code.

If I follow your suggestion to not map the field at all, does this mean I have to assign the ID from the "default child" myself?

Code:
    class parent {
        Long defaultChildId;
        ...
        public void setDefaultChild(Child aDfltChild)  {
            aDfltChild.setParent(this);
            defaultChildId = aDfltChild.getId();
        }
        ...
    }

If so, how do ensure the child has been assigned and id? Do I have to do something like?
Code:
    class parent {
        Long defaultChildId;
        ...
        public void setDefaultChild(EntityManager aEm, Child aDfltChild)  {
            aDfltChild.setParent(this);
            aEm.persist(aDfltChild);
            defaultChildId = aDfltChild.getId();
        }
        ...
    }

Regards,
John


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 25, 2008 8:21 pm 
Expert
Expert

Joined: Mon Nov 26, 2007 2:29 pm
Posts: 443
No, I your setDefaultChild method should not try to set a "parent" to the child, because:

- it is not a parent-child relationship, properly speaking,
-there is no real need for it to be bidirectional
-as the child is already part of the children collection, the parent will be set anyway

Instead, you can enhance the value of your method (if you are not expecting many children and your business rules permit) by checking if the child you intend to set as a default is already among the children.

_________________
Gonzalo Díaz


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.