Java 具有共享主键的两个表之间的OneToOne
我正在尝试使用JPA/Hibernate设置以下表:Java 具有共享主键的两个表之间的OneToOne,java,database,hibernate,jpa,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)
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实体传递给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:notnull属性引用null或暂时值:User.code
如果您有任何关于如何最好地解决此问题的帮助,我们将不胜感激 您需要同时设置
userId
和user
如果仅设置用户
,则验证
的id
为0,并被视为已分离。如果只设置userId
,则需要将user
属性设置为null,这在这里没有意义
为了安全起见,您可能可以在一个方法调用中同时设置它们:
@Transient
public void setUserAndId(User user){
this.userId = user.getId();
this.user = user;
}
我标记了方法
@Transient
,以便Hibernate忽略它。此外,您还可以让setUser
和setUserId
按预期工作,而不会产生任何“副作用”。您使用的是JPA还是JPA2.0
如果Validation PK是一个FK to User,那么您不需要Validation类中的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();
}
...
}
尝试使用它并告诉我们您的结果。如果您使用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相同。我已经能够以纯JPA 2.0的方式解决“两个表之间使用共享主键的OneToOne”的问题(感谢SOF上的许多现有线程)。事实上,JPA有两种方法来处理这个问题。我使用eclipselink作为JPA提供者,使用MySql作为数据库。为了再次强调,这里没有使用任何专有的eclipselink类
- 父实体必须包含OneTONE关系中的子实体类型成员(级联类型PERSIST和mappedBy=子实体的父实体类型成员)
- 子实体不能包含标识符字段。它必须包含具有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的默认DB表将值分配给标识符字段。如果尚未出现,则需要按如下方式创建此表
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);
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);
- 除上述标识符字段中的更改外,父实体中的所有其他内容都保持不变
@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 table APP_SEQ_存储将值分配给标识符字段。此表需要按如下方式创建
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);
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时不会给您一个无限循环?如果您不定义
setUser()
字段,如何设置userId
字段?