Spring JPA Many To One - One To Many Relationship with RestController - java

I am trying to retrieve all Many To One relationship data from Postgresql database with Rest Controller with foreign key data. Like for Course and Instructor :
{
{
"id": 1,
"course_name": "IT 101",
"instructor":{
"instructor_name":"Jack King"
}
},
{
"id": 2,
"course_name": "CS 101",
"instructor":{
"instructor_name":"Homer Love"
}
},
{
"id": 3,
"course_name": "DB 101",
"instructor":{
"instructor_name":"Jack King"
}
}
}
My Code as follows:
Database
create table instructor
(
id integer generated always as identity,
instructor_name text,
primary key (instructor_name),
unique (id)
);
create table course
(
id integer generated always as identity,
course_name text not null,
instructor_fk integer references instructor (id),
primary key (course_name, instructor_fk),
unique (id)
);
insert into instructor (instructor_name) values ('Jack King');
insert into instructor (instructor_name) values ('Homer Love');
insert into author (course_name, instructor_fk) values ('IT 101', 1, 1);
insert into author (course_name, instructor_fk) values ('CS 101', 2, 2);
insert into author (course_name, instructor_fk) values ('DB 101', 3, 1);
Entities
I am using Project Lombok annotations so instead of constractions, getters and setters for less boilerplate code.
Course
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Table(name = "course")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String course_name;
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} )
#JsonIgnore
#JoinColumn(name = "instructor_fk", nullable = false)
private Instructor instructor;
}
Instructor
#Entity
#Table(name = "instructor")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Instructor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(unique = true)
private String instructor_name;
#OneToMany(
mappedBy = "instructor",
cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}
)
private List<Course> courses;
}
Repository
#Repository
public interface CourseRepository extends CrudRepository<Author, Integer> {
}
Service
#Service
public class CourseService {
#Autowired
private CourseRepository courseRepository;
public List<Author> getAllCourses() {
return (List<Author>) courseRepository.findAll();
}
}
Controller
#RestController
public class CourseController {
#Autowired
private CourseService courseService;
#GetMapping("/courses")
private List<Author> getAllCourses() {
return courseService.getAllCourses();
}
}
What I get:
{
{
"id": 1,
"course_name": "IT 101"
},
{
"id": 2,
"course_name": "CS 101"
},
{
"id": 3,
"course_name": "DB 101"
}
}

#JsonIgnore annotation should be on One To Many relation, which means it should be on Instructor entity not on Course entity:
Course
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Table(name = "course")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String course_name;
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} )
#JoinColumn(name = "instructor_fk", nullable = false)
private Instructor instructor;
}
Instructor
#Entity
#Table(name = "instructor")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Instructor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(unique = true)
private String instructor_name;
#OneToMany(
mappedBy = "instructor",
cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}
)
#JsonIgnore
private List<Course> courses;
}

Related

Spring JPA not returning Foreign keys in response

I have a database with some entities, ( in parent child relationship )I can say and when When I try to make a query to child table to get all the rows, I only get the fields which are not foreign keys. How can I include the foreign key in the response json. Can anyone help me figure this out ?
Parent Class which is working fine when I try to repository.findAll() and it works as expected.
#Entity
#Table(name = "Employees")
#Data
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private long id;
private String name;
private String description;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "employee")
private List<Projects> projects;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "employee")
private Address address;
}
Child class:
#Entity
#Table(name = "Address")
#Data
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "emp_id", nullable = false)
#JsonBackReference
private Employee employee;
}
Here is the repository class for Address Entity
#Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
}
When I try AddressRepository.findAll()
What I get:
[{
"id": 1,
"city": "new york",
"state": "new york"
}]
what I want to get:
"id": 1,
"city": "new york",
"state": "new york",
"emp_id": 1 //which is the foreign key referring to Employee table
}]
What I tried is I updated my Employee column in Address Entity as follow but no luck
#Entity
#Table(name = "Address")
#Data
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
#OneToOne(fetch = FetchType.EAGER)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "client_id", scope = Client.class)
#JsonIdentityReference(alwaysAsId = true)
#JoinColumn(name = "client_id", nullable = false)
#JsonBackReference
#JsonProperty("clientId")
private Employee employee;
}
You could use a JPA projection:
public class AddressDto {
private long id;
private String city;
private String state;
private long employeeId;
public AddressDto(long id, String city, String state, Employee employee) {
this.id = id;
this.city = city;
this.state = state;
this.employeeId = employee.getId();
}
// getters etc..
}
#Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
List<AddressDto> findAllProjectedBy();
}
Use #JsonProperty on each Foreign Key.
#Entity
#Table(name = "Employees")
#Data
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private long id;
private String name;
private String description;
#JsonProperty
#OneToMany(fetch = FetchType.EAGER, mappedBy = "employee")
private List<Projects> projects;
#JsonProperty
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "employee")
private Address address;
}

How to save entities in table with unique constraint

I work with Spring Boot and have 2 PostgreSQL tables: USERS and CITIES. FOREIGN KEY (USERS.city_id) REFERENCES CITIES (id). CITIES has an unique constraint for city name field. I receive an object in the #PostMapping method of the controller and try to save it via service layer. All is fine while I don't send an object with same city name field, and I don't know how to solve it. Postman JSON example:
*1st attempt*
{
"name": "JHON",
"city": {
"name": **"MOSCOW"**
}
} ---> ALL OK
*2nd attempt*
{
"name": "TOM",
"city": {
"name": **"MOSCOW"**
}
}--->**org.postgresql.util.PSQLException: ERROR: Duplicate key value violates unique constraint "cities_name_key"
Details: The key "(name) = (MOSCOW)" already exists.**
Entity:
#Entity
#Table(name = "cities")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
}
#Entity
#Data
#Accessors(chain = true)
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "city_id")
private City city;
}
Tables:
cities
id SERIAL PRIMARY KEY NOT NULL,
name TEXT UNIQUE
users
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(10),
city_id INT,
CONSTRAINT users_cities_id_fk FOREIGN KEY (city_id) REFERENCES cities (id)
and Service
#Service
#RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User getUser(Long id){
return userRepository.findById(id).get();
}
public User saveUser(User user){
return userRepository.save(user);
}
}
Ok, I found an answer for question.
So if you have fields with unique constraints in the your tables, you have to define a constructor inside the entity for the primary key field. And don't hope that Lombok will do it. It means that annotation like #AllArgsConstructor does not help for this case.
#Entity
#Table(name = "cities")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
//add this *********************
public City(Long id) {
this.id = id;
}
}

Insert a record into multiple Table using JPA

I have three entities namely
product
product details
stock
category
reference is given below
when I try to get the details of product it works fine when I try to save it shows below error in the console
2020-08-12 13:17:22.279 WARN 18612 --- [nio-9002-exec-1] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.eMart.main.entity.Product]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (java.util.List) not compatible with managed type (com.eMart.main.entity.Product)
My question is how to add the product into the database and how did I need to optimize my entities
Input
{
"skuId": "2",
"category": {
"categoryId": 2,
"categoryName": "food"
},
"description": "cow milk",
"stock": {
"stockId": 1,
"inventoryCount": 5,
"selfCount": 5
},
"productDetails": [
{
"productDetailId": 1,
"cost": 10.0,
"currency": "inr",
"expiryDate": "2020-08-11T18:30:00.000+00:00",
"supplierCode": 1
}
]
}
controller method
#PostMapping(value = "/test")
public ResponseEntity<Product> test(#RequestBody Product product) throws Exception {
productRepositry.save(product);
return new ResponseEntity(productRepositry.findAll(),OK);
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Product {
#Id
#Column(name = "SKU_ID")
String skuId=null;
#ManyToOne
#JoinColumn(name = "category_id")
#JsonManagedReference
Category category;
#Column(name = "description")
String description=null;
#JsonManagedReference
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "stock_id", referencedColumnName = "id")
Stock stock=null;
#JsonManagedReference
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
Set<ProductDetails> productDetails;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Stock {
#Id
#Column(name = "id")
Integer stockId;
#Column(name = "inventory_count")
Integer inventoryCount;
#Column(name = "self_count")
Integer selfCount;
#JsonBackReference
#OneToOne(mappedBy = "stock",cascade = CascadeType.ALL)
Product product;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Category {
#Id
#Column(name = "category_id")
Integer categoryId;
#Column(name = "category_name")
String categoryName;
#OneToMany(mappedBy = "category", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JsonBackReference
List<Product> product;
#Override
public String toString() {
return "Category{" +
"categoryId=" + categoryId +
", categoryName='" + categoryName + '\'' +
", product=" + product +
'}';
}
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class ProductDetails {
#Id
#Column(name = "id")
Integer productDetailId;
#Column(name = "cost")
Double cost;
#Column(name = "currency")
String currency;
#Column(name = "expiry_date")
Date expiryDate;
Integer supplierCode;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
Product product;
}
I think you have missed adding #JoinColumn on Product in ProductDetails entity
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SKU_ID")
Product product;
Also, you can try be removing #JsonManagedReference from Category or Stock

Spring JPA ManyToMany with additional table persists with null id

I have these entities:
#Entity
#Data
#NoArgsConstructor
public class Hero {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
private String name;
#OneToMany(mappedBy = "character", cascade = CascadeType.ALL, orphanRemoval = true)
#NotEmpty
private List<HeroSkill> heroSkills;
}
#Entity
#Data
#NoArgsConstructor
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String name;
}
#Entity
#Data
#NoArgsConstructor
public class HeroSkill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name = "hero_id", referencedColumnName = "id")
#JsonIgnore
private Hero hero;
#ManyToOne
#JoinColumn(name = "skill_id", nullable = false)
private Skill skill;
private int ranks;
}
My HeroService is like this:
#Transactional
public void create(Hero hero) {
heroRepository.save(hero);
}
I am using postman to create a new Hero and this is a sample POST request:
{
"name": "Test",
"heroSkills": [
{
"skill": {
"id": 1
},
"ranks": 4
},
{
"skill": {
"id": 2
},
"ranks": 4
}
]
}
Although a hero is created and two entities on HeroSkill table are also created, the HeroSkill hero_id column is null, so my new heroes are not associated with their skills as they should be.
It works if I modify my service like this:
#Transactional
public void save(Hero hero) {
Hero result = heroRepository.save(hero);
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(result);
heroSkillRepository.save(it);
});
}
But I think that I shouldn't have to pass the Hero manually to each HeroSkill. Isn't that the point of adding CascateType.ALL (which includes CascadeType.PERSIST)?
What is the correct approach to that?
Thanks in advance.
There is no reference of Hero(Parent) in HeroSkill(child) that's why JPA can't resolve hero_id and set as null. Use this heroSkill.setHero(hero) to set parent in child.
To save child with parent in bidirectional relation you have to make sync both side.
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(hero);
});
Hero result = heroRepository.save(hero);

Hibernate ORM: Saving Parent Entity Saves the Children too?

I have the below JSON as input:
{
"type": "Student",
"numOfPeople": "1",
"tenantMembers": [
{
"firstName": "Chris",
"lastName": "C"
}
],
"tenantDetails": {
"firstName": "John",
"lastName": "J",
"email" "xyz#gmail.com"
}
}
I want to use this to do a save:
tenantRepo.save(tenant);
This should save the parent "Tenant" and the children "TenantMembers" and "TenantDetails".
But when I do it does with NULL 'tenant_id's in the children. (If I have foreign keys in the DB gives 'tenant_id' can't be null constraint exception)
My question is: Is this possible in Hibernate?
My models:
Parent class:
#Entity
#Table(name = "tenant")
public class Tenant {
#GeneratedValue
#Id
private Long id;
private String type;
#Column(name = "num_of_people")
private String numOfPeople;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TenantMember> tenantMembers;
#OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL)
private TenantDetails tenantDetails;
TenantMember child class:
#Entity
#Table(name = "tenant_member")
public class TenantMember {
#GeneratedValue
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
TenanatDetails child class:
#Entity
#Table(name="tenant_details")
public class TenantDetails {
#GeneratedValue
#Id
private Long id;
#OneToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
EDIT:
Following up Dragan Bozanovic's suggestion, tried using #JsonIdentityInfo
for the three tables:
#Entity
#Table(name = "tenant")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Tenant {
#Entity
#Table(name="tenant_details")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantDetails {
#Entity
#Table(name = "tenant_member")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantMember {
and did the following to save:
#RequestMapping(value = "/set", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Tenant test(#RequestBody Tenant tenant) {
Tenant t = new Tenant();
t.setType(tenant.getType());
t.setNumOfPeople(tenant.getNumOfPeople());
tenantRepo.save(t);
tenant.setId(t.getId());
tenant.getTenantDetails().setTenant(tenant);
for(TenantMember member: tenant.getTenantMembers()) {
member.setTenant(tenant);
}
return tenantRepo.save(tenant);
}
Would this be the best approach that is possible?
Hibernate does save the children (hence the constraint violation) because of the cascading options you specified, but it does not save the relationship information (join column value) in your case.
TenantMember and TenantDetails are the owners of the association with Tenant (mappedBy attributes in the association annotations in Tenant).
That means that you have to properly update the tenant field in the TenantMember and TenantDetails instances, because Hibernate ignores inverse side of the association when maintaining the relationship.

Categories

Resources