【问题标题】:OneToOne between two tables with shared primary key具有共享主键的两个表之间的 OneToOne
【发布时间】:2011-04-27 13:57:32
【问题描述】:

我正在尝试使用 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: detached entity passing to persist: User

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

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 property references a null or transient value: User.code

对于如何最好地解决此问题的任何帮助,我们将不胜感激!

【问题讨论】:

    标签: java database hibernate jpa


    【解决方案1】:

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

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

      • 父实体必须包含 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 必须指定 DB 表的 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. 第二种方法是在 Parent Entity 的 Identifier 字段上使用自定义的 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);
        

    【讨论】:

    • 第一种方法在您尝试加载 UserLogin 时不会给您无限循环?
    【解决方案2】:

    如果你使用 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 将确保 Validation 的 ID 与 User 实体集的 ID 相同。

    【讨论】:

      【解决方案3】:

      您使用的是 JPA 还是 JPA 2.0 ?

      如果 Validation PK 是对 User 的 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();
          }
      
          ...
      }
      

      试试看,告诉我们你的结果。

      【讨论】:

      • 不定义userId字段如何设置setUser()
      【解决方案4】:

      您需要同时设置userIduser

      如果您只设置user,则Validationid 为0,并被视为分离。如果你只设置userId,那么你需要让user属性为空,这里没有意义。

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

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

      我标记了方法@Transient,这样Hibernate 就会忽略它。此外,您仍然可以让setUsersetUserId 按预期工作,而不会产生任何“副作用”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-15
        • 2017-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多