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.
Related
#Entity
class Person{
private int id;
#OneToMany(mappedBy=owner)
private List<Pet> pets;
}
#Entity
class Pet{
private name;
private ZonedDateTime birthDate;
#ManyToOne
#JoinColumn(name="owner_id")
private Person owner;
}
I want to find all the persons and order them by their oldest pet birthday
The only way I can solve this is through #Formula , something like
#Entity
class Person{
private int id;
private List<Pet> pets;
#Formula("(SELECT p.birth_date FROM pet p WHERE p.owner_id = id order by p.birth_date ASC LIMIT 1)")
private ZonedDateTime oldestPetBirthday;
}
then
public List<Person> findPersonByOrderByOldestPetBirthdayAsc
But I don't want to touch raw sql, I am looking for something like
public List<Person> findPersonByOrderByPetsTop1OrderByBirthDateAsc
OR by using pageable something like:
PageRequest.of(page,pageSize,Sort.by(ASC, "pets.sort(BirthDateComparator).get(0)"))
is that possible?
Try to use #OrderBy annotation from #javax.persistence.OrderBy package on your one to many collection object.
#OrderBy("birthDate")
private List<Pet> pets;
Your solution with the formula is ok but suffers from some issues. Anyway, since you don't want to write SQL, you will have to use something like 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(Person.class)
public interface PersonDto {
#IdMapping
Long getId();
#Limit(limit = "1", order = "birthDate desc)
#Mapping("pets")
OldestPetDto getOldestPet();
#EntityView(Pet.class)
interface OldestPetDto {
#IdMapping
Long getId();
ZonedDateTime getBirthDate();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
PersonDto a = entityViewManager.find(entityManager, PersonDto.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<PersonDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
Also, you can add a Sort for oldestPet.birthDate and it will work just like you would like it to!
I have a simple project that has a User model, Sports team model and a Many To Many table where a user can "like" the sports team.
User
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "likes",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "team_id")
)
private List<Team> teamsLiked;
Team
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(min=2, max=30)
private String teamName;
#NotBlank
private String city;
private String sport;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "likes",
joinColumns = #JoinColumn(name = "team_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> likers;
My problem is, when I'm using Spring MVC forms for a user to edit a team, upon submission it completely wipes out existing likes on the Team object under likers. On the edit page, I am using #ModelAttribute and pre populating the existing team object, and have tried to put the likers as a hidden attribute so the data will persist, but that throws an error. I've tried on the #PostMapping backend, to set the origin list of likers before re-saving the DB and that's not working either. Besides using Normal HTML forms to update an object, is there a way I can have the list of users who liked a team persist after updating? Thanks in advance.
What you need here is a DTO and map that onto an existing entity. 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(Team.class)
#UpdatableEntityView
public interface TeamDto {
#IdMapping
Long getId();
String getTeamName();
void setTeamName(String teamName);
String getCity();
void setCity(String city);
String getSport();
void setSport(String sport);
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
TeamDto a = entityViewManager.find(entityManager, TeamDto.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<TeamDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
And in your case of saving data, you can use the Spring WebMvc integration
that would look something like the following:
#Transactional
#PostMapping("/teams")
void save(#RequestBody TeamDto dto){
repository.save(dto);
}
I was trying to use #MappedSuperclass to share the same table between two entities following this article here (How to map multiple JPA entities to one database table with Hibernate),
So I have these 3 classes:
#MappedSuperclass
abstract class UserDao {
#Id
#Column(name = "username", nullable = false, unique = true)
var username: String? = null
#OneToMany(fetch = FetchType.EAGER)
var groups: Set<GroupDao>? = null
}
then:
#Entity(name = "basic_user_auth")
#Table(name = "users")
class BasicUserDao : UserDao() {
}
and:
#Entity(name = "full_auth_user")
#Table(name = "users")
class FullUserDao : UserDao() {
#OneToOne
#PrimaryKeyJoinColumn
var profileJpa: ProfileDao? = null
}
what I was trying is to save some queries overhead of loading the user profile when its not needed, but now when i try to run the app i get the following error:
could not execute statement; SQL [n/a]; constraint [full_auth_user_username" of relation "users_groups];
not sure why Hibernate creates this relation since they both share the same table.
I would recommend you don't share types on the entity level. Sharing a one-to-many association will probably not work as you expect with respect to flushing/synchronization when multiple such entities are involved. IMO you should try out 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(User.class)
public interface BasicUserDao {
#IdMapping
String getUsername();
Set<GroupDao> getRoles();
#EntityView(Group.class)
interface GroupDao {
#IdMapping
Long getId();
String getName();
}
}
#EntityView(User.class)
public interface FullUserDao extends BasicUserDao {
#Mapping("profileJpa")
ProfileDao getProfile();
#EntityView(Profile.class)
interface ProfileDao {
#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.
BasicUserDao a = entityViewManager.find(entityManager, BasicUserDao.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
#Repository
interface UserRepository {
List<BasicUserDao> findAll();
}
The best thing about it is, that it will only fetch the data that is actually needed.
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.
is it possible to have a projection with nested collection with Spring JPA?
I have the following 2 simple entity (to explain the problem)
#Entity
#Table(name = "person")
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList = new ArrayList<>();
}
#Entity
#Table(name = "address")
public class Address implements Serializable {
private Integer id;
private String city;
private String street;
}
Is it possible to have a projection of Person with following attributes filled in ? {person.name, address.city}
I might be wrong in semantics of word Projection. but the problem is what i need to achieve. Maybe it is not possible with Projection, but is there another way to achieve the end goal? Named Entity graph perhaps ?
P.S. please suggest a solution for Spring JPA not Spring Jpa REST
thanks in advance
You're right, Entity Graphs serve this exact purpose - control field loading.
Create entity graphs dynamically from the code or annotate target entities with Named Entity Graphs and then just use their name.
Here is how to modify your Person class to use Named Entity Graphs:
#Entity
#Table(name = "person")
#NamedEntityGraph(name = "persion.name.with.city",
attributeNodes = #NamedAttributeNode(value = "addressList", subgraph = "addresses.city"),
subgraphs = #NamedSubgraph(name = "addresses.city", attributeNodes = #NamedAttributeNode("city")))
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList;
}
And then when loading your person:
EntityGraph graph = em.getEntityGraph("person.name.with.city");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return em.find(Person.class, personId, hints);
The same applies for queries, not only em.find method.
Look this tutorial for more details.
I think that that's not usual scenario of Data JPA usage. But you can achieve your goal with pure JPQL:
SELECT a.street, a.person.name FROM Address a WHERE …
This solution has 2 drawbacks:
It forces you to have bidirectional relationship Address ←→ Person
It returns List
Another solution (and that's preferred JPA way) is to create DTO like this:
class MyPersonDTO {
private String personName;
private List<String> cities;
public MyPersonDTO(String personName, List<Address> adresses) {
this.personName = personName;
cities = adresses
.stream()
.map(Address::getCity)
.collect(Collectors.toList());
}
}
And the execute JPQL query like this:
SELECT NEW package.MyPersonDTO(p.name, p.addressList) FROM Person p WHERE …
Return type will be List<MyPersonDTO> in that case.
Of course you can use any of this solutions inside #Query annotation and it should work.