Spring JPA paginated query with Join Fetch - Count Query gives fetch error - spring

(Note: all code examples are extremely simple. I know there are other ways to do such simple queries. The problem I am demonstrating, however, is a bigger deal for more complex queries).
There is a known issue with Spring JPA Repositories and paginated queries that I'm really hoping there is a good solution for. In JPQL, it is possible to use JOIN FETCH to specify that I want to eagerly fetch a related entity, rather than doing it lazily. This avoids the N+1 problem, among other things. JOIN FETCH requires that the owner of the association is included in the select clause. Here is a very simple example of the type of query I'm talking about:
#Query("""
SELECT p
FROM Person p
JOIN FETCH p.address
""")
Page<Person> getPeopleAndAddresses(Pageable page);
The problem with this kind of query is the pagination piece. When returning a Page, Spring will do the query I wrote but then also do a count query to get the total possible records. Spring appears to take my query exactly as written, and just replace SELECT p with SELECT COUNT(p). Doing this means that the owner of the JOIN FETCH association is no longer present in the SELECT clause, which then results in the JPQL giving an error.
The only way I know how to resolve this is to construct the query with separate query and countQuery values, like this:
#Query(query = """
SELECT p
FROM Person p
JOIN FETCH p.address
""", countQuery = """
SELECT COUNT(p)
FROM Person p
""")
Page<Person> getPeopleAndAddresses(Pageable page);
This resolves the JPQL JOIN FETCH error, because the count query no longer contains a JOIN FETCH clause. However, for complex queries with sophisticated JOINs and WHERE clauses, this will lead to excessive code duplication as I will have to write all that logic in two places.
This seems like the kind of issue where there really should be a better solution available. I'm exploring various alternatives, including Specifications (mixed feelings), Blaze Persistence, and others. However, I'm wondering if there is some way in Spring itself to resolve this issue so that the first code example would work without an error?

Related

Room relational query method with paging

In Room 2.4, there is a new feature called relational query method in DAO which you can write your custom query to select columns from 2 entities and Room can be able to aggregate into Map<TableA, List<TableB>> return type.
I have a fairly complicated query which do left join with nested queries to return a map of railway stations and their associated rail lines (many-to-many relationship). I tried to make a #Query method returns Flow<Map<RailStation, List<RailLine>>> and it can return the map that I want.
Now, I want to go one step further to make it returns paging 3's PagingSource. As the original type is a Map, so I think I should make the paging #Query method as PagingSource<Int, Map.Entry<RailStation, List<RailLine>>>. (Map.Entry should be the representative type of a single list item rather than Map as it represent the whole query result.) However, the Room annotation processor complainted about this line saying that it cannot handle this type:
[ksp] RailStationDao.kt:130: Not sure how to convert a Cursor to this method's return type (androidx.paging.PagingSource<java.lang.Integer, java.util.Map.Entry<RailStation, java.util.List<RailLine>>>).
So my question is: does the Room annotation processor and Paging 3 support for relational query method with paging 3? If not, is there any alternative way to archive the same goal? It seems like the #Relation annotation in Room can only support for simple table joining, but my case is I need to write nested query in the LEFT JOIN clause.

Neo4j Spring OGM query for list of entities always return distinct

Lets say i have a graph:
A - follows -> B
A - follows -> C
Now, i have a query to get followers for both B and C (which should return me A 2 times).
MATCH (a)<-[:FOLLOWS]-(followers)
WHERE a.username IN ['B','C']
RETURN followers
If i make this query through Neo4J browser, i get 2 records: A node 2 times. This is correct.
If i make the same query through Spring Repository i get a list with only 1 object (A).
So, through Spring's repository any query for entities performs as if i add DISTINCT, so there is no difference between regular query and DISTINCT one.
If i query for some property of a node, i.e. A.username, i get a list with two duplicate strings (as intended).
Is this behaviour expected?
Why ?
Is there a way to query fo full entities with duplicates, the same
way that Cypher query works in Neo4J itself?
In general this behaviour is correct:
The A is always the same and gets mapped as one object. It won't make any sense to create the very same object twice.
I don't know from your question what the query should map to. But assuming it should create List<A> for this query it is correct.
Returning the a.username will not map to any entity but can only get collected in a projection / #QueryResult. This result has no concept of equality or similar and will always get created for any returned "row" from the response.

Can Spring Data Jpa Repository be used to create complex queries?

First of all some context: i have 2 entities CommodityData(commodityId, code, defaultDescription) and CommodityDescription(commodityId, lang, description). The defaultDescription field in first table use english and the localizated description is in second table.
Using pure JPA i get what i need but i would like to know if it is possible to implement it using SpringDataJpa Repository and Specifications (avoiding JPQL).
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CommodityLang> query = cb.createQuery(CommodityLang.class);
Root<CommodityData> commodity = query.from(CommodityData.class);
Join<CommodityData, CommodityDescription> join = commodity.join("descriptions", JoinType.LEFT);
join.on(cb.equal(join.get("lang"), lang));
query.select(
cb.construct(CommodityLang.class,
commodity.get("categoryId"),
commodity.get("code"),
commodity.get("parent"),
commodity.get("path"),
commodity.get("defaultDescription"),
commodity.get("legacyLink"),
commodity.get("documentId"),
commodity.get("type"),
commodity.get("status"),
join.get("description"),
join.get("lang")
));
TypedQuery<CommodityLang> typedQuery = em.createQuery(query);
List<CommodityLang> results = typedQuery.getResultList();
The use of Spring Data Repository would be interesting because it would avoid handling pagination and sorting, but reading the source of SimpleJpaRepository I think it's not possible. It's correct?
Thank you,
Gabriele
PS: Left join use a on condition to do some filter on records.
You can combine repositories and specifications:
UserSpecification useSpecification =
new UserSpecification(new SearchCriteria("lastname", ":", "Trump"));
List<User> results = repository.findAll(useSpecification);
You also get paging, look at this example
After one year of development we have come to a compromise.
With JPA we have made even custom repositories very complex but we have found that over time they are not very maintainable and you cannot do a great optimization.
For CRUD operations we use SpringData over JPA, for complex report we use MyBatis that allows us to tune queries.
With a similar stack so i suggest to use this combo.

"the method join is not supported" with Tridion OData service & Linq

I'm trying to a join CustomMeta & PageContents to select a specific page via some metadata that has been set, but I'm getting a "the method join is not supported" error. I think the issue is with my linq statement, as the error happens before anything gets sent to the OData service. But what is the issue exactly? The linq statement looks fine to me:
var pages2 = (from p in cds.PageContents
join m in cds.CustomMetas on p.PageId equals m.ItemId
where m.ItemType==64 && m.KeyName=="SomeKey" && m.StringValue=="SomeValue"
select p).ToList<SDLODataClient.SDLOData.PageContent>();
UPDATE 1
This Tridion OData article has an example of a join but some of the MS Linq to OData articles I'm reading seem to suggest that joins aren't supported in Linq to OData (here)
To my knowledge, LINQ queries against Data Services (OData) does not support several methods. The one you are using is join is also falls under the same category hence you are seeing the error even though the syntax is very valid from LINQ point of view. join falls under "Projection and filtering operators" which is not supported query with LINQ against OData .
Here is the link that explains all the unsupported methods (Refer section - Unsupported LINQ Methods)
http://msdn.microsoft.com/en-us/library/ee622463(v=vs.100).aspx
Back to your question, I am not quite how to achieve what you are looking for but I would try the following (you might have to get the results in multiple iterations):
Get the list of Page IDs that match the custom meta query (sample snippet - not tested)
_client.CustomMetas.Where (
m => m.KeyName == "somekey" && m.StringValue == "somevalue" && m.ItemType == 64)
.ToList();
Now you could query Page Contents using the above page ids. You might have build the filter type query for each page id.
Hope this helps.
Have you tried using the concept of expand ?
In OData service we do not have the join query but there is Expand keyword, wherein two entities (tables) that have a foreign key relationship can be used together to get a result set. It works as follows:
from item in table1.Expand(table2).AsEnumerable()
where (item.property1.Equals("x") & item.table2[0].prop2.Equals("y"))
select item
Here the second entity is exposed as an property of the first entity.

Linq Query instead of Contains Operator for Performance issue

I have to Pull all customers whose ids are in the list
I have a list of CustomerID`s
List custidlist=new List{1,2,3....etc.}();
i have to write a linq query to get all customers whose id`s are in the above list
custidlist.
var customers=db.Customers.Where(c=> custidlist.Contains(c.customerid));
Using Contains is not good in performance issue.
Can we use COMPARE OPERATOR LIKE THIS
var customers=db.Customers.Where(c=> custidlist.Compare(c.customerid)); ????
I Heard Compare is best for Performance
Since this is Linq to SQL / Entities your Linq Contains query will be translated to a SQL statement roughly like:
select * from Customers where customerId in (1,2,3)
Not only is your other suggestion not supported, but also you cannot do any better than this SQL performance wise.
When you write a Contains query in Linq to SQL it iwll be fired as an in query in SQL and running your query on the databse should be the fastest..
one caveats to this though is to remember in query might have a limit on the number of entities I think its around 2000+ in sql server and the way around this would be to batch your query.

Resources