Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OneToOne between two tables with shared primary key

I'm trying to set up the following tables using JPA/Hibernate:

User:

userid - PK
name 

Validation:

userid - PK, FK(user)
code

There may be many users and every user may have max one validation code or none.

Here's my classes:

public class User 
{
    @Id
    @Column(name = "userid") 
    @GeneratedValue(strategy = GenerationType.IDENTITY)    
    protected Long userId;

    @Column(name = "name", length = 50, unique = true, nullable = false)
    protected String name;

    ...
}

public class Validation 
{
    @Id
    @Column(name = "userid")
    protected Long userId;

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn(name = "userid", referencedColumnName = "userid")
    protected User user;

    @Column(name = "code", length = 10, unique = true, nullable = false)
    protected String code;

    ...

    public void setUser(User user)
    {
        this.user = user;
        this.userId = user.getUserId();
    }

    ...
}

I create a user and then try to add a validation code using the following code:

public void addValidationCode(Long userId)
{   
    EntityManager em = createEntityManager();
    EntityTransaction tx = em.getTransaction();

    try 
    {
        tx.begin();

        // Fetch the user
        User user = retrieveUserByID(userId);

        Validation validation = new Validation();
        validation.setUser(user);
        em.persist(validation);
        tx.commit();
    }
    ...
}

When I try to run it I get a org.hibernate.PersistentObjectException: detached entity passed to persist: User

I have also tried to use the following code in my Validation class:

public void setUserId(Long userId)
{
    this.userId = userId;
}

and when I create a validation code I simply do:

Validation validation = new Validation();
validation.setUserId(userId);
em.persist(validation);
tx.commit();

But then since User is null I get org.hibernate.PropertyValueException: not-null property references a null or transient value: User.code

Would appreciate any help regarding how to best solve this issue!

like image 640
Jonas Avatar asked Apr 27 '11 13:04

Jonas


People also ask

Can two tables share a primary key?

Every table can have (but does not have to have) a primary key. The column or columns defined as the primary key ensure uniqueness in the table; no two rows can have the same key. The primary key of one table may also help to identify records in other tables, and be part of the second table's primary key.

What is shared primary key?

Also know as One-to-One Primary Key Associations, means two related tables share the same primary key values. The primary key of one table is also a foreign key of the other.


1 Answers

I have been able to solve this problem of "OneToOne between two tables with shared primary key" in pure JPA 2.0 way(Thanks to many existing threads on SOF). In fact there are two ways in JPA to handle this. I have used eclipselink as JPA provider and MySql as database. To highlight once again no proprietary eclipselink classes have been used here.

  1. First approach is to use AUTO generation type strategy on the Parent Entity's Identifier field.

    • Parent Entity must contain the Child Entity Type member in OneToOne relationship(cascade type PERSIST and mappedBy = Parent Entity Type member of Child Entity)

      @Entity
      @Table(name = "USER_LOGIN")
      public class UserLogin implements Serializable {
          @Id
          @GeneratedValue(strategy = GenerationType.AUTO)
          @Column(name="USER_ID")
          private Integer userId;
      
          @OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
          private UserDetail userDetail;
      // getters & setters
      }
      
    • Child Entity must not contain an identifier field. It must contain a member of Parent Entity Type with Id, OneToOne and JoinColumn annotations. JoinColumn must specify the ID field name of the DB table.

      @Entity
      @Table(name = "USER_DETAIL")
      public class UserDetail implements Serializable {
          @Id
          @OneToOne
          @JoinColumn(name="USER_ID")
          private UserLogin userLogin;
      // getters & setters
      }
      
    • Above approach internally uses a default DB table named SEQUENCE for assigning the values to the identifier field. If not already present, This table needs to be created as below.

      DROP TABLE TEST.SEQUENCE ;
      CREATE TABLE TEST.SEQUENCE (SEQ_NAME VARCHAR(50), SEQ_COUNT DECIMAL(15));
      INSERT INTO TEST.SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0);
      
  2. Second approach is to use customized TABLE generation type strategy and TableGenerator annotation on the Parent Entity's Identifier field.

    • Except above change in identifier field everything else remains unchanged in Parent Entity.

      @Entity
      @Table(name = "USER_LOGIN")
      public class UserLogin implements Serializable {
          @Id
          @TableGenerator(name="tablegenerator", table = "APP_SEQ_STORE", pkColumnName = "APP_SEQ_NAME", pkColumnValue = "USER_LOGIN.USER_ID", valueColumnName = "APP_SEQ_VALUE", initialValue = 1, allocationSize = 1 )  
          @GeneratedValue(strategy = GenerationType.TABLE, generator = "tablegenerator")
          @Column(name="USER_ID")
          private Integer userId;
      
          @OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
          private UserDetail userDetail;
      // getters & setters
      }
      
    • There is no change in Child Entity. It remains same as in the first approach.

    • This table generator approach internally uses a DB table APP_SEQ_STORE for assigning the values to the identifier field. This table needs to be created as below.

      DROP TABLE TEST.APP_SEQ_STORE;
      CREATE TABLE TEST.APP_SEQ_STORE
      (
          APP_SEQ_NAME VARCHAR(255) NOT NULL,
          APP_SEQ_VALUE BIGINT NOT NULL,
          PRIMARY KEY(APP_SEQ_NAME)
      );
      INSERT INTO TEST.APP_SEQ_STORE VALUES ('USER_LOGIN.USER_ID', 0);
      
like image 116
nirmalsingh Avatar answered Sep 18 '22 09:09

nirmalsingh