orphanRemoval causes error with lazy loaded collection - spring

I use hibernate 5.0.8 and spring data jpa 1.10.1
Given these entities
class Model {
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
#JoinColumn(nullable = false)
private Configuration configuration;
//more fields and methods
}
class Configuration {
#OneToMany(mappedBy = "configuration", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Setting> settings = new ArrayList<>();
//more fields and methods
//settings is never assigned again - I use settings.add(...) and settings.clear()
}
class Setting {
#ManyToOne
#JoinColumn(nullable = false)
private Configuration configuration;
//more fields and methods
}
Model is the master, but multiple models can use the same configuration. The cascading on configuration in Model is required, because if I change anything in the Configuration, I want it to be applied in all Models using this Configuration
Now when I retrieve an existing Model with a Configuration that has settings, and save this Model, without applying any changes to the settings I get following exception
#Transactional
public void doSomething() {
Model model = modelRepository.findOne(0);
//change something in the model, but no changes are made in its configuration
//or do nothing
modelRepository.save(model);
}
I get the following exception
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: Configuration.settings
I suspect this has something to do with the settings being lazy loaded and hibernate trying to merge an empty list into the configuration.
What am I doing wrong?

Check the getters and setters of the objects you are trying to clean up as orphans while dereferencing:
Try and use :
public void setChildren(Set<Child> aSet) {
//this.child= aSet; //Results in this issue
//change to
this.child.clear();
if (aSet != null) {
this.child.addAll(aSet);
} }

The problem was being caused by using enableLazyInitialization from the hibernate-enhance-maven-plugin. I still have no idea why it was causing this error, but removing this plugin resolved the issue.
I used this plugin, because I wanted to lazy load a large String field in Model that I would cache in the application. I will now change it to a OneToOne relation that is fetched lazily.

Related

How to prevent saving over an existing entity with Spring Data REST

(Samples in Kotlin)
I have an entity with manually assigned IDs:
#Entity
#Table(name = "Item")
class Item {
#Id
#Column(name = "ItemId", nullable = false, updatable = false)
var id: Int? = null
#Column(name = "Name", nullable = false)
var name: String? = null
}
and the Spring Data REST repository for it:
interface ItemRepository : PagingAndSortingRepository<Item, Int>
If I do a POST to /items using an existing ID, the existing object is overwritten. I would expect it to throw back an error. Is there a way to configure that behavior without rolling my own save method for each resource type?
Thanks.
I ended up using a Spring Validator for this with the help of this article.
I created the validator like this:
class BeforeCreateItemValidator(private val itemRepository: ItemRepository) : Validator {
override fun supports(clazz: Class<*>) = Item::class.java == clazz
override fun validate(target: Any, errors: Errors) {
if (target is Item) {
itemRepository
.findById(target.id!!)
.ifPresent {
errors.rejectValue("id",
"item.exists",
arrayOf(target.id.toString()),
"no default message")
}
}
}
}
And then set it up with this configuration:
#Configuration
class RestRepositoryConfiguration(
private val beforeCreateItemValidator: BeforeCreateItemValidator
) : RepositoryRestConfigurer {
override fun configureValidatingRepositoryEventListener(
validatingListener: ValidatingRepositoryEventListener) {
validatingListener.addValidator("beforeCreate", beforeCreateItemValidator)
}
}
Doing this causes the server to return a 400 Bad Request (I'd prefer the ability to change to a 409 Conflict, but a 400 will do) along with a JSON body with an errors property containing my custom message. This is fine for my purposes of checking one entity, but if my whole application had manually assigned IDs it might get a little messy to have to do it this way. I'd like to see a Spring Data REST configuration option to just disable overwrites.
You can add a version attribute to the entity annotated with #Version this will enable optimistic locking. If you provide always the version 0 with new entities you'll should get an exception when that entity does already exist (with a different version).
Of course you then need to provide that version for updates as well.

LazyInitializationException when fetching #EntityGraph from Hibernate 2-nd level cache

I'm developing a Spring Boot 2.3.4 web application with Spring Data JPA.
I want to use the Hibernate 2-nd level query cache for a repository method with #EntityGraph. However, I get a LazyInitializationException when generating a Thymeleaf view in case data is already in the 2-nd level cache unless I have Spring’s Open Session In View turned on. When fetching data for the first time from the database or without the 2nd level cache everything is OK even with spring.jpa.open-in-view=false. Moreover, if I enable spring.jpa.open-in-view there is no exception when fetching data from the cache without any select to the database.
How can I make Hibernate fetch at once all the associations specified in the #EntityGraph when using Hibernate 2nd level cache?
Here is my repository method:
#org.springframework.data.jpa.repository.QueryHints({#javax.persistence.QueryHint(name = "org.hibernate.cacheable", value = "true")})
#EntityGraph(attributePaths = { "venue.city", "lineup.artist", "ticketLinks" }, type = EntityGraphType.FETCH)
Optional<Event> findEventPageViewGraphById(long id);
and part of the entity:
#Entity
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "venue_id")
private Venue venue;
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#OrderBy("orderId")
private Set<TicketLink> ticketLinks = new LinkedHashSet<>();
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
#OrderBy("orderId")
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<ArtistEvent> lineup = new LinkedHashSet<>();
}
That's a known issue. Hibernate does not check the 2nd level cache for associations when constructing "just proxies". You need to access the objects to initialize them, which will then trigger a 2nd level cache hit.
I would recommend you use a DTO approach instead. 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(Event.class)
public interface EventDto {
#IdMapping
Long getId();
VenueDto getVenue();
#MappingIndex("orderId")
List<TicketLinkDto> getTicketLinks();
#MappingIndex("orderId")
List<ArtistEventDto> getLineup();
#EntityView(Venue.class)
interface VenueDto {
#IdMapping
Long getId();
CityDto getCity();
}
#EntityView(City.class)
interface CityDto {
#IdMapping
Long getId();
String getName();
}
#EntityView(TicketLink.class)
interface TicketLinkDto {
#IdMapping
Long getId();
String getName();
}
#EntityView(ArtistEvent.class)
interface ArtistEventDto {
#IdMapping
Long getId();
ArtistDto getArtist();
}
#EntityView(Artist.class)
interface ArtistDto {
#IdMapping
Long getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
EventDto a = entityViewManager.find(entityManager, EventDto.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
Optional<EventDto> findEventPageViewGraphById(long id);
Thank you Christian for your answer. I solved the problem by initializing entities with the static method Hibernate.initialize() as described here https://vladmihalcea.com/initialize-lazy-proxies-collections-jpa-hibernate/
#Transactional(readOnly = true)
public Optional<Event> loadEventPageViewGraph(long id) {
Optional<Event> eventO = eventRepository.findEventPageViewGraphById(id);
if(eventO.isPresent()) {
Hibernate.initialize(eventO.get());
Hibernate.initialize(eventO.get().getVenue().getCity());
for (ArtistEvent artistEvent: eventO.get().getLineup()) {
Hibernate.initialize(artistEvent.getArtist());
}
Hibernate.initialize(eventO.get().getTicketLinks());
return eventO;
} else {
return Optional.empty();
}
}
Though, I agree that in general it is better to use DTO's/projections. However, with DTO's there is a problem with fetching projections that include associated collections (#OneToMany properties) as described here https://vladmihalcea.com/one-to-many-dto-projection-hibernate/. In particular in the case when we don't want to select all of the entity properties. I found that Blaze-Persistence Entity Views has a nice solution for that https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/#subset-basic-collection-mapping. I'll check it out.

Hibernate5Module doesn't fetch OneToMany collection

First of all I have had two related entities mapped with bidirectional OneToMany:
#Data
#Entity
#Table(name = "tbl_parent")
public class Parent {
#Id
#Column(name = "parent_id")
private Long id;
...
#OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
}
and
#Data
#Entity
#Table(name = "tbl_child")
#ToString(exclude = "parent")
public class Child {
#Id
#Column(name = "child_id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Parent parent;
}
However, I had StackOverFlow for method findAll() for Parent entity as far as infinite loop occurred during serialization.
Then I added property in application.yml:
spring:jsp:open-in-view: false
And had another problem with serialization. To solve it I added dependency in build.gradle:
implementation "com.fasterxml.jackson.datatype:jackson-datatype-hibernate5"
And configuration class:
#Configuration
public class JacksonConfiguration {
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
}
Now I have no error but list in parent entity is always null.
What I'm doing wrong? How can I fetch all related children entities without StackOverflow?
#Data annotation from Lombok can cause stackoverflows due to circular dependencies between classes if both classes have #Data and collections/toStrings mentioning each other.
See Lombok.hashCode issue with "java.lang.StackOverflowError: null"
The issue is quite simple: you cannot hope to serialize both sides of a bidirectional association. If you try to do that, you'll get a JSON representation of Parent with a children property, and each object in children will have a parent property, with each parent containing a children property, and so on, ad nauseam.
From what you describe, I'm assuming you want to opt out of serializing the parent property, in which case you should put #JsonIgnore on Child.parent.
Also, since you enabled the HibernateModule, which by default ignores lazy properties, you should either disable it, enable its FORCE_LAZY_LOADING feature, or configure Parent.children to be eagerly fetched. The third option also eliminates the need for #JsonIgnore.
(all three options have potentially far-reaching consequences, so you need to decide which one works best for your use case; also note that having to use open-jpa-in-view is a code smell, because forcing the lazy load happens outside of any transactional context)

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

Spring Data Mongo MongoDB DBRef lazy initialization

I'm using Spring + Spring Data MongoDB.
My model is like this:
#Document(collection = "actors")
public class Actor extends DomainEntity {
private String name;
private String surname;
#DBRef(lazy = true)
private List<Class> classes;
The other class is pretty generic, so I don't post it.
My problem is that the list "classes" isn't loaded when i try to access it, the attribute remains being some kind of proxy object.
Example:
Actor a = actorRepository.findOne(id);
//At this moment classes are a proxy object because of the lazy
//Now I try to load the reference and nothing works
a.getClasses();
a.getClasses().size();
a.getClases().get(0).getAttr();
for(Class g:a.getClasses()){
g.getAttr();
}
I considered a ton of options, but no way to make it working...
I'm using spring-data-mongodb-1.7.0.RELEASE and I was able to solve this issue by initializing the lazy-loaded collection in its declaration, for instance:
#DBRef(lazy = true)
private List<Class> classes = new ArrayList<>();

Resources