Spring Data JPA Optional handling - spring-data-jpa

I have such a query in a project:
#Query("FROM UserDataEntity ud " +
"JOIN FETCH ud.userDepartments userDep " +
"JOIN FETCH userDep.department WHERE ud.userId = :userId")
Optional<UserDataEntity> findByUserIdWithDepartments(Long userId);
JPA spec says (EJB 3.0 persistency, paragraph 4.4.5.3 “Fetch Joins”) that parent relation in FETCH JOIN statement is duplicated, i.e if I rewrite the repository method to return
List<UserDataEntity> findByUserIdWithDepartments(Long userId) there will be 2 references to the same UserDataEntity if it has 2 child departments.
In my understanding there should be a runtime exception (Optional awaits for a single result to be returned). But everything works fine. How does Spring Data handle it?

I don't see why you would use a custom query here, why not let Spring Data magic work for you with smart naming and simply calling the getter on the relation that is lazy loaded:
// In your repository you have this (if you are sure that userId is unique) which will be implemnted by JPA
Optional<UserDataEntity> findByUserId(Long userId);
// In you service
UserDataEntity userDatEntity = userDataRepository.findByUserId(userId).orElseThrow();
userDatEntity.getUserDepartments;
...
Keep in mind that all entity relations should be lazy loaded and when you need these relations, you just call the getters.

Related

Updating entity without having the know primary key

Given the following code, how can I add an element to one of the properties of an entity without knowing its Id and retrieving it from the database?
public async Task BookInPersonVisitAsync(Guid propertyId, DateTime dateTime, CancellationToken token)
{
var entity = new OnBoardingProcessEntity{ ExternalId = propertyId };
DbContext.OnBoardingProcesses.Attach(entity);
entity.OnBoardingProcessVisits.Add(new OnBoardingProcessVisitEntity
{
DateTime = dateTime,
Occurred = false
});
await DbContext.SaveChangesAsync(token);
}
ExternalId is just a guid we use for external reference. This doesnt work cause it does not have the id set, but without hitting the database we cant have it.
With entity framework if you have to reference an entity (referencedEntity) from another entity (entity) you have to know referencedEntity.
Otherwise you can add just add the entity setting the referencedEntity to null.
To know the referencedEntity or you know the Id or you have to retrieve it in some ways (from the database).
In SQL (DML) if (and only if) ExternalId is a candidate key noy nullable you can insert the OnBoardingProcessVisit record with a single roundtrip but the insert statement will contain an inner query.
OnBoardingProcessVisit.OnBoardingProcess_Id = (
SELECT
Id
FROM
OnBoardingProcess
WHERE
ExternalId = #propertyId)
EDIT
No way to generate that query with EF. You can have a look to external components (free and not free, for example EntityFramework Extended but in this case I think that doesn't help).
In this case I probably would try to use standard entity framework features (so 1 roundtrip to retrieve the OnBoardingProcess from the ExternalId).
Then, if the roundtrip is too slow, run the SQL query directly on the database.
About performances (and database consistency) add a unique index on OnBoardingProcess.ExternalId (in every case).
Another suggestion if you decide for the roundtrip.
In your code, the entity will be a proxy. If you don't disable lazy load, using your code you will do one more roundtrip when you will access to property
entity.OnBoardingProcessVisits (in the statement entity.OnBoardingProcessVisits.Add).
So, in this case, disable lazy load or do the same using a different way.
The different way in your case is something like
var onBoardingProcessVisitEntity new OnBoardingProcessVisitEntity
{
DateTime = dateTime,
Occurred = false,
OnBoardingProcess = entity
});
DbContext.OnBoardingProcessVisits.Add(onBoardingProcessVisitEntity);
await DbContext.SaveChangesAsync(token);

Purpose of Eclipselink 2.6.0 query - SELECT ID FROM TBL WHERE ID=?

Has anyone got an idea why JPA Provider - Eclipselink (using version 2.6.0) generates query like:
SELECT ID FROM OPERATION WHERE (ID = ?);
or
SELECT ID FROM SUBSCRIPTION WHERE (ID = ?);
Why it needs to get ID providing an ID...
Maybe 1st or 2nd level Cache synchronization?
Maybe my queries are inaccurate...
In my queries I never ask directly this to execute - I use JPQL and never ask for ID giving some ID.
I have quite complex model and queries so I see no point in providing full code (or only if U really insist but I dont think it will help a lot).
Using Oracle 12c DB.
We have encountered the same issue with our Java application using EclipseLink 2.5.2. Specifically, we saw it whenever we inserted a new entity that had a M:1 relationship with another entity. A simplified use case would be:
A a = new A(); // the new entity
B b = lookupB(); // get existing entity from database
a.setB(b); // set M:1 relationship
em.persist(a); // save
In this case, we would always see a query for B (i.e., SELECT ID FROM B WHERE ID = #). After some research, we traced it down to the existence checking that EclipseLink performs before merging records.
We found that annotating the entity class for B with #ExistenceChecking(ExistenceType.ASSUME_EXISTENCE) prevented EclipseLink from running the query.
For a further discussion of this, see this related post.

Why are #OneToMany relationships are duplicated?

I have a 'save()' method inside a Session EJB (UpdateService) which persists an JPA Entity Bean.
The method first removes all existing #OneToMany relations to the Entity Bean. Than the method creates new relations and persists them. This all works so far as expected.
But now I have a situation, where another Session EJB (SuperService) which is calling my first Session Bean (UpdateService) needs to update a specific JPA Entity Bean twice. So during one transaction the method UpdateService.save() is called twice for the same entity.
I can see, that in this case the second call of UpdateService.save() did not recognize that the first call has created new relationship (which - as a result, will not be removed) and so it creates an additional relationship. As a result the relationship is doubled. The magic thing is, that other data which is part of attributes of my entity bean are already updated in the second call. It seems that only the pending new relationships are not removeable.
I played already with setting the TransactionIsolation to TRANSACTION_READ_UNCOMMITTED, but with no effect.
So my question is: how can I see that my entity has uncommited new relationships so that I can remove them?
I am running EclipseLink 2.5 on WildFly with MySQL.
This is a simplified part of my EJB code:
....
manager.setFlushMode(FlushModeType.COMMIT);
...
activeEntity = manager.find(Entity.class, sID);
// remove current TextItem List
for (TextItem aItem : activeEntity.getTextItems())
manager.remove(aItem);
activeEntity.getTextItems().clear();
..
// update the relationships
for (Object asingleValue : valueList) {
TextItem newItem = new TextItem(index.getName(),
asingleValue.toString());
manager.persist(newItem);
activeEntity.getTextItems().add(newItem);
}
manager.flush();
manager.detach(activeEntity);
return statusResult;

How to force JPA(eclipselink) to use joins instead of independent queries?

I'm using JPA 2 in my project (eclipselink provider), and I have the following problem:
When I execute the following code:
em.createQuery("select t from " + entityName + " t where t.id = ?1"
).setParameter(1, id)
.setHint(QueryHints.REFRESH, HintValues.TRUE)
.setHint(QueryHints.REFRESH_CASCADE, CascadePolicy.CascadeAllParts)
.getSingleResult();
JPA generates tons of queries to fetch all dependent objects( i.e ~90 queries to fetch an entity).
It there any way to force JPA to use joins instead of independent queries?
You can use join fetching or batch fetching to optimize relationships. Also you should use LAZY on your relationships to avoid loading them when not required.
See,
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html
In your query you can use join and fetch to construct the query yourself.
So for example if your entity t has a
#OneToMany public List<RelatedEntity> things;
then your query can use:
select t from entityName t join fetch t.things where t.id = ?1
Read the documentation for JPQL (Java Persistence Query Language).

Criteria API: Fetch of a list returns repeated main entity

I have the following Entities; Ticket contains a set of 0,N WorkOrder:
#Entity
public class Ticket {
...
#OneToMany(mappedBy="ticket", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<WorkOrder> workOrders = null;
...
}
#Entity
public class WorkOrder {
...
#ManyToOne
#JoinColumn(nullable = false)
private Ticket ticket;
}
I am loading Tickets and fetching the attributes. All of the 0,1 attributes present no problem. For workOrders, I used this answer to get the following code.
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Ticket> criteriaQuery = criteriaBuilder
.createQuery(Ticket.class);
Root<Ticket> rootTicket = criteriaQuery.from(Ticket.class);
ListAttribute<? super Ticket, WorkOrder> workOrders =
rootTicket.getModel().getList("workOrders", WorkOrder.class);
rootTicket.fetch(workOrders, JoinType.LEFT);
// WHERE logic
...
criteriaQuery.select(rootTicket);
TypedQuery<Ticket> query = this.entityManager.createQuery(criteriaQuery);
return query.getResultList();
The result is that, in a query that should return me 1 Ticket with 5 workOrders, I am retrieving the same Ticket 5 times.
If I just make the workOrders an Eager Fetch and delete the fetch code, it works as it should.
Can anyone help me? Thanks in advance.
UPDATE:
One explanation about why I am not just happy with JB Nizet's answer (even if in the end it works).
When I just make the relationship eager, JPA is examining exactly the same data that when I make it lazy and add the fetch clause to the Criteria / JPQL. The relationships between the various elements is also clear, as I define the ListAttribute for the Criteria query.
There is some reasonable explanaition for the reason that JPA does not return the same data in both cases?
UPDATE FOR BOUNTY: While JB Nizet's answer did solve the issue, I still find it meaningless that, given two operations with the same meaning ("Get Ticket and fetch all WorkOrder inside ticket.workOrders"), doing them by an eager loading needs no further changes while specifying a fetch requires a DISTINCT command
There is a difference between eager loading and fetch join. Eager loading doesn't mean that the data is loaded within the same query. It just means that it is loaded immediately, although by additional queries.
The criteria is always translated to an SQL query. If you specify joins, it will be join in SQL. By the nature of SQL, this multiplies the data of the root entity as well, which leads to the effect you got. (Note that you get the same instance multiple times, so the root entity is not multiplied in memory.)
There are several solutions to that:
use distinct(true)
Use the distinct root entity transformer (.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)).
When you don't need to filter by child properties, avoid the join
When you need to filter by child properties, filter by a subquery (DetachedCriteria).
Optimize the N+1 problem by using batch-size
Have you tried calling distinct(true) on the CriteriaQuery?
The JPA 2 specification, page 161, says:
The DISTINCT keyword is used to specify that duplicate values must be
eliminated from the query result.
If DISTINCT is not specified, duplicate values are not eliminated.
The javadoc also says:
Specify whether duplicate query results will be eliminated.A true
value will cause duplicates to be eliminated. A false value will cause
duplicates to be retained. If distinct has not been specified,
duplicate results must be retained.
The reason why you don't need the distinct when the association is eagerly loaded is probably just that the association is not loaded using a fetch join, but using an additional query.