Issue
I am working on a simple application built using Spring MVC
, JSP
, Hibernate
, and MySQL
.
I have two entities: User
, and UserDetail
.
User
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "name")
@NotNull(message = "required field")
@Size(min = 3, message = "should contain at-least 3 characters")
private String name;
@Column(name = "salary")
@NotNull(message = "required field")
@Min(value = 0, message = "should be a valid positive number")
private int salary;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
@Valid
private List<UserDetail> addressList;
...
}
UserDetail
@Entity
@Table(name = "user_detail")
public class UserDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "address")
@NotNull(message = "required field")
@Size(min = 5, message = "should contain at-least 5 characters")
private String address;
@ManyToOne(cascade = {
CascadeType.DETACH, CascadeType.MERGE,
CascadeType.PERSIST, CascadeType.REFRESH}
)
@JoinColumn(name = "user_id")
private User user;
...
}
There is a one-to-many
relation between User
and UserDetail
entities - a single user can have multiple addresses.
There is a form on the frontend which contains following fields:
- name
- salary
- address (could be multiple addresses, user can add more input fields using a button)
Upon submission of a form from the frontend, i validate the form data and once it is determined that form data is valid, i call a method that inserts the data in the MySQL
database.
Problem
When the form is submitted with only one address, data is saved in the database correctly.
Problem is when the form is submitted with more than one address. For example, if the form is submitted with two addresses, data is inserted in the user
table correctly BUT in the user_detail
table, instead of inserting two records, 3 records get inserted.
Following is the screenshot of the user_detail
table with incorrect data:
In the above screenshot, user with the id of 1 has two addresses but there are three rows inserted and the middle one is not associated with the user, i.e user_id
is null
.
Following image shows the sql statements logged by the hibernate on the console:
Following screenshot shows the data inserted in the user_detail
table if there are 3 addresses in the submitted form data:
"test address 2" and "test address 3" are duplicated.
Following image shows the sql statements logged by the hibernate on the console:
Following method is used to insert the data:
public static void saveUserData(User user) {
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(User.class)
.addAnnotatedClass(UserDetail.class)
.buildSessionFactory();
Session session = factory.getCurrentSession();
try {
session.beginTransaction();
session.save(user);
UserDetail userDetail;
for (int i = 0; i < user.getAddressList().size(); i++) {
userDetail = user.getAddressList().get(i);
userDetail.setUser(user);
session.save(userDetail);
// hibernate batch insert
// 10 --> hibernate's batch size
if (i % 10 == 0) {
session.flush();
session.clear();
}
}
session.getTransaction().commit();
} catch (HibernateException | ConstraintViolationException e) {
if (session.getTransaction() != null) {
session.getTransaction().rollback();
}
System.out.println(e.getLocalizedMessage());
}
finally {
session.close();
factory.close();
}
}
Question
Why is hibernate inserting an extra record in the user_detail
table? How can i fix this issue?
Versions:
Spring (5.3.4)
Hibernate (5.4.29.Final)
Solution
Do you really need the batch insert there? It doesn't work at all regarding logs! You have to add an additional parameter to a database URL and enable it for Hibernate.
Without batch insert
@Entity
@Table(name = "USERS")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<UserDetail> userDetails = new ArrayList<>();
@Transient
public void addUserDetail(UserDetail detail) {
detail.setUser(this);
userDetails.add(detail);
}
}
@Entity
@Table(name = "USER_DETAILS")
public class UserDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "USER_ID")
private User user;
}
for each userDetail
user.add(userDetail)
save(user);
So just save a user.
- Use plurals for table names
- Use uppercase for table names
- Use
Long
for id (so it can be null) - Use
orphanRemoval
- Don't use cascade at the
@ManyToOne
- Use
fetch = FetchType.LAZY
everywhere
With batch insert
Check that User
doesn't have details at all
Save user
Set user for each detail
And save details with batch
Answered By - v.ladynev
Answer Checked By - Terry (JavaFixing Volunteer)