How to resolve cascade deleting in Hibernate? - java

I have three entities. The first one is Company entity (see below).
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String name;
#JoinColumn(name = "company_id")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Employee> employees;
#OneToMany(mappedBy = "company")
private List<HistoryRecord> historyRecords;
The second is Employee
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#Column
String name;
#ManyToOne
#JoinColumn(name = "company_id", nullable = true)
private Company company;
#OneToMany(mappedBy = "employee")
private List<HistoryRecord> historyRecords;
Here is my HistoryRecord class
#Entity
public class HistoryRecord {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#ManyToOne
#JoinColumn(name = "company_id")
Employee employee;
#ManyToOne
#JoinColumn(name = "employee_id")
Company company;
#Column(name = "hire_date")
Date hireDate;
#Column(name = "resign_date")
Date resignDate;
When I'm trying to execute delete operation on Employee I'm getting this error
HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [delete from employee where id=?]; constraint ["CONSTRAINT_12: PUBLIC.HISTORY_RECORD FOREIGN KEY(EMPLOYEE_ID) REFERENCES PUBLIC.EMPLOYEE(ID)
I think the problem is in cascade operation but I'm not sure. Is anybody can say how can I fix it?

The problem is due to the relationship of Employee -- HistoryRecord. The employee property on HistoryRecord is not nullable. If you want the HistoryRecord to be deleted when an employee is being deleted you need to add the cascade attribute to the #OneToMany(mappedBy = "employee") for historyRecords on Employee.
#OneToMany(mappedBy = "employee",cascade = CascadeType.REMOVE)

The ENDDM generates
ALTER TABLE "public"."project_group" ADD CONSTRAINT "mandant" FOREIGN KEY (mandant_id) REFERENCES "mandant" ("mandant_id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
for the database and
#OneToMany(cascade = CascadeType.REMOVE)
in Java.

Update your relation mapping in Company class is is missing cascade.
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private List<HistoryRecord> historyRecords;

Related

JPA/Hibernate/SpringBoot: Inserting #OneToMany relationship entities

I'm struggling with inserting #OneToMany entities in the JPA-Hibernate setup.
There are two associated tables with one of the table having the foreign key as the primary key of the source table.
employee
- id (PK)
employee_location
- employee_id (FK to employee)
Here are my entities:
Employee
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue()
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id")
private List<EmployeeLocation> employeeLocations;
}
Employee Location
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#Column(name = "employee_id")
private Long employeeId;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
Saving the entities:
List<EmployeeLocation> locations = Arrays.asList(new EmployeeLocation(), new EmployeeLocation());
Employee employee = new Employee();
employee.setLocations(locations);
employee.save(); // Throws exceptions
Which throws me this error:
org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save():
I tried changing #Entity to #Embeddable and removed the #Id on EmployeeLocation, but it gave me other Unmapped entity exceptions.
How do I handle inserting/updating #OneToMany entities? Is this possible?
How do I handle inserting/updating #OneToMany entities? Is this possible?
If you want the DB to generate the primary key values for you, you need to ask for it by using the #GeneratedValue annotation
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue // mean -> "Hey, DB, give me an ID"!
private Long id;
Same applies for EmployeeLocation
More details can be found here
If this does not fully solve your problem, leave a comment.
In your EmployeeLocation entity (detail) you cannot have as primary key the master's primary key, it needs its own. As follows:
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "employee_location_id")
private Integer id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
In the case of Employee entity having a OneToOne relationship with EmployeeLocation entity you can use just #MapsId. This way, the EmployeeLocation id property is populated with the identifier of the post association.
class EmployeeLocation {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private Employee employee;
}
but since your Employee entity has an OneToMany relationship with EmployeeLocation, Employee id property value can't be used as EmployeeLocation id property value because two or more EmployeeLocation entities asociated to the same Employee entity will have the same id value.
You'll need something like this:
#Entity
public class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#ManyToOne Employee employee;
}
In both cases you need to bind the EmployeeLocation entity to Employee entity, for example:
class Employee {
....
public void addLocation(EmployeeLocation location) {
location.setEmployee(this);
this.employeeLocations.add(location);
}
public void setLocations(List<EmployeeLocation> locations) {
for (EmployeeLocation location : locations) {
location.setEmployee(this);
}
this.employeeLocations = locations;
}
....
}
ANOTHER OPTION: Using ElementCollection
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue
private Long id;
#ElementCollection
#CollectionTable(
name="employee_location",
joinColumns=#JoinColumn(name="EMPLOYEE_ID"))
private Set<EmployeeLocation> employeeLocations;
}
#Embeddable
class EmployeeLocation {
// properties
}

Spring JPA stop creating a new table with relationship

I am trying to stop my relationship making new tables. I have tried multiple approaches to this problem, but there seems to be an error every way I turn. For instance when I try the following code:
//other variables
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<User> users= new ArrayList<>();
I get the following error:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`eb322`.`#sql-3140_2e7`, CONSTRAINT `FK20sqpkpotyyf5wx4jfmp519lu` FOREIGN KEY (`user_id`) REFERENCES `year` (`year_id`))
I have checked all my tables and indexes in the database and I cannot find this constraint anywhere. How do I go about removing it. I basically want to have my schema be like this:
Year will have a list of all students, teachers. When a student is enrolled they will be added to that year etc.
If I don't add the join Column I simply get another table saying
Year.students
How do I combine these together.
This is my student class just incase there's something wrong here:
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int User_id;
}
How I am adding data to year table
//get data about student
Student s = ssrepo.findByName(name);
Year y = yyrepo.findByYear(year);
List<Student> students = y.getStudents();
students.add(s);
yyrepo.save(y)
You seem to be using Unidirectional OneToMany relationship
Hibernate uses an association table to map the relationship so when you remove #JoinColumn annotation an association table is created.
As Year has one to many relationship with student, the type of the List should be List<Student> instead of List<User>
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Student> users= new ArrayList<>();
And using OneToMany Unidirectional association is normally not recommended because of its performance issues. You can consider using bidirectional association. It would be something as follows
public class Year {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "YEAR_ID")
private Long id;
#Column(name = "TYPE_ID")
private Long typeId
#Column(name = "TYPE")
private Boolean type // 1 or 0 to know if typeId is of student or teacher
#Column(name = "YEAR")
private Date year
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Student> students;
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Teacher> teachers;
}
public class Teacher{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "TEACHER_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "STUDENT_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
There are two ways to do this. The first is bidirectional. Where you do the mapping in the two entities. here in this link.(https://dzone.com/articles/introduction-to-spring-data-jpa-part-4-bidirection)
hava exemples.
public class MyClass {
#OneToMany(mappedBy = "myClass", fetch = FetchType.LAZY,
cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<User> users;
}
mappedBy is to say who is the dominate in the relationship. In this case, MyClass has the strongest relationship.
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private MyClass myClass;
}
I believe that this is the best way, because her realities are apparent in both entities. There is a way to do it in a unidirectional way. Exemple in link (How to define unidirectional OneToMany relationship in JPA)

Hibernate / JPA: How to map two #Immutable entities (views of same table) with another entity by same parameter

There is a table ORDERS from which were created views ORDER_VIEW_A and ORDER_VIEW_B.
I have created entity classes OrderViewA and OrderViewB where in each of them is mapping on entity named 'TransactionRecord'.
It is #OneToOne relationship.
There is column ORDER_ID in TRANSACTION_RECORD table and field orderId in TransactionRecord entity.
Field orderId is same for OrderViewA.id and for OrderViewB.id, cause views are selected from the same table.
My question is, how to map in Hibernate two views in OneToOne relationship with another entity by same field.
My code looks like this and it doesn't work in any way, Hibernate always end up with:
org.hibernate.AnnotationException: Referenced property not a
(One|Many)ToOne: com.example.app.model.TransactionRecord.orderId in
mappedBy of com.example.app.model.views.OrderViewA.orderViewA
#Entity
#Immutable
#Table(name = "ORDER_VIEW_A")
public class OrderViewA {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderARecord;
...
}
#Entity
#Immutable
#Table(name = "ORDER_VIEW_B")
public class OrderViewB {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderBRecord;
...
}
#Entity
#Table(name = "TRANSACTION_RECORD")
public class TransactionRecord {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "order_id")
private Long orderId;
...
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewA orderViewA;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewB orderViewB;
...
}

Entities cannot be mapped properly via hibernate annotations

I have an Invoice entity which contains another entity - Counterparty and a list of another entities - Items. While setting up relations between list of items and invoice, there were no problems. However, when I try to set up a similar relation between invoice entity and counterparty entity, I get an error:
#OneToOne or #ManyToOne on pl.coderstrust.model.Invoice.counterparty references an unknown entity: pl.coderstrust.model.counterparty.Counterparty
This is my invoice, which expects to contain only one counterparty and a list of items.
#Entity
#Table(name = "invoices")
public class Invoice implements Comparable<Invoice>, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id")
private int id;
#Column(name = "date")
private LocalDate date = LocalDate.now();
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
#JoinColumn(name = "nip")
private Counterparty counterparty;
#OneToMany(mappedBy = "invoice", cascade = {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
#JsonBackReference
private List<InvoiceItem> invoiceItems = new ArrayList<>();
This is my item entity, which can be related to one invoice:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private int id;
private String description;
private int numberOfItems;
private BigDecimal amount;
private BigDecimal vatAmount;
#JoinColumn(name = "vat_code")
#Enumerated(EnumType.ORDINAL)
private Vat vat;
#OneToOne(mappedBy = "invoice_id", fetch = FetchType.EAGER, cascade = {CascadeType.DETACH,
CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
#JsonManagedReference
private Invoice invoice;
This is my counterparty, which is supposed to be related to many invoices:
#Entity
#Table(name = "counterparties")
public class Counterparty implements Serializable {
#Id
#Column(name = "nip")
private String nip;
private String companyName;
private String phoneNumber;
private String bankName;
private String bankNumber;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "counterparty", cascade = CascadeType.ALL)
private Address address;
#OneToMany(mappedBy = "counterparty", fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
private List<Invoice> invoices;
What is wrong with invoice-counterparty relations?
That Hibernate error is usually thrown when the class is not added to the hibernate configuration. Hibernate needs to be told all of the classes that serve as entities before it can use them.
How do you make your classes known to Hibernate? I.e. either by adding the class to the Configuration object:
configuration.addClass(Counterparty.class);
or by adding the class into a package that is scanned for entities when you are using Spring?
On another note: there seems to be something odd with the Item class perhaps? It specifies a OneToOne relation to Invoice; should this not be a ManyToOne (meaning that a single invoice can have 0 or more Items)?

Exception thrown when having multiple relations between two entities

I have two classes that have 2 relations between them.
User class:
#Entity
#Table(name="User")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="Id")
private long id;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="admin")
private Set<Group> ownedGroup;
#ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="members")
private Set<Group> memberGroups;
//.......
}
Group class:
#Entity
#Table(name="Group")
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="Id")
private long id;
#NotNull
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="Admin", referencedColumnName="Id")
private User admin;
#ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name = "Group_User",
joinColumns = #JoinColumn(name = "Group"),
inverseJoinColumns = #JoinColumn(name = "User"))
private Set<User> members;
//....
}
When I want to start the App, I get this exception:
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: com.myproject.model.User.memberGroups[com.querydsl.core.group.Group]
I saw other similar posts, but apparently they didn't use javax.persistence.Entity and it was the root cause of this error. Any idea how to resolve this issue ?
Group is a reserved keyword. Use backtick to escape a reserved keyword. Check out this answer.
#Table(name = "`Group`")
Also your #ManyToMany mapping is unfortunately not correct.
Update your Group entity like so :
#Entity
#Table(name="`Group`") // Change your table name
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="Id")
private long id;
#NotNull
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="Admin", referencedColumnName="Id")
private User admin;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "Group_User",
joinColumns = #JoinColumn(name = "group_id", referencedColumnName = "id"), //Give a column name 'group_id' and map it to Group primary key id
inverseJoinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id") //Give a column name 'user_id' and map it to User primary key id.
)
private Set<User> members;
...
}

Categories

Resources