2011-04-27 90 views
12

我试图建立使用JPA /休眠下表:OneToOne与共享主键

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

当我尝试运行它,我得到一个组织。 hibernate.PersistentObjectException:传递给persist的分离实体:用户

我也尝试在我的验证类中使用以下代码:

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

,当我创建一个验证码,我只是做:

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

但后来因为用户是零,我得到org.hibernate.PropertyValueException:非空属性引用null或瞬时值:用户.code

希望有关如何最好地解决这个问题的任何帮助!

回答

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。

3

您需要同时设置userIduser

如果您只设置了user,那么id对于Validation为0并且被视为分离。如果你只设置了userId,那么你需要使user属性为空,这在这里没有意义。

为了安全起见,你也许可以将它们都在一个方法调用:

@Transient 
public void setUserAndId(User user){ 
    this.userId = user.getId(); 
    this.user = user; 
} 

标志着我的方法@Transient使Hibernate会忽略它。此外,所以您仍然可以按照预期的方式使setUsersetUserId无任何“副作用”。

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

    ... 
} 

试一下,告诉我们你的结果。

+0

+1:这将是理想的。 – Jeremy 2011-04-27 14:18:09

+0

如何在'setUser()'中设置'userId'字段? – Lu55 2015-09-25 12:09:09

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. 第二种方法是使用自定义表生成型战略和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 
      } 
      
    • 子实体没有变化。它和第一种方法一样。

    • 此表生成器方法在内部使用数据库表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

当您尝试加载UserLogin时,第一种方法不会给您一个无限循环? – squallsv 2015-08-03 13:30:11