how to make a composite primary key (java persistence annotation)
-
06-07-2019 - |
Question
How to make it so that the table user_roles defines the two columns (userID, roleID) as a composite primary key. should be easy, just can't remember/find.
In user
entity:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles")
public List<RoleDAO> getRoles() {
return roles;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getUserID() {
return userID;
}
In roles
entity:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles")
public List<UserDAO> getUsers() {
return users;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getRoleID() {
return roleID;
}
Thank you.
** MORE INFO
So there is a third table user_roles
(auto generated by above) that takes userID
from user
entity and roleID
from roles
entity. Now I need those two columns in the generated table (user_roles
) to be a composite primary key.
Solution
You've already had a few good answers here on how to do exactly as you ask..
For reference let me just mention the recommended way to do this in Hibernate instead, which is to use a surrogate key as primary key, and to mark business keys as NaturalId's:
Although we recommend the use of surrogate keys as primary keys, you should try to identify natural keys for all entities. A natural key is a property or combination of properties that is unique and non-null. It is also immutable. Map the properties of the natural key inside the element. Hibernate will generate the necessary unique key and nullability constraints and, as a result, your mapping will be more self-documenting.
It is recommended that you implement equals() and hashCode() to compare the natural key properties of the entity.
In code, using annotations, this would look something like this:
@Entity
public class UserRole {
@Id
@GeneratedValue
private long id;
@NaturalId
private User user;
@NaturalId
private Role role;
}
Using this will save you a lot of headaches down the road, as you'll find out when you frequently have to reference / map the composed primary key.
I found this out the hard way, and in the end just gave up fighting against Hibernate and instead decided to go with the flow. I fully understand that this might not be possible in your case, as you might be dealing with legacy software or dependencies, but I just wanted to mention it for future reference. (if you can't use it maybe someone else can!)
OTHER TIPS
In order to fulfill your requirement, you can map your @ManyToMany as a @OneToMany mapping. This way, USER_ROLE will contain both USER_ID and ROLE_ID as compound primary key
I will show you how to:
@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;
}
}
}
remember
If an object has an assigned identifier, or a composite key, the identifier SHOULD BE ASSIGNED to the object instance BEFORE calling save().
So we have created a JoinedUserRoleId constructor like this one in order to take care of it
public JoinedUserRoleId(User user, Role role) {
this.user = user;
this.role = role;
}
And finally Role class
@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);
}
}
According to test it, let's write the following
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();
Notice according to code above NO REFERENCE to JoinedUserRole class.
If you want to retrieve a JoinedUserRole, try the following
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();
regards,
Composite keys are done using @IdClass (the other way is using @EmbeddedId and @Embeddable not sure which one you are looking for) the @IdClass is as follows
@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();
}
}
my Category here will be your user_roles and the name and createDate will be your userid and roleid
Thank you for improving your question ... and taking into account the suggestions.
(Sorry, It is a bit strange that you postfix your entities with Daos, but it is not the point.)
I'm not sure there is any problem left :
- The two entities you describe each have one PK, not a pair-of.
- The link-table has no corresponding entity, it is defined implicitely by the two entities and their ManyToMany relationship. If you need a third Entity, change your ManyToMany for a pair of OneToMany and ManyToOne relationships.
I had the same Problem with the primary keys. I also knew the solution with the @Embeddable and @EmbeddedId Class. But i wanted just the simple solution with the annotations.
Well i found enlightenment through this article: http://www.vaannila.com/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
and here is the magic:
this generates a primary key on the join table:
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="classA_classB")
private Set<ClassA> classesA;
this dosn't generate a primary key on the join table:
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="classA_classB")
private List<ClassA> classesA;
at least in my enviroment
Note that the difference is using Set or List