I have a entity relationship setup as #OneToOne. For example (User <--> Pet)
#Entity
public class Pet {
#OneToOne
#JoinColumn(name = "user_id", nullable = true)
User user;
}
#Entity
public class User {
#OneToOne(mappedBy = "user")
Pet pet;
}
To my surprise, Spring will allow you to assign multiple entities into the relationship (Multiple Pets associated with User) when you save via the repositories.
//for example:
pet.user = user1;
pet2.user = user1;
petRepository.save(pet);
petRepository.save(pet2); //allowed
But then after the save, the database will now be corrupted.
By calling petRepository.findAll or findById or anything really...will now yield this error
nested exception is org.hibernate.HibernateException: More than one row with the given identifier was found: 1245, for class: package.models.Pet
So what is the proper way to ensure OneToOne relationship so as to not corrupt the db?
Related
I am trying to update the fields of an entity that has a ManyToMany relationship, however, as I just want to update the table fields and ignore the ManyToMany relationship. The relationship is between the Company and UserSystem entities, it was defined in the relationship that company_user_system is the union table of the entities. The problem is that when executing my update in Company, always before my update, Hibernate makes an update in company and the relationship delete in user_system_company and this erases the relationship between Company and UserSystem and I don't understand why these two queries occur if I don't execut.
These are the queries, the first and second are not executed by my code:
Hibernate: update company set active=?, email=?, identification_code=?, trading_name=?, update_on=? where id=?
Hibernate: delete from company_user_system where company_id=?
Hibernate: update company set email=?, phone=?, corporate_name=?, trading_name=?, identification_code=?, email=?, phone2=? where id=?
Hibernate: select company0_.id as id1_0_, company0_.active as active2_0_, company0_.corporate_name as corporat3_0_, company0_.created_on as created_4_0_, company0_.email as email5_0_, company0_.email2 as email6_0_, company0_.identification_code as identifi7_0_, company0_.phone as phone8_0_, company0_.phone2 as phone9_0_, company0_.trading_name as trading10_0_, company0_.update_on as update_11_0_ from company company0_ where company0_.id=?
Following is the update implementation code:
public class CompanyRepositoryImpl implements CompanyRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public Company updateCompanyFields(Company company) {
// ... fieldSql implementation omitted
String sql = "UPDATE Company SET "+ fieldsSql +" WHERE id = :id ";
Query query = entityManager.createQuery(sql);
// set the values for the fields
for (Method method : getMethods) {
query.setParameter(lowercaseFirstCharInString(cutGetInMethods(method.getName())), method.invoke(company));
}
// set id
query.setParameter("id", company.getId());
// execute update and search the database to return the updated object
if (query.executeUpdate() == 1) {
query = entityManager.createQuery("SELECT c FROM Company c WHERE c.id = :id");
query.setParameter("id", company.getId());
Company getCompany = (Company) query.getResultList().get(0);
return getCompany;
}
return null;
}
// ... Other methods omitted
}
Repository Code:
#Repository
public interface CompanyRepository extends JpaRepository<Company, Long>, JpaSpecificationExecutor<Company> , CompanyRepositoryCustom {
#Modifying
Company updateCompanyFields(Company company);
}
Company entity code, I just added the attributes that I think may contain something useful to try to solve the problem:
#Entity
#DynamicUpdate
#Table(name = "company")
public class Company implements Serializable {
#CreationTimestamp
#Column(name = "created_on", nullable = false)
private Instant createdOn;
#UpdateTimestamp
#Column(name = "update_on")
private Instant updateOn;
#ManyToMany
#JoinTable(
name = "company_user_system",
joinColumns = #JoinColumn(
name = "company_id", referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "user_system_id", referencedColumnName = "id"
)
)
private Set<UserSystem> userSystems = new HashSet<>();
}
The UserSystem class defines the relationship as follows:
#ManyToMany(mappedBy = "userSystems")
private Set<Company> companies = new HashSet<>();
What may be causing this update and delete before my update?
This happens because you changed somewhere the value(s) of your relationship. EntityManager tracks such changes and marks the entity as dirty. When you execute a custom SQL query Hibernate will perform all the pending queries (submit any dirty entities).
You may prevent it by calling EntityManager.clear().
My User class looks like this :
#Data
#Entity
public class User {
#Id
Long userID;
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = new ArrayList<>();
}
And my ClassRoom class like this :
#Data
#Entity
public class ClassRoom {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long classRoomID;
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList<>();
}
And in my UserController class, I have :
#PostMapping("user/{id}/c")
User addClassRoom(#PathVariable Long id,#RequestBody ClassRoom newClassRoom)
{
logger.debug(repository.findById(id));
return repository.findById(id)
.map(user -> {
user.getClassRooms().add(newClassRoom);
user.setClassRooms(user.getClassRooms());
return repository.save(user);
})
.orElseGet(() -> {
return null;
});
}
And I POST and empty JSON ({}) and I see no change in my users. The Classroom or an empty Classroom doesn't get added in the User.
What is the problem here? How can I resolve this ?
user.getClassRooms().add(newClassRoom); is suffice, user.setClassRooms(user.getClassRooms()); not required.
You will have to perform cascade save operation.List all cascade types explicitly and don't use mappedBy, instead use joincolumns annotation.
Can you paste the logs, please? Is Hibernate doing any insert into your table? Has the database schema been created in the DB correctly? One thing I recommend you to do is to add a custom table name on the top of your User class, using annotations like so: #Table(name = "users"). In most SQL dialects user is a reserved keyword, hence it is recommended to always annotate User class a bit differently, so that Hibernate won't have any problems to create a table for that entity.
IMO you must find classRoom by its id from repository, if it's new, you must create a new entity and save it first. Then assign it to user and save it.
The object you receive from the post method was not created by the entity manager.
After using user.getClassRooms().add(newClassRoom);
We must use userRepository.save(user);
I am planning to store data from multiple tables which has one to many JPA relationship. I am creating my Repository interface which extends from JPARepository. My question is If I want to save a data on Many sides of relationship (in the below scenario it's Tour) then shall I do with TourRepository or PersonRespository?
On a similar note Is it ideal to create individual repository classes for every JPA entities where data need to be stored? or any better way with limited repository classes the data store to database can be achieved?
#Entity
#Table(name="Person")
public class Person implements Serializable{
...
...
#OneToMany(mappedBy = "person")
private List<Tour> tours;
...
#Entity
#Table(name = "Tour")
public class Tour implements Serializable{
...
...
#ManyToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
...
You have two independent entities. Person can exist without Tour and Tour can exist without Person. So you should have two repositories - for Person and Tour to store their data independently:
Tour tour1 = new Tour("tour1");
tourRepo.save(tour1);
Person person1 = new Person("person1");
person1.addTour(tour1);
personRepo.save(person1);
You chose the bidirectional one-to-many association so you have to use a 'helper' method like addTour to link both entities:
public Person addTour(Tour tour) {
tour.setPerson(this);
this.tours.add(tour);
return this;
}
Additional info: Best Practices for Many-To-One and One-To-Many Association Mappings
Add cascade to tours:
#OneToMany(mappedBy = "person", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Tour> tours;
When you save person object, his tours will be saved automatically.
By the way, in Person class, you should have an addTour(...) utilities method like this:
// Person.java
public void addTour(Tour tour){
this.tours.add(tour);
tour.setPerson(this);
}
I would suggest you to use CascadeType.ALL on #OneToMany mapping in Person entity:
#OneToMany(mappedBy = "person",cascade = {CascadeType.ALL})
private List<Tour> tours;
And then create repository for person to save person object with the list of tours .
CascadeType.ALL means persistence will propagate all EntityManager operations like PERSIST, REMOVE, REFRESH, MERGE, DETACH to the relating entities.
I have an application that have two domain model
Organization and TicketQuestion .
Authenticated User want to create ticket that have an organization property to solve that
each user permit to some organization like this:
User1 permit to Organization1
User2 permit to Organization2
TicketController.java have save method that create ticket.
I have this vulnerability: User1 can invoke method with ticket that have Organization2( that dose not have permission to it ).
I am using Hibernate filter for authorize data in GET methods but i dont know how can i protect data that user want persist and dose not have permission ??;
/ticket/save
{
id:-1,
organization:{
id:2,
title:'organization2' //not allowed this organization
}
}
#Entity
#Table(name = "core_organization_structure")
public class OrganizationStructure {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "title", nullable = false)
private String title;
}
#Entity
#Table(name = "core_Ticket")
public class Ticket {
..some prop
#ManyToOne
#JoinColumn(name = "org_id", nullable = false)
private OrganizationStructure org;
}
When the form is submitted, you need to load the authenticated user's permissions and check that they are authorized to perform the action they are attempting to perform.
If the user is attempting to create a ticket for an organization that they do not have permissions to; don't persist the record, and handle it appropriately. (Throw an exception, return a 401, etc...)
I've been scratching my head for a while and thought I'd get some help :)
I'm working with a legacy database which I cannot change. I have the following domain:
#Entity
public class Institution {
#Id
private Long id;
#OneToMany(mappedBy="institution", fetch=FetchType.EAGER)
private List<Subscription> subscriptions = new ArrayList<Subscription>();
}
#Entity
public class Subscription {
#Id
private Long id;
#ManyToOne
#JoinColumn(name="sub_id", referencedColumnName="ins_sub_id", insertable=false, updatable=false)
private Institution institution;
}
For the sake of brevity I have excluded showing the getters/setters. There is no join table.
So;
1) Is the mapping correct? I want a bidirectional association and I want the Institution to be the owner of the relationship.
2) If I load a Institution, create a new Subscription() and add the subscription to the subscriptions collection...
#RequestMapping(value="/add/{institutionId}", method=RequestMethod.POST)
public String submitSubscriptionForm(#ModelAttribute SubscriptionForm form) {
Institution institution = institutionService.getById(form.getInstitutionId());
Subscription subscription = new Subscription();
//...set properties on subscripton from data in the form
subscription.setInstitution(institution);
institution.getSubscriptions().add(subscription);
institutionService.saveOrUpdateInstitution(institution);
}
...when I save the Institution...
institutionService.saveOrUpdateInstitution(institution); just delegates to a DAO which extends HibernateDaoSupport.
...I get the following error:
org.springframework.orm.hibernate3.HibernateSystemException: Illegal attempt to associate a collection with two open sessions; nested exception is org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:679)
at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:411)
at org.springframework.orm.hibernate3.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:374)
at org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:737)
at com.f1000.dao.hibernate.InstitutionDaoImpl.saveOrUpdate(InstitutionDaoImpl.java:161)
I'm using the Spring and am making use of the OpenSessionInViewFilter and I can't figure out why a second session is created?
There is an issue in your association,
#ManyToOne
#JoinColumn(name="sub_id", referencedColumnName="ins_sub_id")
private Institution institution;
The is a insertable=false, updatable=false set on your institution inside the Subscription. Either you need to remove it or create a new property as below an set that, to the new Subscriptions.
private Long institutionId;
and replace subscription.setInstitution(institution); this by,
subscription.setInstitutionId(institution.getId());
Read here on more about the insertable=false, updatable=false mappings.