Java Spring Data JPA Composite ID with foreign key - java

I am making a Spring web service to learn more about it and I am currently mapping the database. I have a table that has a composite ID, where one of the ID's is a foreign key to another table (ManytoOne).
Creditors
Creditor_Invoices
ID
Creditor_ID
name
Invoice_ID
As anywhere you buy something they use their own way of making ID's it has a composite ID like this.
My Current code:
Serializable class CInvoiceId:
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.io.Serializable;
import java.util.Objects;
#Embeddable
public class CInvoiceId implements Serializable {
#ManyToOne
#JoinColumn(name = "creditors_id", nullable = false)
private Creditor cInvoiceCreditorId;
#Column(name = "invoice_id", nullable = false)
private String cInvoiceId;
public CInvoiceId(Creditor creditor, String cInvoiceId){
this.cInvoiceCreditorId = creditor;
this.cInvoiceId = cInvoiceId;
}
//Setters, Getters, Equals and Hash
}
My Creditor class
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name = "creditors")
public class Creditor {
#Id
#GeneratedValue
#Column(name = "id")
private int creditorId;
#Column(name = "name",nullable = false)
private String creditorName;
#OneToMany(mappedBy = "cInvoiceCreditorId")
private List<CInvoice> cInvoices;
}
My CInvoice class:
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
#Entity
#Table(name = "c_invoices")
public class CInvoice {
#EmbeddedId
private CInvoiceId cInvoiceID;
}
When I start it to try and test it I get the error that it can not find the mapped by from the creditor class, but I don't know what I should map it to as the ID is now made in the CInvoiceId class. What should it be?
Regards
Dany

You can use "derived identities" to map these classes:
Creditor:
#Entity
#Table(name = "creditors")
public class Creditor {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name",nullable = false)
private String name;
#OneToMany(mappedBy = "creditor")
private List<CInvoice> invoices;
}
CInvoiceId:
#Embeddable
public class CInvoiceId implements Serializable {
#Column(name = "invoice_id", nullable = false)
private String invoiceID;
private int creditorID; // corresponds to PK type of Creditor
// ...
}
CInvoice:
#Entity
#Table(name = "c_invoices")
public class CInvoice {
#EmbeddedId
private CInvoiceId id;
#MapsId("creditorID") // maps creditorID attribute of embedded id
#ManyToOne
#JoinColumn(name = "creditors_id", nullable = false)
Creditor creditor;
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.

Related

How to fix JPA in Spring Boot error IllegalStateException: The column type is undefined?

I am using JPA + Spring Boot for my project. This is the first time I'm using JPA and I'm also very new to Spring Boot.
I want to create two tables file_perms and file_perm_values. I am able to get the definition of file_perms right. However, for my second table file_perm_values, I want to define a composite primary key that consists of the primary key of the file_perms, i.e., id and another String file_id. When I write the definition shown below and use the DDL creation button of Intellij, I get the error Caused by: java.lang.IllegalStateException: The column type is undefined. Table - file_perm_values; Column - file_perm_id
What am I doing wrong?
Could some of the experts out there please help me understand what's the problem and how to fix this?
I'll be very grateful
package com.some.project.persistence.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.ZonedDateTime;
import java.util.UUID;
#Entity
#Getter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#ToString(onlyExplicitlyIncluded = true)
#Table(name = "file_perms")
public class FilePermsEntity {
#Id
#GeneratedValue
#Builder.Default
#ToString.Include
#Column(name = "id", nullable = false)
private UUID id = null;
#ToString.Include
#Column(name = "perm_name", nullable = false)
private String permName;
#ToString.Include
#Column(name = "is_active", nullable = false)
private boolean active;
#ToString.Include
#Column(name = "perm_guid")
private String permGuid;
#ToString.Include
#Column(name = "perm_index")
private int permIndex;
#CreatedDate
#Builder.Default
#ToString.Include
#Column(name = "created_at")
private ZonedDateTime createdAt = ZonedDateTime.now();
#Builder.Default
#ToString.Include
#LastModifiedDate
#Column(name = "updated_at")
private ZonedDateTime updatedAt = ZonedDateTime.now();
}
package com.some.project.persistence.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Table;
import java.io.Serializable;
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString(onlyExplicitlyIncluded = true)
#Table(name = "file_perm_values")
public class FilePermValuesEntity {
#EmbeddedId
#ToString.Include
private FilePermValuesPrimaryKey id;
#ToString.Include
#Column(name = "value")
private String value;
#Getter
#Builder
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#ToString(onlyExplicitlyIncluded = true)
public static class FilePermValuesPrimaryKey implements Serializable {
private static final long serialVersionUID = 1223232L;
#MapsId
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "file_perm_id", nullable = false)
private FilePermsEntity filePermsEntity;
#ToString.Include
#Column(name = "file_id", nullable = false)
private String fileId;
#Override
public boolean equals(Object o) {
...
}
#Override
public int hashCode() {
...
}
}
}
The code below solved my problem:
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString(onlyExplicitlyIncluded = true)
#Table(name = "file_perm_values")
public class FilePermValuesEntity {
#EmbeddedId
#ToString.Include
private FilePermValuesPrimaryKey id;
#MapsId("filePermId")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "file_perm_id")
private FilePermsEntity filePermsEntity;
#ToString.Include
#Column(name = "value")
private String value;
#Getter
#Builder
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#ToString(onlyExplicitlyIncluded = true)
public static class FilePermValuesPrimaryKey implements Serializable {
private static final long serialVersionUID = 1223232L;
#ToString.Include
#Column(name = "file_perm_id", nullable = false)
private UUID filePermId;
#ToString.Include
#Column(name = "file_id", nullable = false)
private String fileId;
#Override
public boolean equals(Object o) {
...
}
#Override
public int hashCode() {
...
}
}
}

getting null value from #Requestbody

I am in the process of adding a DTO layer to a restful api. Before, the program used entity (Recipe and Ingredient) directly and now I added a new DTO layer in between (RecipeDTO IngredientDTO). However, the moment I made the change I started getting Null values from #RequestBody. Each recipe contains a list of Ingredients and it is the list of ingredients that are returning null values, the recipe by itself is returning fine.
The controller looks like this
package com.example.recipes.controller;
import com.example.recipes.DTO.RecipeDTO;
import com.example.recipes.Service.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/*...*/
#PostMapping(path = "/post")
public void postRecipes(#RequestBody RecipeDTO recipeDTO){
recipeService.postRecipes(recipeDTO);
}
/*...*/
Recipe Entity
package com.example.recipes.Entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
#Data
#Entity
#Table(name = "recipe", schema = "public")
#AllArgsConstructor
#NoArgsConstructor
public class Recipe {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "name")
private String name;
#Column(name = "instructions")
private String instructions;
#OneToMany(mappedBy = "recipe")
private List<Ingredient> ingredients;
#JsonProperty("date_added")
private String dateAdded = String.valueOf(LocalDateTime.now());
#JsonProperty("last_edited")
private String lastEdited = String.valueOf(LocalDateTime.now());
}
RecipeDTO
package com.example.recipes.DTO;
import lombok.*;
import javax.persistence.OneToMany;
import java.time.LocalDateTime;
import java.util.List;
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#ToString
public class RecipeDTO {
private long id;
private String name;
private String instructions;
private List<IngredientDTO> ingredientsDTO;
private String dateAdded = String.valueOf(LocalDateTime.now());
private String lastEdited = String.valueOf(LocalDateTime.now());
public RecipeDTO(long id, String name, String instructions, String dateAdded, String lastEdited) {
this.id = id;
this.name = name;
this.instructions = instructions;
this.dateAdded = dateAdded;
this.lastEdited = lastEdited;
}
}
Ingredient Entity
package com.example.recipes.Entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
#Data
#Entity
#Table(name = "Ingredient")
#NoArgsConstructor
public class Ingredient {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#JsonProperty("ingredient_id")
private long ingredient_ID;
#JsonProperty("ingredient_name")
private String ingredientName;
#Column(name = "amount")
private int amount;
#Column(name = "unit")
private String unit;
#ManyToOne
#JoinColumn(name = "recipe_id")
#ToString.Exclude
#JsonIgnore
private Recipe recipe;
}
IngredientDTO
package com.example.recipes.DTO;
import lombok.*;
#Data
#AllArgsConstructor
#NoArgsConstructor
public class IngredientDTO {
private long ingredientID;
private String ingredientName;
private int amount;
private String unit;
}
the json i sent
{
"name":"unique2",
"ingredients":[
{
"ingredient_name":"Atlantic",
"amount":13,
"unit":"ton"
},
{
"ingredient_name":"Pacific",
"amount":15,
"unit":"boatload"
},
{
"ingredient_name":"Indian",
"amount":38,
"unit":"trucload"
}
],
"instructions":"easy on the salt"
}
and the #requestbody the ingredientsDTO is null
this is recipe: RecipeDTO(id=0, name=unique2, instructions=easy on the salt, ingredientsDTO=null, dateAdded=2022-08-08T15:04:10.678748100, lastEdited=2022-08-08T15:04:10.678748100)
Edit: I have just tried copying the code from the entity classes and pasting them in the DTO classes and it still returning null...
package com.example.recipes.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
#Data
#Entity
#Table(name = "recipe", schema = "public")
#AllArgsConstructor
#NoArgsConstructor
public class RecipeDTO {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "name")
private String name;
#Column(name = "instructions")
private String instructions;
#OneToMany(mappedBy = "recipeDTO")
private List<IngredientDTO> ingredientDTOs;
#JsonProperty("date_added")
private String dateAdded = String.valueOf(LocalDateTime.now());
#JsonProperty("last_edited")
private String lastEdited = String.valueOf(LocalDateTime.now());
public RecipeDTO(long id, String name, String instructions, String dateAdded, String lastEdited) {
this.id = id;
this.name = name;
this.instructions = instructions;
this.dateAdded = dateAdded;
this.lastEdited = lastEdited;
}
}
package com.example.recipes.DTO;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
#Data
#Entity
#Table(name = "Ingredient")
#NoArgsConstructor
public class IngredientDTO {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#JsonProperty("ingredient_id")
private long ingredientID;
#JsonProperty("ingredient_name")
private String ingredientName;
#Column(name = "amount")
private int amount;
#Column(name = "unit")
private String unit;
#ManyToOne
#JoinColumn(name = "recipe_id")
#ToString.Exclude
#JsonIgnore
private RecipeDTO recipeDTO;
public IngredientDTO(long ingredientID, String ingredientName, int amount, String unit) {
this.ingredientID = ingredientID;
this.ingredientName = ingredientName;
this.amount = amount;
this.unit = unit;
}
}
#RequestBody
this is recipe: RecipeDTO(id=0, name=unique2, instructions=easy on the salt, ingredientDTOs=null, dateAdded=2022-08-08T15:24:19.325806500, lastEdited=2022-08-08T15:24:19.325806500)
these are the ingredients: null
this is ingredientDTO: null
this is ingredientDTO: null
Edit2: I tried posting only the ingredientDTO and the #RequestBody was able to pick it up just fine
//this is fine
public void testRecipePost(#RequestBody IngredientDTO ingredientDTO) {
System.out.println("ingredientDTO: " + ingredientDTO);
}
You can replace
#OneToMany(mappedBy = "recipeDTO")
private List<IngredientDTO> ingredientDTOs;
to
#OneToMany(mappedBy = "recipeDTO")
private List<IngredientDTO> ingredients;
Or adding
#JsonProperty("ingredients")
Example:
#JsonProperty("ingredients")
#OneToMany(mappedBy = "recipeDTO")
private List<IngredientDTO> ingredientDTOs;
The reason for null is because Jackson doesn't know how to deserialise your fields properly with different names.
In the json, the name is ingredients but, in the DTO, it is ingredientsDTO. Those 2 need to match.
You request
{
"name":"unique2",
"ingredients":[...]
here the name of array you are passing in Json is different in the entity you are using.
Your DTO
#OneToMany(mappedBy = "recipeDTO")
private List<IngredientDTO> ingredientDTOs;
The name of fields in JSON request and Entity must match.
Change the name of field private List<IngredientDTO> ingredientDTOs; to ingredients.

Building Api from mysql database users table, but when testing I get 404 status. Any Advise

I tried to test the API, but I get a 404 status. Any advice?
After testing:
{
"timestamp": "2022-03-29T01:50:58.126+00:00",
"status": 404,
"error": "Not Found",
"path": "/api/v1/users"
}
I'm not sure if the problem is in the database itself or something went wrong with my code for the controller?
User Table:
package com.infosys.models;
import com.infosys.role_type.RoleType;
import lombok.Data;
import javax.persistence.*;
#Entity
#Table(name = "users")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username")
private String userName;
#Column(name = "password")
private String password;
#Column(name = "email" )
private String email;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#ManyToOne
#JoinColumn(name = "role_id", nullable = false)
private RoleType role;
}
Role Table
package com.infosys.models;
import com.infosys.role_type.RoleType;
import lombok.Data;
import javax.persistence.*;
#Entity
#Table(name="role")
#Data
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "role_id")
private RoleType role_type;
}
Employee Table:
package com.infosys.models;
import com.infosys.role_type.RoleType;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalTime;
import java.util.*;
#Entity
#Table(name="employee")
#Data
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "role_id", nullable = false)
private RoleType role;
#ManyToOne
#JoinTable(name = "employee_has_manager",
joinColumns = #JoinColumn(name = "employee_id"),
inverseJoinColumns = #JoinColumn(name = "manager_id")
)
private Set<Manager> manager = new HashSet<>();
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User users;
#Column(name = "start_time")
private LocalTime startTime;
#Column(name = "end_time")
private LocalTime endTime;
#Column(name = "total_hrs")
private Long totalHrs;
}
Manager Table:
package com.infosys.models;
import com.infosys.role_type.RoleType;
import lombok.Data;
import javax.persistence.*;
import javax.persistence.Table;
import java.util.*;
#Entity
#Table(name="manager")
#Data
public class Manager {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User users;
#ManyToOne
#JoinColumn(name = "role_id", nullable = false)
private RoleType role;
#OneToMany(mappedBy = "manager")
private Set<Employee> employee = new HashSet<>();
#Column(name = "employee_review")
private String employeeReview;
}
User Repository:
package com.infosys.repositories;
import com.infosys.models.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
User Controller:
package com.infosys.controllers;
import com.infosys.models.User;
import com.infosys.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.*;
#RestController
#RequestMapping("/api/v1/")
public class UserController {
#Autowired
private UserRepository userRepository;
// get all users
#GetMapping("/users")
public String getAllUsers() {
return "Test";
}
}
Spring App:
package com.infosys;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
#ComponentScan("com.infosys.repositories")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Insert parent and Child in Springboot through Postman

I want to save a record and its child list with spring boot through postman in a One-to-Many relationship. The child list is saved but they don't take the Id of the parent automatically. How can i force the child to automatically take the id of the parent in Post Request In Postman?
Parent Class
package fdsa.edu.pnu.Model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Data
#Table(name = "Concours")
public class Concours implements Serializable {
#Column(name = "ID", nullable = false, length = 10)
#Id
#GeneratedValue(generator = "PNU_CONCOURS_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name = "PNU_CONCOURS_ID_GENERATOR", strategy = "native")
private Integer id;
#Column(name = "DateDebut", nullable = true)
#Temporal(TemporalType.DATE)
private java.util.Date DateDebut;
#Column(name = "DateFin", nullable = true)
#Temporal(TemporalType.DATE)
private java.util.Date DateFin;
#Column(name = "Description", nullable = true, length = 255)
private String description;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "concours",
cascade = CascadeType.ALL,targetEntity = fdsa.edu.pnu.Model.PlannificationConcours.class)
private List<PlannificationConcours> plannificationConcourses;
}
Child Class
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.Serializable;
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "PlannificationConcours")
public class PlannificationConcours implements Serializable {
#Column(name = "ID", nullable = false, length = 10)
#Id
#GeneratedValue(generator = "PNU_PLANNIFICATIONCONCOURS_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name = "PNU_PLANNIFICATIONCONCOURS_ID_GENERATOR", strategy = "native")
private int id;
#ManyToOne(targetEntity = fdsa.edu.pnu.Model.Concours.class, fetch = FetchType.LAZY)
#JoinColumns(value = {#JoinColumn(name = "ConcoursID", referencedColumnName = "ID")}, foreignKey = #ForeignKey(name = "ConcoursPlannificationConCours"))
private Concours concours;
#Column(name = "`Date`", nullable = true)
#Temporal(TemporalType.DATE)
private java.util.Date Date;
#Column(name = "Quotation", nullable = true, length = 10)
private double quotation;
#Column(name = "NoteDePassage", nullable = true, length = 10)
private double noteDePassage;
}```
Screen Shote where the Id of the parent is null
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/LlnhP.png
There are 2 ways to reach it. And this is the minimal setting to do it:
Unidirectional
#Entity
#NoArgsConstructor
#Getter
#Setter
public class Concours {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "concours_id") // this line will play the role that passes the parent's id to its children
private List<PlannificationConcours> plannificationConcourses = new ArrayList<>();
}
#Entity
#NoArgsConstructor
#Getter
#Setter
public class PlannificationConcours {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
#Test
void saveConcours() {
Concours concours = new Concours();
concours.setPlannificationConcourses(List.of(new PlannificationConcours(), new PlannificationConcours()));
this.concoursRepository.save(concours);
}
This's all you need to propagate the parent's id. But the child won't have the reference to its parent by this way.
Bidirectional
#Entity
#NoArgsConstructor
#Getter
public class Concours {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy = "concours" ,cascade = CascadeType.ALL) // mappedBy will create a bidirectional relation for us
private List<PlannificationConcours> plannificationConcourses = new ArrayList<>();
public void addPlannificationConcours(PlannificationConcours child) {
child.setConcours(this); // and don't forget to set the parent instance to the child
this.plannificationConcourses.add(child);
}
}
#Entity
#NoArgsConstructor
#Getter
#Setter
public class PlannificationConcours {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
private Concours concours;
}
#Test
void saveConcours() {
Concours concours = new Concours();
concours.addPlannificationConcours(new PlannificationConcours());
concours.addPlannificationConcours(new PlannificationConcours());
this.concoursRepository.save(concours);
}

Java Spring TransientPropertyValueException with #OneToMany

I have the following classes:
A Product class:
package com.springtraining.hibernate.invoice;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
#NoArgsConstructor
#Getter
#Entity
#Table(name="PRODUCTS")
public class Product {
#Id
#GeneratedValue
#NotNull
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#OneToMany(
targetEntity = Item.class,
mappedBy = "product",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private List<Item> items = new ArrayList<>();
public Product(String name) {
this.name = name;
}
}
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Product;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
#Repository
#Transactional
public interface ProductDao extends CrudRepository<Product, Integer> {
}
Now, an Item class:
package com.springtraining.hibernate.invoice;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
#NoArgsConstructor
#Getter
#Entity
#Table(name="ITEMS")
public class Item {
#Id
#GeneratedValue
#NotNull
#Column(name = "ID")
private int id;
#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "id")
#ManyToOne
private Product product;
#NotNull
#Column(name = "PRICE")
private BigDecimal price;
#NotNull
#Column(name = "QUANTITY")
private int quantity;
#NotNull
#Column(name = "VALUE")
private BigDecimal value;
#JoinColumn(name="INVOICE_ID", referencedColumnName = "id")
#ManyToOne
private Invoice invoice;
public Item(Product product, String price, int quantity) {
this.product = product;
this.product.getItems().add(this);
this.price = new BigDecimal(price);
this.quantity = quantity;
this.value = this.price.multiply(new BigDecimal(quantity));
}
public void setInvoice(Invoice invoice) {
this.invoice = invoice;
invoice.getItems().add(this);
}
}
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Item;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
#Repository
#Transactional
public interface ItemDao extends CrudRepository<Item, Integer> {
}
And an Invoice class:
package com.springtraining.hibernate.invoice;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
#NoArgsConstructor
#Getter
#Entity
#Table(name="INVOICES")
public class Invoice {
#Id
#GeneratedValue
#NotNull
#Column(name = "ID")
private int id;
#NotNull
#Column(name = "NUMBER")
private String number;
#OneToMany(
targetEntity = Item.class,
mappedBy = "invoice",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private List<Item> items = new ArrayList<>();
public Invoice(String number) {
this.number = number;
}
}
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Invoice;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
#Repository
#Transactional
public interface InvoiceDao extends CrudRepository<Invoice, Integer> {
}
Now, when I am running a unit test with these classes, I get the following error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product
The unit test code looks like such:
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Invoice;
import com.springtraining.hibernate.invoice.Item;
import com.springtraining.hibernate.invoice.Product;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
#RunWith(SpringRunner.class)
#SpringBootTest
public class InvoiceDaoTestSuite {
#Autowired
private InvoiceDao invoiceDao;
#Test
public void testInvoiceDaoSave() {
// Given
Product product1 = new Product("prod1");
Product product2 = new Product("prod2");
Item item1 = new Item(product1, "100", 10);
Item item2 = new Item(product1, "200", 10);
Item item3 = new Item(product2, "50", 2);
Item item4 = new Item(product2, "250", 25);
Invoice invoice1 = new Invoice("HK-47");
item2.setInvoice(invoice1);
item3.setInvoice(invoice1);
Invoice invoice2 = new Invoice("HK-48");
item1.setInvoice(invoice2);
item4.setInvoice(invoice2);
// When
invoiceDao.save(invoice1);
invoiceDao.save(invoice2);
int invoice1_id = invoice1.getId();
int invoice2_id = invoice2.getId();
// Then
Assert.assertNotEquals(0, invoice1_id);
Assert.assertNotEquals(0, invoice2_id);
Assert.assertTrue(invoice1.getItems().containsAll(Arrays.asList(item2, item3)));
Assert.assertTrue(invoice2.getItems().containsAll(Arrays.asList(item1, item4)));
Assert.assertTrue(product1.getItems().containsAll(Arrays.asList(item1, item2)));
Assert.assertTrue(product1.getItems().containsAll(Arrays.asList(item3, item4)));
// Clean-up
try {
invoiceDao.deleteById(invoice1_id);
invoiceDao.deleteById(invoice2_id);
} catch (Exception e) {
// Do nothing
}
}
}
I have been looking at this code for a few hours now, and I still do not get it, where I have missed something. Saving Invoice entity, should automatically instantiate Item and Product objects associated with it as well.
Anyone?
In public class Item, add #ManyToOne cascade = CascadeType.ALL property, like so:
#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.ALL)
private Product product;
#JoinColumn(name = "INVOICE_ID", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.ALL)
private Invoice invoice;
When you create a new entity, using the keyword new, it is in the Transient state. To persist/save it to the DB, you first need to add it to the Persistence Context. CascadeType.ALL includes CascadeType.PERSIST, which will instruct Hibernate to persist the product and invoice entities.
Also, remove the #NotNull on your entity fields annotated by #Id. It is not required, since your field is a primary key and instead of this
invoiceDao.save(invoice1);
invoiceDao.save(invoice2);
int invoice1_id = invoice1.getId();
int invoice2_id = invoice2.getId();
You can do:
int invoice1_id = invoiceDao.save(invoice1).getId();
int invoice2_id = invoiceDao.save(invoice2).getId();

Categories

Resources