Spring Data JPA: Generate dynamic query - java

I have an entity that hold some logic data :
#Entity
public class Person {
private Long id.
private String name;
private int age;
private String address;
...
}
I create my Spring data interface
#Repository
public interface CardInventoryRepository extends JpaRepository<Person , Long> {
}
My purpose is to create a dynamic query based on the exist values of my entity for example
if the name is null the query is :
select * from Person p Where p.age=12 AND p.address="adress.."
When the address is null the query should be :
select * from Person p Where p.age=12 AND p.name="ALI"
I want to extract data using only the non empty fields ?
is there any solution suing spring data for building dynamic queries ?
Thanks in advance

Based on Spring doc https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
Query by Example (QBE) is a user-friendly querying technique with a
simple interface. It allows dynamic query creation and does not
require you to write queries that contain field names. In fact, Query
by Example does not require you to write queries by using
store-specific query languages at all.
DEFINITION:
An Example takes a data object (usually the entity object or a sub-type of it) and a specification how to match properties. You can use Query by Example with JPA
Repositories.
To do so, let your repository interface extend QueryByExampleExecutor<T>, for example:
public interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
Here are the available methods in QueryByExampleExecutor :
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
USAGES:
Example<Person> example = Example.of(new Person("Jon", "Snow"));
repo.findAll(example);
ExampleMatcher matcher = ExampleMatcher.matching().
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
Example<Person> example = Example.of(new Person("Jon", "Snow"), matcher);
repo.count(example);
MORE INFO
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
https://github.com/spring-projects/spring-data-examples/tree/master/jpa/query-by-example
Spring Data JPA: Query by Example?

Yes, please take a look at the QueryDSL support for Spring Data. Your use case can be implemented via a Predicate. In a nutshell, you have to create a predicate in which you would pass the non null fields, and then pass that predicate to a findAll method that takes a Predicate as argument. Your repository interface also has to extend QueryDslPredicateExecutor

Need to extend repository from JpaSpecificationExecutor
#Repository
#Transactional
public interface EmployeeDAO extends CrudRepository<Employee,Long>,JpaSpecificationExecutor<Employee>{
}
Use specification and predicate like below
public List<Employee> findByCriteria(String employeeName,String employeeRole){
return employeeDAO.findAll(new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if(employeeName!=null) {
predicates.add(criteriaBuilder.and(criteriaBuilder.like(root.get("employeeName"), "%"+employeeName+"%")));
}
if(employeeRole!=null){
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("employeeRole"), employeeRole)));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});
}

Related

Spring data jpa query methods with criteria api

I have been using spring data JPA with mysql. I mostly use query methods as below :
public interface VehicleRepository extends JpaRepository<Vehicle, Long> {
Vehicle findByRegistrationNumber(String registrationNumber);
Vehicle findByDriver(Driver driver);
Vehicle findByNaturalId(String naturalId);
}
But now for some usecase I want to criteria api as below :
#Repository
public class VehicleCriteriaRepository {
private final EntityManager entityManager;
public VehicleCriteriaRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
public Vehicle find(String naturalId) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Vehicle> criteriaQuery = criteriaBuilder.createQuery(Vehicle.class);
Root<Vehicle> vehicleRoot = criteriaQuery.from(Vehicle.class);
CriteriaQuery<Vehicle> registrationNumber = criteriaQuery
.select(vehicleRoot)
.where(criteriaBuilder.equal(vehicleRoot.get("naturalId"), naturalId));
Vehicle singleResult = entityManager.createQuery(registrationNumber).getSingleResult();
return singleResult;
}
}
I am unable to understand that how can I use both of them together. Because if I want to use criteria api, I'll have to make a concrete class. And in case I make a concrete class I could not understand how will I be able to use jpa query methods, as if I implement the interface, I'll have to provide an implementation.
Can anyone please help me on this.
You can use Spring JPA Specification. They will provide you the flexibility of using the Spring repository with JPA's criteria builder.
It is as simple as making your repository extending the interface JpaSpecificationExecutor.
public interface VehicleRepository extends JpaRepository<Vehicle, Long>, JpaSpecificationExecutor<Vehicle> {
....
}
Then you can query your repository using anonymous implementation of an specification or you can implement it as well, whatever suits you best.
An anonymous implementation will be something like below.
#Autowired
VehicleRepository vehicleRepository;
// somewhere in some methods
vehicleRepository.findOne((root, criteriaQuery, criteriaBuilder) ->
criteriaBuilder.equal(root.get("naturalId"), naturalId)
);

Return custom collection from Spring Data JPA query method

I would like my custom query method to return a specific Guava collection type (ImmutableSet in my example).
Example:
public interface MyRepository extends CrudRepository<User,UserId> {
#Query("SELECT DISTINCT u FROM User u WHERE... ORDER BY ...")
ImmutableSet<User> findByxxxx();
}
When I try it, I get :
Failed to convert from type [java.util.ArrayList<?>] to type [com.google.common.collect.ImmutableSet<?>]
...
Caused by: java.lang.IllegalArgumentException: Could not instantiate Collection type: com.google.common.collect.ImmutableSet
The documentation does not explicitly list the Guava collection types, so I am not sure if it is impossible without a new release, or if there is some configuration that is possible to make it work.
How can I instruct Spring Data JPA to use that type?
Spring data doesn't support collections from any third-party libraries and probably never do...
CrudRepository exposes an Iterable to you and if you want to materialize to custom collection it's up to you to do that.
By the way If you want your repository returns custom collection types you can decorate the spring data repository with yours. Like this
interface JpaUserRepository extends CrudRepository<User,UserId> {
#Query("SELECT DISTINCT u FROM User u WHERE... ORDER BY ...")
Iterable<User> findByxxxx();
}
public class MyRepository {
private final JpaUserRepository jpaUserRepository;
#Autowired
public MyRepository(final JpaUserRepository jpaUserRepository) {
this.jpaUserRepository = jpaUserRepository;
}
public ImmutableSet<User> findByxxxx() {
return ImmutableSet.copyOf(jpaUserRepository.findByXXX());
}
}

How to make a generic repository in Spring Boot that accepts an entity and few attributes and returns all the records based on the attributes?

I want to make a generic repository that accepts the entity class and few attributes like start_date and end_date, etc and returns all the records in the table.
To fetch the results for a single entity using repository I will need to write a custom query. I am not sure how would I write a Custom query in a generic way for any entity that is passed and filter according to the attributes.
Since you are using Spring Data JPA you can declare your own shared repository interface with your own methods and avoid a custom query. The same approach is used by CrudRepository to provide additional methods which are not present in Repsitory.
For example you can declare:
public interface SharedRepository<T, ID> extends CrudRepository<T, ID> {
List<T> findByStartDateAndEndDate(LocalDate startDate, LocalDate endDate);
}
Then extend from this new interface for your entities
#Repository
public interface PersonRepisotry extends SharedRepository<Person, Long> {
}
#Repository
public interface RoomRepository extends SharedRepository<Room, Long> {
}
Both PersonRepository and RoomRepository will have findByStartDateAndEndDate method.
Spring now supports a kind of query by example
Service:
Person person = new Person();
person.setFirstname("Dave");
Example<Person> example = Example.of(person);
Repo Interface:
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
}

Customize a Spring MongoRepository - filter by class

I´ve got several Java documents that are stored in the same collection "app" in mongodb. My intention is use it for the common persistence actions (insert, find, delete...). The problem comes when I try to define repositories for only one of the classes as the repository that I´ve defined will search all the entities, and what I need is a repository where I can call to all the mongorepository standard functions (find, findAll, findby...). My explanation can be hard to understand, this is what I have now:
Base document:
#Document(collection = "app")
#NoRepositoryBean
public abstract class AbstractApplicationDocument {
#Id
public String mongoId;
// more variables, getters, setters...
}
One of the subdocuments (there will be many).
#EqualsAndHashCode(callSuper=true)
public class ClientApplicationDocument extends AbstractApplicationDocument
{
#Id
public String mongoId;
}
Base Repository
#NoRepositoryBean
public interface ApplicationRepository<T, ID extends Serializable> extends MongoRepository<T, ID> {
}
Extended repository (where I want to filter by class)
public interface HttpClientRepository extends ApplicationRepository<HttpClientDocument, String> {
}
Sample of temporary solution that I'd like to avoid if I can reuse the MongoDb utilities
#Component
public class HttpClientRepositoryImpl {
#Autowired
private HttpClientRepository clientRepo;
#Autowired
private MongoTemplate mongo;
public List<HttpClientDocument> findAll() {
Query query = new Query();
query.addCriteria(Criteria.where("_class").is(HttpClientDocument.class.getName()));
mongo.getConverter();
return mongo.find(query, HttpClientDocument.class,"app");
}
}
Relevant information in gradle. I add this because I've seen some solutions that are not valid for me. Probably because they are using different libraries:
compile("org.springframework.boot:spring-boot-starter-data-mongodb:${springBootVersion}")
compile ("org.springframework:spring-context-support:4.1.8.RELEASE");
Is there any simple solution?
You can customize Spring Data repositories in many ways. You can provide custom repository base classes or provide custom implementation classes.
For your question in particular, SimpleMongoRepository does not restrict query on the _class field. You can see below an example for a custom repository base class that restricts the findAll query method to use only the declared entity type.
You would be required to apply that pattern also for other methods which are declared on the MongoRepository level that you want to restrict to the particular class type.
#EnableMongoRepositories(repositoryBaseClass = MyMongoRepository.class)
static class Config {
}
static class MyMongoRepository<T, ID extends Serializable> extends SimpleMongoRepository<T, ID> {
private final MongoEntityInformation<T, ID> metadata;
private final MongoOperations mongoOperations;
public MyMongoRepository(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
super(metadata, mongoOperations);
this.metadata = metadata;
this.mongoOperations = mongoOperations;
}
#Override
public List<T> findAll() {
Query query = new Query();
query.restrict(this.metadata.getJavaType());
return this.mongoOperations.find(query, this.metadata.getJavaType(), this.metadata.getCollectionName());
}
}
findAll queries restrict now the class type and queries for a e.g. ModelRepository extends MongoRepository<Model, String> would look like:
{ "_class" : { "$in" : [ "com.example.Model"]}}
Query methods with #Query can be used to pass in the class type (#Query("{'_class': ?0}")) but there's no way to contrain the class type for derived query methods (List<Model> findByFirstnameAndLastname(…)).
There's an open ticket in our Jira related to restricting types of the repository. That particular ticket is related to polymorphic queries but in its essence, it's related to type restrictions on query methods.

Spring JPA Repository dynamic query

Currently I have been using following Spring JPA Repository base custom query and it works fine,
#Query("SELECT usr FROM User usr WHERE usr.configurable = TRUE "
+ "AND (" +
"lower(usr.name) like lower(:filterText) OR lower(usr.userType.classType.displayName) like lower(:filterText) OR lower(usr.userType.model) like lower(:filterText)"
+ ")"
+ "")
public List<User> findByFilterText(#Param("filterText") String filterText, Sort sort);
I need to modify this query when filter text going to be a comma separated value. But as following manner it will be a dynamic query and how can I execute it.
Dynamic query I need to build,
String sql = "SELECT usr FROM User usr WHERE usr.configurable = TRUE";
for(String word : filterText.split(",")) {
sql += " AND (lower(usr.name) like lower(:" + word + ") OR lower(usr.userType.classType.displayName) like lower(:" + word + ") OR lower(usr.userType.model) like lower(:" + word + "))";
}
Per JB Nizet and the spring-data documentation, you should use a custom interface + repository implementation.
Create an interface with the method:
public interface MyEntityRepositoryCustom {
List<User> findByFilterText(Set<String> words);
}
Create an implementation:
#Repository
public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public List<User> findByFilterText(Set<String> words) {
// implementation below
}
}
Extend the new interface in your existing Repository interface:
public interface MyEntityRepository extends JpaRepository<MyEntity, Long>, MyEntityRepositoryCustom {
// other query methods
}
Finally, call the method somewhere else:
dao.findByFilterText(new HashSet<String>(Arrays.asList(filterText.split(","))));
Query implementation
Your method of producing the sql variable, namely by concatenating some strings into the query is bad. Do not do this.
The word which you are concatenating must be a valid JPQL identifier, namely a : followed by a java identifier start, optionally followed by some java identifier part. This means that if your CSV contains foo bar,baz, you will attempt to use foo bar as an identifier and you'll get an exception.
You can instead use CriteriaBuilder to construct the query in a safe way:
public List<User> findByFilterText(Set<String> words) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> q = cb.createQuery(User.class);
Root<User> user = q.from(User.class);
Path<String> namePath = user.get("name");
Path<String> userTypeClassTypeDisplayName =
user.get("userType").get("classType").get("displayName");
Path<String> userTypeModel = user.get("userType").get("model");
List<Predicate> predicates = new ArrayList<>();
for(String word : words) {
Expression<String> wordLiteral = cb.literal(word);
predicates.add(
cb.or(
cb.like(cb.lower(namePath), cb.lower(wordLiteral)),
cb.like(cb.lower(userTypeClassTypeDisplayName),
cb.lower(wordLiteral)),
cb.like(cb.lower(userTypeModel), cb.lower(wordLiteral))
)
);
}
q.select(doc).where(
cb.and(predicates.toArray(new Predicate[predicates.size()]))
);
return entityManager.createQuery(q).getResultList();
}
I've been looking for the solution myself :
The naming of the "Custom" repository interface and implentation is very strict (as said there How to add custom method to Spring Data JPA)
So, to be clear, the whole code :
(But #beerbajay was right)
The custom method interface
public interface MyEntityRepositoryCustom {
List<MyEntity> findSpecial();
}
The custom method implementation
public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
#PersistenceContext
private EntityManager em;
//custom method implementation
public List<Object> findSpecial() {
List<Object> list = em.createNativeQuery("select name, value from T_MY_ENTITY").getResultList();
return list;
}
}
The "original" repository
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity,Long>, MyEntityRepositoryCustom {
//original methods here... do not redefine findSpecial()...
}
You can now use the "original" repository with the new custom methods
#Service
public class MyService {
#Autowired
private DataRepository r;
public void doStuff() {
List<Object> list = r.findSpecial();
}
}
Spring Data JPA has a way to create Custom and Dynamic queries with "Specifications":
Spring Data - Specifications
First, your interface which extends JpaRepository or CrudRepository should also implement JpaSpecificationExecutor<...> and that's all you need.
Your repository now has a new method findAll which accepts a Specification<...> object, and your can use the method Beerbajay used to create Criteria Queries by overriding the method toPredicate(...) and there you are free to build (almost) any query you want like so:
Specification<...> spec = new Specification<...>() {
#Override
public Predicate toPredicate(Root<...> entity, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> conditions = buildManyPredicates(cb, entity);
return cb.and(conditions.toArray(new Predicate[conditions.size()]));
}
};
repository.findAll(spec, PageRequest.of(0, 10));
This solves the problem of Spring Data trying to parse the methods you added in the custom interface (because there is no custom interface)
try query DSL as illustrated in the official documents

Categories