Home > Back-end >  Why I am obtaining this error trying to implement an Hibernate Many To Many mapping?
Why I am obtaining this error trying to implement an Hibernate Many To Many mapping?

Time:11-09

I am working on a Spring Boot project using Spring Data Jpa in order do persist data on my PostgreSQL DB. I am finding some difficulties with Hibernate Many To Many mapping. Following details of my problem. In my project tables are not created by Hibernate but I am using Hibernate defining entity classes mapping my tables.

I have these 3 DB tables:

  • portal_user table: to store the users of a portal:

     CREATE TABLE IF NOT EXISTS public.portal_user
     (
         id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
         first_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
         middle_name character varying(50) COLLATE pg_catalog."default",
         surname character varying(50) COLLATE pg_catalog."default" NOT NULL,
         sex "char" NOT NULL,
         birthdate date NOT NULL,
         tex_code character varying(50) COLLATE pg_catalog."default" NOT NULL,
         e_mail character varying(50) COLLATE pg_catalog."default" NOT NULL,
         contact_number character varying(50) COLLATE pg_catalog."default" NOT NULL,
         created_at date NOT NULL,
         CONSTRAINT user_pkey PRIMARY KEY (id)
     )
    
  • user_type table: defines the possible user types (as ADMIN, SIMPLE_USER, etc):

     CREATE TABLE IF NOT EXISTS public.user_type
     (
         id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
         type character varying(50)[] COLLATE pg_catalog."default" NOT NULL,
         description text COLLATE pg_catalog."default",
         CONSTRAINT user_type_pkey PRIMARY KEY (id)
     )
    
  • portal_user_user_type table: it is the Many To Many association table that link together the previous two tables: this because a specific user can have multiple user types and a specific user type can be associated to multiple users:

     CREATE TABLE IF NOT EXISTS public.portal_user_user_type
     (
         id bigint NOT NULL,
         portal_user_id_fk bigint NOT NULL,
         user_type_id_fk bigint NOT NULL,
         CONSTRAINT portal_user_user_type_pkey PRIMARY KEY (id),
         CONSTRAINT portal_user_user_type_to_portal_user FOREIGN KEY (portal_user_id_fk)
             REFERENCES public.portal_user (id) MATCH SIMPLE
             ON UPDATE NO ACTION
             ON DELETE NO ACTION,
         CONSTRAINT portal_user_user_type_to_user_type FOREIGN KEY (user_type_id_fk)
             REFERENCES public.user_type (id) MATCH SIMPLE
             ON UPDATE NO ACTION
             ON DELETE NO ACTION
     )
    

Ok, then I have mapped the previous 3 classes with the following entity classes:

The User class map the portal_user table:

@Entity
@Table(name = "portal_user")
@Data
public class User implements Serializable {
     
    private static final long serialVersionUID = 5062673109048808267L;
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @Column(name = "first_name")
    private String firstName;
    
    @Column(name = "middle_name")
    private String middleName;
    
    @Column(name = "surname")
    private String surname;
    
    @Column(name = "sex")
    private char sex;
    
    @Column(name = "birthdate")
    private Date birthdate;
    
    @Column(name = "tex_code")
    private String taxCode;
    
    @Column(name = "e_mail")
    private String eMail;
    
    @Column(name = "contact_number")
    private String contactNumber;
    
    @Temporal(TemporalType.DATE)
    @Column(name = "created_at")
    private Date createdAt;
    
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
    @JsonManagedReference
    private Set<Address> addressesList = new HashSet<>();
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    @JsonManagedReference
    private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();

    public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
            String eMail, String contactNumber, Date createdAt) {
        super();
        this.firstName = firstName;
        this.middleName = middleName;
        this.surname = surname;
        this.sex = sex;
        this.birthdate = birthdate;
        this.taxCode = taxCode;
        this.eMail = eMail;
        this.contactNumber = contactNumber;
        this.createdAt = createdAt;
    }
        
}

NOTE-1: this @OneToMany relationship is related to another use cases and worked fine (so it is not part of this problem):

@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
@JsonManagedReference
private Set<Address> addressesList = new HashSet<>();

NOTE-2: The User class contains this @OneToMany relationship that implement one side of my Many To Many relationship causing the problem:

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
@JsonManagedReference
private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();

The User_UserType class is the entity class mapping my Many To Many relationship.

Then I have the UserType entity classes mapping the previous user_type DB table:

@Entity
@Table(name = "user_type")
@Data
public class UserType implements Serializable {
    
    private static final long serialVersionUID = 6904959949570501298L;

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "description")
    private String description;
    
    @OneToMany(mappedBy = "userType")
    @JsonManagedReference
    private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();
    

    public UserType() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public UserType(String name, String description) {
        super();
        this.name = name;
        this.description = description;
    }


}

NOTE: this class contains this @OneToMany relationship representing the other side of the Many To Many relationship that I am trying to implement.

@OneToMany(mappedBy = "userType")
@JsonManagedReference
private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();

Then I have implemented the User_UserType entity class mapping the portal_user_user_type (my Many To Many association table):

@Entity
@Table(name = "portal_user_user_type")
@Data
public class User_UserType implements Serializable {

    private static final long serialVersionUID = -1334879762781878984L;
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @ManyToOne()
    @JoinColumn(name = "portal_user_id_fk")
    @JsonBackReference
    private User user;
    
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "user_type_id_fk")
    @JsonBackReference
    private UserType userType;

    public User_UserType() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public User_UserType(User user, UserType userType) {
        super();
        this.user = user;
        this.userType = userType;
    }

}

NOTE: this class contains the two @ManyToOne() relationship pointing to the related User and UserType instances.

Finnally I tried to test it by a JUnit test method, this one:

@SpringBootTest()
@ContextConfiguration(classes = GetUserWsApplication.class)
@TestMethodOrder(OrderAnnotation.class)
public class UserRepositoryTest {
    
    @Autowired
    private UsersRepository userRepository;
    
    
    @Test
    @Order(1)
    public void testInsertUser() {
        User user = new User("Mario", null, "Rossi", 'M', new Date(), "XXX", "[email protected]", "329123456", new Date());
        
        Set<Address> addressesList = new HashSet<>();
        addressesList.add(new Address("Italy", "RM", "00100", "Via XXX 123", "near YYY", user));
        
        user.setAddressesList(addressesList);
        
        Set<UserType> userTypesList = new HashSet<>();
        UserType userType1 = new UserType("ADMIN", "Admin user type !!!");
        UserType userType2 = new UserType("USER", "Just a simple user...");
        
        userTypesList.add(userType1);
        userTypesList.add(userType2);
        
        User_UserType user_UserType1 = new User_UserType(user, userType1);
        User_UserType user_UserType2 = new User_UserType(user, userType2);
        
        Set<User_UserType> user_UserType_List = new HashSet<>();
        user_UserType_List.add(user_UserType1);
        user_UserType_List.add(user_UserType2);
        
        user.setUserToUserTypeAssociation(user_UserType_List);
        
        userRepository.save(user);
        assertTrue(true);
        
    }
    
}

Running this method Spring Boot start up without error messages. Running it in debug mode it arrives to the save() method execution where I am trying to save my User instance (expecting that also the defined relationship are saved: so the two created user types and the relation into my association table).

Here something strange happens. The save method seems to give me an exception (I can see it in debug mode) but I obtain no error message in my stacktrace. Following a printscreen of what I am obtaining:

enter image description here

As you can see it enter into the highlighter exception but running the application to the end in the stacktrace there are no error lines related to this exception. This is my entire stacktrace from the Spring Boot startup to the end:

 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.6)

2021-11-06 19:09:01.145  INFO 22688 --- [           main] c.e.u.t.R.UserRepositoryTest             : Starting UserRepositoryTest using Java 16.0.1 on ubuntu with PID 22688 (started by andrea in /home/andrea/git/get-user-ws)
2021-11-06 19:09:01.148  INFO 22688 --- [           main] c.e.u.t.R.UserRepositoryTest             : No active profile set, falling back to default profiles: default
2021-11-06 19:09:01.896  INFO 22688 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-11-06 19:09:01.957  INFO 22688 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 53 ms. Found 2 JPA repository interfaces.
2021-11-06 19:09:02.805  INFO 22688 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-11-06 19:09:02.871  INFO 22688 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.32.Final
2021-11-06 19:09:03.071  INFO 22688 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-11-06 19:09:03.211  INFO 22688 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-11-06 19:09:03.456  INFO 22688 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-11-06 19:09:03.487  INFO 22688 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
2021-11-06 19:09:04.193  INFO 22688 --- [           main] org.hibernate.tuple.PojoInstantiator     : HHH000182: No default (no-argument) constructor for class: com.easydefi.users.entity.User (class must be instantiated by Interceptor)
2021-11-06 19:09:04.323  INFO 22688 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-11-06 19:09:04.330  INFO 22688 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-11-06 19:09:04.553  WARN 22688 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-11-06 19:09:05.626  INFO 22688 --- [           main] c.e.u.t.R.UserRepositoryTest             : Started UserRepositoryTest in 4.96 seconds (JVM running for 6.304)
2021-11-06 19:11:09.255  INFO 22688 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-11-06 19:11:09.257  INFO 22688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-11-06 19:11:09.264  INFO 22688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

So what is wrong with my code? What am I missing? How can i try to fix it? And why am I not obtanining errors line in the stacktrace if this exception was thrown?

CodePudding user response:

The id in portal_user_user_type should be autogenerated but is id bigint NOT NULL?

But portal_user_user_type only holds a reference to the User and the UserType. So you could easily simplify your model.

Something like:

class User {
    
    @ManyToMany(cascade = { CascadeType.ALL })
    @JoinTable(
        name = "portal_user_user_type", 
        joinColumns = { @JoinColumn(name = "portal_user_id_fk") }, 
        inverseJoinColumns = { @JoinColumn(name = "user_type_id_fk") }
    )
    Set<UserType> userTypes;
    
  • Related