I've been trying to use Jackson annotations to avoid cyclic association but it doesn't seem to work as expected and I still get a stackoverflow
Allergens class:
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import java.io.Serializable;
import javax.persistence.*;
/**
* The persistent class for the allergens database table.
*
*/
#Entity
#Table(name="allergens")
#NamedQuery(name="Allergen.findAll", query="SELECT a FROM Allergen a")
//#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
public class Allergen{
private static final long serialVersionUID = 1L;
#Id
private int id;
private boolean isEnabled;
private String title;
//bi-directional many-to-one association to Recipe
#ManyToOne
#JsonManagedReference
private Recipe recipe;
//+getters/setters
Recipe Class
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import java.io.Serializable;
import javax.persistence.*;
import java.util.List;
/**
* The persistent class for the recipes database table.
*
*/
#Entity
#Table(name="recipes")
#NamedQuery(name="Recipe.findAll", query="SELECT r FROM Recipe r")
//#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
public class Recipe{
private static final long serialVersionUID = 1L;
#Id
private int id;
private String complexity;
private int cookingTime;
private String description;
private int estimatedTime;
private String imageUrl;
private String information;
private boolean isPromoted;
private int preparationTime;
private float servings;
private String title;
private String type;
//bi-directional many-to-one association to Allergen
#OneToMany(mappedBy="recipe")
#JsonBackReference
private List<Allergen> allergens;
//+getters/setters
I've also tried annotating both classes with #JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id") but that also didn't work unfortunately
Is there anything I'm missing?
Also, my pom.xml contains
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.8</version>
</dependency>
Do I need anything else for jackson?
You can try to add #JsonIgnore on the property that is causing the circular reference. This will tell Jackson not to serialize that property.
Related
I am working on a project using Springboot to create API to call all provinces in the list. So first i create an entity class
package example.parameter.entity;
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.util.List;
#Data
#Entity
#Table(name = "provinces",indexes = {
#Index(name = "PROVINCES_INDX_0", columnList = "name")
})
public class Province extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "provinces_generator")
#SequenceGenerator(name = "provinces_generator", sequenceName = "provinces_seq", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column(name = "is_deleted")
private Boolean isDeleted;
#OneToMany(mappedBy = "province", cascade=CascadeType.ALL, orphanRemoval = true)
private List<Regency> regencies;
#NotBlank
#Column(name = "name")
private String name;
}
and then I created this responseDTO
package example.parameter.api.response;
import lombok.Data;
#Data
public class ProvinceResponseDTO {
private String id;
private String name;
}
after that I create the repository
package example.parameter.repository;
import example.parameter.api.response.ProvinceResponseDTO;
import example.parameter.entity.Province;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface ProvinceRepository extends JpaRepository<Province,Long> {
public List<ProvinceResponseDTO> findAllByIsDeleted(Boolean isDeleted);
}
when I am trying to hit the API I am getting this error on data layer.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [example.parameter.entity.Province] to type [example.api.response.ProvinceResponseDTO]
I don't know thy this is happening, any solution to fix this issue?
Part:1
JpaRepository Query creation from method name should have the default return type of the entity object. In your case, your repository is for the Province entity. So, it should be like
public interface ProvinceRepository extends JpaRepository<Province, Long> {
public List<Province> findAllByIsDeleted(Boolean isDeleted);
}
Reference:-
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
Part: 2
If you need to return a custom object from the query creation from a method name, then you can use dynamic projections like below:-
public interface ProvinceRepository extends JpaRepository<Province, Long> {
public <T> List<T> findAllByIsDeleted(Boolean isDeleted, Class<T> dynamicClassType);
}
while calling this method you define like
provinceRepository.findAllByIsDeleted(true, ProvinceResponseDTO.class)
Reference:- https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos
I have two related Entities in my application integratorDetails, IntegratorChannelDetails. What I want to achieve is to map integratorDetails and IntegratorChannelDetails to a DTO Object IntegratorAllInfoDto which has similar fields as the entities, using ModelMapper, but I am not sure how to do that, below are the entities
integratorDetails
import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.repository.annotation.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
import java.util.Date;
import java.util.List;
#Document
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class IntegratorDetails {
#Id
private String integratorId;
#Field
private String name;
#Field
private String accountId;
#Field
private String status;
private String privateKey;
private String publicKey;
private List<ThirdPartyKey> thirdPartyKey;
private Date createdTime;
}
IntegratorChannelDetails
import com.couchbase.client.java.repository.annotation.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
import java.util.List;
#Document
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class IntegratorChannelDetails {
#Id
private String integratorChannelid;
private String accountId;
private String type;
private List<ChannelType> channelTypes;
private List<ChannelList> channelList;
private List<String> fixedChannels;
private String timeServiceUrl;
private List<RibbonRules> ribbonRules;
int numberOfSlots=4;
}
And my Dto is
import com.tdchannels.admin.ms.channel.db.entity.ChannelList;
import com.tdchannels.admin.ms.channel.db.entity.RibbonRules;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class IntegratorAllInfoDto<T> {
private String integratorId;
private String name;
private String accountId;
private String status;
private Date createdTime;
private List<ChannelTypeDto> channelTypes;
private List<ChannelList> channelList;
private List<String> fixedSlots;
private String publicKey;
private List<ThirdPartyKeyDto> thirdPartyKey;
private List<RibbonRules> ribbonRules;
}
If you need to map multible objects into a single destination you do like this.
ModelMapper modelMapper = new ModelMapper();
IntegratorDTO dto= modelMapper.map(details, IntegratorDTO.class);
//This will add additional values to the dto.
modelMapper.map(integratorChannelDetails, dto);
Like the documentation http://modelmapper.org/getting-started/
You can concat the names of entities on DTO, like:
Source model
// Assume getters and setters on each class
class Order {
Customer customer;
Address billingAddress;
}
class Customer {
Name name;
}
class Name {
String firstName;
String lastName;
}
class Address {
String street;
String city;
}
Destination Model
// Assume getters and setters
class OrderDTO {
String customerFirstName;
String customerLastName;
String billingStreet;
String billingCity;
}
I've two entities which are named Airport and Route. Route has two field which named startPoint and endPoint. Both of them will be id value of Airport entity. I'm adding two airport entity, after that, I want to add Route by using id values of these airport records. I got an error like that
"message": "JSON parse error: Cannot construct instance of com.finartz.airlines.entity.Airport (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.finartz.airlines.entity.Airport (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)\n at [Source: (PushbackInputStream); line: 2, column: 18] (through reference chain: com.finartz.airlines.entity.Route[\"startPoint\"])"
these are my entities:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
#Data
#Builder
#Entity
#Table(name = "airport")
#NoArgsConstructor
#AllArgsConstructor
#EntityListeners(AuditingEntityListener.class)
public class Airport implements Serializable {
private static final long serialVersionUID = -3762352455412752835L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "code")
private String code;
#Column(name = "name")
private String name;
#Column(name = "city")
private String city;
#Column(name = "country")
private String country;
#Column(name = "description")
private String description;
#CreationTimestamp
#Column(name = "created_on", nullable = false, updatable = false)
private LocalDateTime createdOn;
#UpdateTimestamp
#Column(name = "updated_on")
private LocalDateTime updatedOn;
}
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
#Data
#Builder
#Entity
#Table(name = "route")
#NoArgsConstructor
#AllArgsConstructor
#EntityListeners(AuditingEntityListener.class)
#ApiModel(value = "route")
public class Route implements Serializable {
private static final long serialVersionUID = -8451228328106238822L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_start_point",referencedColumnName = "id")
private Airport startPoint;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_end_point",referencedColumnName = "id")
private Airport endPoint;
#CreationTimestamp
#Column(name = "created_on", nullable = false, updatable = false)
private LocalDateTime createdOn;
#UpdateTimestamp
#Column(name = "updated_on")
private LocalDateTime updatedOn;
}
And repository of the Route is below:
import com.finartz.airlines.entity.Route;
import com.finartz.airlines.repository.RouteRepository;
import com.finartz.airlines.util.HibernateUtil;
import lombok.AllArgsConstructor;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
#AllArgsConstructor
public class RouteRepositoryImpl implements RouteRepository {
public Long add(Route route){
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx;
Long routeId;
tx = session.beginTransaction();
routeId = (Long) session.save(route);
tx.commit();
session.close();
return routeId;
}
}
How can I add new Route by using request which provided below?
{
"startPoint":1,
"endPoint":2
}
I would use the session's entity manager to get a reference to an airport from the DB.
Construct using this code whenever you want to create a Route this way:
EntityManagerFactory emf = session.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
Airport startPoint = em .getReference(Airport.class, startPointID);
Airport endPoint = em .getReference(Airport.class, endPointID);
It should be something like this
session.beginTransaction();
session.save(route);
tx.commit();
Long id = route.getId();
session.close();
return id;
You should not use begin/close transaction. Use Spring Data JPA, it manage transaction session automatically. Your way is not best practices since you use Spring Boot (Let's see https://github.com/donhuvy/library/blob/master/src/main/java/com/example/library/controller/BookController.java#L29 very simple and easy).
I've a Java REST API with JPA. Whenever I create an entity, I also want to create another entity with a forgein key. Or maybe someone can advise me otherwise, I would really appreciate it and learn from it =)
When i successfully create a company it will make a file entity in the database as well, so that works fine. but,
Whenever I execute a findAll method in the JPA repository it will give me a loop of the one company that i've created.
like this:
If you need any more information, please let me know!
Company.class
package nl.hulpvriend.dehulpvriend.company;
import javax.validation.constraints.NotNull;
import lombok.*;
import nl.hulpvriend.dehulpvriend.file.File;
import javax.persistence.*;
import javax.validation.constraints.Size;
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Setter
#Getter
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotNull
private String email;
#Column(unique = true)
#NotNull(message = "The company name cannot be empty")
#Size(max = 30, message = "Company name cannot be longer than 30 characters")
private String name;
#NotNull(message = "Company must contain a service type")
#Enumerated(EnumType.STRING)
private ServiceType serviceType;
private double stars;
private Integer pricePerHour;
private String description;
private String kvk;
#OneToOne(mappedBy="company", cascade = CascadeType.ALL)
private File file;
}
File.class
package nl.hulpvriend.dehulpvriend.file;
import lombok.*;
import nl.hulpvriend.dehulpvriend.company.Company;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
#AllArgsConstructor
#Getter
#Setter
#NoArgsConstructor
#Entity
#Data
public class File {
#Id
private Integer id;
private String fileId;
#OneToOne(fetch = FetchType.EAGER)
#MapsId
private Company company;
#NotNull(message = "Must contain a data")
#Lob
private byte[] data;
private String downloadUrl;
private String fileName;
private String fileType;
public File(String fileName, String fileType, byte[] data) {
this.fileName = fileName;
this.fileType = fileType;
this.data = data;
}
}
Add JsonIgnore to one of the references to break the loop:
For example in the File class:
#JsonIgnore
#OneToOne(fetch = FetchType.EAGER)
#MapsId
private Company company;
I am trying to join three entities (table) using spring-jpa into one table using Many-To-Many relationship.
Three classes are :
1] User
2] Resource
3] Privilege
And I want to combine these three entities into one User_Resource_Privilege table
User Entity
package com.****.acl.domain;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
#Entity
public class User {
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
#Column(name="user_id", nullable=false, length=40)
private String userId;
#Column(name="user_name", nullable=false, length=45)
private String userName;
#Column(name="first_name", nullable=true, length=45)
private String firstName;
#Column(name="last_name", nullable=true, length=45)
private String lastName;
#Column(name="email", nullable=true, length=50)
private String email;
public User(){
}
public User(String userName, String firstName, String lastName, String email) {
this.userName = userName;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
getter and setters .......
}
Resource Entity
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.*;
import org.hibernate.annotations.GenericGenerator;
#Entity
public class Resource {
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
#Column(name="resource_id", nullable=false, length=40)
private String resourceId;
#Column(name="resource_name", nullable=false, length=45)
private String name;
#Column(name="resource_type", nullable=false, length=45)
private String type;
public Resource(){
}
public Resource(String name, String type) {
this.name = name;
this.type = type;
}
getter and setter ......
}
Privilege Entity
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.*;
import org.hibernate.annotations.GenericGenerator;
#Entity
public class Privilege {
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
#Column(name="privilege_id", nullable=false, length=40)
private String privilegeId;
#Column(name="resource_name", nullable=false, length=45)
private String name;
#Column(name="resource_description", nullable=true, length=45)
private String description;
public Privilege(){
}
getters and setters ....
}
Now I want to create one table by joining all the three entities described above.
The join in ER diagram:
Can someone please help me in joining these three tables using Many-To-Many relationship and let me know how to achieve this using spring-jpa and REST ?
Also it will be great if you please explain how to insert data in this "User_Resource_Privilege" table using REST/curl command ?
What you could do is make an embeddable ID and wrap it with the class. You can afterwards even expand this wrapper class to hold other fields.
java geeks example of embedded id
You would get something like
#Embeddable
public class EmbeddedIdClass implements Serializable {
private String userId;
private String resourceId;
private String privilegeId;
// constructors, getters and setters, equals, etc
}
#Entity
public class Wrapper {
#EmbeddedId
private EmbeddedIdClass id;
// constructors, etc
}
Instead of just using the strings in this example, you should use the complete objects and let hibernate (or something like it) do it's stuff. It should only take the id's into the database and do it's magic itself.
edit:
Just wanting to insert the id's as values, but keeping relationships would look something like this
#Entity
public class Wrapper {
#Id
private String id;
private User user;
private Resource resource;
private Privilege privilege;
// constructors
public Wrapper(final User user, final Resource resource, final Privilege privilege) {
this.user = user;
this.resource = resource;
this.privilege = privilege;
}
}