incorrectly joining 2 entities in spring data jpa - spring

#Entity
#Table(name = "product_table")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Product_id" , nullable = false, unique= true , length = 5)
private int ProductId;
#Column(name = "Product_Name" ,nullable = false , length = 50)
private String ProductName;
#Column(name = "Description" ,nullable = false , length = 200)
private String Description;
#Column(name ="Price" , nullable = false, unique= true , length = 5)
private Double Price;
#Column(name = "Discount" , nullable = false, unique= true , length = 5)
private Double Discount;
#Column(name ="Delivery_Charges" , nullable = false, unique= true , length = 5)
private Double DeliveryCharges;
#Column(name = "Avg_Rating",nullable = false, unique= true , length = 5)
private int AvgRating;
#Column(name = "Seller_Name",nullable = false, unique= true , length = 15)
private String SellerName;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Product_id", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
private Cart Cart;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ProductId", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
private WishList WishList;
public String getSellerName() {
return SellerName;
}
public void setSellerName(String sellerName) {
SellerName = sellerName;
}
public int getProductId() {
return ProductId;
}
public void setProductId(int productId) {
ProductId = productId;
}
public String getProductName() {
return ProductName;
}
public void setProductName(String productName) {
ProductName = productName;
}
public String getDescription() {
return Description;
}
public void setDescription(String description) {
Description = description;
}
public Double getPrice() {
return Price;
}
public void setPrice(Double price) {
Price = price;
}
public Double getDiscount() {
return Discount;
}
public void setDiscount(Double discount) {
Discount = discount;
}
public Double getDeliveryCharges() {
return DeliveryCharges;
}
public void setDeliveryCharges(Double deliveryCharges) {
DeliveryCharges = deliveryCharges;
}
public int getAvgRating() {
return AvgRating;
}
public void setAvgRating(int avgRating) {
AvgRating = avgRating;
}
}
#Entity
#Table(name = "Cart_table")
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Cart_Id" , nullable = false, unique = true, length = 5)
private int CartId;
#Column(name = "Product_Name" , nullable = false, length = 50)
private String ProductName;
#Column(name = "Seller_Name" , nullable = false,length = 15)
private String SellerName;
#Column(name = "Quantity" , nullable = false)
private int Quantity;
#Column(name = "Cart_Offer_Price" , unique = true)
private Double CartOfferPrice;
#Column(name = "Product_id" , nullable = false, unique = true, length = 5)
private int ProductId;
public int getProductId() {
return ProductId;
}
public void setProductId(int productId) {
ProductId = productId;
}
#OneToOne(targetEntity = Product.class, mappedBy = "Cart", orphanRemoval = false, fetch = FetchType.LAZY)
private Product product;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Cart_Id", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
private OrdersCartMapping OrdersCartMapping;
public int getCartId() {
return CartId;
}
public void setCartId(int cartId) {
CartId = cartId;
}
public String getProductName() {
return ProductName;
}
public void setProductName(String productName) {
ProductName = productName;
}
public String getSellerName() {
return SellerName;
}
public void setSellerName(String sellerName) {
SellerName = sellerName;
}
public int getQuantity() {
return Quantity;
}
public void setQuantity(int quantity) {
Quantity = quantity;
}
public Double getCartOfferPrice() {
return CartOfferPrice;
}
public void setCartOfferPrice(Double cartOfferPrice) {
CartOfferPrice = cartOfferPrice;
}
}
#Repository
public interface CartRepository extends JpaRepository<Cart , Integer> {
#Query("SELECT new com.megamartonline.dto.CartProductDto(c.ProductName,c.CartOfferPrice,c.Quantity , p.Price ,p.Discount,p.DeliveryCharges,c.CartId ) "
+ "FROM Cart c INNER JOIN c.product p")
List<CartProductDto> fetchProductCartDataInnerJoin();
}
Here i m trying to join Product with Cart using Product_id column but when i test my repository method it is incorrectly joining as below
select
cart0_.product_name as col_0_0_,
cart0_.cart_offer_price as col_1_0_,
cart0_.quantity as col_2_0_,
product1_.price as col_3_0_,
product1_.discount as col_4_0_,
product1_.delivery_charges as col_5_0_
from
cart_table cart0_
inner join
product_table product1_
on cart0_.cart_id=product1_.product_id
please help , what i am doing wrong here.
Here i m trying to join 2 Product with Cart using Product_id column but when i test my repository method it is incorrectly joining.

In Product, instead of productId, you have to declare the join to JPA in the entity this way:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Product_id")
private Product Product;
Hibernate now knows how to handle the join.
You can't use both an ID column and a JoinColumn; it won't know which one to use, so you should remove productId.
I suggest a #ManyToOne because the #OneToOne join you are using seems strange for a cart. Usually a cart has multiple (many) products.

You can easily change the fetch type from lazy to eager

Related

Spring specification with custom cross join by simple attribute

Following scenario
#Entity("YEAR")
public class Year{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "NAME", nullable = false, length = 10)
public Long name;
...
}
#Entity("FOO")
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "FK_YEAR", nullable = false)
public Long yearId;
#Column(name = "NAME", nullable = false, length = 10)
public String name;
...
}
#Entity("FII")
public class Fii {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "FK_YEAR", nullable = false)
public Long yearId;
#Column(name = "CODE", nullable = false, length = 10)
public String code;
...
}
#Entity("NTOM")
public class NtoM {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "FK_FOO", nullable = false)
public Long fooId;
#Column(name = "FK_FII", nullable = false)
public Long fiiId;
#Column(name = "STATE", nullable = false)
public Boolean state;
#Column(name = "VALUES", length = 500)
public String values;
...
}
Resulting in an ERP like this:
I now do have a JpaRepository like this:
#Repository
public interface NtoMRepository extends JpaRepository<NtoM, Long>, JpaSpecificationExecutor<NtoM> {
String BASE_QUERY =
"SELECT"
// prevent jpa to return null instead of id=0
+ " CASE WHEN ntom.ID IS NULL THEN 0 ELSE ntom.ID END AS ID ,"
+ " CASE WHEN ntom.STATE IS NULL THEN 0 ELSE ntom.STATE END AS STATE ,"
+ " ntom.VALUES,"
+ " fii.ID AS FK_FII,"
+ " foo.ID AS FK_FOO "
+ " FROM MYSCHEMA.FOO foo"
+ " INNER JOIN MYSCHEMA.FII fii ON fii.FK_YEAR = foo.FK_YEAR"
+ " OUTER JOIN MYSCHEMA.NTOM ntom ON ntom.FK_FII = fii.ID AND ntom.FK_FOO = foo.ID"
#Query(value = BASE_QUERY + " WHERE fii.ID = :fiiId", nativeQuery = true)
List<Option> findByFiiId(#Param("fiiId") Long fiiId);
#Query(value = BASE_QUERY + " WHERE foo.ID = :fooId", nativeQuery = true)
List<Option> findByFooId(#Param("fooId") Long fooId);
}
So the 2 queries here compute me missing entries whenever I call them, which works out quite nicely.
How could I use the "toPredicate" of the https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/ to accomplish this by creating a similar behavior but with dynamic parameters?
I cant just use criteriabuilder "join" as the values are only "basic attributes". Also do I want to reuse the dynamic approach as the "controller endpoint can look like"
#GetMapping
public List<NtoM> find(#RequestParam(value = "id", required = false, defaultValue = "0") Long id,#RequestParam(value = "fiiId", required = false, defaultValue = "0") Long fiiId, #RequestParam(value = "fooId", required = false, defaultValue = "0") Long fooId){
Specification<NtoM> spec = ... //build as AND construct of all parameters (if not null or empty add it)
// TODO instead of the SELECT * FROM myschema.ntom the custom query here!
return repo.findAll(spec);
}
How can I do this. I can also use the EntityManager and the criteriaBuilder.createTupleQuery(). But it seems to not work (I cant join the tables as there is no "ManyToOne" between them)
Why aren't there relationships in your domain model? It can be handled efficiently if you change your model to have relationships. Here is how I would approach this problem:
Start with creating relationships.
#Entity("YEAR")
public class Year{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "NAME", nullable = false, length = 10)
public Long name;
...
}
#Entity("FOO")
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
//#Column(name = "FK_YEAR", nullable = false)
//public Long yearId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_YEAR", referencedColumnName = "ID")
public Year year;
#Column(name = "NAME", nullable = false, length = 10)
public String name;
...
}
#Entity("FII")
public class Fii {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
//#Column(name = "FK_YEAR", nullable = false)
//public Long yearId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_YEAR", referencedColumnName = "ID")
public Year year;
#Column(name = "CODE", nullable = false, length = 10)
public String code;
...
}
#Entity("NTOM")
public class NtoM {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
//#Column(name = "FK_FOO", nullable = false)
//public Long fooId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_FOO", referencedColumnName = "ID")
public Foo foo;
//#Column(name = "FK_FII", nullable = false)
//public Long fiiId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_FII", referencedColumnName = "ID")
public Fii fii;
#Column(name = "STATE", nullable = false)
public Boolean state;
#Column(name = "VALUES", length = 500)
public String values;
...
}
Change the controller to get the request parameters into map.
Then delegate the data retrieving logic to the service layer (You can also create custom repository impelementation).
#GetMapping
public List<NtoM> find(#RequestParam Map<String, String> requestParams) {
return service.findByRequestParams(requestParams);
}
In the service class
public List<NtoM> findByRequestParams(Map<String, String> requestParams) {
return repository.findAll(createSpec(requestParams));
}
private Specification<NtoM> createSpec(Map<String, String> requstParams ) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
Join<NtoM, Fii> firstJoin = root.join("fii", JoinType.INNER);
Join<NtoM, Foo> secondJoin = fiiJoin("foo", JoinType.LEFT);
String value = requstParams.get("id");
if(StringUtils.isNotBlank(value)) {
Predicate id = criteriaBuilder.equal(secondJoin.get("id"), Long.parseLong(value));
predicates.add(id);
}
value = requestParams.get("fiiId");
if(StringUtils.isNotBlank(value)) {
Predicate fii = criteriaBuilder.equal(secondJoin.get("fii"), Long.parseLong(value));
predicates.add(fii);
}
value = requestParams.get("fooId");
if(StringUtils.isNotBlank(value)) {
Predicate foo = criteriaBuilder.equal(secondJoin.get("foo"), Long.parseLong(value));
predicates.add(foo);
}
//Later you can add new options without breaking the existing API
// For example like search by values
value = requestParams.get("values");
if(StringUtils.isNotBlank(value)) {
Predicate likeValues = criteriaBuilder.like(secondJoin.get("values"), "%" + value + "%");
predicates.add(likeValues);
}
return criteriaBuilder.and(predicates.toArray(Predicate[]::new));
};
}

How to do a ManyToMany relationship insert

I am studying spring boot data using this API SWAPI, I did almost things but now I dont know how to map the relationship about two lists, above you can see my code and entities.
Entity Film
#Data
#Entity
public class Film extends Persistent<Long> {
private String title;
#JsonProperty(value = "episode_id")
private int episodeId;
#JsonProperty(value = "opening_crawl")
#Column(columnDefinition = "CLOB")
private String openingCrawl;
private String director;
private String producer;
#JsonDeserialize(converter = StringToLocalDateConverter.class)
#JsonProperty(value = "release_date")
private LocalDate releaseDate;
#JsonDeserialize(converter = ApiURLToEntitiesConverter.class)
#ManyToMany(mappedBy = "films")
private List<Person> characters;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime created;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime edited;
private String url;
}
Entity Person
#Data
#Entity
public class Person extends Persistent<Long> {
private String name;
private String height;
private String mass;
#JsonProperty(value = "hair_color")
private String hairColor;
#JsonProperty(value = "skin_color")
private String skinColor;
#JsonProperty(value = "eye_color")
private String eyeColor;
#JsonProperty(value = "birth_year")
private String birthYear;
private String gender;
#JsonDeserialize(converter = ApiURLToEntityConverter.class)
#JoinColumn(name = "planet_id", foreignKey = #javax.persistence.ForeignKey(name = "none"))
#OneToOne(optional = true)
private Planet homeworld;
#JsonDeserialize(converter = ApiURLToEntitiesConverter.class)
#ManyToMany
#JoinTable(
name = "film_person",
joinColumns = #JoinColumn(name = "film_fk", referencedColumnName = "id", nullable = true),
inverseJoinColumns = #JoinColumn(name = "person_fk", referencedColumnName = "id", nullable = true))
private List<Film> films;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime created;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime edited;
private String url;
}
I am trying to use the spring jpa method to saveAll
#Override
public List<T> insertAll(List<T> entities) {
for (Persistent entity : entities) {
Set<ConstraintViolation<Persistent>> violations = validator.validate(entity);
if (violations != null && !violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
return repository.saveAll(entities);
}
Converter Method
#Override
public List convert(List<String> s) {
if (s == null || s.isEmpty()) {
return null;
}
List objetos = new LinkedList();
for (String url : s) {
if (url.contains("people")) {
objetos.add(Util.getPerson(url));
}
if (url.contains("planets")) {
objetos.add(Util.getPlanet(url));
}
if (url.contains("starships")) {
objetos.add(Util.getStarship(url));
}
if (url.contains("vehicles")) {
objetos.add(Util.getVehicle(url));
}
if (url.contains("species")) {
objetos.add(Util.getSpecie(url));
}
}
return objetos;
}
}
Util method
public static Person getPerson(String characterApiUrl) {
if (characterApiUrl == null || characterApiUrl.isEmpty()) {
return null;
}
Person person = new Person();
person.setId(StringUtil.getIdEntity(characterApiUrl, "people/"));
return person;
}
The relationship table is being created but no populated

Not null reference a null or transient value

So i am trying to achieve oneToone relationship between two entity classes.First class is a customer entity class which have two foreign keys buyer_id and seller_id.So what i want initially is that when the user fills the initial credentials in the website the buyer_id and seller_id field should be null and after the user fills the required information for the buyer or seller i will update the row of the corresponding customer and add the buyer_id and seller_id.But when i try to create a customer entry i am getting this error that buyer_id cannot be null?
This is my customer table
#Entity
#Table(name = "Customer")
public class Customer {
public enum Status{
ACTIVE,
IN_ACTIVE
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#OneToOne(fetch = FetchType.LAZY,optional = true,cascade=CascadeType.ALL)
#JoinColumn(name = "seller_id",nullable = true,referencedColumnName = "id",updatable = true)
#Basic(optional = true)
private Seller seller_id;
#OneToOne(fetch=FetchType.LAZY,optional = true,cascade=CascadeType.ALL)
#JoinColumn(name = "buyer_id", nullable = true,referencedColumnName="id",updatable = true)
#Basic(optional = true)
private Buyer buyer_id;
#OneToOne(fetch=FetchType.LAZY,optional = false,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id",nullable = false,unique = true,referencedColumnName = "id")
private User user_id;
public Buyer getBuyer_id() {
return buyer_id;
}
public void setBuyer_id(Buyer buyer_id) {
this.buyer_id = buyer_id;
}
#Column(name = "Name")
String name;
#Enumerated(EnumType.STRING)
#Column(name = "Status")
private Status status;
public Customer(String name,Status status){
this.name=name;
this.status = status;
}
public Customer(){
}
public Seller getSeller_id() {
return seller_id;
}
public void setSeller_id(Seller seller_id) {
this.seller_id = seller_id;
}
public User getUser_id() {
return user_id;
}
public void setUser_id(User user_id) {
this.user_id = user_id;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getUser() {
return user_id;
}
public void setUser(User user) {
this.user_id = user;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
This is my buyer table
#Entity
#Table(name="Buyer")
public class Buyer {
#Id
#Column(name = "id") private long id;
#Column(name = "GSTIN")
String GSTIN;
#Column(name = "Legal_Document")
#Lob
private byte[] legalDocument;
#OneToOne(fetch=FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "buyer_id")
#JsonIgnore
private Customer customer;
#Column(name = "Authorized_person_name")
String authorized_person_name;
#Column(name = "Authorized_person_email")
String authorized_person_email;
}
This is my seller table
#Entity
#Table(name = "Seller")
public class Seller {
#Id
#Nullable
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "GSTIN")
private String GSTIN;
#Column(name = "GST_Document")
#Lob
private byte[] gst_document;
#OneToOne(fetch=FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "seller_id")
#JsonIgnore
private Customer customer;
// #OneToOne(fetch = FetchType.LAZY,
// cascade = CascadeType.ALL,
// mappedBy = "sellerId")
// #JsonIgnore
// private PickupAddress pickupAddress;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
public String getGSTIN() {
return GSTIN;
}
public void setGSTIN(String GSTIN) {
this.GSTIN = GSTIN;
}
public byte[] getGst_document() {
return gst_document;
}
public void setGst_document(byte[] gst_document) {
this.gst_document = gst_document;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

#Id Not Mapped For Specific Entity In A Spring-Boot Controller

I have two entities Employee and Department and each have a Spring Web #RestController annotated class with update methods i.e. Http PUT.
For some strange reason (and likely a blindingly obvious solution) whenever the PUT is called for the Employee class, the ID in the JSON payload is NOT mapped to the id class of the Employee entity but it works perfectly for the Department entity.
Employee class:
Entity
#Table(name = "EMPLOYEE")
public class Employee implements Serializable, Identity<Long>, Deleted
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "NAME")
private String name;
#ManyToOne
#Where(clause = "is_deleted = false")
private Department department;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "EMP_QUAL", joinColumns = #JoinColumn(name = "EMPLOYEE_ID"), inverseJoinColumns = #JoinColumn(name = "QUALIFICATION_ID"))
#WhereJoinTable(clause = "IS_DELETED = false")
#SQLDelete(sql = "UPDATE `EMP_QUAL` SET IS_DELETED = true where EMPLOYEE_ID = ? and QUALIFICATION_ID = ? and IS_DELETED = False")
private Set<Qualification> qualifications;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "EMP_PROJ", joinColumns = #JoinColumn(name = "emp_id"), inverseJoinColumns = #JoinColumn(name = "proj_id"))
#Where(clause = "is_deleted = false")
private Set<Project> projects;
#JsonIgnore
#Column(name = "is_deleted", nullable = false)
private Boolean isDeleted = false;
#Override
public Long getId()
{
return this.id;
}
#Override
public void setId(final Long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(final String name)
{
this.name = name;
}
public Set<Project> getProjects()
{
return projects;
}
public void setProjects(final Set<Project> projects)
{
this.projects = projects;
}
public Department getDepartment()
{
return department;
}
public void setDepartment(final Department department)
{
this.department = department;
}
public Set<Qualification> getQualifications()
{
return qualifications;
}
public void setQualifications(final Set<Qualification> qualifications)
{
this.qualifications = qualifications;
}
public Boolean isDeleted()
{
return isDeleted;
}
public void setDeleted(final Boolean deleted)
{
isDeleted = deleted;
}
}
Department class:
#Entity
#Table(name = "DEPARTMENT")
public class Department implements Serializable, Identity<Long>, Deleted
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.EAGER)
#WhereJoinTable(clause = "is_deleted = false")
#JoinTable(name = "DEPARTMENT_EMPLOYEE", joinColumns = {#JoinColumn(name = "department_id")},
inverseJoinColumns = {#JoinColumn(name = "employee_id")})
#SQLDelete(sql = "UPDATE DEPARTMENT_EMPLOYEE SET is_deleted = true where department_id = ? and employee_id = ? and is_deleted = false")
private Set<Employee> departmentMembers;
#Column(name = "is_deleted", nullable = false)
private Boolean isDeleted;
#Override
public Long getId()
{
return this.id;
}
#Override
public void setId(final Long id)
{
this.id = id;
}
#Override
public Boolean isDeleted()
{
return this.isDeleted;
}
#Override
public void setDeleted(final Boolean isDeleted)
{
this.isDeleted = isDeleted;
}
public String getName()
{
return name;
}
public void setName(final String name)
{
this.name = name;
}
public Set<Employee> getDepartmentMembers()
{
return departmentMembers;
}
public void setDepartmentMembers(final Set<Employee> departmentMembers)
{
this.departmentMembers = departmentMembers;
}
}
When call PUT /employees/{id}:
Calling PUT /departments/{id}:
As you can see in the screenshots of the debugger the id field of Department is populated while it is null in Employee. I'm testing this with Swagger and I am setting the ID in the payload. I don't have any specific Jackson configuration set I just use Spring boot's default but I cannot work out why only in Employee the id field is never mapped.
Employee body:
{
"id":1,
"name": "New Name"
}
Department body:
{
"id":2,
"name": "chemistry",
"deleted":false
}
The issue was due to a Jackson annotation #JsonIdentityInfo on another entity Project which is has a relationship with Employee:
#Entity
#Table(name = "PROJECT")
public class Project implements Serializable, Identity<Long>, Deleted
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "EMP_PROJ", joinColumns = #JoinColumn(name = "proj_id"), inverseJoinColumns = #JoinColumn(name = "emp_id"))
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
#Where(clause = "is_deleted = false")
private Set<Employee> employees;

Spring Data rest how to perform CRUD on #manytomany relation ,composite table with extra column

I am unable to perform CRUD via json POST from restful client Postman on Composite table having extra column .I am using Spring boot ,spring data rest and spring JPA.
I have 3 tables in data base
-user
-competency
-user_competency (join/composite table with extra column)
Here are my classes
User
#Entity
#Table(name = "\"user\"", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "userId")
public class User implements java.io.Serializable {
private Long userId;
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#OneToMany(fetch = FetchType.EAGER,cascade = {CascadeType.ALL}, mappedBy = "user")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
Competency
#Entity
#Table(name = "competency", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "competencyId")
public class Competency implements java.io.Serializable {
private Long competencyId;
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "competency_id", unique = true, nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "competency")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
UserCompetency
#Entity
#Table(name = "user_competency", schema = "public")
#JsonIdentityInfo(
generator =ObjectIdGenerators.IntSequenceGenerator.class,
property = "id")
public class UserCompetency implements java.io.Serializable {
private UserCompetencyId id;
private Level level;
private User user;
private Competency competency;
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "competencyId", column = #Column(name = "competency_id", nullable = false)),
#AttributeOverride(name = "userId", column = #Column(name = "user_id", nullable = false)) })
public UserCompetencyId getId() {
return this.id;
}
public void setId(UserCompetencyId id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "level_id")
public Level getLevel() {
return this.level;
}
public void setLevel(Level level) {
this.level = level;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
public Competency getCompetency() {
return this.competency;
}
public void setCompetency(Competency competency) {
this.competency = competency;
}
}
UserCompetencyId
#Embeddable
public class UserCompetencyId implements java.io.Serializable {
private Long competencyId;
private Long userId;
public UserCompetencyId() {
}
public UserCompetencyId(Long competencyId, Long userId) {
this.competencyId = competencyId;
this.userId = userId;
}
#Column(name = "competency_id", nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#Column(name = "user_id", nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof UserCompetencyId))
return false;
UserCompetencyId castOther = (UserCompetencyId) other;
return (this.getCompetencyId() == castOther.getCompetencyId()) && (this.getUserId() == castOther.getUserId());
}
}
Suppose i have already record in User and Competency tables and i want to assocaite both i am trying to post like this ,but it give me error of 405 Method Not Allowed.
help required ,what should be structure of json to be posted User will already exist and competency will might exist or new can be added and associated with existing user.
With this code I was able to post a new relation:
UserCompetency.class
#Entity
#Table(name = "user_competency")
#IdClass(UserCompetencyId.class)
public class UserCompetency implements java.io.Serializable {
#Id #ManyToOne
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
private Competency competency;
#Id #ManyToOne
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
private User user;
UserCompetencyId.class
public class UserCompetencyId implements java.io.Serializable {
private Long competency;
private Long user;
public UserCompetencyId() {
}
public UserCompetencyId(Long competency, Long user) {
this.competency = competency;
this.user = user;
}
UserCompetencyRepository.class
public interface UserCompetencyRepository extends JpaRepository<UserCompetency, UserCompetencyId> {
}
POST http://localhost:8080/userCompetencies
{
"competency": "/competencies/2"
, "user": "/user/4"
}
Apparently there seems to be no "natural/easy" way to get what you want. But there is a promissing project for integrating embeddables by extending the serialization process: https://github.com/gregturn/embeddable-spring-data-rest
UserCompetencyIdJacksonModule, UserCompetencyIdSerializer, ..
Then you should be able PATCH (not POST) your JSON from above.

Resources