Spring JPA Self Reference Issue - spring

I created a table, Category in Postgres which holds both parent and child categories and references to itself (it's a self join table)
The table comprises of following columns: id, parent_id, start_date, end_date, status
I also have one row for root parent whose id = 0. So, any first level categories have root as its parent.
Example: Apparel > Women. Here Apparel(id=1) is a first level category whose parent_id = 0. Women is another category whose parent_id = 1.
I am using Spring JpaRepository findAll on my table and this is leading to infinite recursion.
POJO
#Table(name = "ofr_category")
#Getter
#Setter
#NoArgsConstructor
public class Category {
#Id
#Column(name = "cat_id", updatable = true, unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CATEGORY_SEQ")
#SequenceGenerator(sequenceName = "CATEGORY_ID_SEQ", allocationSize = 1, name = "CATEGORY_SEQ")
private Long id;
#Column(name = "cat_name")
private String name;
#Column(name = "cat_status")
private String status;
#Column(name = "start_date")
private LocalDate startDate;
#Column(name = "end_date")
private LocalDate endDate;
#Column(name = "parent_id")
private Long parentId;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", insertable = false, updatable = false)
private Category parentCategory;
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parentCategory")
private List<Category> childCategories;
public Category getParentCategory(){
return parentCategory;
}
}
Exception seen
"Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: java.util.ArrayList[0]->com.test.category.dataobject.Category[\"parentCategory\"]->com.test.category.dataobject.Category[\"parentCategory\"]->com.test.category.dataobject.Category[\"parentCategory\"])",

Maybe you can have a look into #JsonIdentityInfo, which solved a similar problem for me. You can check if this basic annotation works for you.:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Category {
...
}

Related

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException

This is my orderservice implementation for creating and saving orders here with the customerid I'm getting customer and customer has a cart and in the cart product list is there and for that product list, I should create an order.
public Order save(int custid) {
Optional<Customer> cust = customerRepo.findById(custid);//here customer is there and inside customer cart is there inside cart medicine list is there.
Cart ct= cust.get().getCart();//getting cart from customer
if(ct.getMedicineList().size()!=0) {//create order only if the medicine list is not empty. there are 8 fields **orderDate,dispatchDate,status,medicineList,totalCost,customer and orderId(GeneratedValue)** I can't set the orderId cuz it is auto generated.
LocalDate todaysDate = LocalDate.now();
LocalDate dispatchdate = todaysDate.plusDays(3);
List<Medicine> orderList= new ArrayList<Medicine>();
List<Medicine> cartList= new ArrayList<Medicine>();
cartList=ct.getMedicineList();
orderList.addAll(cartList);
Order ord = new Order();
ord.setCustomer(cust.get());
ord.setMedicineList(orderList);
ord.setDispatchDate(dispatchdate);
ord.setOrderDate(todaysDate);
ord.setStatus("Placed");
ord.setTotalCost((float)ct.getTotalAmount());
logger.info("Add order to the database");
return orderRepository.save(ord);
}
return null;
}
this is my order controller
#PostMapping("/order/{custid}")
public ResponseEntity<Order> addOrder(#Valid #PathVariable("custid") int custid) {
logger.info("Add order in database");
return new ResponseEntity<>(orderService.save(custid), HttpStatus.OK);
}
this is my medicine Entity
#Data
#RequiredArgsConstructor
#ToString
#Table(name = "medicine")
#Entity
public class Medicine {
#Id
#Column(name = "medicine_id", nullable = false)
#NonNull
#GeneratedValue
private int medicineId;
#NonNull
#Size(min = 3, message = "Minimum charecters in medicine name should be 3.")
#NotEmpty
#Column(unique = true, name = "medicine_name", nullable = false)
private String medicineName;
#NonNull
#Column(name = "medicine_cost", nullable = false)
private float medicineCost;
#NonNull
#Column(name = "mfd", nullable = false)
private LocalDate mfd;
#NonNull
#Column(name = "expiry_date", nullable = false)
private LocalDate expiryDate;
#NonNull
#Column(name = "medicine_quantity", nullable = false)
private int medicineQuantity = 1;
#NonNull
private String medicineCategory;
#NonNull
private String medicineDescription;
#JsonIgnore
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "medicineList",fetch = FetchType.EAGER)
private List<Order> orderList;
}
this is my order Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Order {
#Id
#Column(name = "orderId")
#GeneratedValue
private int orderId;
#NonNull
private LocalDate orderDate;
#ManyToMany(targetEntity = Medicine.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "ord_med", joinColumns = { #JoinColumn(name = "ord_id") }, inverseJoinColumns = {
#JoinColumn(name = "med_id") })
private List<Medicine> medicineList = new ArrayList<>();
#NonNull
private LocalDate dispatchDate;
#NotEmpty
private float totalCost;
#NonNull
private String status;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="c_ord_fk",referencedColumnName = "customerId")
#NonNull
private Customer customer;
}
here when i try to create order for the list inside cart it gives me [36m.m.m.a.ExceptionHandlerExceptionResolver[0;39m [2m:[0;39m Resolved [org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction]
i'm not sure but i think its because of the id but it is autogenerated. Idk how to create an order object for the list of products or is this the correct way to do it.
Actually I found the answer, all I have to do is when you use ManyToMany mapping the cascade type must be cascade = {CascadeType.MERGE,CascadeType.REFRESH} instead of {cascade = CascadeType.ALL} and it works fine. reference JPA: detached entity passed to persist: nested exception is org.hibernate.PersistentObjectException

How do I map an #OneToMany and #ManyToOne relationship properly so that I can save and update the #OneToMany side with or without the #ManyToOne side

I have an app with Angular front end and Spring backend. The two classes in question here are (backend):
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "tournament_games")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class TournamentGame {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "code", foreignKey = #ForeignKey(name = "code_fk"))
private TournamentCode code;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "type", foreignKey = #ForeignKey(name = "game_type_fk"))
private GameType type;
#Column(name = "home_score")
private int home_score;
#Column(name = "away_score")
private int away_score;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "result_type", foreignKey = #ForeignKey(name = "result_type_fk"))
private ResultType result_type;
#Column(name = "status")
private boolean status;
#Column(name = "round")
private int round;
#Column(name = "locked")
private boolean locked;
#OneToMany(mappedBy = "game", fetch = FetchType.EAGER)
private List<TournamentGamesPlayers> players = new ArrayList<>();
}
and
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "tournament_games_players")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "game")
public class TournamentGamesPlayers implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "tournament_game_id")
private TournamentGame game;
#Id
#ManyToOne
#JoinColumn(name = "playerid")
private Player player;
#Column(name = "home")
private boolean home;
}
I need help figuring out how to persist the List<TournamentGamesPlayers> when I save and/or update a TournamentGame object. I generate 45 games. The first 30 games have known players, and so I set them before saving. The last 15 do not have entries for the TournamentGamesPlayers join table, because I need to add them later.
I am able to get some results with CascadeType.ALL on the #OneToMany side when I initially generate the games, but it fails when I try to update a game with a seemingly infinite recursion/stack overflow.
If I omit any cascade type, the games side get generated, but the join table #ManyToOne side does not get entered.
I ended up just putting the players back into the game table to make my life easier.
try putting CascadeType.MERGE, CascadeType.ALL "delete parent and orphans" (JPA CascadeType.ALL does not delete orphans).
Also, defining the relationship as EAGER and not ignoring the JSON property can have problems. I would add #JsonIgnore to one of the parts of the relationship

How to write custom findAll() with Specification in JPA repository

When I use skuRepository.getAll(), it works OK, but when I apply filters, defined in Specification (List filteredRegs = skuRepository.getAll(specification)) I still get all the rows of the table
What should i do to apply the specifications to my custom method?
public interface SkuRepository extends CrudRepository<Sku, Integer>, JpaSpecificationExecutor<Sku> {
#Query("select s from Sku s join fetch s.unit un join fetch s.supplier sup WHERE un.id = sku_unit_id AND sup.id = supplier_id")
List<Sku> getAll(#Nullable Specification<Sku> var1);
#Query("select s from Sku s join fetch s.unit un join fetch s.supplier sup WHERE un.id = sku_unit_id AND sup.id = supplier_id")
List<Sku> getAll();
}
UPD:
Here is my entities.
When I make sampling by a Sku table using the Specification API, I see three separate selects in log: one for Sku entity, one for Unit and one for Suppliers. I want my app to make one select with joins.
I read that this is due to the fact that I use EAGER fetch type, so I change it to LAZY, but then I got another problem: "InvalidDefinitionException: No serializer found..." This is logical because related entities Unit and Supplier are not loaded.
Then I decided to write my custom getAll() with request:
#Query("select s from Sku s join fetch s.unit un join fetch s.supplier sup WHERE un.id = sku_unit_id AND sup.id = supplier_id ORDER BY s.name")
But now it does not support Specification.
Please advise what to do.
#Entity
#Table(name = "sku")
public class Sku implements Cloneable, CloneableEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "sku_code", length = 6, nullable = false, unique = true)
private String code;
#Column(name = "sku_name", nullable = false)
private String name;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "sku_unit_id", nullable = false)
private Unit unit;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "supplier_id", nullable = false)
private Supplier supplier;
#Column(name = "qty_in_sec_pkg")
private int quantityInSecondaryPackaging;
#Column(name = "sku_is_active", nullable = false)
private boolean isActive;
//constructors, getters, setters
}
#Entity
#Table(name = "units")
public class Unit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id ")
private int id;
#Column(name = "unit", nullable = false, unique = true)
private String unit;
#Column(name = "description")
private String description;
//constructors, getters, setters
}
#Entity
#Table(name = "suppliers")
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id ")
private int id;
#Column(name = "supplier_code", length = 6, nullable = false, unique = true)
private String supplierCode;
#Column(name = "supplier_name", nullable = false)
private String name;
#Column(name = "create_date", length = 19, nullable = false)
private String createDate;
#Column(name = "update_date", length = 19)
private String updateDate;
//constructors, getters, setters
}
You can't mix #Query and Specification
You can only use JpaSpecificationExecutor interface methods to use Specification.
Find more details here

Inner join on two tables in spring boot

I have 2 entities and want to perform an inner join on the ID of these two tables. How do I do that? After joining the tables, how do I get the values?
First entity: Employee.java
#Entity
#Table(name = "emp")
public class Employee {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "language", nullable = false)
private String language;
Second entity: Username.java
#Entity
#Table(name = "users")
public class Username {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "name", nullable = false)
private String name;
Thanks
I don't know it's helpful for your or not but,
You have to give relationship between those table first(Here i defined bidirectional relationship).
I suppose there is #OneToOne mapping. As like follow,
In Employee Table,
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "username_id")
private Username username;
#OneToOne(mappedBy = "employee")
private Employee employee;
Same way whenever you need those data base on requirement then Place Query as following way in your Employee Repository,
#Query(nativeQuery = true, value="<your-join-query>")
public Employee getEmployeeAllDetails();
For more brief detail follow this kind of tutorials which give you better idea regurding working mechenisum.
https://howtodoinjava.com/
https://www.baeldung.com/

Spring data JPA join table with extra column

I'm trying to implement a meeting model which contains multiple equipment entity with corresponding quantity.
In the view of meeting, user should be able to CRUD equipment and quantity of this equipment of a meeting
databases:
CREATE TABLE IF NOT EXISTS equipment (
equipment_id SERIAL PRIMARY KEY,
equipment_name VARCHAR(20) NOT NULL
);
CREATE TABLE IF NOT EXISTS meeting (
meeting_id SERIAL PRIMARY KEY,
meeting_time TIMESTAMP NOT NULL,
number_people INTEGER NOT NULL,
setup VARCHAR(255)
);
CREATE TABLE IF NOT EXISTS meeting_equipment (
meeting_equipment_id SERIAL PRIMARY KEY ,
meeting_id INTEGER NOT NULL REFERENCES meeting (meeting_id),
equipment_id INTEGER NOT NULL REFERENCES equipment (equipment_id),
quantity INTEGER NOT NULL DEFAULT 0
);
Entity implementation:
#Entity
#Table(name = "meeting")
#Data
public class Meeting {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "meeting_id", updatable = false)
#JsonIgnore
private int id;
#Column(name = "meeting_time")
#JsonFormat(pattern = "yyyy-MM-dd HH:mm")
#NotNull
private LocalDateTime meetingTime;
#Column(name = "number_people")
#NotNull
#Min(1)
private int numberPeople;
#Column(name = "setup")
#NotNull
private String setup;
#OneToMany(mappedBy = "meeting", cascade = CascadeType.ALL)
#JsonManagedReference
List<MeetingEquipment> equipmentList = new ArrayList<>();
}
#Entity
#Table(name = "equipment")
#Data
public class Equipment {
#Id
#Column(name = "equipment_id", updatable = false)
#JsonIgnore
private int id;
#NotNull
#Column(name = "equipment_name", unique = true)
#Size(min = 1, max = 100)
private String equipmentName;
}
Join table metting_equipment:
#Entity
#Table(name = "meeting_equipment", uniqueConstraints = {
#UniqueConstraint(columnNames = {"meeting_id", "equipment_id"})})
#Data
public class MeetingEquipment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "meeting_equipment_id", updatable = false)
#JsonIgnore
private int id;
#ManyToOne
#JoinColumn(name = "meeting_id")
#NotNull
#JsonBackReference
private Meeting meeting;
#ManyToOne
#JoinColumn(name = "equipment_id")
#NotNull
private Equipment equipment;
#Column(name = "quantity")
#NotNull
private int quantity;
}
Using the code above, I can successfully create meeting with equipment included (JSON returned from creation method shows correct content). But once I try to remove an element of equipmentList in meeting entity, it does not delete meetingEquipment entity. I tried
meeting.getEquipmentList().clear() and meetingEquipmentDao.delete(meeting.getEquipmentList()), neither works.
Could anyone tell me the cause of this problem, thanks!

Resources