JPA/JPQL automatic method query generation - java

I have a co-worker that just blew my mind. We have a Java 11/Spring Boot/Hibernate/JPA app talking to a MySQL DB. Apparently JPA JPQL (or something similar to that) is capable of -- but only if you write the repository methods correctly -- building out queries based on your method name.
So for instance if we have a JPA entity:
#Entity
#Table(name = "accounts")
#Data
public class Account {
#Column(name = "account_email")
private String email;
// ... many more fields down here
}
And then a repository for it:
#Repository
public interface AccountRepository extends JpaRepository<Account,Long> {
#Query("FROM Account WHERE email = :email")
Account findByEmail(#Param(value = "email") String email);
}
Apparently (and this might be a bad example) I could just simplify that to:
#Repository
public interface AccountRepository extends JpaRepository<Account,Long> {
Account findByEmail(String email);
}
And JPA/JPQL will figure out that since I want to "findByEmail" and Account#email exists, it just wants me to do a SELECT * FROM accounts where email = ?. Amazing!
The only problem is: I don't see this documented anywhere well, and I don't see it documented anywhere officially. There's a few old blogs that I was able to find that insinuate the same things, but nowhere official (JPA docs, JPQL docs, etc.) that go into detail as to how it works and what its limitations are.
Can anyone point me in the right direction? What is this mysterious feature/technology called and what are its limitations/capabilities? Can it only work on SELECTs or can it handle inserts/updates/deletes as well?

This is part of the Spring Data support for JPA. You can find more info at the documentation Query Methods and all the supported query-keywords in the appendix section. Here is an excerpt from the documentation:
Query subject keywords
Keyword
Description
find…By, read…By, get…By, query…By, search…By, stream…By
General query method returning typically the repository type, a Collection or Streamable subtype or a result wrapper such as Page, GeoResults or any other store-specific result wrapper. Can be used as findBy…, findMyDomainTypeBy… or in combination with additional keywords.
exists…By
Exists projection, returning typically a boolean result.
count…By
Count projection returning a numeric result.
delete…By, remove…By
Delete query method returning either no result (void) or the delete count.
…First…, …Top…
Limit the query results to the first of results. This keyword can occur in any place of the subject between find (and the other keywords) and by.
…Distinct…
Use a distinct query to return only unique results. Consult the store-specific documentation whether that feature is supported. This keyword can occur in any place of the subject between find (and the other keywords) and by.
Query predicate keywords
Logical keyword
Keyword expressions
AND
And
OR
Or
AFTER
After, IsAfter
BEFORE
Before, IsBefore
CONTAINING
Containing, IsContaining, Contains
BETWEEN
Between, IsBetween
ENDING_WITH
EndingWith, IsEndingWith, EndsWith
EXISTS
Exists
FALSE
False, IsFalse
GREATER_THAN
GreaterThan, IsGreaterThan
GREATER_THAN_EQUALS
GreaterThanEqual, IsGreaterThanEqual
IN
In, IsIn
IS
Is, Equals, (or no keyword)
IS_EMPTY
IsEmpty, Empty
IS_NOT_EMPTY
IsNotEmpty, NotEmpty
IS_NOT_NULL
NotNull, IsNotNull
IS_NULL
Null, IsNull
LESS_THAN
LessThan, IsLessThan
LESS_THAN_EQUAL
LessThanEqual, IsLessThanEqual
LIKE
Like, IsLike
NEAR
Near, IsNear
NOT
Not, IsNot
NOT_IN
NotIn, IsNotIn
NOT_LIKE
NotLike, IsNotLike
REGEX
Regex, MatchesRegex, Matches
STARTING_WITH
StartingWith, IsStartingWith, StartsWith
TRUE
True, IsTrue
WITHIN
Within, IsWithin

Not much of a mystery and quite well documented to be perfectly honest. What you mention is called a derived query which basically infers the JPQL query from the method name. A simple Google search can turn up quite some documentation of this feature. Also the Spring Data JPA documentation clearly documents this and well as its usage. Check here for more.

Related

Spring Data JPA and startsWith repository

I have the following Spring Data JPA repository:
#RepositoryRestResource(collectionResourceRel = "product", path = "product")
public interface ProductRepository extends PagingAndSortingRepository<Product, Integer> {
#RestResource(path = "nameStartsWith", rel = "nameStartsWith")
Page findByNameStartsWithOrderByNameDesc(#Param("name") String name, Pageable p);
}
The definition of the Product class is obvious and is a mapped JPA entity on a postgresql database.
It works pretty nice, but it has an annoying problem which I couldn't fix.
I suppose that spring translate this method definition in a sql query with the like operator that uses _ and % as wild cards. I'm afraid anyway that those character are not escaped when passed to this method, with the results that if I search for a product with a name that contains a _ it gets understood as "any character", and this is bad due to the naming convention my products use.
I need a way to escape the name parameter before it gets passed to the method, but the only way I could think of is implementing the method myself loosing all the magic of spring data. Is there a more elegant way to do this?
Thank you!
PS I'm using spring boot 1.4.0
You can wrap around a custom sql query around this method by using #Query annotation. Here is the relevant documentation.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query

Use Spring Data JPA, QueryDSL to update a bunch of records

I'm refactoring a code base to get rid of SQL statements and primitive access and modernize with Spring Data JPA (backed by hibernate). I do use QueryDSL in the project for other uses.
I have a scenario where the user can "mass update" a ton of records, and select some values that they want to update. In the old way, the code manually built the update statement with an IN statement for the where for the PK (which items to update), and also manually built the SET clauses (where the options in SET clauses can vary depending on what the user wants to update).
In looking at QueryDSL documentation, it shows that it supports what I want to do. http://www.querydsl.com/static/querydsl/4.1.2/reference/html_single/#d0e399
I tried looking for a way to do this with Spring Data JPA, and haven't had any luck. Is there a repostitory interface I'm missing, or another library that is required....or would I need to autowire a queryFactory into a custom repository implementation and very literally implement the code in the QueryDSL example?
You can either write a custom method or use #Query annotation.
For custom method;
public interface RecordRepository extends RecordRepositoryCustom,
CrudRepository<Record, Long>
{
}
public interface RecordRepositoryCustom {
// Custom method
void massUpdateRecords(long... ids);
}
public class RecordRepositoryImpl implements RecordRepositoryCustom {
#Override
public void massUpdateRecords(long... ids) {
//implement using em or querydsl
}
}
For #Query annotation;
public interface RecordRepository extends CrudRepository<Record, Long>
{
#Query("update records set someColumn=someValue where id in :ids")
void massUpdateRecords(#Param("ids") long... ids);
}
There is also #NamedQuery option if you want your model class to be reusable with custom methods;
#Entity
#NamedQuery(name = "Record.massUpdateRecords", query = "update records set someColumn=someValue where id in :ids")
#Table(name = "records")
public class Record {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//rest of the entity...
}
public interface RecordRepository extends CrudRepository<Record, Long>
{
//this will use the namedquery
void massUpdateRecords(#Param("ids") long... ids);
}
Check repositories.custom-implementations, jpa.query-methods.at-query and jpa.query-methods.named-queries at spring data reference document for more info.
This question is quite interesting for me because I was solving this very problem in my current project with the same technology stack mentioned in your question. Particularly we were interested in the second part of your question:
where the options in SET clauses can vary depending on what the user
wants to update
I do understand this is the answer you probably do not want to get but we did not find anything out there :( Spring data is quite cumbersome for update operations especially when it comes to their flexibility.
After I saw your question I tried to look up something new for spring and QueryDSL integration (you know, maybe something was released during past months) but nothing was released.
The only thing that brought me quite close is .flush in entity manager meaning you could follow the following scenario:
Get ids of entities you want to update
Retrieve all entities by these ids (first actual query to db)
Modify them in any way you want
Call entityManager.flush resulting N separate updates to database.
This approach results N+1 actual queries to database where N = number of ids needed to be updated. Moreover you are moving the data back and forth which is actually not good too.
I would advise to
autowire a queryFactory into a custom repository
implementation
Also, have a look into spring data and querydsl example. However you will find only lookup examples.
Hope my pessimistic answer helps :)

Spring Data Repositories - Find where field in list

I'm trying to use spring PagingAndSortingRepository with a find MyEntity where field in fieldValues query as follows:
#Repository
public interface MyEntity extends PagingAndSortingRepository<MyEntity, String> {
List<MyEntity> findByMyField(Set<String> myField);
}
But of no success.
I expected the above function to return all entities whose field matches one of the field values but it only returns empty results.
Even though it seems like a pretty straight forward ability i could not find any reference to it in the docs.
Is / How that could be achieved?
Thanks.
This should indeed be possible if you are searching on a specific field within your entity and you want to return a list of all that field matches at least one entry in some collection. The documentation here says this can be achieved using the keyword In example: findByAgeIn(Collection<Age> ages) and is equivalent to … where x.age in ?1
From your post i'm not 100% sure if this is the use case you are after but give this a try. You will need to search on a specific field, so replace 'field' with whatever field you are searching on. If you are searching on multiple fields it may be possible to concatenate the results with the Or keyword and specify multiple fields that way.
#Repository
public interface MyEntity extends PagingAndSortingRepository<MyEntity, String> {
List<MyEntity> findByFieldIn(Set<String> myField);
}
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Your method name has to be findByMyFieldIn so you got to add an In at the end to get a where ... in (..) query.

Counting query in spring-data-couchbase (N1QL)

I'm writing couchbase repository using Spring module and I'm trying to add my own implementation of count method using N1QL query:
public interface MyRepository extends CouchbaseRepository<Entity, Long> {
#Query("SELECT count(*) FROM default")
long myCount();
}
But it doesn't work:
org.springframework.data.couchbase.core.CouchbaseQueryExecutionException: Unable to retrieve enough metadata for N1QL to entity mapping, have you selected _ID and _CAS?
So my question is: how can I write counting query using spring-data-couchbase?
I cannot find anything about this in spring documentation. link
This exception happens because the #Query annotation was designed with the use-case of retrieving entities in mind. Projections to a scalar like count are uncovered corner cases as of RC1. Maybe I can think of some way of adding support for it through explicit boolean flag in the annotation?
Unfortunately I was unable to find a workaround. I was trying to come up with a custom repository method implementation but it appears support for it is broken in 2.0.0-RC1 :(
edit:
The use case of simple return types like long, with a SELECT that only uses a single aggregation, should work so this is a bug/improvement. I've opened ticket DATACOUCH-187 in the Spring Data JIRA.
#Query("SELECT count(*) , META(default).id as _ID, META(default).cas as _CAS FROM default")
Change your query to this one.
Use this query :
#Query("SELECT count(*) as count FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} ")
long myCount();

JPA - Setting entity class property from calculated column?

I'm just getting to grips with JPA in a simple Java web app running on Glassfish 3 (Persistence provider is EclipseLink). So far, I'm really liking it (bugs in netbeans/glassfish interaction aside) but there's a thing that I want to be able to do that I'm not sure how to do.
I've got an entity class (Article) that's mapped to a database table (article). I'm trying to do a query on the database that returns a calculated column, but I can't figure out how to set up a property of the Article class so that the property gets filled by the column value when I call the query.
If I do a regular "select id,title,body from article" query, I get a list of Article objects fine, with the id, title and body properties filled. This works fine.
However, if I do the below:
Query q = em.createNativeQuery("select id,title,shorttitle,datestamp,body,true as published, ts_headline(body,q,'ShortWord=0') as headline, type from articles,to_tsquery('english',?) as q where idxfti ## q order by ts_rank(idxfti,q) desc",Article.class);
(this is a fulltext search using tsearch2 on Postgres - it's a db-specific function, so I'm using a NativeQuery)
You can see I'm fetching a calculated column, called headline. How do I add a headline property to my Article class so that it gets populated by this query?
So far, I've tried setting it to be #Transient, but that just ends up with it being null all the time.
There are probably no good ways to do it, only manually:
Object[] r = (Object[]) em.createNativeQuery(
"select id,title,shorttitle,datestamp,body,true as published, ts_headline(body,q,'ShortWord=0') as headline, type from articles,to_tsquery('english',?) as q where idxfti ## q order by ts_rank(idxfti,q) desc","ArticleWithHeadline")
.setParameter(...).getSingleResult();
Article a = (Article) r[0];
a.setHeadline((String) r[1]);
-
#Entity
#SqlResultSetMapping(
name = "ArticleWithHeadline",
entities = #EntityResult(entityClass = Article.class),
columns = #ColumnResult(name = "HEADLINE"))
public class Article {
#Transient
private String headline;
...
}
AFAIK, JPA doesn't offer standardized support for calculated attributes. With Hibernate, one would use a Formula but EclipseLink doesn't have a direct equivalent. James Sutherland made some suggestions in Re: Virtual columns (#Formula of Hibernate) though:
There is no direct equivalent (please
log an enhancement), but depending on
what you want to do, there are ways to
accomplish the same thing.
EclipseLink defines a
TransformationMapping which can map a
computed value from multiple field
values, or access the database.
You can override the SQL for any CRUD
operation for a class using its
descriptor's DescriptorQueryManager.
You could define a VIEW on your
database that performs the function
and map your Entity to the view
instead of the table.
You can also perform minor
translations using Converters or
property get/set methods.
Also have a look at the enhancement request that has a solution using a DescriptorEventListener in the comments.
All this is non standard JPA of course.

Categories

Resources