I'm using SpringBoot and JPA to build a REST interface.
Now, I have a strange JSON returned for the list of products fetched from the database. Let's say that I have:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "categoryId", nullable = false, updatable = false)
private Category category;
...
}
#Entity
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "category", cascade = CascadeType.DETACH)
#OrderBy("name ASC")
private List<Product> products = Collections.emptyList();
...
}
The JPA repository for the Product is defined as:
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findAll();
}
In my controller I have:
#Autowired
private ProductRepository productRepo;
#RequestMapping("/all-products", method = RequestMethod.GET)
public Map<String,Object> home() {
Map<String,Object> model = new HashMap<String,Object>();
model.put("products", productRepo.findAll());
return model;
}
What is driving my crazy, is that if I try to call this service as follows:
$ curl localhost:8080/all-products
I get a recursive output due to the relationship between tables product and category, e.g.:
{"products":[{"id":1,"name":"Product1","category":
{"id":1,"name":"Cat1","products":[{"id":6,"name":"Product6","category":
{"id":1,"name":"Cat1","products":[{"id":6,"name":"Product6","category":
{"id":1,...
What am I doing wrong?
You're not doing anything wrong (at least at the code level it's rather conceptual) - json serializer just goes like this:
Product - serialize it, but wait - there is a category field, so serializer must serialize the category field
Category - serialize it, but wait - there is a products field, so serializer must serialize each of the product in the list
Product - because your collection contains the product & product contains category it goes in a endless loop untill a timeout.
You must use a view or just skip it.
Use #JsonView
Use a view as a POJO
Return new ProductView that has all fields of product and a reference (category) to new CategoryView (you can end at this point) that has collection of (products) new ProductViewWithoutReferences, and so on
Use #JsonIgnore on a collection of products
And as a side note - if it's a #RestController and you're invoking "all-products" then it's a bit unusual to return something else than a list. Wrapping the response in a map is redundant. Many rest clients expect a list when they invoke list() method.
I know it's a bit late, but adding it here in case anybody faces the same problem.
Here is another relevant answer I could find which discuss about similar topic
https://stackoverflow.com/a/3359884/6785908
quoting it here
Jackson 1.6 has annotation-based support for handling such
parent/child linkage, see
http://wiki.fasterxml.com/JacksonFeatureBiDirReferences.
You can of course already exclude serialization of parent link already
using most JSON processing packages (jackson, gson and flex-json at
least support it), but the real trick is in how to deserialize it back
(re-create parent link), not just handle serialization side. Although
sounds like for now just exclusion might work for you.
EDIT (April 2012): Jackson 2.0 now supports true identity
references, so you can solve it this way also.
Adding #JsonIgnore worked for me
#OneToMany(mappedBy = "policy")
#JsonIgnore
private List<Payment> payments;
#JeanValjean your are the best
Related
I would like to be able to create pagination for pulling all customers from the database (MYSQL), but I encountered a hibernate n+1 problem, which I then solved, but I encountered another problem: 2023-02-09 16:57:04.933 WARN 11660 --- [io-8080-exec-10] o.h.h.internal.ast.QueryTranslatorImpl : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
This problem I tried to solve with EntityGraph, but still nothing. Then I tried to use two Query, which collected the id and then used the IN clause, but this caused a huge sql query, which led to the generation of many "IN" which, with a huge dataset, can be problematic.
I am currently in a quandary and do not know how to solve this problem. I would like the figures to be fetched along with the customers, but I have no idea how to do it in such a way that the pagination works properly
I want to return CustomerDTO who have numberOfCreatedFigures attribute which is mapping from method in customer entity. This method is returning a size of customer figures.
I am using lombok for args/getters/setters. I've been trying to do everything, but nothing seems to fix the issue.
Config class with a mapper
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.createTypeMap(Customer.class, CustomerDTO.class)
.addMappings(mapper -> mapper
.map(Customer::numberOfCreatedFigures, CustomerDTO::setNumberOfFigures));
return modelMapper;
}
Customer class
public class Customer implements UserDetails, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "Your name cannot be blank")
private String name;
#NotBlank(message = "Your name cannot be blank")
private String surname;
#NotBlank(message = "Your login cannot be blank")
private String login;
#NotBlank(message = "Your password cannot be blank")
private String password;
#Enumerated(EnumType.STRING)
private Role role;
private Boolean locked = false;
private Boolean enabled = true;
#OneToMany(mappedBy = "createdBy",
cascade = {CascadeType.MERGE, CascadeType.PERSIST},
fetch = FetchType.LAZY,
orphanRemoval = true)
#ToString.Exclude
private Set<Figure> figures = new HashSet<>() ...;
Figure class
public abstract class Figure implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(updatable = false, insertable = false)
private String figureType;
#Version
private Integer version;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "created_by_id")
#CreatedBy
#ToString.Exclude
private Customer createdBy;
#CreatedDate
private LocalDate createdAt;
#LastModifiedDate
private LocalDate lastModifiedAt;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "last_modified_by_id")
#LastModifiedBy
#ToString.Exclude
private Customer lastModifiedBy;
private Integer numberOfModification = 0 ...;
CustomerDTO class
public class CustomerDTO {
private Long id;
private String name;
private String surname;
private String login;
private Integer numberOfFigures;
private Role role;}
Method from Customer Controller
#GetMapping
public ResponseEntity<Page<CustomerDTO>> listAll(#PageableDefault Pageable pageable) {
return new ResponseEntity<>(customerService.listAll(pageable)
.map(customer -> modelMapper
.map(customer, CustomerDTO.class)), HttpStatus.OK);
}
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Customer.class)
public interface CustomerDTO {
#IdMapping
Long getId();
String getName();
String getSurname();
String getLogin();
#Mapping("SIZE(figures)")
Integer getNumberOfFigures();
Role getRole();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CustomerDTO a = entityViewManager.find(entityManager, CustomerDTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<CustomerDTO> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
You could load the customers with the figures relationship eagerly initialized.
For this case, an entity graph would be suitable. You'd need to create a new repository method like this:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
#EntityGraph(attributePaths = "figures")
List<Customer> findWithFiguresBy(Pageable pageable);
}
Then, you'd need call this repository method when searching instead of the one you are using now. With this approach, your figures relationship can remain lazily fetched (which is generally important as eager fetching is a code smell), but whenever you need to fetch customers with the figures eagerly loaded, you can use this method.
If you want to lear more about entity graphs, I recommend these articles:
JPA Entity Graph by Hibernate maintainer Vlad Mihalcea
JPA Entity Graph by Baeldung
Side note: if you had more than one association which needs to be loaded eagerly, you couldn't use an entity graph for that as it would result in a MultipleBagFetchException. Instead, you would load your parent entities as usual and then collect all ids into a list (say customerIds). Then, you'd need to load all child associations (say figures and otherFigures) by the customer id (JPQL example: select f from Figure f where f.customer.id in :customerIds) and place the figures in a Map<Long, List<Figure> (where the Long parameter is the customer id). Your mapper logic would then need to use the entities from the Maps for the DTOs instead of directly from the parent entity.
I have a JPA code with OneToMany relationship. A Customer has a list of Item to check out. However, the code continue to generate StackOverflowError.
Once, I had resolved this one by applying #JsonIgnore while fetching the List<Item> from Customer entity. But even that does not seem to work anymore.
In Customer class:
#OneToMany(mappedBy = "customer", orphanRemoval = true)
#JsonIgnore
private List<Item> items;
In Item class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CUSTOMER_ID", nullable = false)
private Customer customer;
And CustomerRest class:
#Path("customers")
public class CustomerRest {
#Inject
NewSessionBean newSessionBean;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Customer> getAllCustomers() {
return newSessionBean.getCustomers();
}
}
Method newSessionBean.getCustomers():
public List<Customer> getCustomers(){
TypedQuery<Customer> q= em.createQuery("select c from Customer c", Customer.class);
return q.getResultList();
}
I expect a nicely formatted JSON message but there is no sign of this. All I get is the java.lang.StackOverflowError on the browser and the Server log generates the following:
Generating incomplete JSON|#]
java.lang.StackOverflowError
java.lang.StackOverflowError at org.eclipse.yasson.internal.serializer.DefaultSerializers.findByCondition(DefaultSerializers.java:130)
It looks like you use Yasson project not Jackson. In that case you should use #JsonbTransient annotation. See documentation:
By default, JSONB ignores properties with a non public access. All
public properties - either public fields or non public fields with
public getters are serialized into JSON text.
Excluding properties can be done with a #JsonbTransient annotation.
Class properties annotated with #JsonbTransient annotation are ignored
by JSON Binding engine. The behavior is different depending on where
#JsonbTransient annotation is placed.
See also:
Circular reference issue with JSON-B
This question already has answers here:
Infinite Recursion with Jackson JSON and Hibernate JPA issue
(29 answers)
Closed 3 years ago.
I am trying to create Many to one mapping between two entities in spring. However when I try to fetch the values using a restController I get
Java.lang.IllegalStateException: Cannot call sendError() after the
response has been committed
error and an infinite JSON response. Adding JSON ignore solves this issue but then I don't get that column in my response at all. I don't know how to fix this. Please help. Thanks in advance.
Here are my entities:
#Entity
#Table(name="tbl1")
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "customer")
private String customer;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="data_id", insertable = true, nullable = false, updatable = false)
private DataList dataList1;
}
#Entity
#Table(name="tbl2")
public class DataList {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="data_id")
private Long dataId;
#Column(name="user_name")
private String userName;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,mappedBy = "dataList1")
private List<Data> data1;
}
Repositories:
#Repository
#Transactional
public interface DataList extends JpaRepository<DataList,Long> {
}
#Repository
#Transactional
public interface Data extends JpaRepository<Data,Long> {
}
My error: Java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
1) In general putting fetch = FetchType.EAGER on #OneToMany is not an advisable practice. Try to set it to LAZY instead.
2) Make sure that Data and DataList entities properly implement equals() and hashCode() methods.
It is happening as JSON serializer is trying to serialize both the entities recursively again and again.
So, while serializing Data, its member dataList1 is of type DataList which further contains List<Data>, this loop will be infinite.
Ideally, in such scenarios, the entities should be mapped to some other model meant for the serialization and response or one of the model needs to be #JsonIgnore to avoid this recursive loop going.
EAGER is a bad practice. Use LAZY, and when you need the related entities, use fetch join query.
Most likely the problem here in the bi-directional relation. You fetch DataList with Data list. And each Data in List ListData refers again.
More likely here to be a stack overflow when serializing json. So remember one rule: never give in controllers hibernate entities. Write a mapper for map this entities to Dto objects.
You may think that it is extremely boring to map some models to another. But here in another way. Hibernate entities should not be passed to front end. My application uses several types of objects: Entities (when fetch from DB), DTO (When map entities to DTO and give their in service components), Shared (when map DTO to Shared and share as a response to the controller between my microservices) and Model (when map from Response to Model and give to the template engine, for example freemarker). You may not need this hierarchy of models.
Create DTO's:
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DataListDto {
private Long dataId;
private String userName;
private List<DataDto> data1;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DataDto {
private long id;
private String customer;
}
write your mapper:
public class DataListMapper {
public static DataListDto mapToDto(DataList dataList) {
return new DataListDto(dataList.getDataId(), dataList.getUserName(), dataList.getData1().stream().map(x -> DataListMapper.mapToDto(x).collect(Collectors.toList)))
}
public static DataDto mapToDto(Data data) {
return new DataDto(data.getId(), data.getCustomer());
}
}
your service:
public class DataListService {
#Autowired
private DataListRepository dataListRepository;
public List<DataListDto> getAllData() {
List<DataList> dataLists = this.dataListRepository.findAll();
return dataLists.stream().map(x->DataListMapper.mapToDto(x)).collect(Collectors.toList());
}
}
I use crnk (JSON-API) in java project and I have 3 questions regarding its usage with spring boot and jpa - haven't found exact implementation details in documentation.
For example, I have 2 entities and respective tables:
#Entity
#JsonApiResource(type = "employee")
public class Employee {
#Id
#JsonApiId
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "typeId")
private EmployeeType employeeType; //stored in table as typeId
}
#Entity
#JsonApiResource(type = "type")
public class EmployeeType {
#Id
#JsonApiId
private int id;
private String typeName;
private int salary;
}
How should JsonApiRelation be introduced in order to be able to call "/employee/1" and "/employee/1/type" urls?
For example there is one more entity.
#Entity
#JsonApiResource(type = "project")
public class Project {
#Id
#JsonApiId
private int id;
private String supervisorName;
private String projectName;
}
First, I'd like to have List of Projects for each Employee, where he is a supervisor, joint by name and have it listed as attribute in Json.
Tried implementing it with #OneToMany and #JoinColumn annotations but got StackOverflowException. How could this be implemented. And second, how could this be implemented with Relation? Like "/employee/1/projects" url.
How should I implement custom filtering of results for findAll method? For example, I have a List of all Employees, but I'd like to exclude some of them from the response. Which class/method should be introduced for this behaviour?
#JsonApiRelation annotation should not be necessary. Crnk will detect the #ManyToOne annotation and map it accordingly.
in case of crnk-jpa it is sufficient to specify all relationships in JPA. Matching JSON API relationships. So your approach seems good. What was the StackoverflowException stacktrace? (next to the examples, there are also many example entities in crnk-jpa)
I would make use of a decorator. See http://www.crnk.io/documentation/#_request_filtering. RepositoryDecoratorFactory allows to place a custom repository between the caller and crnk-jpa (or any other kind of repository). There you can do any kind of modification perform (maybe) calling the "real" repository. => Will add an example for this
feel free also make open up tickets in crnk for any documentation/example clarifications.
How do get the object I want, without all of the child associations.
I have my class Site:
#Entity
#Table(name = "Sites")
public class Site {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Id_Site", unique = true, nullable = false)
private long Id_Site;
private String ...;
private boolean ...;
private long ...;
private Date ...;
private Date ...;
private String ...;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Sequence> sequences = new HashSet<>();
#ManyToOne
private ... ...;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<...> ... = new HashSet<>();
#ManyToOne
private ... ...;
public constructor...
public set..
public get..
}
I only need a Site object, without the Sequence Associations.
In my Sequence Table, I have:
#Entity
#Table(name = "Sequences")
public class Sequence {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Id_Sequence", unique = true, nullable = false)
private long Id_Sequence;
private Date ....;
private Date ....;
private String ....;
private String ....;
private String ....;
private int ....;
private int ....;
private double ....;
private double ....;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<TraceSequence> traceSequences = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
private Site site;
public constructor...
public set..
public get..
}
When I use FetchType.Lazy, and call my method:
#Override
public Site findSiteByName(String Name_Site) {
List<Site> sites = entityManager.createQuery("SELECT s FROM Site s").getResultList();
for (Site item : sites) {
if (item.getNom_Site().equals(Name_Site)) {
return item;
}
}
return null;
}
I get this error:
failed to lazily initialize a collection of role: xxx.xxx.xxx.xxx.xxx.site.Site.sequences, could not initialize proxy - no Session
When I use FetchType.EAGER, I get not only a Site object, but I also get all sequence objects, and all objects of other sequence associations. (I know it is the normal response.)
Could someone who knows why this attempt at lazy initialization doesn't work, please, tell me how to resolve this problem.
These lazy errors happens when the jpa tries to get the data after the session is closed.
But using eager will influence all the queries that include that entity.
Try to use a join fetch in the query instead of the eager.
Somewhere in your code you are calling Site.GetSequences(), maybe iterating in the view or in another part of your code. It doesn't look like the piece of code you gave are generating the exception.
I you try to use a collection that is not loaded to your entity, the code throws the exception you mentioned.
To solve this, identify where you are using the sequences and load them before you use by changing the fetch to EAGER or using the JOIN FETCH in your query.
Returning a hibernate managed entity (or a collection of hibernate managed entities) will most likely cause these sort of problems unless you are super cautious on what is being returned and what was populated by hibernate when session was available.
I would say create a DTO (or a collection of DTO) and populate its fields the way you like. There are many Entity to DTO conversion framework; my fav is ModelMapper.
I also tend to agree with other suggestions to play with FetchType but since DTOs are populated by us we know what we populated as opposed to entity-relationships which are populated by hibernate based on annotations.
If you need something in the DTO you simply ask the entity and since session would be available at that point of time you could populate any field that you think you would need on the UI.
I don't want to hijack this topic towards DTO and Entity but that's how I would do it.
This may be helpful too Avoid Jackson serialization on non fetched lazy objects
Error happen becouse you try execute getSequences(), but becouse of is lazy and session is alredy closed hibernate rais the error.
To avoid this error read read sequencese inside query method, "inside" session, like this:
public Site findSiteByName(String Name_Site) {
List sites = entityManager.createQuery("SELECT s FROM Sites").getResultList();
for (Site item : sites) {
if (item.getNom_Site().equals(Name_Site)) {
item.getSites();
return item;
}
}
return null;
}
This is a lazy loading, you read collenction just when you need it!
As stated by other SE members above, you are getting this error because session is already closed.
If you want to load a particular object then you can use Hibernate.initialize method. it will execute one additional query to fetch the data of related entity.
Therefore, it is as per need basis and will not be executed all times as compared to Eager loading
I'm working on a project that aims to solve common JPA problems when mapping entities to DTOs using ModelMapper. This issue has already been solved on the project. Project link: JPA Model Mapper
On this scenario I believe that we'd want to simply get null for all lazy load entities. For this question specifically, this could be done by using de JPA Model Mapper to map an entity to DTO.
I've already answered the same issue on this question: How to solve the LazyInitializationException when using JPA and Hibernate