5

I need to have a friendship relationship. I have a friendship class with two primary keys that each is a Member class. I am receiving following exception:

org.hibernate.MappingException: Foreign key (FK_8ynretl1yt1xe3gcvfytrvpq:Friendship [])) must have same number of columns as the referenced primary key (Member [username])

Friendship

@Entity
public class Friendship implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -1234656876554786549L;
    @Id
    @ManyToOne
    Member requester;
    @Id
    @ManyToOne
    Member friend;
    @Temporal(javax.persistence.TemporalType.DATE)
Date date;

Member

@Entity
public class Member {
    @Id
    @MapsId
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "username")
    Credential credential;
    @Column(nullable = false)
    String fname;
    @Column(nullable = false)
    String lname;
    @Column(nullable = false)
    short gender;

Credential

@Entity
public class Credential {
    @Id
    @Column(nullable = false, unique = true)
    private String username;
    @Column(nullable = false)
    private String password;
    @Column(nullable = false)
    private String authority;
    @Column(nullable = false)
    private boolean enabled;
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Jack
  • 6,430
  • 27
  • 80
  • 151

4 Answers4

1

You have to add a separate ID field to the Member class for the @MapsID annotation to map. Like this:

@Entity
public class Member implements Serializable {
    @Id
    private String username;

    @MapsId
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "username")
    Credential credential;
    @Column(nullable = false)
    String fname;
    @Column(nullable = false)
    String lname;
    @Column(nullable = false)
    short gender;

}
Varis
  • 254
  • 2
  • 11
1

You are missing the modeling of the primary key in the Friendship class. For example:

@Embeddable
public class FriendshipPK implements Serializable
{
    @Column(name = "requester_id")
    protected String requesterId;

    @Column(name = "friend_id")
    protected String friendId;
}

Friendship class can now be modified as follows:

@Entity
public class Friendship implements Serializable
{
    @EmbeddedId
    protected FriendshipPK friendshipId = new FriendshipPK();

    @ManyToOne
    @MapsId("requesterId")
    Member requester;

    @ManyToOne
    @MapsId("friendId")
    Member friend;

    @Temporal(javax.persistence.TemporalType.DATE)
    Date date;
}

I have updated the Member class slightly:

@Entity
public class Member implements Serializable
{
    @Id
    protected String memberId;

    @MapsId
    @OneToOne(optional = false)
    @JoinColumn(name = "username")
    Credential credential;
    @Column(nullable = false)
    String fname;
    @Column(nullable = false)
    String lname;
    @Column(nullable = false)
    short gender;
}

I removed the cascade from Member class, and created the credentials objects first. But you can change this as fit.

codedabbler
  • 1,231
  • 7
  • 13
  • Nice, It worked to persist. But how would you do to update the friendship ? for example, if it has some status. When I try to update, it throws me PSQLException, because of the duplicated key. – Guilherme Alencar May 02 '20 at 14:21
1

Putting aside that Member and Credential should implement Serializable if multiple id properties without identifier type are used, your mappings are good, and this seems to be a bug in Hibernate.

Solution 1

I managed to make this work by declaring referencedColumnName in friend and requester associations in Friendship:

@Id
@ManyToOne
@JoinColumn(referencedColumnName = "username")
Member requester;

@Id
@ManyToOne
@JoinColumn(referencedColumnName = "username")
Member friend;

This way we explicitly tell Hibernate which columns the composite id references, so that it does not have to figure it out itself.

Solution 2

The solution 1 made me think of what could be the cause of the bug in Hibernate. It seems that it is somehow affected by the order in which Hibernate processes the entity mappings. If you explicitly declare the referenced column, everything works fine, otherwise it seems that Hibernate does not know all the details about the referenced column at the time it builds the composite key.

So I changed the order in which I add annotated classes to the session factory configuration to:

Credential
Member
Friendship

and then everything worked with your original mappings (after implementing Serializable in Member and Credential).

I added the classes in this order programmatically to the Configuration class, but I assume the same effect could be achieved by specifying this order in the persistence.xml or hibernate.cfg.xml:

<class>Credential</class>
<class>Member</class>
<class>Friendship</class>

Nevertheless, this solution is just for demonstrative purposes (you or someone else can later reorder the classes without keeping this issue in mind), so I suggest using solution 1.

Note

You know your use cases better, but in my personal opinion you should use @IdClass or @EmbeddedId since they are standardized in JPA; multiple id properties without identifier type is a Hibernate specific feature. Besides being able to easier construct the primary key object by which you will search and query the corresponding entities, a dedicated PK object is usually much lighter and offers better performance when serialized, especially if second level cache is enabled.

Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
0

In the Friendship class try specifying the @JoinColumn as well:

@Entity
public class Friendship implements Serializable {

    @Id
    @ManyToOne
    @JoinColumn(name = "username")
    Member requester;

    ...
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • I did but it returns A Foreign key refering com.myproject.model.Member from com.myproject.model.Friendship has the wrong number of column. should be 0 – Jack Jul 13 '15 at 10:24