java - detalle - llave compuesta jpa
OneToOne entre dos tablas con clave primaria compartida (4)
Necesita configurar tanto el userId
user
como el user
.
Si configura solo al user
, la id
de Validation
es 0 y se considera separada. Si configura solo userId
, entonces necesita hacer que la propiedad del user
nulable, lo cual no tiene sentido aquí.
Para estar seguro, probablemente pueda establecer ambos en una llamada a método:
@Transient
public void setUserAndId(User user){
this.userId = user.getId();
this.user = user;
}
@Transient
el método @Transient
para que Hibernate lo ignore. Además, por lo que todavía puede haber setUser
y setUserId
funcionan como se esperaba sin ningún "efecto secundario".
Estoy intentando configurar las siguientes tablas usando JPA / Hibernate:
User:
userid - PK
name
Validation:
userid - PK, FK(user)
code
Puede haber muchos usuarios y cada usuario puede tener un código de validación máximo o ninguno.
Aquí están mis clases:
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();
}
...
}
Creo un usuario y luego trato de agregar un código de validación utilizando el siguiente código:
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();
}
...
}
Cuando trato de ejecutarlo obtengo una org.hibernate.PersistentObjectException: entidad separada pasada para persistir: Usuario
También intenté usar el siguiente código en mi clase de Validación:
public void setUserId(Long userId)
{
this.userId = userId;
}
y cuando creo un código de validación simplemente lo hago:
Validation validation = new Validation();
validation.setUserId(userId);
em.persist(validation);
tx.commit();
Pero como el Usuario es nulo, obtengo org.hibernate.PropertyValueException: la propiedad no nula hace referencia a un valor nulo o transitorio: User.code
Agradecería cualquier ayuda con respecto a la mejor forma de resolver este problema.
Pude resolver este problema de "OneToOne entre dos tablas con clave primaria compartida" en modo JPA 2.0 puro (gracias a muchos hilos existentes en SOF). De hecho, hay dos formas en JPA para manejar esto. He utilizado eclipselink como proveedor JPA y MySql como base de datos. Para resaltar una vez más, no se han usado clases exclusivas de eclipselink aquí.
El primer enfoque es usar la estrategia de tipo de generación AUTO en el campo Identificador de la Entidad Principal.
La entidad padre debe contener el miembro tipo de entidad hijo en la relación OneToOne (tipo de cascada PERSIST y mappedBy = miembro de tipo de entidad padre de la entidad hijo)
@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 }
Child Entity no debe contener un campo identificador. Debe contener un miembro de tipo de entidad padre con Id, OneToOne y Annotations JoinColumn. JoinColumn debe especificar el nombre del campo de ID de la tabla DB.
@Entity @Table(name = "USER_DETAIL") public class UserDetail implements Serializable { @Id @OneToOne @JoinColumn(name="USER_ID") private UserLogin userLogin; // getters & setters }
El enfoque anterior utiliza internamente una tabla de base de datos predeterminada llamada SEQUENCE para asignar los valores al campo identificador. Si no está presente, esta tabla debe crearse de la siguiente manera.
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);
El segundo enfoque es utilizar una estrategia de tipo de generación TABLE personalizada y una anotación TableGenerator en el campo Identificador de la Entidad Principal.
Excepto el cambio anterior en el campo de identificador, todo lo demás permanece sin cambios en la entidad principal.
@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 }
No hay cambio en la Entidad Infantil. Sigue siendo el mismo que en el primer enfoque.
Este enfoque del generador de tablas utiliza internamente una tabla DB APP_SEQ_STORE para asignar los valores al campo identificador. Esta tabla debe crearse de la siguiente manera.
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);
¿Estás usando JPA o JPA 2.0?
Si la validación PK es un FK para el usuario, entonces no necesita el atributo Long userId en la clase de validación, sino que realiza la anotación @Id
sola. Podría ser:
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();
}
...
}
Pruébalo y cuéntanos tus resultados.
Si usa Hibernate, también puede usar
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 se asegurará de que la ID de la Validación sea la misma que la ID del conjunto de entidades del Usuario.