My problem is this: There is a many to many relationship between two tables - Project and Employee. There is an option to update a given employee, but there is a little problem. After updating the employee, hibernate automatically deletes the employee's record from the connected project_employee table.
Hibernate: update employee set email=?, first_name=?, last_name=? where employee_id=?
And this happens right after that
Hibernate: delete from project_employee where employee_id=?
I'm following a course and I've just noticed this error. Source code of the lecturer is here:
https://github.com/imtiazahmad007/spring-framework-course
I've checked your github page:
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST},
fetch = FetchType.LAZY)
#JoinTable(name="project_employee",
joinColumns=#JoinColumn(name="employee_id"),
inverseJoinColumns= #JoinColumn(name="project_id")
)
#JsonIgnore
private List<Project> projects;
CascadeType.MERGE + CascadeType.PERSIST mean, that if Employee entity is saved, Project entity references must be saved.
In may-to-many cases it means:
DELETE by foreign key
Bulk insert
In case there's no bulk insert, there's an issue with persisntence context (your are saving an entity with empty collection of projects).
Possible solutions:
Remove CascadeType.MERGE + CascadeType.PERSIST if you do not want to change projects every time your save Employee. You can still save the ccollection via Repository
Make sure collection is attached on save action. That will cause Delete+Insert, but the resut will be ok.
Change Many-To-Many to One-To-Many with EmbeddedId
Please, refer to documentation:
When an entity is removed from the #ManyToMany collection, Hibernate simply deletes the joining record in the link table. Unfortunately, this operation requires removing all entries associated with a given parent and recreating the ones that are listed in the current running persistent context.
https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#associations-many-to-many
*** Update from dialog below to make cascade clear.
Say, you have two entities A & B (getters and setters omitted). + repos
#Entity
#Table(name = "a")
public class A {
#Id
private Integer id;
private String name;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="a_b",
joinColumns=#JoinColumn(name="a_id"),
inverseJoinColumns= #JoinColumn(name="b_id")
)
private List<B> bs;
}
#Entity
#Table(name = "b")
public class B {
#Id
private Integer id;
private String name;
}
You sample test looks like this:
#Test
public void testSave() {
B b = new B();
b.setId(1);
b.setName("b");
b = bRepository.save(b);
A a = new A();
a.setId(1);
a.setName("a");
a.setBs(Collections.singletonList(b));
aRepository.save(a);
a.setName("new");
service.save(a); //watch sevice implementations below
}
Version1:
#Transactional
public void save(A a) {
aRepository.save(a);
}
Hibernate logs are the following:
Hibernate:
update
a
set
name=?
where
id=?
Hibernate:
delete
from
a_b
where
a_id=?
Hibernate:
insert
into
a_b
(a_id, b_id)
values
(?, ?)
delete+bulk insert present (despite the fact, that B-s where not in fact changed)
Version2:
#Transactional
public void save(A a) {
Optional<A> existing = aRepository.findById(a.getId());
if (existing.isPresent()) {
a.setBs(existing.get().getBs());
}
aRepository.save(a);
}
Logs:
update
a
set
name=?
where
id=?
Here b-collection was forcibly re-attached, so hibernate understands, that it's not needed to be cascaded.
Related
I am trying to delete an entity using its remove method of its repository from another service class, but it is not getting deleted. Below code works when I hard code the Id:
long id = 1234;
Optional<Employee> employeeOptional = employeeRepository.findById(id);
Employee employee = employeeOptional.get();
employeeRepository.delete(employee);
Above code is working fine, but if I try with below code, deletion is not happening.
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employeeRepository.delete(employee);
}
}
I am trying the code from DepartmentServiceImpl class, but above is not working, but same when id is hardcoded it works.
Inside Department I have relationship like below,
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "deal")
private Set<Employee> employees= new HashSet<>();
And inside Employee I have like below,
#ManyToOne
#JoinColumn(name = "department_id", referencedColumnName = "department_id")
private Department department;
How can I fix this issue?
You are attempting to delete Employees, but your entities still have references to each other.
A better way to delete an employee is to use orphan removal and remove the employee from the collection which will trigger a delete.
Also mappedBy = "deal" should be the name of the attribute on the owning side of the relationship so this should be mappedBy = "department"
#OneToMany(
cascade = CascadeType.ALL,
mappedBy = "department",
orphanRemoval = true
)
private Set<Employee> employees= new HashSet<>();
add a method to Department to remove the Employee and keep the bidirectional relationship in sync by also removing Department from Employee
public void removeEmployee(Employee employee) {
employees.removeEmployee(employee);
employee.setDepartment(null);
}
you can then remove the 'Managers' from your Employees collection which should trigger the delete statements
List<Employee> managers = department.getEmployees.stream()
.filter(e -> e.getRole().equals("Manager"))
.collect(Collectors.toList());
for (Employee manager : managers) {
department.removeEmployee(manager);
}
Not tested but should work fine:
Try tweaking your code a little like this:
Set<Employee>employees= new HashSet<>();
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employees.add(employee);
}
}
department.setEmployees(employees);//I assume you have a setter
departmentRepository.save(department); //I hope you understand what departmentRepository means
Here you are reassigning the valid employees list.
You could follow another method, instead of deleting each entity separately, you could call a bulk-save using saveAll(...) method on the valid list.
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().
Assume I have an Entity0
#Entity
public class Entity0 implements Serializable {
#Id
private Long id;
#ManyToOne
#NotNull
private Entity1 entity1;
public Entity0() {
}
public Entity0(Long id,
Entity1 entity1) {
this.id = id;
this.entity1 = entity1;
}
[getter and setter for id and entity1]
}
which references Entity1
#Entity
public class Entity1 implements Serializable {
#Id
private Long id;
#Basic
private String property0;
public Entity1() {
}
public Entity1(Long id,
String property0) {
this.id = id;
this.property0 = property0;
}
[getter and setter for id and property0]
}
The #NotNull annotation is useful to assert that Entity0.entity1 is set during persist and merge. However, it disallows to delete the reference to Entity1 (set it to null and merge the instance into the persistence context) which is necessary to delete instance of Entity1 which are referenced. I'm wondering whether there's any way to have a #ManyToOne #NotNull property in an entity at all. The following illustrates the problems which arise from the different approaches I took to make it possible to delete Entity1 instances from persistence:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("de.richtercloud_jpa-not-nulll-many-to-one-removal_jar_1.0-SNAPSHOTPU");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Entity1 entity1 = new Entity1(2l, "abc");
Entity0 entity0 = new Entity0(1l, entity1);
entityManager.getTransaction().begin();
entityManager.persist(entity1);
entityManager.persist(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entity1 = entityManager.merge(entity1);
entity0 = entityManager.merge(entity0);
//1: fails due to `ERROR 23503: DELETE on table 'ENTITY1' caused a violation of foreign key constraint 'FK5CQRG47R3H3KQG834IH36DUB' for key (2). The statement has been rolled back.`
//entityManager.remove(entity1);
//entityManager.flush();
//entityManager.merge(entity0);
//entityManager.flush();
//2:
entity0.setEntity1(null);
entityManager.remove(entity1);
entityManager.flush();
entityManager.merge(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManagerFactory.close();
The code contains so many flushs because I'm reproducing this for a JTA-environment where a flush can occur between the parts of a transaction.
I'm aware that I can drop the #NotNull annotation to work around the problem. My question is whether there's any solution to have both annotations and be able to delete. In case that's no possible, is it more common to set the reference temporarily to a bogus Entity1 instance or to give up on #NotNull?
Cascading might be a valid way, if not the way, however I've given up on it because I found it to be hiding issues and behaving in an unwanted fashion - not that it's not possible to master, it's just easier to handle for me personally.
If cascading is a way you could add the following to your entity1
#OneToMany(cascade=CascadeType.REMOVE)
private List<Entity0> entity0s;
This way the referenced entity0s will be deleted when entity1 is deleted.
Cascading can be dangerous and you should always think twice about it whether it is a good idea to always delete the referenced entities. Sometimes it is better to delete the references first and then remove the "parent" entity.
I'm not sure I see what the problem is, why do you think you have to set #NotNull on a #ManyToOne foreign key declaration? #NotNull is not a JPA annotation. If you want to make the database column set to not null you should use:
#ManyToOne
#JoinColumn(name = "entity1_id", nullable=false)
private Entity1 entity1;
So if you want to have nullable=false and delete them both then delete the child first then the parent.
tx.begin();
entity1 = em.find(Entity1.class, 1L);
entity0 = em.find(Entity0.class, 2L);
em.remove(entity0);
em.remove(entity1);
tx.commit();
I have an application that teaches the user how to play various card games. The data model that gets persisted consists of a TrainingSession with a uni-directional one-to-many relationship with the Hands.
[EDIT] To clarify, a Hand has no existence outside the context of a TrainingSession (i.e they are created/destroyed when the TrainingSession is). Following the principals of Data Driven Design, the TrainingSession is treated as an aggregate root and therefore a single spring-data CrudRepository is used (i.e., no repository is created for Hand)
When I try to save a TrainingSession using a CrudRepository, I get: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (blackjack.hand, CONSTRAINT FKrpuxac6b80xc7rc98vt1euc3n FOREIGN KEY (id) REFERENCES training_session (tsid))
My problem is the 'save(trainingSession)' operation via the CrudRepository instance. What I don't understand is why the error message states that FOREIGN KEY (id) REFERENCES training_session (tsid)). That seems to be the cause of the problem but I cant figure out why this is the case or how to fix it. The relationship is uni-directional and nothing in the Hand class refers to the TrainingSession.
The code, minus all the getters and setters, is:
#Entity
public class TrainingSession {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer tsid;
private String strategy;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="id")
private List<Hand> hands;
private int userId;
protected TrainingSession() {
}
public TrainingSession(int userId, Strategy strategy, List<Hand> hands) {
this.strategy = strategy.getClass().getSimpleName();
this.hands = hands;
this.userId = userId;
}
while Hand is
#Entity // This tells Hibernate to make a table out of this class
public class Hand {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private int p1;
private String p1s;
private int p2;
private String p2s;
private int d1;
private String d1s;
private int trials;
private int score;
public Hand() {
}
You need to save your TrainingSession and Hand objects first before saving the adding the hand objects to TrainingSession.
TrainingSession ts1 = new TrainingSession();
trainingSessionManager.save(ts1);
Hand hand1 = new Hand();
handManager.save(hand1);
Hand hand2 = new Hand();
handManager.save(hand2);
ts1.gethands().add(hand1);
ts1.gethands().add(hand2)
trainingSessionManager.save(ts1);
If you check your database you will find 3 tables TrainingSession, Hand and TrainingSession_Hand, The TrainingSession_Hand table references to both TrainingSession and Hand both. Therefore you need to save TrainingSession and hand before saving the relationship.
Found the problem. I was assuming that when spring-data set up the DB tables, it was able to figure out and set up the uni-directional 1-to-many relationship. Apparently that isn't the case. When I configure the relationship as bi-directional everything seems to work.
To fix things I:
removed from TrainingSession the #joincolumn annotation for hands
in Hands I added a TrainingSession field with a #ManyToOne annotation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "tsid", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private TrainingSession tsession;
I also added in the Hand class the getter/setter for tsession
I can now do a save of the entire aggregate construct using only a TrainingSessionRepository.
I'm developing a Spring Boot app with Spring data, JPA, Hibernate combination. Below is the scenario I'm struggling with where the expected behavior is to update only some child entities while the parent entity is being inserted as new.
Entity classes
#Entity
public class A {
#Id
private long id;
#ManyToOne
#JoinColumn (name = "B_ID")
#Cascade ( { CascadeType.ALL } )
private B b;
}
#Entity
#DynamicUpdate
public class B {
#Id
private long id;
#OneToMany (mappedBy = "b")
#Cascade ( { CascadeType.ALL } )
private Set<A> as;
#ManyToOne
#JoinColumn (name = "C_ID")
#Cascade ( { CascadeType.ALL } )
private C c;
}
#Entity
#DynamicUpdate
public class C {
#Id
private long id;
#OneToMany (mappedBy = "c")
#Cascade ( { CascadeType.ALL } )
private Set<B> bs;
#ManyToOne
#JoinColumn (name = "D_ID")
#Cascade ( { CascadeType.ALL } )
private D d;
#ManyToOne
#JoinColumn (name = "E_ID")
#Cascade ( { CascadeType.ALL } )
private E e;
}
#Entity
#DynamicUpdate
public class D {
#Id
private long id;
#OneToMany (mappedBy = "d")
#Cascade ( { CascadeType.ALL } )
private Set<C> cs;
}
#Entity
#DynamicUpdate
public class E {
#Id
private long id;
#OneToMany (mappedBy = "e")
#Cascade ( { CascadeType.ALL } )
private Set<C> cs;
}
Below are the steps I'm performing in the app:
Pull B from repo; if it exists, update some of its field values [Or] create new B with field values.
Create new instance of A and set B into A.
Persist A (by invoking JpaCrudRepository.save(A)).
Success part:
Everything works fine when B doesn't exist in repo already. Which means:
New instances of B (and C,D,E) are created,
This newly created B is set into A,
And A is persisted to repo successfully (a new row is inserted in all corresponding tables in DB/repo).
Failure part:
Now, when B already exists in repo, the existing B is pulled properly,
Some fields of B are updated while C, D, E are left untouched,
And this updated B is set into A.
But when trying to persist A now a Unique constraint violation is thrown on D and E.
All the entities are marked with following:
• ID as auto-generated column (used as PK implicitly),
• Mapping between entities using the ID column,
• CascadeType ALL wherever mappings like OneToMany, ManyToOne are applied,
• Dynamic update annotation.
So far what I could gather from around the web on this subject:
The JPA Repository doesn't have merge() or update() operations available explicitly, and the save() is supposed to cover those. And this save() operation works by calling merge() if the entity instance exists already Or by calling persist() if it is new. And digging further, the entity's ID column is being used to determine its existence in the repo. If this ID is null, entity is determined as new, and as existing if ID is not null.
So in my failure case above, as the existing B entity instance is being pulled from the repo, it already has a non-null ID.
So I'm not currently clear on what exactly I'm missing here.
I tried finding any matching solution online but couldn't arrive at one yet.
Can someone please help me identify what is wrong with my approach?
Found the issue and solution. As A and B were being created as new, subsequently C,D,E were all created as new which caused the violation. So to solve this I had to pull each existing entity of C,D,and E from the DB, and set them respectively/hierarchically inside the newly created B entity, before attempting to persist.
Basically, don't create new instance for an entity if it is nested within a parent entity which is about to be persisted.. and the goal is to update when a similar record already exists.