happy new year:)
I have a Spring MVC project using Hibernate and DataJPA.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id", nullable = false)
#NotNull
private Restaurant restaurant;
As you can see, here is two fields with eager fetch. I want to make both a lazy. I need to user #NamedEntityGraph annotation asI made here:
#NamedQueries({
#NamedQuery(name = Restaurant.GET_BY_ID, query = "SELECT r FROM Restaurant r WHERE r.id=?1"),
})
#Entity
#NamedEntityGraph(name = Restaurant.GRAPH_WITH_MENU_HISTORY, attributeNodes = {#NamedAttributeNode("menuHistory")})
#Table(name = "restaurants")
public class Restaurant extends NamedEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "restaurant")
#OrderBy(value = "date DESC")
private List<Dish> menuHistory;
public static final String GRAPH_WITH_MENU_HISTORY = "Restaurant.withMenuHistory";
I want to know, if I'll write
#NamedEntityGraph(name = "G_NAME", attributeNodes = {#NamedAttributeNode("user", "restaurant")})
and if I'll request one of them, will the second load anyway or it will load only by request to him? May be, I need to user two graphs?
According to JPA 2.1 Spec 3.7.4:
The persistence provider is permitted to fetch additional entity state
beyond that specified by a fetch graph or load graph. It is required,
however, that the persistence provider fetch all state specified by
the fetch or load graph.
So actually the #NamedEntityGraph just guarantees what fields should be eagerly loaded, but not what fields should not be loaded.
So, if you make #NamedEntityGraph with user, your persistence provider (Hibernate for example) can load only user field or both user and restaurant fields eagerly. This is dependent on implementation and not guaranteed.
See this hibernate's issue.
But as far as I know, the Hibernate loads only simple fields in addition to specified in #NamedEntityGraph, and not loads lazy associations.
So if you use hibernate, it should work.
But of course you need two separate #NamedEntityGraphs for user and restaurant fields.
Or you can use ad-hoc spring-data-jpa's feature:
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
With this code you don't need explicitly declare #NamedEntityGraph anymore. You can just specify fields inline.
Related
I am implementing soft delete with Hibernate. Each entity that supports soft-deleting has an attribute for it.
#Column(name = "DELETED")
private boolean deleted;
I have created #FilterDef in package-info.java for package with domain objects.
#FilterDef(name = "deletedFilter",
parameters = #ParamDef(name = "includeDeleted", type = Boolean.class),
defaultCondition = ":includeDeleted = true OR DELETED = false"
)
applied it to all DeleteAware entities
#Filter(name = "deletedFilter")
public class CustomerGroup
and enabled in when using in queries
Session session = em.unwrap(Session.class);
session.enableFilter("deletedFilter")
.setParameter("includeDeleted", fp.isDeleted());
Filter is applied and works correctly for primary entity (for example when I query customers I can see that additional where condition is always applied as needed).
Problem is with filter of association. Let's say Customer entity has collection of CustomerGroup.
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinTable(name = "CUSTOMER_CUSTOMER_GROUP",
joinColumns = #JoinColumn(name = "CUSTOMER_ID"),
inverseJoinColumns = #JoinColumn(name = "CUSTOMER_GROUP_ID"))
private Set<CustomerGroup> groups;
However when I query for Customer, groups collection contains deleted entities. I have turned on sql logging and I can see that condition is not applied for lazy query. However if I change
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
to
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
it works.
Both entities are annotated with #Filter. I have also tried applying #Filter annotation to collection itself success without. For initial testing I have also ensured that filters are not disabled and includeDeleted parameter is always false.
#Where annotation on entities works like a charm, but cannot be disabled (99% percent of queries we want to filter out deleted objects but there is that pesky 1% where we need deleted ones).
We are using Hibernate 6.1.13 provided in WildFly 27 application server. Looks like filters are not applied when relation is lazy loaded. Am I missing something?
In the context of a Spring Boot project using Spring Data JPA, I have defined the following entities:
Ent1 contains a list of Ent2 elements
Ent2 contains a list of Ent3 elements
When fetching a top-level Ent1 object through a repository, I'm seeing that every Ent2 which has more than one child appears multiple times in the Ent1.ent2 list. For example, an Ent2 with two childs will appear twice.
So instead of getting this:
I'm getting this:
Notes:
There are no duplicates in the database
If I delete ent3b in the database, the duplicated ent2 disappears
Here's a simplified version of the code:
```java
#Entity
public class Ent1 {
#OneToMany(mappedBy="parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Ent2> ent2 = new ArrayList<Ent2>();
}
#Entity
public class Ent2 {
#ManyToOne
#JoinColumn(name = "PARENT_ID", nullable = false)
protected Ent1 parent;
#OneToMany(mappedBy="parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Ent3> ent3 = new ArrayList<Ent3>();
}
#Entity
public class Ent3 {
#ManyToOne
#JoinColumn(name = "PARENT_ID", nullable = false)
protected Ent2 parent;
}
```
Solution was to convert Lists into Sets. Lists in JPA require additional data (i.e. an ordering column) to extract a total ordering of elements from the relationship. It can be done but typically the average user only needs Set and it's a reflection of the relationship that most people model.
OP also commented that the previous provider didn't have this requirement so if you were previously using EclipseLink and switching ORM providers this may be a problem for you too.
I'm having issues with defining a foreign key field within an entity. One specific thing that I can't find an answer to, is how to define such field but as a Long type, and not as that target entity type, and also set it up as ON DELETE CASCADE.
E.g.
#Entity
#Table(name = "user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
and
#Entity
#Table(name = "address")
public class AddressEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JoinColumn(
table = "user",
name = "user_id",
referencedColumnName = "id")
private Long userId;
}
This example works fine, but now one can't easily define this DELETE ON CASCADE for the userId field i.e. Address entity.
One specific thing that I can't find an answer to, is how to define
such field but as a Long type, and not as that target entity type, and
also set it up as ON DELETE CASCADE.
It stands to reason that you cannot find an answer, because JPA does not provide one. If you want JPA to manage relationships between entities, then you must define those relationships in the JPA way, with entities holding references to other entity objects and declaring appropriate relationship annotations.* And if you want cascading deletes in your persistence context then you definitely do want them to be managed / recognized by JPA, for any other kind of approach is likely to create problems involving the context falling out of sync with the underlying data store.
It's unclear what problem you are trying to solve by avoiding JPA-style relationship management, but I'm inclined to think that there must be a better way. For example, if you want to avoid requiring the persistence context to load the associated UserEntity whenever an AddressEntity is loaded, then you would define the relationship with a lazy fetch strategy:
#Entity
public class AddressEntity {
// ...
#OneToOne(optional = true, fetch = FetchType.LAZY)
private UserEntity user;
}
#Entity
public class UserEntity {
// ...
#OneToOne(optional = true, fetch = FetchType.LAZY, cascade = CascadeType.ALL,
mappedBy = user)
AddressType address;
}
(Do note, however, that FetchType.LAZY is a hint, not a constraint. The context might sometimes still load the user together with its address if that's convenient.)
If you want to get the associated user id from an address, then the best way to do so is to read it from the user:
// ...
public Long getUserId() {
return (user == null) ? null : user.getId();
}
That does require the UserEntity to define an accessible getId() method, but since you are using JPA field-based access, you do not need also to provide a setter, and you may give the method default access. Or you could just declare UserEntity.id such that it is directly accessible by AddressEntity.
On the other hand, if you want to provide for the user ID to be accessible without loading the user entity then instead of a method such as the above getUserId(), in addition to the relationship field you could define a persistent, read-only AddressEntity.userId field, mapped to the appropriate column. It must be read-only because the value of the id in the underlying data store will necessarily be managed via the entity relationship, so it cannot also be managed via this separate field. For example:
#Entity
public class AddressEntity {
// ...
#OneToOne(optional = true, fetch = FetchType.LAZY)
private UserEntity user;
#Column(name = "user_id", insertable = false, updatable = false, nullable = true)
public Long userId;
}
This is a brittle approach, and I do not recommend it. It will be prone to problems with the userId field falling out of sync with the user entity. That may be bearable for the usage you have in mind, but this sort of weirdness is fertile ground for future bugs.
*Side note: as far as I know or can determine, JPA does not define semantics for a #JoinColumn annotation on a non-relationship field such as in your original code. That doesn't mean that your particular persistence provider can't interpret it in a way that you characterize as "works fine", but at minimum you are on thin ice with that.
I know for a fact that with clause on fetch join are not allowed by hibernate
I am using spring data jpa and postgres.
Here is how my entity is designed
public class Organisation {
#Id
private Long id;
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.EXTRA)
private Set<Assignment> assignments = new HashSet<>();
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
private List<Event> events;
}
public class Event {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisations_id", nullable = false)
private Organisation organisation;
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
}
public class EventValue {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "event_id")
private Event Event;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "assignment_id")
private Assignment assignment;
}
public class Assignment {
#Id
private Long id;
#OneToMany(mappedBy = "assignment", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
}
Kind of a three way mapping. What the above entity design says is:
one organisation can have many events
one events can have many event values
one organisation can have many assignments
one assignment can be mapped to only one organisation and whithin the event of this organisation it is supposed to have only one event value (but as per entity design above it can have set of values which is not directly mapped to assignment)
So, I tried to query something like this.
#Query("select assignment from Assignment left join fetch assignment.organisation org
left join fetch org.event event left join fetch event.eventValues eventValue
with eventValue.assignment.id=?1 where assignment.id=?1)
Assignment getByAssignmentId(Long id);
What am I trying to achive with the query ?
To get assignment with given (id) -> organisation -> list of activities with HashSet containing only ONE activity value mapped to assignment.
The query is obviously going to fail because of using with clause on fetch join. I somehow feel the entity has 3 way dependency so it might be wrong.
I do not want to generic jdbcTemplate solution or SqlResultMapping solution where we need to do some kind of projection and set values manually. Is there a ORM solution to solve this problem ?
The reason why a WITH or ON clause is disallowed for join fetches is pretty simple. Hibernate works on managed entities, which means, once the entities are managed by the current persistence context, changes done to these objects will be flushed back to the database at the end of the transaction.
Now, if you were allowed to use the WITH or ON clause in a join fetch, the querying itself could alter the managed state of a collection, which would lead to UPDATE/DELETE statements to flush the collection changes back. Since this is completely unexpected, but a necessary side effect, it is disallowed.
Having said that, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A DTO mapping for your model could look as simple as the following
#EntityView(Assignment.class)
interface AssignmentDto {
Long getId();
OrganisationDto getOrganisation();
}
#EntityView(Organisation.class)
interface OrganisationDto {
Long getId();
List<EventDto> getEvents();
}
#EntityView(Event.class)
interface EventDto {
Long getId();
#Mapping("eventValues[assignment.id = VIEW_ROOT(id)]")
EventValueDto getEventValue();
}
#EntityView(EventValue.class)
interface EventValueDto {
Long getId();
// Other stuff
}
The JOIN condition is modeled in the mapping expression eventValues[assignment.id = VIEW_ROOT(id)] which translates to what you would expect.
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AssignmentDto dto = entityViewManager.find(entityManager, AssignmentDto.class, id);
But 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
It will only fetch the mappings that you tell it to fetch.
I am developing REST application using spring boot and I am trying to optimize the performance of the queries. I am currently using findAll from the repositories which is causing performance issues. Code is given below:
Person Entity
#Entity
#Table(name = "cd_person")
#Data
#NoArgsConstructor
public class Person {
....
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "password_id")
#Fetch(FetchMode.JOIN)
private Password password;
....
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "cd_person_role",
joinColumns = #JoinColumn(name = "person_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
#Fetch(FetchMode.JOIN)
private Set<Role> roles = new HashSet<>();
}
Password Entity
#Entity
#Table(name = "cd_password")
#Data
#NoArgsConstructor
public class Password {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column(name = "password_hash", nullable = false)
private String passwordHash;
.......
}
Role Entity
#Entity
#Table(name = "cd_role")
#Data
#NoArgsConstructor
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "role_type")
#Enumerated(EnumType.STRING)
private RoleType roleType;
....
}
Person Repository
public interface PersonRepository extends CrudRepository<Person, Long> {
Optional<Person> findByEmail(String email);
}
When I do a personRepository.findAll() there are select queries fired for each row in the person table to fetch the password and roles when I access the person. I know I can use #Query annotation with JOIN FETCH in the repository to make it force generate the single query but I was wondering if there was any other way to do so. I am looking for something which we can do at the entity level to reduce queries.
Using spring boot 2.1.5-RELEASE version and related dependencies.
PS. The #Data and #NoArgsConstructor are Lombok annotations.
The most minimal code change is to use the ad-hoc EntityGraph feature from spring data . Just override PersonRepository 's findAll() and use #EntityGraph to configure the graph. All entities in this graph will be fetched together.
public interface PersonRepository extends CrudRepository<Person, Long> {
#EntityGraph(attributePaths = { "password", "roles" })
public List<Person> findAll();
}
Behind scene it works like JOIN FETCH. Only single SQL with LEFT JOIN will be generated.
I would leave the Entity as is and override the findAll method in the repository with an #Query annotation.
This way, the code refactor is minimal (only one repository change instead of an entity change).
The unsatisfying answer to your question is: no, there's no way to annotate/configure the entities so that the fetch mode applies to a query as well.
As you correctly found yourself, you can manipulate the query itself. Alternatives to this are using Hibernate's fetch profiles or leveraging JPA entity graphs - but all of them require programmatic intervention at the query/session level as well.
You should place #BatchSize on top of Password class
#Entity
#Table(name = "cd_password")
#Data
#NoArgsConstructor
#BatchSize(size = 50)
public class Password {
...
}
Here are the queries with #BatchSize:
Hibernate:
select
person0_.id as id1_1_,
person0_.password_id as password2_1_
from
cd_person person0_
Hibernate:
select
password0_.id as id1_0_0_,
password0_.password_hash as password2_0_0_
from
cd_password password0_
where
password0_.id in (
?, ?, ?, ?, ?
)
Can't you use lazy fetch and remove the #Fetch ? Using #NamedQuery on top of your entity and using an hibernate session to call session.createNamedQuery in a custom service would do it.
If you can afford to not use the default personRepository.findAll() but this custom service you would run an optimized query. I get that it does not exactly answer your question but my team and I faced the exact same issue and this is how we did it.
My suggestions would be:
Try to refactor and use lazy fetching.
I might not understand this part well, but why do you need personRepository.findAll() exactly? I think you would merely need something like personRepository.findById(), so you could fetch the roles and other data easily. Selecting all persons seems to be a huge overload here.
You might need the extended functions of JpaRepository later, so it might be worth changing it now instead of working a little bit more later.
This should works:
public interface PersonRepository extends CrudRepository<Person, Long> {
#Override
#Query("SELECT p FROM Person p JOIN FETCH p.roles JOIN FETCH p.password ")
Iterable<Person> findAll();
}