I have an entity:
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
private List<Genre> genre;
}
Then I have a controller whose purpose is to retrieve books, my problem is that, the genre field is being included in the json response of my controller. Any way I can exclude those fields that are lazy loaded when jackson serializes the object?
This is the configuration of my ObjectMapper:
Hibernate4Module hm = new Hibernate4Module();
hm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
registerModule(hm);
configure(SerializationFeature.INDENT_OUTPUT, true);
Thanks!
I can't mark it as JsonIgnore, as it will be forever out of the serialization box. There will be times where I will need to retrieve the genres along with the book, and by then I will use "fetch join" on my query so it will not be null.
You can do this with the Jackson #JsonInclude annotation.
According to the latest version's javadoc (2.4 right now) you can specify with a simple annotation if to include or not the annotated property if the field value is null or empty.
By default, it's JsonInclude.Include.ALWAYS, and this means that even if your lazily not-loaded values are null, Jackson does include the property.
Specifying to don't include empty or null values can significantly reduce the size of the JSON response, with all the benefits included..
If you want to change this behavior, you can add the annotation at class-level or single property/getterMethod level.
Try to add the following annotations to the fields you don't want to include if empty:
#JsonInclude(JsonInclude.Include.NON_EMPTY)
#OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
private List<Genre> genre;
You can use a spring configuration to disable force lazy loading by default!
#Configuration
public class JacksonConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.configure(Feature.FORCE_LAZY_LOADING, false);
// Enable below line to switch lazy loaded json from null to a blank object!
//hibernate5Module.configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
mapper.registerModule(hibernate5Module);
return mapper;
}
}
You can use Jackson's JSON Filter Feature:
#Entity
#JsonFilter("Book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
private List<Genre> genre;
}
#Entity
#JsonFilter("Genre")
public class Genre {
...
}
Then in the Controller you specify what to filter:
#Controller
public class BookController {
#Autowired
private ObjectMapper objectMapper;
#Autowird
private BookRepository bookRepository;
#RequestMapping(value = "/book", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public ResponseEntity<String> getBooks() {
final List<Book> books = booksRepository.findAll();
final SimpleFilterProvider filter = new SimpleFilterProvider();
filter.addFilter("Book", SimpleBeanPropertyFilter.serializeAllExcept("genre");
return new ResponseEntity<>(objectMapper.writer(filter).writeValueAsString(books), HttpStatus.OK)
}
}
In this way, you can control when you want to filter the lazy relation at runtime
Maybe this is related to a known issue about lazy loading.
I don't use jackson-datatype-hibernate, but what I've done to solve the same problem is to get the persistent collection out of the picture by using a DTO instead of serializing a Hibernate object directly. Tools like Dozer can help you out with that. Alternatively, there's a small utility I wrote to do mappings like this.
If you just want to experiment with what a DTO could look like, you can replace the unloaded persistent collection with a regular empty collection, like books.setGenre(new ArrayList<>()); Unfortunately I don't know of a way to tell if a lazily loaded object has been loaded or not, so you can't do this reassignment automatically. The places where you replace persistent collections would need to be determined by you on a case by case basis.
You can use Gson instead of ObjectMapper and while defining the entity mark the field as "transient"
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
private **transient** List<Genre> genre;
}
while Deserialization using gson.toJson(book) , Gson will not deserialize that element.
Related
How to prevent model mapper from eager loading associated collections in spring data jpa ?
Ugh I had this issue last time and I must say that this is the point I start to not like ModelMapper :)
Basically the only way to do this is to define your own TypeMap with proper mappings that will ommit the collection mapping. The problem is if you have fetched Entity instance and sometimes you want to map it to the target class with collections and sometimes not (depends on whether collection was fetched or not).
This causes the situation when you should check whether the Collection field is fetched using some weird mechanisms like checking LazyInitializationException or the type of proxy implementation properties of the field if you are using Hibernate... madness
What I suggest is to create a few types of target Model class with or without the collection field, and for each of them custom mappings configuration (if your application design allows it) or to not using ModelMapper at all in case of this specific entity (and just provide your own mapping mechanism)
I was facing this issue where I had a User entity with a One-to-Many mapping to Role, i.e. one user has many roles
#Entity
#Table(name = "user")
public class User implements Serializable {
#Id
#Basic(optional = false)
#Column(name = "id")
private String id;
#Basic(optional = false)
#Column(name = "name")
private String name;
#Basic(optional = false)
#Column(name = "email_id")
private String emailId;
.............
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private Collection<UserRole> userRoleCollection;
}
I wanted to map this class to a DTO
#Data
public class UserVO implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7985084979805425972L;
private String name;
private String emailId;
......
private List<RoleVO> roles;
}
Basically I needed to convert Collection<UserRole> to List<RoleVO> using ModelMapper, where Collection<UserRole> was lazy-loaded.
I setup a ModelMapper Converter as follows:
#Configuration
public class ModelMapperConfiguration {
public ModelMapper modelMapper() {
ModelMapper mapper = new ModelMapper();
TypeMap<User, UserVO> userToUserVOTypeMap = mapper.createTypeMap(User.class, UserVO.class);
TypeMap<Role, RoleVO> roleRoleVOTypeMap = mapper.createTypeMap(Role.class, RoleVO.class);
Converter<Collection<UserRole>, List<RoleVO>> collectionListConverter =
ctx -> (ctx == null) ? null : ctx.getSource().stream()
.map(ur -> roleRoleVOTypeMap.map(ur.getRole()))
.collect(Collectors.toList());
userToUserVOTypeMap.addMappings(mpr ->
mpr.using(collectionListConverter)
.map(src -> src.getUserRoleCollection(), UserVO::setRoles));
}
}
The stream() method should trigger a FETCH for lazy-loaded data.
Please note that I have looked at similar questions and I have explained why they haven't worked for me
I have a simple Spring boot JPA-Hibernate application with one to one mapping between User and Address. (Please note that I do not have this issue with one to many mapping)
User Entity
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Address address;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Note> notes;
}
Address Entity
#Entity
#Table(name = "addresses")
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String street;
#Column
private String city;
#JsonIgnore
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
Note Entity
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
My problem is that whenever I call the controller mapped to get all users I was getting the address and all the associated notes with it as well. But I would expect FetchType.LAZY to take care of that.
I read a lot of questions on StackOverflow mentioning that Jackson might be the culprit here:
Post 1
I also read that spring.jpa.open-in-view defualt value might be the culprit:
Post 2
Post 3
So i tried the following options:
I disabled default open in view property by adding spring.jpa.open-in-view=false to my application.properties which started giving me
Could not write JSON: failed to lazily initialize a collection of role error
I am assuming its because Jackson is calling the getters on my lazily loaded objects so I followed the instructions from another post and added the following for Jackson to leave the lazily loaded collections alone:
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.9</version>
</dependency>
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
This solution above fixed the issue with the One to Many mapping but still has the Address associated in the response.
I am not sure what can I do here. The User Entity on the default landing page does not need any address details so I do not want to load it on the landing page. When the record is clicked then it navigates to another page and that's where I would like all the lazy loaded objects to be returned in the response.
I have tried everything I could find online but still nothing has worked so far. I would really appreciate some help with this.
As mentioned by one of the users that it might a duplicate of another question on SO:
Suggested Possible duplicate
I would like to mention that I got the Lazy loading working by disabling spring.jpa.open-in-view property but adding
mapper.registerModule(new Hibernate5Module());
brings back the address associated to the User in the response.
It's working as in the JPA spec:-
Refer the below URL
https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FetchType.html
LAZY fetching strategy is only a hint (as the javadoc says the data can be lazily fetched).. not a mandatory action.
Eager is mandatory (as the javadoc says the data must be eagerly fetched).
You may take a look at Jackson Serialization Views.
I´ve taken a look into the Hibernate5 module you tried and it has some interesting features... but none should fix this issue out of the box for you.
By the way, I normally fix this issue by not returning the Entity as the response but DTOs instead.
The problem is jackson triggering initialization when he writes the JSON, so just don't write the current field (address). But you should not use #jsonIgnore so at other places you could return an Eager obj.
You can use the #jsonView annotation that can provide different JSON for the same obj at different requests. You can look this example :
Create view class:
public class ViewFetchType {
static class lazy{ }
static class Eager extends lazy{ }
}
Annotate your Entity
#Entity
public class User {
#Id
#JsonView(ViewFetchType.Lazy.class)
private String id;
#JsonView(ViewFetchType.Eager.class)
#OneToOne( fetch = FetchType.LAZY)
private Address address ;
}
Specify the FetchType class in your controller:
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping("get-user-details")
#JsonView(ViewFetchType.Eager.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
{
#RequestMapping("get-all-users")
#JsonView(ViewFetchType.Lazy.class)
public #ResponseBody List<User> getUsers() {
return userRepository.findAll();
}
}
Here is the answer that i took the idea from... https://stackoverflow.com/a/49207551/10162200
I have tried several things I found while searching but nothing helped or I did not implement it correctly.
Error I'm getting
Direct self-reference leading to cycle (through reference chain: io.test.entity.bone.Special["appInstance"]->io.test.entity.platform.ApplicationInstance["appInstance"])
Both these extend the base entity and in the base (super class) it has an appInstance as well.
Base entity looks similar to this
#MappedSuperclass
public abstract class BaseEntity implements Comparable, Serializable {
#ManyToOne
protected ApplicationInstance appInstance;
//getter & setter
}
Application entity looks like this
public class ApplicationInstance extends BaseEntity implements Serializable {
private List<User> users;
// some other properties (would all have the same base and application instance . User entity will look similar to the Special.)
}
Special entity
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "objectType")
#JsonIgnoreProperties({"createdBy", "appInstance", "lastUpdatedBy"})
public class Special extends BaseEntity implements Serializable {
#NotNull
#Column(nullable = false)
private String name;
#Column(length = Short.MAX_VALUE)
private String description;
#NotNull
#Column(nullable = false)
private Double price;
#OneToOne
private Attachment image;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = SpecialTag.class)
#CollectionTable(name = "special_tags")
#Column(name = "specialtag")
private List<SpecialTag> specialTags;
#Temporal(TemporalType.TIME)
private Date specialStartTime;
#Temporal(TemporalType.TIME)
private Date specialEndTime;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = WeekDay.class)
#CollectionTable(name = "available_week_days")
#Column(name = "weekday")
private List<WeekDay> availableWeekDays;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialStatus> statuses;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialReview> specialReviews;
#Transient
private Integer viewed;
private Boolean launched;
#OneToMany(mappedBy = "special")
private List<CampaignSpecial> specialCampaigns;
#Override
#JsonIgnore
public ApplicationInstance getAppInstance() {
return super.getAppInstance();
}
}
All entities in Special inherits from BaseEntity which contains AppInstance
then i have a method to get the special
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public Special findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return special;
}
On the special entity I tried the following
Added jsonIgnoreProperties
Added an override for appInstance to annotate with #JsonIgnore
#JsonIdentityInfo
links for the above
https://stackoverflow.com/a/29632358/4712391
Jackson serialization: how to ignore superclass properties
jackson self reference leading to cycle
none of those solutions works. Am I doing something wrong?
Note: Would it also just be possible to edit special, since the other entities are in a different package and would not like to edit them.
Usually excluding attributes in a response is as easy as adding a #JsonIgnore annotation to their getters, but if you don't want to add this annotation to a parent class, you could override the getter and then add the annotation on it:
public class Special extends BaseEntity implements Serializable {
...
#JsonIgnore
public ApplicationInstance getAppInstance() {
return this.appInstance;
}
...
}
NOTE: As there are several frameworks, make sure that you are using the correct #JsonIgnore annotation or it will be ignored, see this answer for instance.
Another option, more "manual", is just creating a bean for the response which would be a subset of the Special instance:
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public SpecialDTO findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return new SpecialDTO(special);
}
public class SpecialDTO {
//declare here only the attributes that you want in your response
public SpecialDTO(Special sp) {
this.attr=sp.attr; // populate the needed attributes
}
}
To me, problem seems to be in the Special object and the fields being initialized in it.
I guess that there is a circular reference detected when serialisation happens.
Something similar to:
class A {
public A child;
public A parent;
}
A object = new A();
A root = new A();
root.child = object;
object.parent = root;
In the above code, whenever you will try to seralize either of these objects, you will face the same problem.
Note that public fields are not recommended.
I'll suggest to peek into your Special object and the references set in it.
I have the following code:
public interface JSONInvoiceView {
public interface JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesView extends JSONInvoiceBasicView {
}
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="INVOICE")
public class Invoice extends FrameworkEntity {
#Id
#SequenceGenerator(name = "PK_INVOICE_GEN", sequenceName = "PK_INVOICE_GEN", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PK_INVOICE_GEN")
#Column(name = "ID")
#JsonView(JSONInvoiceView.JSONInvoiceBasicView.class)
private Long id;
#OneToMany(mappedBy="invoiceLine", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
#JsonManagedReference
private List<InvoiceLine> lines = new ArrayList<InvoiceLine>();
#Temporal(TemporalType.DATE)
#Column(name = "DATE")
#JsonView(JSONInvoiceView.JSONInvoiceBasicView.class)
private Date startDate;
//...
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="INVOICE_LINE")
public class InvoiceLine extends FrameworkEntity {
#Id
#Column(name = "ID")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_INVOICE")
#JsonBackReference
private Invoice invoice;
#Column(name = "AMOUNT")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
private BigDecimal amount;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_GOOD")
private Good good;
//...
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="GOOD")
public class Good extends FrameworkEntity {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "DESCRIPTION", length=200)
private String description;
//...
}
So – one Invoice can have multiple InvoiceLines and each line has reference to Good. I need to get two JSON views: Inovice-only view and Invoice+InvoiceLine-only view. My domain is far richer than these 3 classes – the whole entity graph involves tens of classes and I need careful control how much of this graph I am loading in my entities. But I need to control also how much of loaded graph the JSON serialization facility should try to serialize. And I have the problem with this second control.
entityList is list of Invoices which has loaded InvoiceLines (with touch, e.g. invoiceLines.size();) but InvoiceLines have not further loaded Goods (invoiceLine.good is not touched during lazy load). So, entityList if Invoice+InvoiceLines.
I use the following code for Invoice-only view and this code works:
jsonString = objectMapper.writerWithView(JSONInvoiceView.JSONInvoiceBasicView.class).writeValueAsString(entityList);
Code for retrieving JSON view with Invoice+InvoiceLine-only data:
jsonString = objectMapper.writerWithView(JSONInvoiceView.JSONInvoiceWithLinesView.class).writeValueAsString(entityList);
And this code does not work, it raises error message:
org.codehaus.jackson.map.JsonMappingException: could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->mycom.entities.Invoice["invoiceLines"]->org.hibernate.collection.internal.PersistentBag[0]-> mycom.entities.Good["good"]-> mycom.entities.Good_$$_jvst4f9_c["id"])
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218)
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183)
at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286)
So, the question is – what Jackson views/annotations should I apply to serialized Invoice+InvoiceLine only parts of entity graph which has loaded only Invoice+InvoiceLine data? How should I indicate that Jackson should not try to go further along association chain and Jackson should not try to serialize 3rd, 4th and so order associations, Jackson should not try to serialize good entities?
p.s. Ignore annotations (or any similar global annotation on entities) is not applicable in my case, because there will be cases when I need only Invoice data and then there will be cases when I will need Invoice+InvoiceLine+Good data and further I will need data Invoice+InvoiceLine+Good+GoodSupplier, etc.
I have found solution - Jackson perceives fields without #JsonView annotation as the fields belonging to every view. Therefor I should introduce additional view:
public interface JSONInvoiceView {
public interface JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesView extends JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesViewExt extends JSONInvoiceWithLinesView {
}
}
And apply new interace to the Good field:
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_GOOD")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesExtView.class)
private Good good;
So - I should define new JSON view interfeice for each level of associations for my entities. After appling #JsonView all works like a charm.
Page entity.
#Entity
#Table(name = "pages", schema = "admin")
public class Page implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true)
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne(targetEntity = Partition.class, fetch = FetchType.LAZY)
private Partition partition;
#Column(name = "is_startable")
private Boolean isStartable;
#Column(name = "priority")
private Integer priority;
#Column(name = "prefix_granted_authority")
private String prefixGrantedAuthority;
#OneToMany(mappedBy = "page", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Permission> permissions;
#Column(name = "link", unique = true)
private String link;
PageRepository
List<Page> findByPermissionsGroupsOrderByPartitionNameAscNameAsc(#Param(value = "group") Group group);
PageServiceImpl
#Override
public Collection<Page> getAccessedPages(Group group) {
try {
List<Page> pages = pageRepository.findByPermissionsGroupsOrderByPartitionNameAscNameAsc(group);
return pages;
} catch (Exception ex) {
logger.error("getPage error", ex);
return null;
}
}
getAccessedPages return real List of page entities(not null), but all fields in entities are null.
Why?
I also encounter this problem while ago, it looks like spring data does some kind lazy instantiation.
So if you not access this fields inside of your transaction, they will stay null. Add annotation #Transactional on method where are you calling this request and problem will be solved.
I wanted to expand on #user902383's answer, which ultimately also solved my issue, but it was too long for a comment.
In my case, I had repository method fetching an entity, Helper, called inside a #PostLoad listener that used Helper for calculations for filling a field in another entity, Child. The listener method was already annotated with org.springframework.transaction.annotation.Transactional.
When called by Child's repository it fetched a Helper entity with all fields filled, but when called by the repository of an entity Parent which had a child Child, it fetched an empty Helper object with only the id filled even though it was properly annotated.
The issue was that I was using this hack to access the repository outside of a Spring #Component (I couldn't make the listener a component). I suspect that the Spring magic for detecting when a field is dereferenced in a #Transactional method does not work when the repository was not properly #Autowired. I still do not know why it worked in Child's repository but not in Parent.
My solution to this particular problem was moving the repository call and dereferencing to a #Service, which properly #Autowires the repository, and doing the hackish static call for getting that service instead, which makes for better code structure anyway.