Why am I getting a Unique index or primary key violation? - java

I am trying to add a friendship relationship between two persons using Spring MVC.
The first call goes well but the second one throws a Unique index or primary key violation?, why am i getting it?
#Entity
#Table(name="PERSON")
public class Person {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#ElementCollection
private List<Person> friends;
#ManyToMany(mappedBy="friends", fetch = FetchType.EAGER)
#ElementCollection
private List<Person> friendOf;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Person> getFriends() {
return friends;
}
public void setFriends(List<Person> friends) {
this.friends = friends;
}
public List<Person> getFriendOf() {
return friendOf;
}
public void setFriendOf(List<Person> friendOf) {
this.friendOf = friendOf;
}
public void addFriend(Person person){
if(this.getFriends() == null){
this.friends = new ArrayList<>();
}
this.friends.add(person);
}
public void setFriendship(Long firstPersonId, Long scndPersonId){
Person firstPerson = personService.getPerson(firstPersonId);
firstPerson.addFriend(personService.getPerson(scndPersonId));
personService.savePerson(firstPerson);
Person scndPerson = personService.getPerson(scndPersonId);
scndPerson.addFriend(personService.getPerson(firstPersonId));
personService.savePerson(scndPerson);
}
Person pup1 = new Person();
Long pupId1 = controller.savePerson(pup1);
Person pup2 = new Person();
long pupId2 = controller.savePerson(pup2);
setFriendship(pupId1, pupId2);
Person pup3 = new Person();
long pupId3 = controller.savePerson(pup3)
controller.setFriendship(pupId3, pupId1); //This throws an exception
controller.setFriendship(pupId3, pupId2);
Why is the marked line is causing an exception? The first call to setFrienship between p1 and p2 succeddes, but the when i try to make a connection between p1 and p3 it fails with the following exception :
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.UK_6VEJRSUXRONFC95I7O5XJNRMU_INDEX_D ON PUBLIC.PERSON_FRIENDS(FRIENDS_ID) VALUES 2

Please have a look at this answer,i hope you will get it right. The more you needed to do was to specific the join columns
Hibernate recursive many-to-many association with the same entity
There could also be a possibility that you trying to persist the same entity twice,in your case pub1 . Trying using merge instead!
Your setFriendship method calls the your service to save the entity which you have already done once,this is the reason that it gives you unique key violation as the entity is already there.
Leave comments if you need more explanation!

You need to specify join and inverse join columns directly. Please note that they are swapped on collections.
#ManyToMany
#JoinTable(name="friends",
joinColumns=#JoinColumn(name="personId"),
inverseJoinColumns=#JoinColumn(name="friendId")
)
private List<User> friends;
#ManyToMany
#JoinTable(name="friends",
joinColumns=#JoinColumn(name="friendId"),
inverseJoinColumns=#JoinColumn(name="personId")
)
private List<User> friendOf;

Related

How to load the collection of a LAZY association for an already found entity

Consider the following:
#Entity
#Table(name = "cars")
public class Car {
#Id
private int id;
private String name;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "ownerCar")
private Set<Wheel> wheels = new HashSet<>();
private Car() {
}
public Car(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public Set<Wheel> getWheels() {
return wheels;
}
}
#Entity
#Table(name = "wheels")
public class Wheel {
#Id
private int id;
#ManyToOne
private Car ownerCar;
private Wheel() {
}
public Wheel(int id) {
this.id = id;
}
public int getId() {
return id;
}
public Car getOwnerCar() {
return ownerCar;
}
public void setOwnerCar(Car ownerCar) {
this.ownerCar = ownerCar;
}
}
#Override //CommandLineRunner
public void run(String... args) throws Exception {
Car car = new Car(1);
car.setName("Ferrari");
Wheel wheel = new Wheel(1);
wheel.setOwnerCar(car);
car.getWheels().add(wheel);
carService.saveCar(car);
// Assume we have found the car already
car = carService.getById(1).get();
// Load the wheels of this car
carService.loadWheelsForCar(car);
System.out.println(car.getWheels().size());
}
The code above will throw org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: test.app.Car.wheels.
My question is how to implement loadWheelsForCar(Car c) method without having to find the car again.
With other words, how to SELECT * FROM WHEELS WHERE owner_car_id = car.id and add the result to the collection. I can probably do it manually, but is this the only way to go?
I am aware that LazyInitializationException is thrown when there is no active session(Doesn't #Transactional cause the creation of a new one?). I have tried to:
#Transactional
public void loadWheelsForCar(Car c) {
c.getWheels().size(); // will load wheels
}
but the exception is thrown.
In case of a XY problem, the reason I don't do this (CarService):
#Transactional
public Optional<Car> getByIdWithWheels(int carId) {
Optional<Car> possibleCar = carRepository.findById(carId);
possibleCar.ifPresent(c -> c.getWheels().size());
return possibleCar;
}
is because the parent entity (Car entity) in the main application has multiple #OneToMany associations and some them have nested ones as well. If i follow this approach I will end up with multiple #Transactional methods like getCarByIdWithWheels, getCarByIdWithSeats, getCarByIdWithSeatsAndWheels, etc. But what I want is to be able to do something like:
Car c = carService.getById(1);
carService.loadWheelsForCar(c);
carService.loadSeatsForCar(c);
I tried somethings found in web but every solution I found was "re-loading" the "Car" entity.
I am aware that LazyInitializationException is thrown when there is no active session(Doesn't #Transactional cause the creation of a new one?)
Hibernate does create a new session. But that session is fresh and it is not aware of your Car c as that car is not fetched, saved or updated in that new session because you fetched that car in a different session.
You know that your car you are passing is exactly same as the car in the database and there is no update in between. Unfortunately there is no API in hibernate to tell that here is a car exactly as it is in database, don't check it with it database and just believe you. There is no api like session.attach(car).
So the closest you can do is session.merge(car). Hibernate will issue a select to check if the car you are passing and the car in the database are same, and since it is same, it will not issue an update. As part of that select wheels would have been loaded too.
#Autowired
EntityManager em;
#Transactional
public Car loadWheelsForCar(Car car){
Car merged = em.merge(car);
merged.getWheels().size();
return merged;
}
You will see the above method does not issue any update but just one select query similar to below
select
car0_.id as id1_0_1_,
car0_.name as name2_0_1_,
wheels1_.owner_car_id as owner_ca3_1_3_,
wheels1_.id as id1_1_3_,
wheels1_.id as id1_1_0_,
wheels1_.name as name2_1_0_,
wheels1_.owner_car_id as owner_ca3_1_0_
from
cars car0_
left outer join
wheels wheels1_
on car0_.id=wheels1_.owner_car_id
where
car0_.id=?
You can see the repo that does the above here. https://github.com/kavi-kanap/stack-overflow-62843378

Deleting a Many to Many entry while keeping both objects in the database

I currently have a Many to Many relationship between Events and Users. The auto generated table in my DB called event_registrations keeps track of the relationship and which user goes to which event based on their ids.
What I want to do is have a controller method that takes in an Event id together with a list of user IDs so that the given users get removed from the given event.
Here are my model classes:
#Entity
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(mappedBy = "eventRegistrations")
private List<User> userList;
public Event() { this.userList = new ArrayList<>(); }
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<User> getUserList() {
return userList;
}
public void registerUser(User user){
this.userList.add(user);
}
public void removeUserRegistration(long userId){
this.userList.removeIf(user -> user.getId() == userId);
}
}
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
#ManyToMany
#JsonIgnore
#JoinTable(
name = "event_registrations",
joinColumns = #JoinColumn(name="user_id", referencedColumnName =
"id"),
inverseJoinColumns = #JoinColumn(name = "event_id",
referencedColumnName = "id"))
private List<Event> eventRegistrations;
public Integer getId() {
return id;
}
public List<Event> getEventRegistrations() {
return eventRegistrations;
}
public void setEventRegistrations(List<Event> eventRegistrations) {
this.eventRegistrations = eventRegistrations;
}
}
What I've tried so far in the EventController:
#DeleteMapping(value = "/{id}/registrations", consumes =
{"application/json"})
public ResponseEntity deleteEventRegistrations(#RequestBody ArrayList<Long>
data, #PathVariable("id") long id){
try {
Event event = eventService.getEventById(id);
data.forEach(userId -> event.removeUserRegistration(userId));
return ResponseEntity.ok().build();
} catch(DataNotFoundException ex){
return ResponseEntity.notFound().build();
}
}
This runs without problems but the the entries still exist in the join table afterwards. When debugging this, the users do get deleted from the Event object but the changes don't get persisted to the database.
Any help is appreciated!
#Entity
public class Event {
#ManyToMany(mappedBy = "eventRegistrations")
private List<User> userList;
}
The mappedBy here means User 's eventRegistrations list are used to maintain this many-to-many relationship , which means Hibernate will update the relationship table (event_registrations) based on the content of User 's eventRegistrations list. You have to do it the other way round which remove that events from an user 's event list :
public void removeUserRegistration(long userId){
//remove the event from the given user 's event list
for(User user : userList){
if(user.getId().equals(userId)){
user.getEventRegistrations().removeIf(event->event.getId().equals(this.id));
}
}
//remove given user from the event 's user list
//This will not have effects on DB record (as mentioned above) but suggest to also do it for keep the data in java model to be consistency.
this.userList.removeIf(user -> user.getId() == userId);
}
The above codes just for showing the ideas. You may have to polish it.
Depending on the JPA implementation, you may have to remove from the owning side. I've had problems after migrating from WebLogic + TopLink to Spring Boot + Hibernate where many-to-many links weren't updated correctly if I updated the non-owning side.
In your case, you have two sides: User is the owning side, and Event is the non-owning side (you can see this from the "mappedBy" on Event). That means that you should remove from User.eventRegistrations, not from Event.userList.
A quick attempt without any additional helper methods:
#DeleteMapping(value = "/{id}/registrations", consumes =
{"application/json"})
public ResponseEntity<?> deleteEventRegistrations(#RequestBody ArrayList<Long>
data, #PathVariable("id") long id){
try {
Collection<User> users = userService.findUsersByIds(data); // you need to create this
users.forEach(user -> user.getEventRegistrations().removeIf(event -> event.getId() == id));
return ResponseEntity.ok().build();
} catch(DataNotFoundException ex){
return ResponseEntity.notFound().build();
}
}

Foreign Key Violation on Delete and Update

I am trying to implement a tree referencing itself (same class) with CRUD operations using Java and Hibernate. My class is :
#Entity
#Table(name="Person")
public class Person implements Comparable<Person>{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne(cascade = {CascadeType.ALL})
private Person father;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private List<Person> children = new ArrayList<Person>();
}
Insertion works good, at each insertion I set person's father to person and add person to father's children. While deleting, if I delete the person, it complains that person id is referenced by father, if I delete father, it complains that father id is referenced by person. So, what is the correct procedure of deleting or updating? There are similar questions, but I can not find the exact explanation for this bidirectional referencing problem.
So, I have found a solution to the problem thanks to #Al1's mapped byannotation. Still, after that I could not retrieve objects due to LazyInitializationException , but was able to delete the Leafs in a tree.
I have fixed that issue by changing private List<Person> children= new ArrayList<Person>(); to private Collection<Person> children = new LinkedHashSet<Person>();
The class now looks like:
public class Person implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne(cascade = {CascadeType.ALL})
private Person father;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}, mappedBy= "father")
private Collection<Person> children = new LinkedHashSet<Person>();
}
In order to delete the tree node, I had to to load the children by Hibernate.initialize(this.getChildren()); and then recursively delete every node. My function for deletion:
public static String deletePerson(Person p){
Transaction trns = null;
Session session = HibernateUtil.buildSessionFactory().openSession();
try {
trns = session.beginTransaction();
Hibernate.initialize(p.getChildren());
if (p.hasChildren()){
Collection<Person> children = p.getChildren();
for (Person person : children) {
deletePerson(person);
}
String hql = "delete from Person where name = :name";
session.createQuery(hql).setString("name", p.getName()).executeUpdate();
session.getTransaction().commit();
return "success";
}
else {
String hql = "delete from Person where name = :name";
session.createQuery(hql).setString("name", p.getName()).executeUpdate();
session.getTransaction().commit();
return "success";
}
} catch (RuntimeException e) {
if (trns != null) {
trns.rollback();
}
e.printStackTrace();
} finally {
session.flush();
session.close();
}
return "failure";
}
Hope this helps somebody who works with Hibernate and trees:)

EJB/JPA: Foreign Key Constraint violation when trying to persist entity in many-many relationship

I have an EJB many-to-many (bi-directional) relation between classes (entity-classes) Person and Hobby. There are corresponding tables in the database, called PERSON and HOBBY, as well as a table PERSON_HOBBY for the many-to-many relationship.
As I will detail below, the problem is that whenever I try to persist a person with hobbies, I run into a Foreign Key constraint violation. This is because the entityManager tries to save new rows into PERSON_HOBBY that contain references to a person-entity with ID=0, which doesn’t exist in the PERSON table. I’ll come back to that later, but first I’ll show the relevant parts of the entity classes.
First, here is entity class Person:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String email;
#ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
/* Note: I used to have CascadeType.PERSIST in the above line as well, but
it caused "Detached object passed to persist" exceptions whenever I tried to
persist a person with hobbies. So I suppose I was right in deleting
CascadeType.PERSIST...? */
#JoinTable(name = "PERSON_HOBBY",
joinColumns = #JoinColumn(name="personId", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="hobbyId", referencedColumnName="id"))
private List<Hobby> hobbies = new ArrayList<Hobby>();
public List<Hobby> getHobbies () {
return hobbies;
}
public void setHobbies (List<Hobby> hobbies) {
this.hobbies = hobbies;
for(Hobby h:hobbies) { // this is to maintain bi-directionality
h.addPerson(this);
}
}
// other getters and setters omitted here.
Then entity class Hobby:
#Entity
public class Hobby {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String description;
#ManyToMany(mappedBy = "hobbies", fetch = FetchType.EAGER)
private List<Person> persons;
public Hobby() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// getter and setter for Description omitted here.
public List<Person> getPersons () {
return persons;
}
public void setPersons (List<Person> persons) {
this.persons = persons;
}
public void addPerson (Person p) {
this.persons.add(p);
}
}
I also have a stateless session bean, that’s shown here as far as relevant to the issue:
#Stateless
#Default
public class PersonRepositoryImpl implements PersonRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public Person create(Person p) {
entityManager.persist(p);
entityManager.flush();
return p;
}
#Override
public Person createPersonWithHobbies(Person p, List<Hobby>hobbyLijst) {
p = create(p); // I've also tried simply: create(p);
System.out.println("The newly assigned ID for the persisted
person is: " + p.getId());
// That last line **always** prints the person-ID as being 0 !!!!
p.setHobbies(hobbyLijst);
entityManager.merge(p); // This should save/persist the person's hobby's!
entityManager.flush();
return p;
}
}
Now from my servlet, I've been trying in two different ways. First, I tried calling method create(p) on the above session bean. That is, after creating a new Person instance p, setting all its non-relational fields, AND calling setHobbies on it (with a non-zero list of Hobby objects taken from the database), I called:
personRepo.create(p);
But this resulted in the Foreign Key (FK) exception:
INSERT on table 'PERSON_HOBBY' caused a violation of foreign key
constraint 'FK_EQAEPVYK583YDWLXC63YB3CXK' for key (0). The statement
has been rolled back.”
The FK-constraint mentioned here is the one in PERSON_HOBBY referring to PERSON.
The second way I tried was to make the following call from the servlet:
personRepo.createPersonWithHobbies(p, hobbyLijst);
where, just like before, p is the new person object; and hobbyLijst is that person's list of hobbies. And this resulted in the exact same FK-exception as the earlier call to personRepo.create(p).
Importantly, the println statement within method createPersonWithHobbies, calling getId() on the newly persisted person-object, ALWAYS gives that object's ID as being 0. Which I suppose does explain the FK-exception, since there's no person entity/row in the PERSON table with an ID of 0, nor is there supposed to be one. But of course the getId() call should not output 0. Instead, it should output the newly generated ID of the newly persisted person entity. (And yes, it IS persisted correctly in the PERSON tabel, with a correctly generated ID>0. So the correct ID is there in the PERSON-table - it just seems to be invisible to the entityManager and/or the container.)
Thanks.

Hibernate creates two rows instead of one

pals.
I have an issue with Hibernate's JPA implementation. I use spring-boot-starter-data-jpa and PostgreSql v9.
I have two entities with bidirectional connection via OneToMany & ManyToOne:
#Entity
public class ShoppingCart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "shoppingCart", cascade = {CascadeType.ALL})
private List<Good> goods = new ArrayList<>();
public void addGood(Good good) {
good.setShoppingCart(this);
goods.add(good);
}
public Good removeGood(Good good) {
goods.remove(good);
good.setShoppingCart(null);
return good;
}
public ShoppingCart() {
}
public List<Good> getGoods() {
return goods;
}
public ShoppingCart(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
And second entity is
#Entity
public class Good {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cart_id")
#JsonIgnore
private ShoppingCart shoppingCart;
public ShoppingCart getShoppingCart() {
return shoppingCart;
}
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
public Good(String name) {
this.name = name;
}
public Good() {
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
Also I use CrudRepository to access ShoppingCart
public interface ShoppingCartRepository extends CrudRepository<ShoppingCart, Long> {}
And when I'm trying to fill existing cart I have two goods in my database. This is a code to add some goods into existing cart:
ShoppingCart cart = shoppingCartRepository.findOne(id);
cart.addGood(new Good("Butter"));
return shoppingCartRepository.save(cart);
In table "good" I have now two elements with different PKey and same data
5;"Butter";100
6;"Butter";100
Why it happens?
Also, when I'm trying to insert breakpoint at repository.save line, I see only one good in goods list in cart.
So, the problem is solved.
First way to solve is to make method with save code #Transactional.
Secon way is to use getGoods() instead of goods. We should change this code
public void addGood(Good good) {
good.setShoppingCart(this);
goods.add(good);
}
public Good removeGood(Good good) {
goods.remove(good);
good.setShoppingCart(null);
return good;
}
to this
public void addGood(Good good) {
good.setShoppingCart(this);
this.getGoods().add(good);
}
public Good removeGood(Good good) {
this.getGoods().remove(good);
good.setShoppingCart(null);
return good;
}
getGoods() here forces hibernate to update state of object and everything works fine.
As for me, I use both ways together
It happens because you create a new Good object without id. So Hibernate will generate a new id and persist the new object. If you don't want to create a new object, but only assign an already existing one, you either have to fetch the existing one from the database and assign it to the ShoppingCart oder add the ID if you create the new Good object.

Categories