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:
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;