[ACCEPTED]-how to make a composite primary key (java persistence annotation)-many-to-many
You've already had a few good answers here 29 on how to do exactly as you ask..
For reference 28 let me just mention the recommended way 27 to do this in Hibernate instead, which is 26 to use a surrogate key as primary key, and 25 to mark business keys as NaturalId's:
Although 24 we recommend the use of surrogate keys 23 as primary keys, you should try to identify 22 natural keys for all entities. A natural 21 key is a property or combination of properties that 20 is unique and non-null. It is also immutable. Map 19 the properties of the natural key inside 18 the element. Hibernate will generate 17 the necessary unique key and nullability 16 constraints and, as a result, your mapping 15 will be more self-documenting.
It is recommended 14 that you implement equals() and hashCode() to 13 compare the natural key properties of 12 the entity.
In code, using annotations, this 11 would look something like this:
@Entity
public class UserRole {
@Id
@GeneratedValue
private long id;
@NaturalId
private User user;
@NaturalId
private Role role;
}
Using this 10 will save you a lot of headaches down the 9 road, as you'll find out when you frequently 8 have to reference / map the composed primary 7 key.
I found this out the hard way, and in 6 the end just gave up fighting against Hibernate 5 and instead decided to go with the flow. I 4 fully understand that this might not be 3 possible in your case, as you might be dealing 2 with legacy software or dependencies, but 1 I just wanted to mention it for future reference. (if you can't use it maybe someone else can!)
In order to fulfill your requirement, you 14 can map your @ManyToMany as a @OneToMany 13 mapping. This way, USER_ROLE will contain 12 both USER_ID and ROLE_ID as compound primary 11 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 10 object has an assigned identifier, or 9 a composite key, the identifier SHOULD 8 BE ASSIGNED to the object instance BEFORE 7 calling save().
So we have created a JoinedUserRoleId 6 constructor like this one in order to take 5 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 4 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 3 according to code above NO REFERENCE to 2 JoinedUserRole class.
If you want to retrieve 1 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 5 other way is using @EmbeddedId and @Embeddable 4 not sure which one you are looking for) the 3 @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 2 be your user_roles and the name and createDate 1 will be your userid and roleid
Thank you for improving your question ... and 4 taking into account the suggestions.
(Sorry, It 3 is a bit strange that you postfix your entities 2 with Daos, but it is not the point.)
I'm 1 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 7 keys. I also knew the solution with the 6 @Embeddable and @EmbeddedId Class. But i 5 wanted just the simple solution with the 4 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 3 dosn't generate a primary key on the join 2 table:
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="classA_classB")
private List<ClassA> classesA;
at least in my enviroment
Note that 1 the difference is using Set or List
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.