我正在尝试使用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
非常感谢有关如何最好地解决此问题的任何帮助!
答案 0 :(得分:12)
我已经能够以纯JPA 2.0的方式解决“具有共享主键的两个表之间的OneToOne”的问题(感谢SOF上的许多现有线程)。事实上,JPA有两种方法来处理这个问题。我使用eclipselink作为JPA提供程序,使用MySql作为数据库。再次强调,这里没有使用专有的eclipselink类。
第一种方法是在父实体的标识符字段上使用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);
第二种方法是在父实体的标识符字段上使用自定义的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)
您需要同时设置userId
和user
。
如果您只设置user
,那么id
的{{1}}为0并被视为已分离。如果您只设置Validation
,那么您需要使userId
属性可以为空,这在这里没有意义。
为安全起见,您可以在一次方法调用中设置它们:
user
我标记了方法@Transient
public void setUserAndId(User user){
this.userId = user.getId();
this.user = user;
}
,以便Hibernate忽略它。此外,您仍可以@Transient
和setUser
按预期工作,不会产生任何“副作用”。