views:

3135

answers:

5

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 userdao

@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 rolesdao

@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 userdao and roleId from roledao. now i need those two columns in the generated table (user_roles) to be a composit primary key.

A: 

Does this help:

http://technology.amis.nl/blog/3177/mapping-composite-primary-keys-in-jpa-%E2%80%93-how-to-work-around-a-bug-in-hibernate-annotations

?

jon hanson
no. not sure if it is even related. was looking for something along ... @MakeThisAlsoPK after @JoinTable or sth...
b0x0rz
A: 

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.
KLE
but that is not what i want. here it seems to make the composite key on the class that is not auto generated. i need a composite key in the table user_roles for which i do not have ANY class. the table is auto generated from user and role dao classes. so if i wanted a composite key in for example userdao then this would work. but i don't need/want that.
b0x0rz
dao postfix is legacy from someone else.
b0x0rz
"It is also possible to map a many-to-many relationship between your two classes." Yes I have already done this (in question). Now I need for that GENERATED table to not be without a primary key. AND I need that primary key to be a composite primary key of all (both) columns in user_roles table.
b0x0rz
also, thank you for the help. i improved a question a bit. not really sure what more to write :(
b0x0rz
Well, if you thank me for helping... Would you be so kind as to remove the "Not Helpful" click you made on my answer ?
KLE
i still feel you do not understand my problem (hence the not helpful as it does not pertain to the question). YES the the entities i described have one PK, but in the THIRD entity (a many to many table) should have a compound PK made from each of the PKs in the two entities.
b0x0rz
I believe that there is no third Java Entity, it is implicit when you use ManyToMany relationship. If you need a third Entity, use a OneToMany and a ManyToOne relationships.
KLE
+1  A: 

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

OpenSource
Yeah the import thing is that you need a additional class for combined key. I recommend using a mapping auto generation tool to make the ORM layer for you. I personally use Netbeans.
James McMahon
+2  A: 

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!)

Tim
I don't know if Hibernate is going to give you anything over using JPA.
James McMahon
+3  A: 

Hi,

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,

Arthur Ronald F D Garcia
Wow, you put a lot of work in to this! +1
Tim