How to shorten names of query methods in Spring Data JPA Repositories? - spring-data

Consider a Spring Data Jpa Repository:
public interface UserRepository extends JpaRepository<User, Long> {
User findOneByDeletedIsFalseAndActivationKey(String activationKey);
List<User> findAllByDeletedIsFalseAndActivatedIsFalseAndCreatedDateBefore(DateTime dateTime);
User findOneByDeletedIsFalseAndLogin(String login);
User findOneByDeletedIsFalseAndEmail(String email);
}
Notice each method has "DeletedIsFalse" in it. Is there a simple way to make method names shorter? Like i.e.:
#FullMethodName("findOneByDeletedIsFalseAndEmail")
User findOneByEmail(String email);

Use default Java 8 feature for wrapping, just like so:
interface UserInterface extends JpaRepository<User, Long> {
// use findOneByEmail instead
User findOneByDeletedIsFalseAndEmail(String email);
default User findOneByEmail(String email) {
return findOneByDeletedIsFalseAndEmail(email);
}
}
See an example.
With Kotlin, you can use extension functions, for example:
interface UserRepository : JpaRepository<User, Long> {
// use findOneByEmail instead
fun findOneByDeletedIsFalseAndEmail(email: String): User
}
fun UserRepository.findOneByEmail(email: String) =
findOneByDeletedIsFalseAndEmail(email)

Now you can use Java 8 default interface methods as #Maksim Kostromin described. But there is no such a feature in Spring.
-- Old answer
There is no such a way. You can specify any name for a method and add an annotation #Query with parameter value which holds desired query to database like this:
#Query(value="select u from User u where u.deleted=false and u.email=:email")
User findOneByEmail(#Param("email")String email);
or, with native sql query:
#Query(value="SELECT * FROM users WHERE deleted=false AND email=?1", nativeQuery=true)
User findOneByEmail(String email);
You can also use names that follow the naming convention for queries since #Query annotation will take precedence over query from method name.
#Query docs
Upd:
from Spring docs:
Although getting a query derived from the method name is quite convenient, one might face the situation in which ... the method name would get unnecessarily ugly. So you can either use JPA named queries through a naming convention ... or rather annotate your query method with #Query.

Related

JPA - How to add same query to a repository twice

I am trying to use the same query more than once, with different options set by annotations. Similar to:
#EntityGraph(attributePaths = {"books"})
Optional<User> findById(long id);
#EntityGraph(attributePaths = {"courses"})
Optional<User> findById(long id);
Optional<A_Projection> findById(long id);
Is there a way to add a prefix or a postfix to the method name, such that I can have different function signatures but it would be interpreted as same JPA query to avoid using #Query? Such as:
#EntityGraph(attributePaths = {"books"})
Optional<User> findByIdQ1(long id); //Q1,Q2,Q3 prefixes help distinguish these methods
#EntityGraph(attributePaths = {"courses"})
Optional<User> findByIdQ2(long id); //but they mess up the JPA syntax
Optional<A_Projection> findByIdQ3(long id);
As an example, for Projection case, this is a valid syntax:
Optional<A_Projection> findByIdByProjection(long id);
You can use EntityGraphJpaSpecificationExecutor to pass different entitygraph based on your method.
#Repository
public interface UserRepository extends JpaSpecificationExecutor<User>, JpaRepository<User, Long>, EntityGraphJpaSpecificationExecutor<User> {
}
In your service class, you can call findOne with entity graph.
List<User> users = userRepository.findOne(specification, new NamedEntityGraph(EntityGraphType.FETCH, "graphName"))
I found what I was looking for. Apparently, JPA searches for find....By in derived queries. Therefore, you can add whatever between these clauses:
Optional<User> findWithBooksById(long id); //Q1,Q2,Q3 prefixes help distinguish these methods
#EntityGraph(attributePaths = {"courses"})
Optional<User> findWithWhateverById(long id); //but they mess up the JPA syntax
Optional<A_Projection> findById(long id);
From Spring documentation:
Any text between find (or other introducing keywords) and By is considered to be descriptive unless using one of the result-limiting keywords such as a Distinct to set a distinct flag on the query to be created or Top/First to limit query results.

How to override save method in jdbc repository with insert sql statement

I'm using Micronaut Data JDBC (not JPA). It works fine with queries, where I can extend the CrudRepositry interface and add the queries as needed. For example:
#JdbcRepository
public interface MyRespository extends CrudRepository<MyEntity, String> {
#Query(
value = "...",
countQuery = "..."
)
Page<String> findByNameEquals(String name, Pageable pageable);
}
All seems fine until I want to override the save(MyEntity myEntity) method. I'm expecting something like this:
#Insert(
value = "insert into my_entity (id, name) values (uuid_to_bin(uuid()), :myEntity.name"
)
public MyEntity(MyEntity myEntity)
But it doesn't seem to exist.
Does it mean I have to write another class for this save method, and as a result will end up with two repository classes, one for queries and one for overriding the save method? If yes how do I get a database connection object with which I can create and execute my sql statement?
Thanks!
-Fujian

Spring Data JPA: Work with Pageable but with a specific set of fields of the entity

I am working with Spring Data 2.0.6.RELEASE.
I am working about pagination for performance and presentation purposes.
Here about performance I am talking about that if we have a lot of records is better show them through pages
I have the following and works fine:
interface PersonaDataJpaCrudRepository extends PagingAndSortingRepository<Persona, String> {
}
The #Controller works fine with:
#GetMapping(produces=MediaType.TEXT_HTML_VALUE)
public String findAll(Pageable pageable, Model model){
Through Thymeleaf I am able to apply pagination. Therefore until here the goal has been accomplished.
Note: The Persona class is annotated with JPA (#Entity, Id, etc)
Now I am concerned about the following: even when pagination works in Spring Data about the amount the records, what about of the content of each record?.
I mean: let's assume that Persona class contains 20 fields (consider any entity you want for your app), thus for a view based in html where a report only uses 4 fields (id, firstname, lastname, date), thus we have 16 unnecessary fields for each entity in memory
I have tried the following:
interface PersonaDataJpaCrudRepository extends PagingAndSortingRepository<Persona, String> {
#Query("SELECT p.id, id.nombre, id.apellido, id.fecha FROM Persona p")
#Override
Page<Persona> findAll(Pageable pageable);
}
If I do a simple print in the #Controller it fails about the following:
java.lang.ClassCastException:
[Ljava.lang.Object; cannot be cast to com.manuel.jordan.domain.Persona
If I avoid that the view fails with:
Caused by:
org.springframework.expression.spel.SpelEvaluationException:
EL1008E:
Property or field 'id' cannot be found on object of type
'java.lang.Object[]' - maybe not public or not valid?
I have read many posts in SO such as:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to
I understand the answer and I am agree about the Object[] return type because I am working with specific set of fields.
Is mandatory work with the complete set of fields for each entity? Should I simply accept the cost of memory about the 16 fields in this case that never are used? It for each record retrieved?
Is there a solution to work around with a specific set of fields or Object[] with the current API of Spring Data?
Have a look at Spring data Projections. For example, interface-based projections may be used to expose certain attributes through specific getter methods.
Interface:
interface PersonaSubset {
long getId();
String getNombre();
String getApellido();
String getFecha();
}
Repository method:
Page<PersonaSubset> findAll(Pageable pageable);
If you only want to read a specific set of columns you don't need to fetch the whole entity. Create a class containing requested columns - for example:
public class PersonBasicData {
private String firstName;
private String lastName;
public PersonBasicData(String firstName, String lastName) {
this.firstName = fistName;
this.lastName = lastName;
}
// getters and setters if needed
}
Then you can specify query using #Query annotation on repository method using constructor expression like this:
#Query("SELECT NEW some.package.PersonBasicData(p.firstName, p.lastName) FROM Person AS p")
You could also use Criteria API to get it done programatically:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<PersonBasicData> query = cb.createQuery(PersonBasicData.class);
Root<Person> person = query.from(Person.class);
query.multiselect(person.get("firstName"), person.get("lastName"));
List<PersonBasicData> results = entityManager.createQuery(query).getResultList();
Be aware that instance of PersonBasicData being created just for read purposes - you won't be able to make changes to it and persist those back in your database as the class is not marked as entity and thus your JPA provider will not work with it.

Spring Data Rest with Spring Security - find all by current user

Is it possible to use Spring Data Rest and Spring Security to return current user related entities, using the findAll() method without specifying this user in the GET query parameter?
My only solution is to pass user as a parameter, but maybe it's another option to get him from SpringSecurityContext
public interface InvoiceRepository extends CrudRepository<Invoice, Long> {
#RestResource
#PreAuthorize("hasRole('ROLE_ADMIN') or user?.username == authentication.name")
List<Invoice> findAllByUser(#Param("user") User user);
You can use SpEL EvaluationContext extension that makes security properties and expressions available in SpEL expressions in the #Query annotations. This allows you to get only those business objects that relate to the current user:
interface SecureBusinessObjectRepository extends Repository<BusinessObject, Long> {
#Query("select o from BusinessObject o where o.owner.emailAddress like ?#{hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress}")
List<BusinessObject> findBusinessObjectsForCurrentUser();
}
More details are here.

Spring Data JPA JPQL queries on parent interface

Say I have a #MappedSuperClass like this:
#MappedSuperclass
public abstract class Rating
{
#Id
private Long id;
#Column(name="USER_ID")
private Long userId;
private int rating;
...
With a concrete child entity like this
#Entity
#Table(name="ACTIVITY_RATING")
public class ActivityRating extends Rating
{
private Long activitySpecificData;
...
Then there is a Spring Data JPA repository like this:
#NoRepositoryBean
public interface RatingRepository<R extends Rating> extends JpaRepository<R, ID>
{
public List<R> findByUserId(Long userId);
...
and this:
public interface ActivityRatingRepository extends RatingRepository<ActivityRating>
{
}
This all works great and I can call findByUserId() on any of specific rating repositories that extend RatingRepository. I am now wanting to write some JPQL in the RatingRepository that all the child interfaces can inherit. I just don't know what (or if it's even possible) to put after the FROM in the query. For example:
#Query("SELECT NEW com.foo.RatingCountVo(e.rating, COUNT(e.rating)) FROM ??????? e GROUP BY e.rating")
public List<RatingCountVo> getRatingCounts();
I can add this method to each of the individual repositories that extend RatingRepository but everything would be exactly the same except for the specific entity name. If I want to change the query, I'd then have to go to all the child repositories and update them individually. I really want the query to live in the parent class and not be duplicated. Is there any way to accomplish this?
I'm currently using spring-data-jpa 1.7.2 and eclipselink 2.5.2. I'm not necessarily opposed to switching to newer versions if necessary.
Will it work if you will split query into 3 parts: start, entity and end of query? Than, if it'll work, in each interface you define constant like
String ENTITY = "ActivityRating";
And then you can use it like
#Query(RatingRepository.QUERY_START + ENTITY + RatingRepository.QUERY_END)
List<RatingCountVo> getRatingCounts();
BTW, there is no need to define public modifier in interface.
UPDATE: here is described another way:
#Query("SELECT NEW com.foo.RatingCountVo(e.rating, COUNT(e.rating)) FROM #{#entityName} e GROUP BY e.rating