JPA JoinTable with additional columns - spring-boot

Spring Boot
Spring Data
JPA Hibernate
Came across a requirement where JPA ManyToMany relationship table with an extra column. Have looked at StackOverflow and found several questions related to same requirement. Most of the answers on the forums ask for EmbeddedId field with a composite primary key with two columns. I tried the solutions from the forums, here is the code snippet.
#Data
#Entity
#Table (name = "TABLE_A")
public class TableA {
#Id
#Column (name = "table_a_id")
private Integer id;
...
#OneToMany (mappedBy = "pk.tableA")
private List<TableABMapping> mappingTable;
}
#Data
#Entity
#Table (name = "TABLE_B")
public class TableB {
#Id
#Column (name = "table_b_id")
private Integer id;
...
#OneToMany (mappedBy = "pk.tableB")
private List<TableABMapping> mappingTable;
}
#Data
#Entity
#Table (name = "TABLE_A_TABLE_B_MAPPING")
public class TableABMapping implements Serializable {
#EmbeddedId
private MappingKey pk = new MappingKey();
#Column(name = "addon_field")
private Double additionalField;
#Transient
public TableA getTableA() {
return getPk().getTableA();
}
public void setTableA(TableA tableA) {
getPk().setTableA(tableA);
}
#Transient
public TableB getTableB() {
return getPk().getTableB();
}
public void setTableB(TableB tableB) {
getPk().setTableB(tableB);
}
// equals() & hashCode() method override
}
#Data
#Embeddable
public class MappingKey implements Serializable {
#ManyToOne
#JoinColumn(name = "table_a_id", referencedColumnName = "table_a_id")
private TableA tableA;
#ManyToOne
#JoinColumn(name = "table_b_id", referencedColumnName = "table_b_id")
private TableB tableB;
// No argument constructor, two arguments constructor.
// equals() & hashCode() method override
}
Trying save operation from service class like this:
for (TableB tabB : tableA.getTableB()) {
TableABMapping abMapping = new TableABMapping();
abMapping.setTableA(tableA);
abMapping.setProduct(tabB);
abMapping.setAdditionalField(tabB.getAddonField());
if (tableA.getMappingTable() == null) {
tableA.setMappingTable(new ArrayList<TableABMapping>());
}
tableA.getMappingTable().add(abMapping);
}
TableA ta = tableARepository.save(tableA);
System.out.println("TableA.save(): " + ta);
Getting this error on save operation.
Unable to find TableABMapping with id MappingKey(tableA = TableA( ... ), tableB = TableB ( ... ))
Both the entities have proper ids at the time of saving the entity. But still it throws this error. Where I am making mistake?

Related

Spring Boot JPA Using Many-to-Many relationship with additional attributes in the join table

I have two simple classes Student and Course. I am trying to set up many to many relationship between these classes. I want to use additional table whose PRIMARY KEY is the combination of the primary keys of student and course tables (student_id and course_id).
The student class:
#Entity
#Table(name = "student")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#OneToMany(mappedBy = "student")
private Set<CourseStudent> courses;
}
The course class:
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(mappedBy = "course")
Set<CourseStudent> students;
}
The entity that stores the relationship between course and the student:
#Entity
#NoArgsConstructor
#Data
public class CourseStudent {
#EmbeddedId
CourseStudentKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
public CourseStudent(Student student, Course course) {
this.student = student;
this.course = course;
this.rating = 0;
}
int rating;
}
Attention: Since I want to have additional features in this entity (for example, storing the rating of the students for courses), I don't want to use #JoinTable idea that we implement in the Student class.
Since I have multiple attributes in the primary key of CourseStudent entity, I used the following class
#Embeddable
#Data
public class CourseStudentKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
I have the following POST request to insert the student into a course:
#PostMapping("/insert/students/{studentId}/courses/{courseId}")
public CourseStudent insertStudentIntoCourse(#PathVariable(value = "studentId") Long studentId,
#PathVariable(value = "courseId") Long courseId) {
if (!studentRepository.existsById(studentId)) {
throw new ResourceNotFoundException("Student id " + studentId + " not found");
}
if (!courseRepository.existsById(courseId)) {
throw new ResourceNotFoundException("Course id " + courseId + " not found");
}
CourseStudent courseStudent = new CourseStudent(
studentRepository.findById(studentId).get(),
courseRepository.findById(courseId).get()
);
return courseStudentRepository.save(courseStudent);
}
I have manually added Student and the Course into my local database and send this request by using Postman.
http://localhost:8080/insert/students/1/courses/1
However, I get the following error:
{
"timestamp": "2022-08-04T12:33:18.547+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/insert/students/1/courses/1"
}
In the console, I get NullPointerException. What is the thing I am doing wrong here?

spring jpa findBy column of inside entity

I have a couple of related entities in a relational DB:
#Entity
public class A {
#Id
#Column(name = "id" ...)
private UUID id;
#OneToMany(mappedBy = "a")
private Set<B> bs;
}
and
#Entity
public class B {
#Id
#Column(name = "id" ...)
private UUID id;
#ManyToOne
#JoinColumn(name = "a_id")
private A a;
}
So I have a B Repository
#Repository
public interface BRepository extends JpaRepository<B, UUID> {
List<B> findByA(A a);
}
And id like to have a query by which I obtain the all Bs with the same A. I'm used to have an a_id column in B, not the whole entity. I don't know how to manage this, nor with a JPA query nor with a NamedQuery.
Many thanks in advance!
Luis
As already answered, the proposed code should already work but it didn't... The fix that worked for me was
List<B> findByA_Id(UUID id);
Table_Field references field field to table Table.

Many to Many relation with embedded key is inserting null in primary key

Facing problem inserting records in intermediate table.
Post and Tag tables are sharing many to many relationship using intermediate table post_tag;
Post table: id (pk), post, created_by, created_on, modified_by, modified_on
Tag table: id (pk), tag, created_by, created_on, modified_by, modified_on
Post_Tag table: post_id, tag_id (pk post_id and tag_id), created_by, created_on, modified_by, modified_on
Entities:
// ID class
#Embeddable #Data #NoArgsConstructor #AllArgsConstructor
public class PostTagId
implements Serializable {
#Column(name = "post_id")
private Long postId;
#Column(name = "tag_id")
private Long tagId;
}
#Entity
#Table(name = "post_tag")
#EntityListeners(AuditingEntityListener.class)
#Data
#EqualsAndHashCode(callSuper = false)
public class PostTag extends Auditable<String> {
#EmbeddedId
private PostTagId id;
#ManyToOne
#MapsId("postId")
#JoinColumn(name="post_id")
private Post post;
#ManyToOne
#MapsId("tagId")
#JoinColumn(name="tag_id")
private Tag tag;
public PostTag(Post post, Tag tag) {
this.post = post;
this.tag = tag;
this.id = new PostTagId(post.getId(), tag.getId());
}
}
Using springboot(AuditingEntityListener) to add audit columns. Post and Tag entities are somewhat like -
///////////Tag
#Data
#EqualsAndHashCode(callSuper = false, exclude = {"postTags"})
#ToString( exclude = {"postTags"})
#Entity
#Table(name = "tag")
#EntityListeners(AuditingEntityListener.class)
public class Tag extends Auditable<String> {
#Id
private Long id;
#Column(name = "tag")
private String tag;
#OneToMany(mappedBy = "tag", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<PostTag> postTags;
}
//////////////POST
#Data
#EqualsAndHashCode(callSuper = false, exclude = {"postTags"})
#ToString( exclude = {"postTags"})
#Entity
#Table(name = "post")
#EntityListeners(AuditingEntityListener.class)
public class Post extends Auditable<String> {
#Id
private Long id;
#Column(name = "post")
private String post;
#OneToMany(mappedBy = "post", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<PostTag> postTags;
public void addTag(Tag tag) {
PostTag postTag = new PostTag(this, tag);
if (postTags == null) {
postTags = new HashSet<>();
}
postTags.add(postTag);
tag.getPostTags().add(postTag);
}
}
Code snippet for insertion of tags using addTag() method -
public Post create(Post post) {
// DefaultTag is some default tag getting from repository
post.addTag(DefaultTag)
return postRepo.save(post);
}
Insert in post_tag is trying to insert null post_id.
Hibernate:
insert
into
post_tag
(created_by, created_on, modified_by, modified_on, post_id, tag_id)
values
(?, ?, ?, ?, ?, ?) [0;39m [36mo.h.engine.jdbc.spi.SqlExceptionHelper SQL Error: 0, SQLState:
23502 [0;39m [36mo.h.engine.jdbc.spi.SqlExceptionHelper ERROR: null
value in column "post_id" violates not-null constraint Detail:
Failing row contains (null, 100, anonymousUser, anonymousUser,
2020-07-06 15:22:19.227-06, 2020-07-06 15:22:19.227-06).
Removed #EmbededId and used #Id in PostTag class. This way do not need to create embededId in constructor, and hibernate handles id creation. Changes look like this -
#Entity
#Table(name = "post_tag")
#EntityListeners(AuditingEntityListener.class)
#Data
#EqualsAndHashCode(callSuper = false)
public class PostTag extends Auditable<String> implements Serializable {
#ManyToOne
#Id
#JoinColumn(name="post_id")
private Post post;
#ManyToOne
#Id
#JoinColumn(name="tag_id")
private Tag tag;
}
Since PostTag(Post, Tag) constructor is removed, addTag(Tag tag) method needs to be refactored in Post entity.
public void addTag(Tag tag) {
PostTag postTag = new PostTag();
postTag.setPost(this);
postTag.setTag(tag);
if (postTags == null) {
postTags = new HashSet<>();
}
postTags.add(postTag);
tag.getPostTags().add(postTag);
}

Representing Parent Child Relationship in Spring JPA

I have the following scenario. I have a parent class User.java which represents user table. I have two child classes - Doctor.java and Patient.java. The data for these classes are in 'user' table. The mapping between patient and doctor is provided in doctorpatient table. And this mapping can be ManyToMany. This 'doctorpatient' table is represented by DoctorPatient.java class
I need to map my doctor and patient class in such a way that my doctor class should return all patient for a particular doctor and patient class should return all doctors for a particular patient.
I am new to hibernate and would appreciate any help.
User.java
#Entity
#Table(name = "User")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#Column(name="UserName", nullable = false, length = 16)
private String userName;
#Column(name="FirstName", nullable = false, length = 45)
private String firstName;
#Column(name="LastName", nullable = false, length = 45)
private String lastName;
#Column(name="Email", nullable = false, length = 45)
private String email;
// Getters and Setters
}
Doctor.java
public class Doctor extends User {
#OneToMany(mappedBy="doctor")
List<DoctorPatient> patientList;
//Getters and Setters
}
Patient.java
public class Patient extends User {
#OneToMany(mappedBy="patient")
List<DoctorPatient> doctorsList;
//Getters and Setters
}
DoctorPatient.java
#Entity
#Table(DoctorPatient)
public class DoctorPatient {
#EmbeddedId
private DoctorPatientId doctorPatientId;
#ManyToOne(targetEntity = Doctor.class)
#JoinColumn(name="doctorId")
private Doctor doctor;
#ManyToOne(targetEntity = Patient.class)
#JoinColumn(name="patientId")
private Patient patient;
//Getter Setters
}
Create command for DoctorPatient table
CREATE TABLE 'DoctorPatient' (
'DoctorId''INT(11) NOT NULL,
'PatientId' INT(11) NOT NULL,
PRIMARY KEY ('DoctorId', 'PatientId'),
INDEX 'FK_Doctor_idx' ('DoctorId'),
INDEX 'FK_Patient_idx' ('PatientId'),
CONSTRAINT 'FK_Patient' FOREIGN KEY ('PatientId') REFERENCES 'User' ('UserId'),
CONSTRAINT 'FK_Doctor' FOREIGN KEY ('DoctorId') REFERENCES 'User' ('UserId')
)
Currently for each doctor, I am getting the patientid (userId) from the DoctorPatient table using which I need to query the user table to get the patient details.
Similarly for patients, I am able to retrieve only doctorid (UserId) from DoctorPatient table. Then I need to iterate through the list and hit the User table to get complete details.
Solution
Updated classes
Patient.java
public class Patient extends User {
#ManyToMany
#JoinTable(name="DoctorPatient", joinColumns= {#JoinColumn(name="PatientId") }, inverseJoinColumns={ #JoinColumn(name="DoctorId") } )
private Set<Doctor> DoctorList = new HashSet<>();
//Getters and Setters
}
Doctor.java
public class Doctor extends User {
#ManyToMany(mappedBy="DoctorList")
private Set<Patient> patients = new HashSet<>();
//Getters and Setters
}

How to get data from tables in spring data jpa?

I have two tables
#Entity
#Table(name = "TAX_CATEGORY")
public class TaxCategory {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "CATEGORY", nullable = false)
private String category;
#Column(name = "TAX", nullable = false)
private Double tax;
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "PRICE", nullable = false)
private Double price;
#Column(name = "NAME", nullable = false)
private String name;
#OneToOne
#JoinColumn(name = "TAX_CATEGORY_ID")
private TaxCategory taxCategory;
Now I want to query
"Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id"
So List it would return is
ProductName ProductPrice Tax
but I am not able to get this data from two tables. Single table data is working fine.
public interface CustomRepositoryCustom {
public void customMethod();
}
public interface CustomRepository
extends JpaRepository<Account, Long>, CustomRepositoryCustom { }
public class CustomRepositoryImpl implements CustomRepositoryCustom {
public void customMethod() {
Query nativeQuery = entityManager.createNativeQuery("Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id");
return query.getResultList();
}
}
This throws exception that object is not managed bean. If I create custom object then also it gives similar type of issues.
Use the following JPA query to get the both tables data. Here used jpa query to fetch the product. From product object, can get the taxCategory.
public interface CustomRepository extends JpaRepository<Account, Long>, CustomRepositoryCustom {
Query("select product from Product as product join fetch product.taxCategory as taxCategory where taxCategory.id = :taxCategoryId")
public Product getProductByCategory(#Param Long taxCategoryId);
}
Instead of query method you can directly define JPA method to find products based on category Id as.
#Repository
#RepositoryRestResource
public interface ICountryRepository extends JpaRepository<Product , Long > {
List<Product> findByTaxCategory_Id(#Param Long Id);
}

Resources