How to join 3 tables into one table with JPA? - spring

I am creating an API where I have 3 tables called User, Book and Status. And I want to create a combined table User_Book_Status. I am not sure how to implement this in JPA. I think all tables have M:N relationship with each other. How should I join these 3 tables?
Here is my design for the database.
User.java
import javax.persistence.*;
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Column(nullable = false)
private String username;
#Column(nullable = false)
private String password;
public User() {}
//Getters and setters omitted.
}
Book.java
import javax.persistence.*;
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Column(nullable = false)
private String title;
#Column(nullable = false)
private String author;
#Column(nullable = false)
private int pages;
public Book() {}
//Getters and setters omitted.
}
Status.java
import javax.persistence.*;
#Entity
public class Status {
public enum ReadingStatus {READING,
FINISHED,
ONHOLD}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Enumerated(EnumType.STRING)
private ReadingStatus status;
}

here is an example of how to implement your solution already working:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name = "user_book_status")
public class UserBookStatus {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "book_id")
private Book book;
#ManyToOne
#JoinColumn(name = "status_id")
private Status status;
}
#Table to specify the name of the Table, so you can change the name of the entity freely.
#ManyToOne means that you can have many records of UserBookStatus for a single user, or book or status.
#JoinColumn use to specify the name of the column in your entity that maps to the #Id from the entity (User,Book,Status) you are referencing.

It seems to me that you don't really need an entity for Status. I would model it like this:
#Entity
public class Loan {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
private Book book;
#ManyToOne
private User user;
#Enumerated(EnumType.STRING)
private ReadingStatus status;
//...
}
Also, you could make the relationships bidirectional, so that both User and Bookhas a list of Loans.

Related

How to implements entity with 2 entity as primary key with jpa annotation and repository

i want to implement a many to many association with quantity information in it . like this :
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Banque banque;
private int quantity;
}
the GroupSanguin and the Banque are two class stored in the database two . here is the code for the two if you need :
#Entity
#Table(name = "groupe_sanguins")
public class GroupeSanguin {
#Id
private String groupe;
#OneToMany(mappedBy = "groupeSanguin")
private List<Donneur> donneurs;
}
#Entity #Getter #Setter #NoArgsConstructor
public class Banque {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true,nullable = false)
private String nom;
private String adresse;
#Column(unique = true)
private String telephone;
private String localisation;
}
so my i want to know how to annotate the JpaRepository to take the two as primary key like this and is my annotation good for it to work ?
public interface ReserveRepository extends JpaRepository<
Reserve,
//what to put here ?
>
This isn't a JPA question in fact, it's a relationnal database conception.
If Reserve has is own data and links with other entity it has it own Id
You can add unicity constraint
#Entity
#Table(name = "reserves", uniqueConstraints={
#UniqueConstraint(columnNames = {"banque_id", "groupe_id"})
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
i've found this solutions too.
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
#IdClass(ReserveId.class) //this annotation will tell that id that the
// the id will be represented by a class
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin groupeSanguin;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
and the id class should implements Serializable like this :
#Getter #Setter
public class ReserveId implements Serializable {
private Banque banque;
private GroupeSanguin groupeSanguin;
}
and finally the repository will be like that :
#Repository
public interface ReserveRepo extends JpaRepository<Reserve, ReserveId>{}
See your Reserve class has nowhere mentioned composite primary key. First you need to fix the model, You can refer to the solution here How to create and handle composite primary key in JPA

REST API Infinite loop

My API shows me infinite loop for adress field
When I insert #JsonIgnore, #JsonManagedReference or #JsonBackReference
I can clearly see one result as it should be, but than i don't have nested address fields.
What should I do to have also that address fields but one result?
These are my main entities:
1.Property
package com.realestate.petfriendly.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.Data;
#Entity
#Data
#Table(name = "property")
public class Property {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_property")
private int id_property;
#Column(name = "title")
private String title;
#Column(name = "type")
private String type;
#Column(name = "room")
private String room;
#Column(name = "price")
private double price;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "address_id_address")
// #JsonBackReference
private Address address;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id_user")
// #JsonBackReference
private User user;
}
User
package com.realestate.petfriendly.entity;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
#Entity
#Getter
#Setter
#Table(name = "user")
class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_user")
private int id_user;
#Column(name = "username")
private String username;
#Column(name = "name")
private String name;
#Column(name = "lastname")
private String lastname;
#Column(name = "phone")
private String phone;
#Column(name = "notes")
private String notes;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_address_id_user_address")
// #JsonManagedReference
private UserAddress userAddress;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
// #JsonManagedReference
private List<Property> property = new ArrayList<>();
}
Address
package com.realestate.petfriendly.entity;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
#Entity
#Getter
#Setter
#Table(name="address")
class Address{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_address")
private int id_address;
#Column(name = "city")
private String city;
#Column(name = "municipality")
private String municipality;
#Column(name = "place")
private String place;
#Column(name = "street")
private String street;
#Column(name = "house_number")
private double house_number;
#OneToOne(mappedBy = "address")
// #JsonManagedReference
private Property property;
}
You actually have the solution to your problem in your code, but the key annotations are commented-out and in the wrong places (according to your requirements). One of the ways to tackle this is by using #JsonManagedReference and #JsonBackReference as follows:
#Entity
#Data
#Table(name = "property")
public class Property {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_property")
private int id_property;
#Column(name = "title")
private String title;
#Column(name = "type")
private String type;
#Column(name = "room")
private String room;
#Column(name = "price")
private double price;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "address_id_address")
#JsonManagedReference
private Address address;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id_user")
#JsonBackReference
private User user;
}
#Entity
#Getter
#Setter
#Table(name = "user")
class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_user")
private int id_user;
#Column(name = "username")
private String username;
#Column(name = "name")
private String name;
#Column(name = "lastname")
private String lastname;
#Column(name = "phone")
private String phone;
#Column(name = "notes")
private String notes;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_address_id_user_address")
private UserAddress userAddress;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
#JsonManagedReference
private List<Property> property = new ArrayList<>();
}
#Entity
#Getter
#Setter
#Table(name="address")
class Address{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_address")
private int id_address;
#Column(name = "city")
private String city;
#Column(name = "municipality")
private String municipality;
#Column(name = "place")
private String place;
#Column(name = "street")
private String street;
#Column(name = "house_number")
private double house_number;
#OneToOne(mappedBy = "address")
#JsonBackReference
private Property property;
}
Keep in mind the following:
#JsonManagedReference is the forward part of the relationship: the one that gets serialized normally.
#JsonBackReference is the back part of the relationship: it will be omitted from serialization.
If you want to have a reference to the back part of the relationship, you can use #JsonIdentityInfo as follows:
#Entity
#Data
#Table(name = "property")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id_property")
public class Property {
(...)
}
#Entity
#Getter
#Setter
#Table(name = "user")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id_user")
class User {
(...)
}
#Entity
#Getter
#Setter
#Table(name="address")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id_address")
class Address{
(...)
}
You can read more about these and other techniques in the following online resource: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion.
You have circular dependency between Property and Address class. In order to block infinite JSON serialization loop you can add #JsonIgnore annotation on one side of related properties

(Do not display relationship values)

I have two entity with name of the article and article Category.
and they have one-to-many relationships.
I use #JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,property = "id")
but I cant see data of article category(category_id) in spring data rest.
ArticleCategory.class
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory", fetch = FetchType.LAZY)
private Set<Article> articles = new HashSet<>();
}
Article.class
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable {
public Article() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
#Column(name = "title")
private String title;
#Column(name = "image_url")
private String image_url;
#Column(name = "short_description")
private String short_description;
#Column(name = "text")
private String text;
#Column(name = "keywords", nullable = true)
private String keywords;
#Column(name = "visit", nullable = false)
private int visit;
#Column(name = "code", nullable = false)
private UUID code;
#Column(name = "date_created")
#CreationTimestamp
private Date dateCreated;
#Column(name = "date_updated", nullable = false)
#UpdateTimestamp
private Date dateUpdated;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
public Article(String title, String image_url, String short_description, String text, String keywords, int visit, UUID code) {
this.title = title;
this.image_url = image_url;
this.short_description = short_description;
this.text = text;
this.keywords = keywords;
this.visit = visit;
this.code = code;
}
}
Article Repository
#CrossOrigin("http://localhost:4200")
#RepositoryRestResource(collectionResourceRel = "article", path = "article")
public interface ArticleRepository extends JpaRepository<Article,Long> {
Article findByCode(UUID uuid);
}
And this is output of spring data rest
enter image description here
That is exactly because you used #JsonManagedReference and #JsonBackReference. Keep in mind the following when using them:
#JsonManagedReference is the forward part of the relationship and is the one that gets serialized normally.
#JsonBackReference is the back part of the relationship and it will be omitted from serialization.
The serialized Article object does not contain a reference to the ArticleCategory object.
If you want to have any ArticleCategory data when serializing Article you can either use #JsonIdentityInfo so that one of the properties is serialized (in this case I've chosen id for both):
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
}
#Entity
#Table(name = "article_category")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If you are only interested in categoryId another possibility would be to use #JsonIgnore on private Set<Article> articles property so that it is not serialized:
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If none of those suits your needs you might need to implement your own custom serializer. You can read more about all those options at https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion.
I solved the problem using the controller
And that's why #JsonManageRefrence and #JsonBackRefrence do not work
I replaced the lazy load with the eager load in both entity
#ManyToOne(fetch = FetchType.Eager)
#JoinColumn(name = "user_id")
#JsonManageRefrence
private User user;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory",
fetch = FetchType.Eager)
#JsonBackRefrence
private Set<Article> articles = new HashSet<>();
and then add a controller
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
#RequestMapping("/getAllArticle")
public class MyController {
private ArticleRepository articleRepository;
// you must do constructor injection
#GetMapping("/getAllArticle")
public List<Article> allArticle()
{
return articleRepository.findAll();
}
}

Embed complex object in entity

I want to embed the following
#Embeddable
public class BaseEntity implements Serializable {
#Id
#GeneratedValue
private UUID id;
#CreatedDate
#Column(name = "created_date", updatable = false)
private LocalDateTime createdDate;
}
into my room entity
#Entity
#Data
#NoArgsConstructor
#Table(name = "room")
public class room {
#EmbeddedId
private BaseEntity baseEntity;
#Column(length = 80, nullable = false)
private String name;
}
So that my generated table looks like this
room
id
createdDate
name
But id and createdDate are not getting embedded
Instead of #Embeddable just extend your BaseEntity
#MappedSuperclass
#Getter
#Setter
public class BaseEntity implements Serializable {
#Id
#GeneratedValue
private UUID id;
#CreatedDate
#Column(name = "created_date", updatable = false)
private LocalDateTime createdDate;
}
#Entity
#Data
#NoArgsConstructor
#Table(name = "room")
public class room extends BaseEntity{
#Column(length = 80, nullable = false)
private String name;
}

JPA/Hibernate. How to get child objects contained in a list of Parent object using createQuery method

I have Certificate class that contains list of Tag classes
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
#Entity
#Table(name = "gift_certificate")
public class Certificate {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private BigDecimal price;
private Integer duration;
#Column(name = "create_date")
private LocalDateTime createDate;
#Column(name = "last_update_date")
private LocalDateTime lastUpdateDate;
#ManyToMany
#JoinTable(name = "gift_certificate_tag",
joinColumns = #JoinColumn(name = "tag_id"),
inverseJoinColumns = #JoinColumn(name = "gift_certificate_id")
)
private List<Tag> tags;
getters and setters and other code...
....
import javax.persistence.*;
import java.util.Objects;
#Entity
#Table(name = "tag")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
getters and setters and other code...
And I'm trying to get List of Certificate from DB using JPA/Hibernate.
I'm using EntityManager
public List<Tag> getCertificateTags(Long certificateId) {
return entityManager.createQuery("select c.tags from Certificate c where c.id=:id")
.setParameter("id", certificateId)
.getResultList();
}
And it works, but I get just list, not List and IDEA is warning Unchecked assignment: 'java.util.List' to 'java.util.List<Tag>'.
And when I use createQuery with second parameter Tag.class like this:
entityManager.createQuery("select c.tags from Certificate c where c.id=:id", Tag.class)
.setParameter("id", certificateId)
.getResultList();
I get java.lang.IllegalArgumentException: Type specified for TypedQuery [Tag] is incompatible with query return type [interface java.util.Collection]
How can I fix it?
Try to change the query this way
select t from Certificate c join c.tags t where c.id=:id
The reason is that select c.tags means every result row contains a list of tags. But when you select t from Certificate c join c.tags t every row contains one tag

Resources