why a manyToMany join table does not insert - spring-data-jpa

I use all the default table name and column names, so I didn't add a jointable (actually I tried with jointable). Bidirectional, only one of the two Entities has a mappedBy.
#Entity
public class User extends AbstractPersistable<Long> {
#ManyToMany
private Set<House> houses;
#Entity
public class House extends AbstractPersistable<Long> {
#ManyToMany(mappedBy = "houses")
Set<User> users;
Should be simple enough, except that the hibernate does not insert jointable after running test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class DemoSmApplicationTests {
#Test
public void contextLoads() throws IOException {
House p =new House();
Set us = new HashSet<>();
User u = userRepository.findOne(1L);
us.add(u);
p.setUsers(us);
houseRepository.save(p);
}
The console only has one "insert":
Hibernate: select user0_.id as id1_5_0_, user0_.name as name2_5_0_ from user user0_ where user0_.id=?
Hibernate: insert into house (random) values (?)

When we are using bidirectional associations we have to provide 'helper' methods to preserve synchronicity between both sides, for example:
public House addUsers(User... users) {
Stream.of(users).forEach(user -> {
user.getHouses().add(this);
this.users.add(user);
});
return this;
}
More other we have to set the cascade property of the #ManyToMany annotation at least to MERGE to tell Hibernate to update nested objects as well, when we update the 'parent' entity:
#ManyToMany(mappedBy = "houses", cascade = CascadeType.MERGE)
private final Set<User> users = new HashSet<>();
Then we can get the right result:
List<User> users = userRepo.save(Arrays.asList(
new User("user1"),
new User("user2"),
new User("user3")
));
List<House> houses = houseRepo.save(Arrays.asList(
new House("address1"),
new House("address2")
));
houses.get(0).addUsers(users.get(0), users.get(1));
houses.get(1).addUsers(users.get(1), users.get(2));
houseRepo.save(houses);

Related

Jpa not inserting the record in the join table for many to many relatioship

I have a Many-to-Many relationship with User and Role JPA entities. When I try to save the entities, both User and Role entities gets persisted in the table, but the junction table is not getting inserted with the records, Where am I going wrong
User Entity
#Entity
#Table(name="users")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "roles")
#ToString(exclude = "roles")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String password;
private double salary;
public User(String name, String password, double salary) {
super();
this.name = name;
this.password = password;
this.salary = salary;
}
#ManyToMany(
mappedBy = "users")
private Set<Role> roles = new HashSet<>();
public void addRole(Role role) {
this.roles.add(role);
role.getUsers().add(this);
}
}
Role Entity
#Entity
#Table(name = "roles")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "users")
#ToString(exclude = "users")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String roleName;
public Role(String roleName) {
super();
this.roleName = roleName;
}
#ManyToMany
#JoinTable(
name="user_roles",
joinColumns = #JoinColumn(name="role_id", nullable = false),
inverseJoinColumns = #JoinColumn(name="user_id", nullable = false)
)
private Set<User> users = new HashSet<>();
}
Client class
#EventListener(ApplicationReadyEvent.class)
public void onApplicationStartup(ApplicationReadyEvent event) {
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
Role userRole = new Role("ROLE_USER");
Role adminRole = new Role("ROLE_ADMIN");
kiran.addRole(userRole);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(kiran);
this.userRepository.save(vinay);
}
Where am I going wrong?
You've mapped a bidirectional relationship, but are only setting one side of it in your object model - the wrong side. Should there ever be a discrepancy, the owning side controls the values of foreign keys, and since you have left the owning side empty, they aren't being set. You are responsible to set both sides of relationships and keeping them in synch with what you want in the database.
Since you don't have cascade options set on the relationships, you are also responsible for persisting the roles independently from the Users. Something more like:
public void onApplicationStartup(ApplicationReadyEvent event) {
// you might want to check to see if these roles already exists and use those instead of creating new ones
Role userRole = roleRepository.save(new Role("ROLE_USER"));
Role adminRole = roleRepository.save(new Role("ROLE_ADMIN"));
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
kiran.addRole(userRole);//assumes this adds the user to the role.users as well.
this.userRepository.save(kiran);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(vinay);
}
Also, you are using Set in your entities with Lombok using "#EqualsAndHashCode" generation. Don't do that!
Set uses the equals/hashcode logic to determine if two objects are the same to filter out duplicates, while Lombok generates those methods to use what are mutable fields. In the case you have new entities in those sets (ie this usecase), the IDs are null and will change when set from JPA. You are better off keeping Java equals/hashcode logic if you don't know what effects those will have on your application. try using either List in your model and/or not having Lombok generate those method for you.

Cannot delete or update a parent row: a foreign key constraint fails Spring JPA

I have this query
DELETE
FROM bookings as b
WHERE b.check_out = CURRENT_DATE;
and I get
Cannot delete or update a parent row: a foreign key constraint fails (online_booking_app.booked_rooms, CONSTRAINT FK3x1lpikb2vk75nx41lxhdicvn FOREIGN KEY (booking_id) REFERENCES bookings (id))
My Booking entity has CascadeType.ALL and mapped by matches the other side - from my research these are some of the mistakes that could lead to this message.
Here is the BookingEntity:
#Entity
#Table(name = "bookings")
public class BookingEntity extends BaseEntity {
#OneToMany(mappedBy = "booking",cascade = CascadeType.ALL, orphanRemoval = true)
private List<BookedRoomsEntity> bookedRooms = new ArrayList<>();
private String firstName;
private String lastName;
public List<BookedRoomsEntity> getBookedRooms() {
return bookedRooms;
}
public BookingEntity setBookedRooms(List<BookedRoomsEntity> bookedRooms) {
this.bookedRooms = bookedRooms;
return this;
}
BookedRoomsEntity
#Entity
#Table(name = "booked_rooms")
public class BookedRoomsEntity extends BaseEntity {
#ManyToOne()
private BookingEntity booking;
public BookingEntity getBooking() {
return booking;
}
public BookedRoomsEntity setBooking(BookingEntity booking) {
this.booking = booking;
return this;
}
The CascadeType does only apply to EntityManager operations.
You therefore have two options:
Load the entities to be deleted first and then use EntityManager.remove
Remove the referencing entities first with a separate JPQL statement.

how to update Bidirectional #OneToMany relationship

I have Employee and Functions, Bank classes
Employee and Function have #OneToMany relationship and
Employee and Bank have also #OneToMany relationship.
if the user edits the form and change the function and/or bank
I want to update the relationship. but when I change the relationship
I get Duplicate entry exception due to the uniqueness of a column because
the Employee object persisted as a new entity
I tried to remove the employee from the function and set the employee's function to null and get a new function and add the employee to it
and set the new function but it doesn't work. any idea, please
#Entity
public class Employee extends GeneratedIdEntity<Long> {
#ManyToOne(optional = false)
private Functions function;
#ManyToOne(optional = false)
private Bank bank;
#OneToMany(
mappedBy = "employee",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<RubricValue> rubricsValues = new ArrayList<>();
#OneToMany(
mappedBy = "employee",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
List<EmployeeStatus> employeesStatus=new ArrayList<>();
}
#Entity
public class Functions extends GeneratedIdEntity<Long>{
#OneToMany(
mappedBy = "function",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<Employee> employees=new ArrayList<>();
public void addEmployee(Employee employee ){
employees.add(employee);
}
public void removeEmployee(Employee employee){
employees.remove(employee);
}
}
#Entity
public class Bank extends GeneratedIdEntity<Long> {
#OneToMany(
mappedBy = "bamk",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee employee ){
employees.add(employee);
}
public void removeEmployee(Employee employee){
employees.remove(employee);
}
}
#Stateless
public class EmployeeService extends BaseEntityService<Long, Employee> {
#Inject
FunctionService functionService;
#Inject
BankService bankService;
public void update(Employee employee, String newFunctionName, String newBankName) {
if (!employee.getBank().getName().equals(newBankName)) {
employee.getBank().removeEmployee(employee);
employee.setBank(null);
Bank newBank = bankService.getByName(newBankName);
newBank.addEmployee(employee);
employee.setBank(newBank);
}
if (!employee.getFunction().getName().equals(newFunctionName)) {
employee.getFunction().removeEmployee(employee);
employee.setFunction(null);
Functions newFunction = functionService.getByName(newFunctionName);
newFunction.addEmployee(employee);
employee.setFunction(newFunction);
}
}
}
the exception stack trace Caused by:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry
'dkfhks32' for key 'REGISTRATIONNUMBER' at
com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:115)
at
com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95)
at
com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:960)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1116)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1066)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1396)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1051)
at
com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:127)
at sun.reflect.GeneratedMethodAccessor54.invoke(Unknown Source) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498) at
com.sun.gjc.spi.jdbc40.ProfiledConnectionWrapper40$1.invoke(ProfiledConnectionWrapper40.java:437)
at com.sun.proxy.$Proxy268.executeUpdate(Unknown Source) at
org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:898)
You don't have to change the bank for an employee by removing the reference of existing bank first. You can simply go:
if (!employee.getBank().getName().equals(newBankName)) {
Bank newBank = bankService.getByName(newBankName);
//You must also do an entity validation/null check here. The newBank might not be present after all.
employee.setBank(newBank);
}
This will update the mappings correctly. Same goes for updating function of an employee

JPA - List of Foreign Keys instead of Entities in ManyToMany relationship

I want to import data from an existing database which contains a table of appointments and a join table for appointments and rooms.
TABLE Appointment {
id
...
}
TABLE Appointment_Room {
appointment_id,
room_id
}
I do not have access to the Room table.
For my application I have the following Appointment Entity:
#Entity
public class Appointment {
private int id;
...
private List<Integer> roomIdList;
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
...
#JoinColumn (
table = "Appointment_Room",
name = "appointment_id",
referencedColumnName = "id"
)
public List<Integer> getRoomIdList() {
return roomIdList;
}
public void setRoomIdList(List<Integer> roomIdList) {
this.roomIdList = roomIdList;
}
}
Since I only need the foreign key values of the rooms associated with an appointment, I want that an instance of Appointment contains a list of these foreign keys.
But right now I get the following error message:
org.hibernate.MappingException: Could not determine type for: java.util.List, at table: Appointment_Room, for columns: [org.hibernate.mapping.Column(roomIdList)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:314)
at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:292)
at org.hibernate.mapping.Property.isValid(Property.java:239)
...
I really don't understand what is causing the problem here, maybe somebody out there knows a solution?
Maybe the use of an ORM framework is not the right approach to this kind of scenario and probably there are other solutions but the problem seems to be so easy that I am curious if there is a possibility to map this ManyToOne relation onto lists of foreign keys.
The problem is that you forgot to annotate your getRoomIdList() method with #ElementCollection, and that JoinColumn is not the appropriate annotation to use to describe which table and columns must be used.
Here's an example showing hos to do.
#Entity
public class User {
[...]
public String getLastname() { ...}
#ElementCollection
#CollectionTable(name="Nicknames", joinColumns=#JoinColumn(name="user_id"))
#Column(name="nickname")
public Set<String> getNicknames() { ... }
}

efficient JPQL: update entities in #onetomany

i have two entities Customer and Order (trivial setters and getters excluded)
#Entity
public class Customer {
#GeneratedValue
#Id
private int id;
#OneToMany
List<Order> orderList;
}
#Entity
public class Order {
#GeneratedValue
#Id
private int id;
#ManyToOne
Customer customer;
private boolean paid;
public Order(Customer customer) {
this.customer = customer;
customer.getOrderList().add(this)
}
}
Now i want to set 'paid = true' for all the orders of a given customer
Below query seem to do the trick, but I get a feeling it is innefficient and the fact that i stored the reverse relationship in Customer.orderList hints that there should be some other way to do this.
UPDATE Order o SET o.paid = true WHERE EXISTS
(SELECT c.orderList FROM Customer c WHERE o MEMBER OF c.orderList AND c = :customer)
I'm using container managed transactions, glassfish and javaDb. But I'd prefer if improvements could be done in JPA/JPQL domain and not specific to container or db.
private id; ?? missed field type
Add to #OneToMany annotation,cascade = CascadeType.All
Customer entity = entityManager.find(Customer.class, id)
for (Order order : entity.getOrderList())
{
order.setPaid(true);
}
if you are using cantainer managed transaction then true will be saved to DB