trying to understand how #JoinTable and #JoinColumn works - java

I am learning hibernate and stuck a bit with the below problem
have two tables
CREATE TABLE department (
department_id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
caption varchar(255) DEFAULT NULL) ENGINE=InnoDB;
CREATE TABLE employee (
employee_id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
fio varchar(255) DEFAULT NULL,
fk_department_id int(11) NOT NULL,
FOREIGN KEY (fk_department_id) REFERENCES department (department_id)
) ENGINE=InnoDB ;
and two classes (in the first class commented out code looks like working solution)
#Entity
#Table(name = "department")
public class Department {
....
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "employee", joinColumns = {
#JoinColumn(name = "fk_department_id", referencedColumnName = "department_id") })
/*
* #OneToMany(fetch = FetchType.LAZY, mappedBy = "department", cascade =
* CascadeType.ALL)
*/
public Set<Employee> getEmployies() {
return employees;
}
#Entity
#Table(name = "employee")
public class Employee {
......
#ManyToOne
#JoinColumn(name = "fk_department_id")
public Department getDepartment() {
return department;
}
this results into
INFO: HHH000423: Disabling contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
Exception in thread "main" org.hibernate.MappingException: Foreign key (FK3cspe1b06hmsik5l8y1i11xmd:employee [employies_employee_id])) must have same number of columns as the referenced primary key (employee [fk_department_id,employies_employee_id])
at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:148)
at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:130)
Please help me to understand why this doesn't work

The following should work just fine. You'll notice I am not specifying any join column relations because I am allowing Hibernate to generate those automatically for me.
#Entity
public class Department {
#OneToMany
#JoinTable(name = "department_employees")
private List<Employee> employees;
}
#Entity
public class Employee {
#ManyToOne
private Department department;
}
But lets assume you want to be explicit about the join columns.
#Entity
public class Department {
#Id
#Column(name = "department_id")
private Integer id;
#OneToMany
#JoinTable(
name = "department_employees",
joinColumns = #JoinColumn(name = "department_id"),
inverseJoinColumns = #JoinColumn(name = "employee_id"))
private List<Employee> employees;
}
#Entity
public class Employee {
#Id
#Column(name = "employee_id")
private Integer id;
#ManyToOne
#JoinTable(
name = "department_employees",
joinColumns = #JoinColumn(name = "department_id", insertable = false, updatable = false),
inverseJoinColumns = #JoinColumn(name = "employee_id", insertable = false, updatable = false))
private Department department;
}
The key points to take away from this are:
The name of the join table specifies the middle table that maintains the relationship between the Department and Employee entities. It should not refer to the Employee table as your code illustrates.
The joinColumns attribute represents the primary key attributes of the containing entity, in this case that is Department, hence I used department_id.
The inverseColumns attribute represents the primary key attributes of the associated entity, in this case that is Employee, hence I used employee_id.
Update:
If you'd like to eliminate the #JoinTable and merely maintain the relationship between Department and Employee, you'd change your mappings as follows:
#Entity
public class Department {
#OneToMany(mappedBy = "department")
private List<Employee> employees;
}
#Entity
public class Employee {
#ManyToOne
private Department department;
}
Hope that helps.

Related

how do i create entity class for a table having primary key as foreign key?

how do i create entity for area_item_table which have foreign key as primary key
the Item entity:
#Entity
#Table(name = Item.ITEM)
public class Item {
#Id
#Column(name = ITEM_ID)
private long itemId;
#OneToOne
#JoinTable(name = "area_item_table",
joinColumns = {#JoinColumn(name = ITEM_ID, referencedColumnName = ITEM_ID)},
inverseJoinColumns = {
#JoinColumn(name = AREA_ID, referencedColumnName = ID)})
private Area area;
[removed get set and unrelated fields from entity]
}
the Area entity:
#Entity
#Table(name = Area.AREA)
public class Area {
#Id
#Column(name = ID, nullable = false)
private Long id;
[removed get set and unrelated fields from entity]
the table area_item_table :
item_id
area_id
1
121
is there a way to create an Entity for this table without creating new primary key field

JPA cascading delete fails with custom delete method

I ran into an error with custom delete method in spring data jpa. Basically there's a bag which contains items, and when deleting the bag, all the items in it should be deleted.
Here're the entities:
#Entity
#Table(name = "bag")
public class Bag {
#Id private Long id;
#Column("uid") private Long uid;
#Column("name") private String name;
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Item> items;
}
#Entity
#Table(name = "item")
public class Item {
#Id private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bid", referencedColumnName = "id")
private Bag bag;
}
and the repository:
#Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
Bag findByUidAndName(Long uid, String name);
#Transactional
#Modifying
#Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
void deleteByUidAndName(#Param("uid") Long uid, #Param("name") String name);
}
When I call bagRepository.deleteByUidAndName(uid, name), I get an Exception from hibernate relating to foreign key constraint. Setting spring.jpa.show-sql=true shows it does not try to delete the items first before deleting the bag.
However, if I call Bag bag = bagRepository.findByUidAndName(uid, name) and then bagRepository.deleteById(bag.getId()) everything is fine.
I'd like to know what's wrong about customizing this delete method and how to fix it.
In case deleting entity via bagRepository.deleteById(bag.getId()) Hibernate will remove from parent to child entity because you defined cascade = CascadeType.ALL on the relation. When we perform some action on the target entity, the same action will be applied to the associated entity.
Logic is in Hibernate and does not utilize database cascades.
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Item> items;
In case bagRepository.deleteByUidAndName(uid, name) you defined native query for deletion. This means that Hibernate logic will be ignored and the query will be executed as-is. You are working directly with the database in this case and to delete record via native SQL you need to define ON DELETE CASCADE on the database level to have similar logic.
#Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
void deleteByUidAndName(#Param("uid") Long uid, #Param("name") String name);
Solution 1, #OnDelete(action = OnDeleteAction.CASCADE)
In case you have auto-generated tables you can add Hibernate-specific annotation #OnDelete to the relation. During tables generation ON DELETE CASCADE will be applied to the foreign key constraint.
Relation definition:
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Item> items;
Auto generated constaint:
alter table item
add constraint FK19sn210fxmx43i8r3icevbeup
foreign key (bid)
references bag
on delete cascade
Implemetation:
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name = "bag")
public class Bag {
#Id
private Long id;
#Column(name = "uid")
private Long uid;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Item> items;
}
Solution 2, #JoinColumn annotation with foreign key ON DELETE CASCADE
Specify foreign key with ON DELETE CASCADE for Item entity
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bid", referencedColumnName = "id",
foreignKey = #ForeignKey(
name="FK_ITEMS_ID",
foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
private Bag bag;
Implementation:
import javax.persistence.*;
#Entity
#Table(name = "item")
public class Item {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bid", referencedColumnName = "id",
foreignKey = #ForeignKey(
name="FK_ITEMS_ID",
foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
private Bag bag;
}
Solution 3, do not use native query
In this case Hibernate logic will be applied.
Define repository like:
#Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
Bag findByUidAndName(Long uid, String name);
#Transactional
#Modifying
void deleteByUidAndName(#Param("uid") Long uid, #Param("name") String name);
}
Solution 4, Add ON DELETE CASCADE manually to the database
In case your table is not auto-generated you can manually add ON DELETE CASCADE to the database.
alter table item
add constraint FK_BAG_BID
foreign key (bid)
references bag
on delete cascade

JPA mappedBy reference an unknown target entity

I am writing a simple inventory database that contains tables for products, orders and customers. The database definition can be found here:
CREATE TABLE public.customers
(
id integer NOT NULL DEFAULT nextval('customers_id_seq'::regclass),
title character varying(10) COLLATE pg_catalog."default" NOT NULL,
first_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
middle_names character varying(50) COLLATE pg_catalog."default",
last_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
email character varying(50) COLLATE pg_catalog."default" NOT NULL,
phone_number character varying(50) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT customers_pkey PRIMARY KEY (id)
)
CREATE TABLE public.products
(
id integer NOT NULL DEFAULT nextval('products_id_seq'::regclass),
name character varying(100) COLLATE pg_catalog."default" NOT NULL,
sku integer NOT NULL,
inventory_on_hand integer NOT NULL,
reorder_threshold integer NOT NULL,
price numeric(5,2),
inventory_to_be_shipped integer NOT NULL,
CONSTRAINT products_pkey PRIMARY KEY (id)
)
CREATE TABLE public.order_items
(
id integer NOT NULL DEFAULT nextval('order_items_id_seq'::regclass),
product_id integer NOT NULL,
order_id integer NOT NULL,
CONSTRAINT order_items_pkey PRIMARY KEY (id),
CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id)
REFERENCES public.orders (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT order_items_product_id_fkey FOREIGN KEY (product_id)
REFERENCES public.products (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
CREATE TABLE public.orders
(
id integer NOT NULL DEFAULT nextval('orders_id_seq'::regclass),
customer_id integer,
order_date date NOT NULL DEFAULT now(),
arrival_date date,
CONSTRAINT orders_pkey PRIMARY KEY (id),
CONSTRAINT orders_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES public.customers (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
I am trying to implement a Spring Security Resource server to perform CRUD operations on the database. I have implemented entity classes for each table in the database but when try to start the server I get a
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: edu.finalyearproject.imsresourceserver.models.Order.customers in edu.finalyearproject.imsresourceserver.models.Customer.orders
My entity and repository classes can be found below:
Product.java:
#Entity
#Table(name = "products")
#Data
public class Product
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer sku;
private Float price;
private Integer inventory_on_hand;
private Integer reorder_threshold;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
Customer.java
#Entity
#Table(name = "customers")
#Data
public class Customer
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String title;
private String first_name;
private String middle_names;
private String last_name;
private String email;
private String phone_number;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
Order.java
#Entity
#Table(name = "orders")
#Data
public class Order
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name="customer_id", nullable=false)
private Customer customer;
private Date order_date;
private Date arrival_date;
#ManyToMany(mappedBy = "orders", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Product> products = new HashSet<>();
}
I know the problem is related to the relationships between the entities, but I haven't been able to find a solution. Any help would be greatly appreciated.
Try to correct this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "orders", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
to this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
See additional explanation in the documentation.
And you should correct also your Product-Order #ManyToMany association. Only one side of this association should use #JoinTable other side should use mappedBy property of the #ManyToMany annotation. Something like this:
#Entity
public class Product
{
// ...
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY
)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
#Entity
public class Order
{
// ...
#ManyToMany(
mappedBy = "orders",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY)
private Set<Product> products = new HashSet<>();
}
As it is stated in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.
Also as this is explained in this section of the documentation:
If you forget to JOIN FETCH all EAGER associations, Hibernate is going to issue a secondary select for each and every one of those which, in turn, can lead to N+1 query issues.
For this reason, you should prefer LAZY associations.

How I can Join this 3 Tables with JPA

I want to join these 3 Tables.
Here you see my Person Entity
#Entity
#Table(name = "Person", schema = "public")
public class PatientEntity {
#Id
#Column(name = "id")
private Long id;
#Column(name = "lastname")
private String name;
#OneToMany
#JoinTable(name = "person_contact", joinColumns = { #JoinColumn(name = "person_id") }, inverseJoinColumns = { #JoinColumn(referencedColumnName = "id") })
#Column(name = "contact")
private Set<ContactEntity> contacts;
//Getter Setters
And here is my contact entity:
#Entity
#Table(name="contact",schema="public")
public class ContactEntity {
#Id
#Column(name="id")
private Long id;
#Column(name="phone")
private String phone;
//Getter Setters
I just read the Persons from the Table with findById with a Spring JPARepository, but there is no Contact mapped. There is no error during my HTTP request, but instead of a Contact there is null and this error message:
com.sun.jdi.InvocationException occurred invoking method.
The business case is, that every Person can have one or more contact. Is it possible to make it with JPA Annotations or do I need to map it by myself with a JPQL? Or should I create an Entity for the middle table? (person_contact)
The Database is a PostgreSQL Database.
There is this notification too in the Log:
ERROR: column contacts0_.contacts_id does not exist
Perhaps you meant to reference the column "contacts0_.contact_id".
Position: 306
Your #JoinTable has incorrect #JoinColumn specifications and corresponds to the following ddl.
create table person_contact (person_id bigint not null, contacts_id bigint not null, primary key (person_id, contacts_id))
To map your db structure, use following (note removed #Column annotation)
#OneToMany
#JoinTable(name = "person_contact", joinColumns =
{
#JoinColumn(name = "person_id", referencedColumnName = "id"),
},
inverseJoinColumns = {
#JoinColumn(name = "contact_id", referencedColumnName = "id")
})
private Set<ContactEntity> contacts;
I also encourage you to read https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ and reconsider a db structure without a join table (depending on your load and the effort to make this db change)

Hibernate many-to-many cascading delete

I have 3 tables in my database: Students, Courses and Students_Courses
Students can have multiple courses and courses can have multiple students. There is a many-to-many relationship between Students and Courses.
I have 3 cases for my project and courses added to my Courses table.
(a) When I add a user, it gets saved fine,
(b) When I add courses for the student, it creates new rows in User_Courses - again, expected behaviour.
(c) When I am trying to delete the student, it is deleting the appropriate records in Students and Students_Courses, but it is also deleting Courses records which is not required. Even if I don't have any user in a course, I want the course to be there.
Below is my code for tables and annotate classes.
CREATE TABLE `Students` (
`StudentID` INT(11) NOT NULL AUTO_INCREMENT,
`StudentName` VARCHAR(50) NOT NULL
PRIMARY KEY (`StudentID`)
)
CREATE TABLE `Courses` (
`CourseID` INT(11) NOT NULL AUTO_INCREMENT,
`CourseName` VARCHAR(50) NOT NULL
PRIMARY KEY (`CourseID`)
)
CREATE TABLE `Student_Courses` (
`StudentId` INT(10) NOT NULL DEFAULT '0',
`CourseID` INT(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`StudentId`, `CourseID`),
INDEX `FK__courses` (`CourseID`),
INDEX `StudentId` (`StudentId`),
CONSTRAINT `FK__courses` FOREIGN KEY (`CourseID`) REFERENCES `courses` (`CourseID`) ON DELETE NO ACTION,
CONSTRAINT `FK_students` FOREIGN KEY (`StudentId`) REFERENCES `students` (`StudentId`)
)
This is the Java code generated by Hibernate:
#Entity
#Table(name = "Students")
public class Students implements java.io.Serializable {
private Integer StudentID;
private String Students;
private Set<Courses> Courseses = new HashSet<Courses>(0);
public Students() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "StudentID", unique = true, nullable = false)
public Integer getStudentID() {
return this.StudentID;
}
public void setStudentID(Integer StudentID) {
this.StudentID = StudentID;
}
#Column(name = "Students", nullable = false, length = 50)
public String getCampaign() {
return this.Students;
}
public void setCampaign(String Students) {
this.Students = Students;
}
#ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
#JoinTable(name = "Student_Courses", joinColumns = {
#JoinColumn(name = "StudentId", nullable = false, updatable = false)}, inverseJoinColumns = {
#JoinColumn(name = "CourseID", nullable = false, updatable = false)})
public Set<Courses> getCourseses() {
return this.Courseses;
}
public void setCourseses(Set<Courses> Courseses) {
this.Courseses = Courseses;
}
}
#Entity
#Table(name = "Courses")
public class Courses implements java.io.Serializable {
private Integer CourseID;
private String CourseName;
private Set<Students> Studentses = new HashSet<Students>(0);
public Courses() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CourseID", unique = true, nullable = false)
public Integer getCourseID() {
return this.CourseID;
}
public void setCourseID(Integer CourseID) {
this.CourseID = CourseID;
}
#Column(name = "CourseName", nullable = false, length = 100)
public String getCourseName() {
return this.CourseName;
}
public void setCourseName(String CourseName) {
this.CourseName = CourseName;
}
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "Courseses")
public Set<Students> getStudentses() {
return this.Studentses;
}
public void setStudentses(Set<Students> Studentses) {
this.Studentses = Studentses;
}
}
How can I achieve what I have described? I could not find any reasonable documentation on the web.
I found the correct mapping (and tested that with JUnit with an extensive case) in a similar scenario. I don't think I am going to post testing code because it would take long time to adapt to this example. Anyway the key is to:
Not use mappedBy attribute for the annotations, use join columns
List the possible CascadeTypes excluding REMOVE
In OP's example
#ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Course.class)
#JoinTable(name = "XTB_STUDENTS_COURSES",
inverseJoinColumns = #JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
joinColumns = #JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = #ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = #ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Course> courses = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Student.class)
#JoinTable(name = "XTB_STUDENTS_COURSES",
joinColumns = #JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
inverseJoinColumns = #JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = #ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = #ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Student> students = new HashSet<>();
Extensive JUnit testing verified that:
I can add courses to students and vice versa flawlessly
If I remove a course from a student, the course is not deleted
Vice versa
If I remove a student, all courses are detached but they are still persisted (to other students) in database
Vice versa
Based on what you've told me you don't want cascade=CascadeType.ALL on the getCourseses method in Student. Keep in mind that Hibernate cascades are not the same as database cascades. Even if you don't have any cascades then Hibernate will delete the Students_Courses record.
The best way to think of Hibernate cascades is that if you call an operation on an entity and that operation is listed in the cascade list then that operation will be called on all of the child entities.
For example, when you call delete on Student, since delete is in the cascade list for Courses, Hibernate will call delete on each of the Course entities referenced by that student. That is why you are seeing the Course records disappearing.
Don't worry about database cascades, Hibernate will take care of those on its own.
You just need to Remove cascade = CascadeType.ALL in Student class only no change is required in Courses class
and add the below code
cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.DETACH}..
It means while deleting owner class record it will not delete a non-owner record.
After this, On Delete it will delete only from Student table and student_course.
course table data remains the same.

Categories