How to use different EntityGraph as annotation on findAll and findOne repository methods - spring

I have a Spring project which uses Hibernate to interact with an underlying MySQL db. Please note EHCACHE is used as Hibernate second level cache.
I am trying to load different dependent objects by using different #EntityGraph annotations on findAll and fineOne method. I first load the entity using findAll method and then load the same object using findOne method - to my surprise it simply neglects the #EntityGraph specified on findOne method and instead returns data using the EntityGraph specified on findAll.
To explain the problem better I am copying the entity class and repository class below:
#Entity
#Table(name = "COURSE")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName="course")
#NamedEntityGraphs({
#NamedEntityGraph(name = "categories", attributeNodes = #NamedAttributeNode("categories")),
#NamedEntityGraph(name = "batches", attributeNodes = #NamedAttributeNode("batches"))
})
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
public class Course implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "course")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Batch> batches = new HashSet<>();
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "COURSE_CATEGORY",
joinColumns = #JoinColumn(name="courses_id", referencedColumnName="ID"),
inverseJoinColumns = #JoinColumn(name="categorys_id", referencedColumnName="ID"))
private Set<Category> categories = new HashSet<>();
// GETTERS AND SETTERS
}
The repository class is:
public interface CourseRepository extends JpaRepository<Course,Long> {
#EntityGraph(value = "categories", type = EntityGraphType.FETCH)
Page<Course> findAll(Pageable pageable);
#EntityGraph(value = "batches", type = EntityGraphType.FETCH)
Course findByIdAndPublished(Long id);
}
And after load up the calls happen in this format:
courseRepository.findAll() => This loads and returns all Course objects with categories loaded
courseRepository.findOne(1) => This returns Course with id 1 but again categories are loaded and not batches (Please note as per the EntityGraph I was expecting batches to be loaded and not categories)
Thanks in advance for helping out,
Ankit

Related

Spring Boot, Hibernate, bidirectional One-To-Many. Strange behaviour. Why is there two selects insdead of an error?

Spring Boot, Hibernate, bidirectional One-To-Many. Strange behavior. Why is there two selects instead of an error?
I have a basic Spring boot application.
It simulates throwing dices.
I have two entity classes Dice and DiceBatch.
DiceBatch has List<Dice> dices;
Dice has DiceBatch diceBatch; as two sides of bidirectional ManyToOne, or OneToMany.
I use JpaRepository<DiceBatch, UUID> to get one instance of DiceBatch by callig a method of JpaRepository findById(UUID id)
I call this method inside DiceBatchService's method findDiceBatchById(UUID diceBatchId).
Method is marked as #Transactional.
When i do that Hibernate logs one SQL select:
/* select
d
from
DiceBatch d
where
d.id = ?1 */ select
dicebatch0_.dice_batch_id as dice_bat1_1_,
dicebatch0_.batch_creation_time as batch_cr2_1_,
dicebatch0_.batch_name as batch_na3_1_
from
dice_batch dicebatch0_
where
dicebatch0_.dice_batch_id=?
At this point everything is ok.
Method returns DiceBatch entity with lazily initialized List<Dice> dices.
This is important. Method is #Transactional when method returns I should leave transactionla context.
Lazy fields should stay lazy and should cause LazyInitializationException if I try to access them.
Now control goes back to the controller method of DiceBatchController findDiceBatchById(UUID diceBatchId)
And here something strange happens.
Hibernate logs another select
select
dices0_.dice_batch_id as dice_bat5_0_0_,
dices0_.dice_id as dice_id1_0_0_,
dices0_.dice_id as dice_id1_0_1_,
dices0_.dice_batch_id as dice_bat5_0_1_,
dices0_.sequential_number as sequenti2_0_1_,
dices0_.throw_result as throw_re3_0_1_,
dices0_.throw_time as throw_ti4_0_1_
from
dice dices0_
where
dices0_.dice_batch_id=?
...and response JSON contains DiceBatch with all Dice entities related to it.
So I have several question.
Why didn't I get LazyInitializationException?
How come the List<Dice> inside DiceBatch got initialized outside of Transactional context?
How Spring managed to build a complete entity of DiceBatch, including the content of the List<Dice> without any exceptions?
How to modify my code to avoid this strange implicit bahavior?
Here is all the relevant code.
package org.dice.model;
#Entity
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Dice {
#Id
#GenericGenerator(name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO,
generator = "UUID")
#Column(name = "dice_id",
nullable = false)
private UUID id;
#Column(name = "throw_result",
nullable = false)
private Integer throwResult;
#Column(name = "throw_time",
nullable = false)
private LocalDateTime throwTime;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "dice_batch_id",
nullable = false,
foreignKey = #ForeignKey(name = "fk_dice_dice_batch_id_dice_batch_dice_batch_id")
)
#JsonBackReference
private DiceBatch diceBatch;
#Embedded
private SequentialNumber sequentialNumber;
}
package org.dice.model;
#Entity
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class DiceBatch {
#Id
#GenericGenerator(name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(strategy = GenerationType.AUTO,
generator = "UUID")
#Column(name = "dice_batch_id",
nullable = false)
private UUID id;
#Column(name = "batch_name",
nullable = false)
private String batchName;
#Column(name = "batch_creation_time",
nullable = false)
private LocalDateTime batchCreationTime;
#OneToMany(
mappedBy = "diceBatch",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
#JsonManagedReference
private List<Dice> dices = new ArrayList<>();
public void addDice(Dice dice) {
dices.add(dice);
dice.setDiceBatch(this);
}
public void removeDice(Dice dice) {
dices.remove(dice);
dice.setDiceBatch(null);
}
}
package org.dice.repo;
#Repository
public interface DiceBatchRepo extends JpaRepository<DiceBatch, UUID> {}
package org.dice.service;
#Service
#RequiredArgsConstructor
public class DiceBatchService {
#Transactional
public DiceBatch findDiceBatchById(UUID diceBatchId) {
DiceBatch diceBatch = diceBatchRepo
.findById_my(diceBatchId)
.orElseThrow();
return diceBatch;
}
}
package org.dice.controller;
public class DiceBatchController {
#GetMapping(path = "/get/{diceBatchId}")
public ResponseEntity<DiceBatch> findDiceBatchById(
#PathVariable(name = "diceBatchId") UUID diceBatchId) {
log.info("<C>[/batch/get] endpoint reached.\n" +
"Dice Batch Id: {}\n",
diceBatchId);
return ResponseEntity.ok(diceBatchService.findDiceBatchById(diceBatchId));
}
}

Spring - JPA join abstract class in abstract class

I have a problem with JPA inheritance. The database model is also specially built. It contains several tables with the same attributes (the tables were intentionally cut by country) and all these tables connect to another table (OneToOne).
Here is an example of the data model:
usa_user, germany_user, austria_user. All these tables have the same attributes (id, name, address). Now the address was also built up according to the countries e.g. usa_address, germany_address, austria_address.
Now I don't know or have the problem that I have been mapping them correctly for a long time. I have the following:
// All Lombok Getter, Setter Args,...
#MappedSuperclass
public abstract Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", referencedColumnName = "id")
#JsonIgnore
private User user;
private String name;
private String addr_num;
...
}
// All Lombok Getter, Setter Args,...
#MappedSuperclass
public abstract User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JsonIgnore
private Address address;
private String name;
}
#Entity
#Table(name = "usa_user")
public class UsaUser extends User {}
#Entity
#Table(name = "austria_user")
public class AustriaUser extends User {}
#Entity
#Table(name = "germany_user")
public class GermanyUser extends User {}
#Entity
#Table(name = "usa_address")
public class UsaAddress extends Address {}
#Entity
#Table(name = "austria_address")
public class AustriaAddress extends Address {}
#Entity
#Table(name = "germany_address")
public class GermanyAddress extends Address {}
But unfortunately this does not work. Every time I start it JPA notices that it can't map the Entities Address - User (which is understandable because they are not entities but abstract classes). What would be the best way to solve this? I want to avoid that I have to list the attributes in all these entities because it would be redundant.
The goal is to find out how I can use a #MappedSuperclass in a #MappedSuperclass.
MappedSuperclass is not queryable and thus also not joinable. You need to map this as an abstract entity with the table per class inheritance strategy. Just switch to #Entity on the Address and User and add #Inheritance(TABLE_PER_CLASS).

Using Entity with OneToMany and HATEOAS RessourceAssembler leads to infinite recursion

I'm using two JPA entities annotated with #OneToMany (parent) <-> #ManyToOne (child) and I also wrote a RessourceAssembler to turn the entities into resources in the controller of a Springboot application (see below for code samples).
Without the relationship #OneToMany in the parent entity, Ressource assembling and serialisation works just fine.
As soon as I add the OneToMany relation on the parent the serialisation breaks with this:
"Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.hateoas.Resource[\"content\"]->com.marcelser.app.entities.Storage[\"storageStock\"])"
As you can see the infinite loop comes from the hateoas Resource, not the entities themselves.
I already tried to add #JsonManagedReference and #JsonBackReference on the entities or #JsonIgnore on the child but nothing really helps. The Hateoas RessourceAssembler always ends up in a infinite loop as soon as the child entity is embedded. It seems that shose #Json.... annotations help with the JSON serialisation of the entity itself but they don't solve problems with the RessourceAssembler
I have these 2 entities (Storage & Stock)
#Entity
#Table(name = "storage")
#Data
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;
}
#Entity
#Table(name = "stock")
#Data
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
and I'm using a RessourceAssemlber like follows for the parent entity 'Storage':
#Component
public class StorageResourceAssembler implements ResourceAssembler<Storage, Resource<Storage>> {
#Override
public Resource<Storage> toResource(Storage storage) {
return new Resource<>(storage,
linkTo(methodOn(StorageController.class).one(storage.getId())).withSelfRel(),
linkTo(methodOn(StorageController.class).all()).withRel("storages"));
}
}
and in the controller I have 2 get classes to list all or just a single Storage with its childs
public class StorageController {
private final StorageRepository repository;
private final StorageResourceAssembler assembler;
#GetMapping
ResponseEntity<?> all() {
List<Resource<Storage>> storages = repository.findAll().stream()
.map(assembler::toResource)
.collect(Collectors.toList());
Resources<Resource<Storage>> resources = new Resources<>(storages,
linkTo(methodOn(StorageController.class).all()).withSelfRel());
return ResponseEntity.ok(resources);
}
private static final Logger log = LoggerFactory.getLogger(StorageController.class);
StorageController(StorageRepository repository, StorageResourceAssembler assembler) {
this.repository = repository;
this.assembler = assembler;
}
#GetMapping("/{id}")
ResponseEntity<?> one(#PathVariable Long id) {
try {
Storage storage = repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(id));
Resource<Storage> resource = assembler.toResource(storage);
return ResponseEntity.ok(resource);
}
catch (EntityNotFoundException e) {
log.info(e.getLocalizedMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body (new VndErrors.VndError("Storage not found", "could not find storage with id " + id ));
}
}
... omitted Put/Post/Delete
}
Can anyone enlighten me how I can solve this infinite loop in HateOAS. What I want is that the embedded child entries just either don't link back to the parent (so no links to parent are created) or they contain the link for the one level but no further processing is done.
To handle the problem related to the serialization of the model using Jackson API when the model attributes have a lazy loading defined, we have to tell the serializer to ignore the chain or helpful garbage that Hibernate adds to classes, so it can manage lazy loading of data by declaring #JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) annotation.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {...
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {...
or you can just declare unilaterally mapping commenting the Storage entity declaration and changing the private Storage storage; to fetch EAGER #ManyToOne(fetch = FetchType.EAGER) in Stock class.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
/*#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;*/
}
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
Maybe a little late, but I've had this problem or very similar and I've only found one solution. The same error 500 gave me the clue on how to solve it:
Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.hateoas.PagedModel["_embedded"]->java.util.Collections$UnmodifiableMap["usuarios"]->java.util.ArrayList[0]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Usuario["rol"]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Rol$HibernateProxy$QFcQnzTB["hibernateLazyInitializer"])
So I only had to add in the application.properties:
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false

Why hibernate entity graph fetch nested lazy collections

I am trying to use entity graph for triggering lazy collections to load but unfortunately entity graph also triggers all nested collections. I am using spring-data-jpa-entity-graph library for creating entity graphs at runtime.
#Entity
public class Brand implements Serializable {
#OneToMany(mappedBy = "brand", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Vehicle> vehicles;
}
#Entity
public class Vehicle implements Serializable {
#ManyToOne
#JoinColumn(name = "brand_id")
private Brand brand;
#OneToMany(mappedBy = "vehicle", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<VehiclePart> parts;
}
#Entity
public class VehiclePart implements Serializable {
#ManyToOne
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
}
Spring service with JPA repository:
public interface BrandsRepository extends EntityGraphJpaRepository<Brand, Long> {
Page<Brand> findAll(Pagable pagable, EntityGraph entityGraph);
}
#Service
public class BrandsService {
public List<Brand> find() {
return repository.findAll(PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "id")), EntityGraphUtils.fromAttributePaths("vehicles")).getContent();
}
}
In this case service also return parts collection for each vehicle but I would like to fetch only list of brands with vehicles collection for each brand.
How can we trigger to load lazy collections just on the first level (only brand's vehicles -- without vehicle's parts)?
I had the same problem. In my case: Spring and hibernate acted correctly, but I can see, that unused (lazy) fields are queried from sql.
When you use the fields, then they will be loaded over sql.
Iam using lombok and #EqualsAndHashCode.Exclude and #ToString.Exclude helps to prevent that.
In your case: Add a DTO-layer. Do not return the entities themself.
Or use #JsonIgnore annotation to ignore fields.

How to load OneToMany Collections data in response using FetchType.LAZY?

I have created a sample jHipster sample app( url: http://jhipster.github.io/creating_an_app.html), using entity sub-generator I have created an Event entity which has OneToMany relationship with EventImages, EventTickets and EventQuestions when I retrieve all(app running in local machine, the api url: http://127.0.0.1:8080/api/events ) events I couldn't find EventImages, EventTickets and EventQuestions data in response.
Event Entity
#Entity
#Table(name = "JHI_EVENT")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Event implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/* other fields */
#OneToMany(mappedBy = "event")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private Set<EventTicket> eventTickets = new HashSet<>();
#OneToMany(mappedBy = "event")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private Set<EventImage> eventImages = new HashSet<>();
#OneToMany(mappedBy = "event")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private Set<EventQuestion> eventQuestions = new HashSet<>();
/* getter and setters */
}
EventImages entity
#Entity
#Table(name = "JHI_EVENTIMAGE")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class EventImage implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "image_url")
private String imageUrl;
#ManyToOne
private Event event;
/* getters and setters */
}
similarly, EventTickets and EventQuestions entities.
After some research i found that i need to remove #JsonIgnore annotation to load OneToMany Collections data using lazy fetch, the response i got is null for EventImage, EventTicket and EventQuestions, as below.
[ {
"id": 1,
"title": "First Event",
"eventVenue": "xyz",
"startDate": "2015-05-28T10:10:00Z",
"endDate": "2015-06-20T10:10:00Z",
"eventTickets": null,
"eventImages": null,
"eventQuestions": null
} ]
Then I found I need use #JsonManagedReference and #JsonBackReference on parent/child relation, but need to use fetch = Fetch.EAGAR (I want load OneToMany Collections when I set FetchType.LAZY which is default, as an when Event entity is called).
Event entity when I used #JsonManagedReference
#Entity
#Table(name = "JHI_EVENT")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Event implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/* other fields */
#OneToMany(mappedBy = "event", fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonManagedReference
private Set<EventTicket> eventTickets = new HashSet<>();
#OneToMany(mappedBy = "event", fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonManagedReference
private Set<EventImage> eventImages = new HashSet<>();
#OneToMany(mappedBy = "event", fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonManagedReference
private Set<EventQuestion> eventQuestions = new HashSet<>();
/* getter and setters */
}
EventImage entity when I used #JsonBackReference
#Entity
#Table(name = "JHI_EVENTIMAGE")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class EventImage implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "image_url")
private String imageUrl;
#ManyToOne
#JsonBackReference
private Event event;
/* getters and setters */
}
How to load OneToMany Collections lazily i.e. EventImages, EventTickets and EventQuestions in response when Event entity requested i.e http://127.0.0.1:8080/api/events REST call is made.
Thanks
I don't actually understand your question, but I try to explain how JPA works.
As you mentioned, using JPA you can either load collections LAZILY or EAGERLY.
LAZY means that you do not load the collection from the beginning. The collection is only loaded when you access the collection (this only works in the same transaction where the entity is loaded or attached).
EAGER means that the collection is loaded from the beginning (as the entity itself is loaded).
So if you want to provide the collection by the REST service, then you have to load the collections during the transaction.
This can be done in several ways:
One way is to define the FetchType of the collection to EAGER
Another way is to LAZY load the collection and after loading the entity, access the collection (for example by calling size() -> event.getEventImages().size();)
Another way is to load the entity and the collection with a JPQL-Query (SELECT e FROM Event JOIN FETCH e.eventImages ...)
There are even more ways to achieve this depending on the JPA implementation you are using
So, if I understood your question rigth, then you could Annotate your Spring-Data-DAO-Find-Method with
#Query("SELECT e FROM Event JOIN FETCH e.eventTickets, JOIN FETCH e.eventImages, JOIN FETCH e.eventQuestions")

Resources