I have one to many relationships between person class and car class. A person can own many cars and vice versa. I am using restful API to post data. My annotations and Get service is working fine but my post service throws " java.sql.SQLIntegrityConstraintViolationException: ORA-01400: cannot insert NULL" error whenever I try to insert new data. Child table foreign key is being inserted as null.
Here is part of my code.
Person.java
private List<Car> cars = new ArrayList<Car>();
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="person")
#JsonManagedReference
public List<Car> getCars() {
return cars;
}
Car.java
private Person person;
#ManyToOne
#JoinColumn(name = "PERSON_ID", nullable = false, updatable = true, insertable = true)
#JsonBackReference
public Person getPerson() {
return person;
}
My service class:
#POST
#Path("/PersonRegistration")
#Consumes(MediaType.APPLICATION_JSON)
public Response postPersonCars(Person person) throws Exception{
Session session = null;
ObjectMapper mapper = new ObjectMapper();
//Person per = new Person();
//Car cars = new Car();
try{
session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
//per.setCars(person.getCars());
session.save(person);
session.getTransaction().commit();
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
if(null != session){
session.close();
}
}
return Response.status(201).entity(mapper.writeValueAsString(person)).build();
}
This annotation:
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="person")
has two consequences:
mappedBy implies that Car is the owning side of the relationship. This means that whenever you want to establish a relationship between Car and Person, you need to do it by setting the Car.person property to the appropriate value. Changes to Person.cars will be ignored by Hibernate.
cascade=CascadeType.ALL means that whenever you save a Person, Hibernate will also invoke the save operation on all entities contained in Person.cars
Result: you are calling Session.save() on a bunch of Car entities that do not have the Car.person property set properly.
Solution: either change the owning side of the relationship (be aware that you will also need a #JoinColumn on Person.cars if you do not want an extra database table to be created) or loop through Person.cars and set the Car.person property properly in each of them.
cascade=CascadeType.ALL suggests the first solution fits your use case better.
Related
I was testing out how cascade works, and ran into some stuff that confuses me. I have two simple entities:
#Entity
public class Child {
#Id
Long id;
#OneToOne()
#JoinColumn(name = "JOINCOLMN", referencedColumnName = "ID")
Person person;
}
#Entity
public class Person {
#Id
Long id;
#OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
Child child;
}
I was testing out the cascade type persist. So i wrote this piece of code:
Person person = new Person();
person.setId(100L);
person.setName("SomeName");
Child child = new Child();
child.setId(60L);
child.setPerson(person);
personRepository.save(person);
However, even though both of them were suppose to get persisted, only the person gets persist. So the first question is :
Why is this not working?
I searched around a bit, and found people using the Hibernate entity manager. Here is an example where the persist cascade type works:
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author a = new Author();
a.setFirstName(“John”);
a.setLastName(“Doe”);
Book b1 = new Book();
b1.setTitle(“John’s first book”);
a.getBooks().add(b1);
Book b2 = new Book();
b2.setTitle(“John’s second book”);
a.getBooks().add(b2);
em.persist(a);
em.getTransaction().commit();
em.close();
My second question is:
I can see that entity manager is used to manage the transaction, and manage the entities. But i never use it, so what happens in my piece of the code? Who manages the transaction? Who persist the entity?
Spring JPA can help you manage your transaction by using repositories, or the #Transactional annotation. It basically wraps any method so the core of the method is performed in a transaction.
In your case, the call personRepository.save(person) opens a transaction and commits the changes to the database.
Regarding your first question, the problem comes from your #OneToOne relationship and its setters implementation. Calling child.setPerson(person); does not set the person's child. Therefore, when calling personRepository.save(person) , as the person's child is null, there is no Child object to persist.
You want to make sure to keep the object state coherent:
Person person = new Person();
person.setId(100L);
person.setName("SomeName");
Child child = new Child();
child.setId(60L);
child.setPerson(person);
person.setChild(child);
personRepository.save(person);
I am using Spring Data and #Transactional annotation(for automatic rollback after tests).
I have simple bidirectional relation between account and user(owning side):
#Entity
#Table(name = "ACCOUNT_T")
public class AccountEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
private String verificationCode;
private Boolean active = false;
#OneToOne(mappedBy = "account", fetch = FetchType.EAGER,
cascade = {CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.DETACH, CascadeType.REFRESH})
private UserEntity user;
}
#Entity
#Table(name = "USER_T")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String phone;
private LocalDate birthDate;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name = "account_id")
private AccountEntity account;
}
I am using JpaRepositories and fetching is set to eager.
Why sometimes when I get objects from database I can't get their child
objects-null is returned. It depends on from which side I add objects.
I have written simple test using Junit5:
#ExtendWith(SpringExtension.class)
#SpringBootTest
#Transactional
class UserAndAccountRepositoriesTest {
void testA() {
UserEntity userEntity = new UserEntity();
setUserProperties(userEntity);
AccountEntity accountEntity = new AccountEntity();
setAccountProperties(accountEntity); //just setting values for fields
accountEntity.setUser(userEntity);
accountRepository.save(accountEntity);
accountRepository.findAll().get(0).getUser(); //returns user
userRepository.findAll().get(0).getAccount(); //returns null,but should return account related to that user
}
void testB() {
UserEntity userEntity = new UserEntity();
setUserProperties(userEntity);
AccountEntity accountEntity = new AccountEntity();
setAccountProperties(accountEntity);
accountEntity.setUser(userEntity);
accountRepository.save(accountEntity);
accountRepository.findAll().get(0).getUser(); //again returns null,but shouldn't
userRepository.findAll().get(0).getAccount(); //returns account
}
}
Without #Transactional everything works fine - I am not getting null.
What am I doing wrong?
You'd need to set both sides of a relationship for explicitly defining it.
Try adding userEntity.setAccount(accountEntity) during your setup case, this would resolve the issue.
Hibernate won't help you and assume just because you set a -> b, it would set b <- a for you within the other entity.
The reason why it might work without #Transactional is that, without the annotation you are committing your setup data into whatever datasource you are using, and nothing is rollbacked at the end, and since you are selecting data without any id with findAll, you are getting previous user/account entites that have already been committed, some with relationship & some without, thus the random error you are getting.
It is because you are not setting account in userEntity. Please try like following:
userEntity.setAccount(accountEntity);
I will explain why the behavior is different depending on whether your are in a transaction or not :
When you are in a transaction :
a) Any get to fetch an entity A you have created prior to this transaction (so which is already in DB) will return a new object in term of memory adress, and hibernate will resolve its bidirectional relationship, even if you did not set it explicitly.
b) Any get to fetch an entity B you have created earlier in this transaction (so which is not yet in DB) will return the same object in term of memory adress, so it really is the same object, thus if you did not set its bidirectional relationship explicitly, it will not exist until you set it or until the transaction is over (as it will effectively persist B in DB) and you fetch B again.
When you are not in a transaction :
Any get to fetch any entity will behave like described in case a).
Conclusion :
The author was in case b).
I have one to many relationships between person class and car class. A person can own many cars and vice versa. I am using restful API to post data. My annotations and Get service is working fine but my post service throws " java.sql.SQLIntegrityConstraintViolationException: ORA-01400: cannot insert NULL" error whenever I try to insert new data. Child table foreign key is being inserted as null.
Here is part of my code.
Person.java
private List<Car> cars = new ArrayList<Car>();
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="person")
#JsonManagedReference
public List<Car> getCars() {
return cars;
}
Car.java
private Person person;
#ManyToOne
#JoinColumn(name = "PERSON_ID", nullable = false, updatable = true, insertable = true)
#JsonBackReference
public Person getPerson() {
return person;
}
My service class:
#POST
#Path("/PersonRegistration")
#Consumes(MediaType.APPLICATION_JSON)
public Response postPersonCars(Person person) throws Exception{
Session session = null;
ObjectMapper mapper = new ObjectMapper();
//Person per = new Person();
//Car cars = new Car();
try{
session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
//per.setCars(person.getCars());
session.save(person);
session.getTransaction().commit();
}catch(Exception e){
e.printStackTrace();
throw e;
}finally{
if(null != session){
session.close();
}
}
return Response.status(201).entity(mapper.writeValueAsString(person)).build();
}
This annotation:
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="person")
has two consequences:
mappedBy implies that Car is the owning side of the relationship. This means that whenever you want to establish a relationship between Car and Person, you need to do it by setting the Car.person property to the appropriate value. Changes to Person.cars will be ignored by Hibernate.
cascade=CascadeType.ALL means that whenever you save a Person, Hibernate will also invoke the save operation on all entities contained in Person.cars
Result: you are calling Session.save() on a bunch of Car entities that do not have the Car.person property set properly.
Solution: either change the owning side of the relationship (be aware that you will also need a #JoinColumn on Person.cars if you do not want an extra database table to be created) or loop through Person.cars and set the Car.person property properly in each of them.
cascade=CascadeType.ALL suggests the first solution fits your use case better.
I'm having a problem deleting a child entity item. Everytime I delete it nothing happens and the association between the parent and the child is still there. I've searched through the net and some people suggest using orphanremoval but I've tried it and it didn't work. Appreciate if any could advise.
My codes as below:
ClientProfile Entity (PARENT)
Collapse | Copy Code
#Entity (name="ClientProfile")
public class ClientProfile implements Serializable {
#OneToMany(orphanRemoval = true)
private List<Address> address;
#OneToMany(orphanRemoval = true)
private List<ClientJob> clientJob;
#OneToMany(orphanRemoval = true)
private List<Asset> clientAsset;
...
}
Asset Entity (CHILD)
is a uni-directional relationship so asset entity doesnt contain any #ManyToOne
In my SQL Database table my relationship is CLIENTPROFILE_CLIENTASSET
adn they are connected by the clientid to the assetid
In my session bean this is my remove method:
#Override
public void removeAsset(Long assetId) throws DoesNotExistsException{
Query query = em.createQuery("SELECT as FROM Asset as WHERE as.assetId = :assetid");
query.setParameter("assetid", assetId);
if (query.getResultList().isEmpty()){
throw new DoesNotExistsException("Asset does not exist!");
} else {
em.remove(query.getSingleResult());
}
}
the assetid is being parsed into from the managedbean.
I'm not sure if the remove method is wrong because this is the method I used to remove other entities items without relationship.
This should help you. It is along the same lines. I usually utilize Hibernate instead of JPA to get automatic deletion through cascade attributes.
JPA OneToMany not deleting child
I have a m:n relation beetwen objects (Meeting, Person) as many persons can be participant of many meetings.
I've set it like this
Meeting
#ManyToMany(mappedBy = "meetings")
protected Set<Person> participants = new HashSet<Person>();
Person
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "person_meeting",
joinColumns = {#JoinColumn(name = "person_id")},
inverseJoinColumns = {#JoinColumn(name = "meeting_id")}
)
protected Set<Meeting> meetings = new HashSet<Meeting>();
Id DB hibernate created me table meeting_participants with two fields: meeting_id, person_id. Cool, just as I wanted.
Now problematic case. I've create Meeting object and I saved it to DB. Than I create set of users, I add it to meeting
this.saveMeeting.setParticipants(set);
Hibernate displays:
Hibernate: update Meeting set duration=?, meetingDate=?, room=? where meeting_id=?
Nothing added to association. What do I need to change ?
// EDIT
I've changed in Meeting definition of the field
#ManyToMany(mappedBy = "meetings", cascade = {CascadeType.ALL})
protected Set<Person> participants = new HashSet<Person>();
Now I get error
org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
It is in this method
public static Long add(Meeting meeting) {
SessionFactory sf = null;
Session session = null;
sf = HibernateUtil.getSessionFactory();
session = sf.openSession();
session.beginTransaction();
try{
session.save(meeting);
session.getTransaction().commit();
session.flush();
} catch(HibernateException e){
session.getTransaction().rollback();
e.printStackTrace();
return new Long(-1);
}
session.close();
return meeting.getId();
}
The line that is causing the problem is:
session.save(meeting);
EDIT
Ok I've closed session properly. Everything works find BUT only when I'm creating new objects. When I want to update association it doesn not work. So the question is. How to update association ??
This question is asked every two days. You just initialized one side of the association, and you chose the one that Hibernate ignores. A bidirectional association has an owner side, and an inverse side. Hibernate only considers the owner side. And the owner side is the one which doesn't have the mappedBy attribute.
So you need to initialize the other side of the association:
for (Participant p : set) {
p.getMeetings().add(saveMeeting);
}
I've solved the problem. I didn't think about that so I didn not wrote it. Person was a top class and it was extended by two others. I've changed update action so that it took an instance of Object class. That way I'm able to update association