具有共享主键的两个表之间的OneToOne

时间:2011-04-27 13:57:32

标签: java database hibernate jpa

我正在尝试使用JPA / Hibernate设置以下表格:

User:

userid - PK
name 

Validation:

userid - PK, FK(user)
code

可能有许多用户,每个用户可能最多只有一个验证码或没有。

这是我的课程:

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();
    }

    ...
}

我创建了一个用户,然后尝试使用以下代码添加验证代码:

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();
    }
    ...
}

当我尝试运行它时,我得到一个org.hibernate.PersistentObjectException:传递给persist的分离实体:User

我还尝试在Validation类中使用以下代码:

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

当我创建验证码时,我只是这样做:

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

但是,由于User为null,我得到org.hibernate.PropertyValueException:not-null属性引用null或transient值:User.code

非常感谢有关如何最好地解决此问题的任何帮助!

4 个答案:

答案 0 :(得分:12)

我已经能够以纯JPA 2.0的方式解决“具有共享主键的两个表之间的OneToOne”的问题(感谢SOF上的许多现有线程)。事实上,JPA有两种方法来处理这个问题。我使用eclipselink作为JPA提供程序,使用MySql作为数据库。再次强调,这里没有使用专有的eclipselink类。

  1. 第一种方法是在父实体的标识符字段上使用AUTO生成类型策略。

    • 父实体必须包含OneToOne关系中的子实体类型成员(级联类型PERSIST和mappedBy =子实体的父实体类型成员)

      @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
      }
      
    • 子实体不得包含标识符字段。它必须包含具有Id,OneToOne和JoinColumn注释的父实体类型的成员。 JoinColumn必须指定数据库表的ID字段名称。

      @Entity
      @Table(name = "USER_DETAIL")
      public class UserDetail implements Serializable {
          @Id
          @OneToOne
          @JoinColumn(name="USER_ID")
          private UserLogin userLogin;
      // getters & setters
      }
      
    • 上述方法在内部使用名为SEQUENCE的默认数据库表将值分配给标识符字段。如果尚未出现,则需要按如下方式创建此表。

      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. 第二种方法是在父实体的标识符字段上使用自定义的TABLE生成类型策略和TableGenerator注释。

    • 除了标识符字段的上述更改外,其他所有内容在父实体中保持不变。

      @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
      }
      
    • 儿童实体没有变化。它与第一种方法相同。

    • 此表生成器方法在内部使用DB表APP_SEQ_STORE将值分配给标识符字段。此表需要创建如下。

      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);
      

答案 1 :(得分:9)

如果你使用Hibernate,你也可以使用

public class Validation {

    private Long validationId;
    private User user;

    @Id
    @GeneratedValue(generator="SharedPrimaryKeyGenerator")
    @GenericGenerator(name="SharedPrimaryKeyGenerator",strategy="foreign",parameters =  @Parameter(name="property", value="user"))
    @Column(name = "VALIDATION_ID", unique = true, nullable = false)
    public Long getValidationId(){
        return validationId;
    }

    @OneToOne
    @PrimaryKeyJoinColumn
    public User getUser() {
        return user;
    }

}

Hibernate将确保验证的ID与用户实体集的ID相同。

答案 2 :(得分:4)

您使用的是JPA还是JPA 2.0?

如果验证PK是用户的FK,则验证类中不需要Long userId属性,而是单独执行@Id注释。它将是:

Public class Validation 
{
    @Id
    @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();
    }

    ...
}

尝试使用它并告诉我们您的结果。

答案 3 :(得分:3)

您需要同时设置userIduser

如果您只设置user,那么id的{​​{1}}为0并被视为已分离。如果您只设置Validation,那么您需要使userId属性可以为空,这在这里没有意义。

为安全起见,您可以在一次方法调用中设置它们:

user

我标记了方法@Transient public void setUserAndId(User user){ this.userId = user.getId(); this.user = user; } ,以便Hibernate忽略它。此外,您仍可以@TransientsetUser按预期工作,不会产生任何“副作用”。