Cómo hacer una clave primaria compuesta (anotación de persistencia de Java)
-
06-07-2019 - |
Pregunta
Cómo hacer que la tabla user_roles defina las dos columnas (userID, roleID) como una clave primaria compuesta. debería ser fácil, simplemente no puedo recordar / encontrar.
En la entidad usuario
:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles")
public List<RoleDAO> getRoles() {
return roles;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getUserID() {
return userID;
}
En roles
entidad:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles")
public List<UserDAO> getUsers() {
return users;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getRoleID() {
return roleID;
}
Gracias.
** MÁS INFORMACIÓN
Entonces, hay una tercera tabla user_roles
(generada automáticamente por arriba) que toma userID
de la entidad user
y roleID
de la entidad roles
. Ahora necesito que esas dos columnas en la tabla generada ( user_roles
) sean una clave primaria compuesta.
Solución
Ya has tenido algunas buenas respuestas aquí sobre cómo hacer exactamente lo que pides ...
Como referencia, permítanme mencionar la forma recomendada de hacer esto en Hibernate, que es utilizar una clave sustituta como clave principal y marcar las claves comerciales como NaturalId:
Aunque recomendamos el uso de claves sustitutas como claves primarias, usted debería tratar de identificar claves naturales para todas las entidades. Una clave natural es un propiedad o combinación de propiedades eso es único y no nulo. Es También inmutable. Mapear las propiedades de la llave natural dentro del elemento. Voluntad de hibernación generar la clave única necesaria y restricciones de nulabilidad y, como resultado, su mapeo será más autodocumentado.
Se recomienda que implemente equals () y hashCode () para comparar el propiedades clave naturales de la entidad.
En código, usando anotaciones, esto se vería así:
@Entity
public class UserRole {
@Id
@GeneratedValue
private long id;
@NaturalId
private User user;
@NaturalId
private Role role;
}
El uso de esto le ahorrará muchos dolores de cabeza en el futuro, como descubrirá cuando tenga que hacer referencia / mapear con frecuencia la clave principal compuesta.
Descubrí esto de la manera difícil, y al final dejé de luchar contra Hibernate y en su lugar decidí seguir la corriente. Entiendo completamente que esto podría no ser posible en su caso, ya que podría estar lidiando con software heredado o dependencias, pero solo quería mencionarlo para referencia futura. ( si no puede usarlo, tal vez alguien más pueda !)
Otros consejos
Para cumplir con su requisito, puede asignar su @ManyToMany como una asignación @OneToMany. De esta manera, USER_ROLE contendrá USER_ID y ROLE_ID como clave principal compuesta
Te mostraré cómo:
@Entity
@Table(name="USER")
public class User {
@Id
@GeneratedValue
private Integer id;
@OneToMany(cascade=CascadeType.ALL, mappedBy="joinedUserRoleId.user")
private List<JoinedUserRole> joinedUserRoleList = new ArrayList<JoinedUserRole>();
// no-arg required constructor
public User() {}
public User(Integer id) {
this.id = id;
}
// addRole sets up bidirectional relationship
public void addRole(Role role) {
// Notice a JoinedUserRole object
JoinedUserRole joinedUserRole = new JoinedUserRole(new JoinedUserRole.JoinedUserRoleId(this, role));
joinedUserRole.setUser(this);
joinedUserRole.setRole(role);
joinedUserRoleList.add(joinedUserRole);
}
}
@Entity
@Table(name="USER_ROLE")
public class JoinedUserRole {
public JoinedUserRole() {}
public JoinedUserRole(JoinedUserRoleId joinedUserRoleId) {
this.joinedUserRoleId = joinedUserRoleId;
}
@ManyToOne
@JoinColumn(name="USER_ID", insertable=false, updatable=false)
private User user;
@ManyToOne
@JoinColumn(name="ROLE_ID", insertable=false, updatable=false)
private Role role;
@EmbeddedId
// Implemented as static class - see bellow
private JoinedUserRoleId joinedUserRoleId;
// required because JoinedUserRole contains composite id
@Embeddable
public static class JoinedUserRoleId implements Serializable {
@ManyToOne
@JoinColumn(name="USER_ID")
private User user;
@ManyToOne
@JoinColumn(name="ROLE_ID")
private Role role;
// required no arg constructor
public JoinedUserRoleId() {}
public JoinedUserRoleId(User user, Role role) {
this.user = user;
this.role = role;
}
public JoinedUserRoleId(Integer userId, Integer roleId) {
this(new User(userId), new Role(roleId));
}
@Override
public boolean equals(Object instance) {
if (instance == null)
return false;
if (!(instance instanceof JoinedUserRoleId))
return false;
final JoinedUserRoleId other = (JoinedUserRoleId) instance;
if (!(user.getId().equals(other.getUser().getId())))
return false;
if (!(role.getId().equals(other.getRole().getId())))
return false;
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + (this.user != null ? this.user.hashCode() : 0);
hash = 47 * hash + (this.role != null ? this.role.hashCode() : 0);
return hash;
}
}
}
recuerda
Si un objeto tiene asignado identificador, o una clave compuesta, el El identificador DEBE SER ASIGNADO al instancia de objeto ANTES de llamar a save ().
Así que hemos creado un constructor JoinedUserRoleId como este para cuidarlo
public JoinedUserRoleId(User user, Role role) {
this.user = user;
this.role = role;
}
Y finalmente Clase de rol
@Entity
@Table(name="ROLE")
public class Role {
@Id
@GeneratedValue
private Integer id;
@OneToMany(cascade=CascadeType.ALL, mappedBy="JoinedUserRoleId.role")
private List<JoinedUserRole> joinedUserRoleList = new ArrayList<JoinedUserRole>();
// no-arg required constructor
public Role() {}
public Role(Integer id) {
this.id = id;
}
// addUser sets up bidirectional relationship
public void addUser(User user) {
// Notice a JoinedUserRole object
JoinedUserRole joinedUserRole = new JoinedUserRole(new JoinedUserRole.JoinedUserRoleId(user, this));
joinedUserRole.setUser(user);
joinedUserRole.setRole(this);
joinedUserRoleList.add(joinedUserRole);
}
}
De acuerdo con probarlo, escribamos lo siguiente
User user = new User();
Role role = new Role();
// code in order to save a User and a Role
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Serializable userId = session.save(user);
Serializable roleId = session.save(role);
session.getTransaction().commit();
session.clear();
session.close();
// code in order to set up bidirectional relationship
Session anotherSession = HibernateUtil.getSessionFactory().openSession();
anotherSession.beginTransaction();
User savedUser = (User) anotherSession.load(User.class, userId);
Role savedRole = (Role) anotherSession.load(Role.class, roleId);
// Automatic dirty checking
// It will set up bidirectional relationship
savedUser.addRole(savedRole);
anotherSession.getTransaction().commit();
anotherSession.clear();
anotherSession.close();
Aviso según el código anterior SIN REFERENCIA a la clase JoinedUserRole.
Si desea recuperar un JoinedUserRole, intente lo siguiente
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Integer userId;
Integer roleId;
// Lets say you have set up both userId and roleId
JoinedUserRole joinedUserRole = (JoinedUserRole) session.get(JoinedUserRole.class, new JoinedUserRole.JoinedUserRoleId(userId, roleId));
// some operations
session.getTransaction().commit();
session.clear();
session.close();
saludos,
Las claves compuestas se realizan usando @IdClass (la otra forma es usar @EmbeddedId y @Embeddable no estoy seguro de cuál está buscando) la @IdClass es la siguiente
@Entity
@IdClass(CategoryPK.class)
public class Category {
@Id
protected String name;
@Id
protected Date createDate;
}
public class CategoryPK implements Serializable {
String name;
Date createDate;
public boolean equals(object other) {
//implement a equals that the PP can use to determine
//how the CategoryPK object can be identified.
}
public int hashCode(){
return Super.hashCode();
}
}
mi categoría aquí serán sus roles de usuario y el nombre y createDate serán su ID de usuario y rol
Gracias por mejorar su pregunta ... y tener en cuenta las sugerencias.
(Lo siento, es un poco extraño que fijes tus entidades con Daos, pero no es el punto).
No estoy seguro de que quede algún problema:
- Las dos entidades que describe tienen una PK, no un par de ellas.
- La tabla de enlaces no tiene una entidad correspondiente, está definida implícitamente por las dos entidades y su relación ManyToMany. Si necesita una tercera entidad, cambie su ManyToMany por un par de relaciones OneToMany y ManyToOne.
Tuve el mismo problema con las claves principales. También conocía la solución con la clase @Embeddable y @EmbeddedId. Pero solo quería la solución simple con las anotaciones.
Bueno, encontré la iluminación a través de este artículo: http: // www .vaannila.com / hibernate / hibernate-example / hibernate-mapping-many-to-many-using-annotations-1.html
y aquí está la magia:
esto genera una clave principal en la tabla de combinación:
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="classA_classB")
private Set<ClassA> classesA;
esto no genera una clave principal en la tabla de combinación:
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="classA_classB")
private List<ClassA> classesA;
al menos en mi entorno
Tenga en cuenta que la diferencia es utilizar Establecer o Lista