質問

I am building a sample for ManyToMany relationship between: User(1) - ()AccessLevel() - (1)Role

I have implemented 3 classes in Java with hibernate implementation as follow:

Class User

@Entity
@Table(name="user")
public class User {
    @Id
    @GeneratedValue
    @Column(name="USER_ID")

    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name = "access_level", joinColumns = { 
            @JoinColumn(name = "user_id", nullable = false, updatable = false) }, 
            inverseJoinColumns = { @JoinColumn(name = "role_id", nullable = false, updatable = false) })
    private Set<Role> roles = new HashSet<Role>(0);

Class Role

@Entity
@Table(name="role")
public class Role {

    @Id
    @GeneratedValue
    @Column(name="role_id")
    private Integer id;

    @Column(name="role_name")
    private String roleName;

Class AccessLevel

@Entity
@Table(name="access_level")
public class AccessLevel {

    @Id
    @GeneratedValue
    private Integer id;
    @Column(name="role_id")
    private Integer  roleId;
    @Column(name="user_id")
    private Integer  userId;

Problem:

When I am persisting the User bean using merge method then an exception arise:

@Service
public class UserServiceImpl implements UserService {

    @PersistenceContext
    private EntityManager em;

    @Override
    @Transactional
    public void save(User user) {
        em.merge(user);
    }

Exception

org.springframework.web.util.NestedServletException: Request process

ing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [insert into access_level (user_id, role_id) values (?, ?)]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:722)

As you can see hibernate is trying to run this query:

insert into access_level (user_id, role_id) values (?, ?)

From my point of view it seems like hibernate is not generating the primary key for AccessLevel even though I have added the @GeneratedValue to the id attribute.

Note: I am working on production environment with Postgresql and evelopment environment with HSQL database that creates all schemas from the begining based on the entities description. Both environments generate same issue.

Regards, Cristian Colorado

役に立ちましたか?

解決

Reason:

It seems for ManyToMany relationships you do not need to define a class for the "Joining Table". Therefore if I eliminate AccessLevel entity the logic would work perfectly fine. I explain further:

Explanation:

When I defined the User class I also described the relationship with Role:

@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name = "access_level", joinColumns = { 
            @JoinColumn(name = "user_id", nullable = false, updatable = false) }, 
            inverseJoinColumns = { @JoinColumn(name = "role_id", nullable = false, updatable = false) })
    private Set<Role> roles = new HashSet<Role>(0);

Important thing here is I have told hibernate that User entity will relate to Role entity through a table known as "access_level" and such table will have user_id and role_id columns in order to join previous entities.

So far this is all hibernate needs in order to work the many to many relationship, therefore when mergin it uses that information to create and tun this script:

insert into access_level (user_id, role_id) values (?, ?)

Now, the problem cames when I defined a new entity for AccessLevel:

@Entity
    @Table(name="access_level")
    public class AccessLevel {

        @Id
        @GeneratedValue
        private Integer id;
        @Column(name="role_id")
        private Integer  roleId;
        @Column(name="user_id")
        private Integer  userId;

Now I am telling hibernate that there is a table "access_level" related to AccessLevel entity and it has 3 columns, the most important would be Id which is primary key.

So I defined "access_level" twice!

Solution:

  • I eliminated the Entity for access_level table.
  • I re-write my production script in order to have "access_level" with user_id/role_id columns only.

Note: It would be good to know how to add a primary key to the joining table without generating issues. An alternative would be adding a composed primary key in database(user_id/role_id) which would be independient from hibernate.

他のヒント

Why do you need a PK column in the join table? There will be a composite PK composed of user_id and role_id. Now, as you have discovered a JoinTable for @ManyToMany will only ever have two columns and at some point you may require additional data about this relationship.

e.g.

user_id role_id date_granted

You may then want to use your AccessLevel entity however you replace the @ManyToMany with @OneToMany from User to AccessLevel and optionally from Role > AccessLevel.

The Hibernate docs themselves advise against @ManyToMany:

Do not use exotic association mappings:

Practical test cases for real many-to-many associations are rare. Most of the time you need additional information stored in the "link table". In this case, it is much better to use two one-to-many associations to an intermediate link class. In fact, most associations are one-to-many and many-to-one. For this reason, you should proceed cautiously when using any other association style.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top