Is there a way to inject an Entity property dynamically to #Query? My need is to implement a method like follows:
#Query("select e from #{#entityName} e where e.***columnName*** = ?2")
List<T> findAll(String ***columnName***, String value);
Any other simple ways of doing this?
You can do it using Spring Specification.
Your specification method will be similar to the following one:
public static Specification<Entity> byColumnNameAndValue(String columnName, String value) {
return new Specification<Entity>() {
#Override
public Predicate toPredicate(Root<Entity> root,
CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.equal(root.<String>get(columnName), value);
}
};
}
Please read a little about Specification, it's a great tool.
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
and
http://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/domain/Specifications.html
Related
How to mapping Map to query in Repository in Spring Data Jpa?
Below is my Generic Repository source.
#NoRepositoryBean
public interface GenericRepository<T, ID> extends JpaRepository<T, ID> {
#Query("select t from #{#entityName} t " +
"where t.queryParams.getKey = queryParmas.getValue")
Page<T> findByQueryParams(HashMap<String, String> queryParams, Pageable pageable);
}
As follows, I want to receive queryParams as a parameter and map the key and value corresponding to queryParams to the where clause.
Since it is a GenericRepository, it cannot be implemented, and I want to put the contents of the Map in #Query.
I've been searching, but I can't find anything about it, so I don't know how to solve this problem.
If there is any way, please let me know.
Best Regards
You might want to use JPA criteria queries https://www.baeldung.com/hibernate-criteria-queries
You need Spring JPA dynamic query. I have prepared a small application that will meet your needs. I hope it will be useful.
github: dynamic query
Please try this:
EntityManager em;
public List<Member> get(Map<String, String> map) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> root = cq.from(Member.class);
List<Predicate> predicates = new ArrayList<>();
for (Map.Entry<String, String> e : map.entrySet()) {
Predicate a = cb.equal(root.get("name"), e.getKey());
Predicate b = cb.equal(root.get("relationName"), e.getValue());
predicates.add(cb.and(a, b));
}
cq.where(cb.or(predicates.toArray(new Predicate[0])));
return em.createQuery(cq).getResultList();
}
how to pass Map or List as a parameter to JPA query
How to Access EntityManager with Spring Data
I have the following webservice the automatically translates get parameter queries to database selects:
public interface PersonRepo extends
JpaRepository<Person, Long>,
QuerydslPredicateExecutor<Person> {
}
#GetMapping
public ResponseEntity getFiltered(
#QuerydslPredicate(root = Person.class) Predicate predicate, Pageable pageable) {
return ResponseEntity.ok(personRepo.findAll(predicate, pageable)));
)
}
The following queries could eg be executed:
GET /people?name=John&age=18
GET /people?name=John&age=18&page=1&sort=name,desc
Problem: I want to apply comparator queries as follows:
GET /people?name=John&age>18
GET /people?name=John&age>18&age<30
GET /people?name=John&age<30
Question: how could I achieve this? At least the later queries don't work.
I found a solution by defining a placeholder for the field, and using a QuerydslBinderCustomizer:
public interface PersonRepo extends
JpaRepository<Person, Long>,
QuerydslPredicateExecutor<Person>,
QuerydslBinderCustomizer<Person> {
default void customize(final QuerydslBindings bindings, final QPerson person) {
bindings.bind(cache.ageMin).first((path, value) -> person.age.goe(value));
bindings.bind(cache.ageMax).first((path, value) -> person.age.loe(value));
}
}
Of course the age fields then have to exist as transient fields, so that querydsl knows them:
#Entity
class Person {
#Transient
#QueryType(PropertyType.NUMERIC)
public int ageMin;
#Transient
#QueryType(PropertyType.NUMERIC)
private int ageMax;
}
You could use a single binding and use expressions from Query DSL value operators.
public interface PersonRepo extends
JpaRepository<Person, Long>,
QuerydslPredicateExecutor<Person>,
QuerydslBinderCustomizer<Person> {
default void customize(final QuerydslBindings bindings, final QPerson person) {
bindings.bind(cache.age).all((path, values) -> ExpressionProviderFactory.getPredicate(path, values));
}
}
So, are there any methods to execute native SQL queries from Repository interface?
Yes, I know about #Query annotation, but how to execute queries that can be changed in runtime? Like in JDBC executeQuery() method?
Implement JpaRepository and use
#PersistenceContext
private EntityManager em;
to use the full power of Java to create query of type string and then:
final Query emQuery = em.createNativeQuery(query);
final List<Object[]> resultList = emQuery.getResultList();
If you mean using Spring Data you could do something like :
#Query(value = "SELECT p from Person p where r.name = :person_name")
Optional<Person> findPersonByName(#Param("person_name") String personName);
You can use native query as well :
#Query(value = "select * from person p where r.name = :person_name")", nativeQuery = true)
enter code here
You can use a Specification with your JpaRepository to make a dynamic query built at runtime.
Add JpaSpecificationExecutor to your JpaRepository interface...
#Repository
public interface MyRepo extends JpaRepository<MyEntity, Long>, JpaSpecificationExecutor {
}
Then make a class with a static method that returns a Specification....
public class MyEntitySearchSpec {
private MyEntitySearchSpec() {
// Remove this private constructor if need to add public non-static methods.
}
public static Specification<MyEntity> myEntitySearch(
final mysearchCriteria MySearchCriteria) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (mysearchCriteria.isOnlyActive()) {
predicates.add(cb.isNull(root.get("closeDate")));
}
if (mysearchCriteria.getCaseNumber() != null) {
predicates.add(cb.equal(root.get("caseNumber"),
mysearchCriteria.getCaseNumber()));
}
return cb.and(predicates.toArray(new Predicate[] {}));
};
}
}
The you can call like this...
myRepo.findAll(myEntitySearch(mysearchCriteria));
I use Spring Boot 1.5 and spring data JPA with MySQL. I tried to run a simple counting query on a single table, but could not find a better way to map the Query results than this.:
Repository:
public interface VehicleRepository extends JpaRepository<Vehicle, String> {
#Query("select v.sourceModule as sourceModule, count(v) as vehicleCount from Vehicle v group by v.sourceModule")
List<Object[]> sourceModuleStats();
}
Service:
#Override
public List<SourceModuleStatDTO> getSourceModuleStats() {
List<Object[]> objects = vehicleRepository.sourceModuleStats();
return objects.stream()
.map(o->SourceModuleStatDTO.from((String)o[0], (Long)o[1]))
.collect(Collectors.toList());
}
I use org.immutables, so the DTO.:
#Value.Immutable
#JsonSerialize(as = ImmutableSourceModuleStatDTO.class)
#JsonDeserialize(as = ImmutableSourceModuleStatDTO.class)
public abstract class SourceModuleStatDTO {
public abstract String sourceModule();
public abstract long vehicleCount();
public static SourceModuleStatDTO from(String sm, long c) {
return ImmutableSourceModuleStatDTO.builder()
.sourceModule(sm)
.vehicleCount(c)
.build();
}
}
The problem here is the mapping, I need to cast the results or manually check everything. Even JdbcTemplate has better mapping capabilities, I can't believe there is no better way to do this.
I tried this too: https://stackoverflow.com/a/36329166/840315 , but you need to hard code classpaths into the Query to get it work and also I would still need to map the objects to Immutables.
Using JdbcTemplate, you can use the RowMapper (src) :
private static final class EmployeeMapper implements RowMapper<Employee> {
#Override
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee employee = new Employee();
employee.setCountry(rs.getString("country"));
employee.setEmployeeName(rs.getString("employee"));
return employee;
}
}
Is there something similar for spring data JPA #Query?
How about using Projections as below?
static interface VehicleStats {
public String getSourceModule();
public Long getVehicleCount();
}
And your repository method would be
#Query("select v.sourceModule as sourceModule, count(v) as vehicleCount from Vehicle v group by v.sourceModule")
List<VehicleStats> sourceModuleStats();
In your Service class, you can use the interface methods as below.
List<VehicleStats> objects = vehicleRepository.sourceModuleStats();
return objects.stream()
.map(o->SourceModuleStatDTO.from(getSourceModule(),getVehicleCount() )
.collect(Collectors.toList());
sorry for my english first.
i want use jpa to groupby, like : select scrip, dustup, count(*) from data flow group by scrip, dstip.
so, write these code:
public class DataflowSpec {
public static Specification<Dataflow> search(final String[] group, final String[] sort, final String[] desc) {
return new Specification<Dataflow>() {
#Override
public Predicate toPredicate(Root<Dataflow> root1, CriteriaQuery<?> query1, CriteriaBuilder builder) {
// TODO Auto-generated method stub
CriteriaQuery<Tuple> query = builder.createQuery(Tuple.class);
Root<Dataflow> root = query.from(Dataflow.class);
query.multiselect(root.get("srcip"), root.get("dstip"), builder.count(root));
query.groupBy(root.get("srcip"), root.get("dstip"));
query.orderBy(builder.desc(root.get("srcip").as(BigInteger.class)));
return query.getRestriction();
}
};
}
}
but , SQL log is:
Hibernate:
select
count(dataflow0_.id) as col_0_0_
from
Dataflow dataflow0_
Hibernate:
select
dataflow0_.id as id1_2_,
dataflow0_.byteall as byteall2_2_,
dataflow0_.bytedn as bytedn3_2_,
dataflow0_.byteup as byteup4_2_,
dataflow0_.dstip as dstip5_2_,
dataflow0_.dstport as dstport6_2_,
dataflow0_.engieid as engieid7_2_,
dataflow0_.flag as flag8_2_,
dataflow0_.netid as netid9_2_,
dataflow0_.pkgall as pkgall10_2_,
dataflow0_.pkgdn as pkgdn11_2_,
dataflow0_.pkgup as pkgup12_2_,
dataflow0_.protocolid as protoco17_2_,
dataflow0_.rtt as rtt13_2_,
dataflow0_.srcip as srcip14_2_,
dataflow0_.srcport as srcport15_2_,
dataflow0_.updatetime as updatet16_2_
from
Dataflow dataflow0_ limit ?
so, how to resolve it? thanks!
For people still looking for how to apply "group by" in Spring jpa Specification, you can use something like the following snippet:
...
private Dataflow dataflowFilter;
#Override
public Predicate toPredicate(Root<Dataflow> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();
predicate.getExpressions().add(cb.equal(root.get("id"), dataflowFilter.getId()));
...
cq.groupBy(root.get("id"));
...
return predicate;
}
You can achieve spring data group by by specification, just follow
[section 2.6][1] or [section 3.6][2] for version before or after 2.0. For single repository manipulation, the two versions have identical solution. For the *all * repository solution, before 2.0 use [customized factory bean][3], while after 2.0 this factory bean manipulation is omitted.
public Map<AlarmMsg.AlarmLevel, Long> testSpecification(String neId) {
SingularAttribute attribute = AlarmData_.isClear;
Specification<Object> where = Specification.where(
(root, query, cb) -> cb.equal(root.get(attribute), false)
);
final Map<AlarmMsg.AlarmLevel, Long> result = alarmDataRepository.groupAndCount(AlarmData_.alarmLevel, where );
return result;
}
repository:
public interface AlarmDataRepository extends JpaRepository<AlarmData, Long>, JpaSpecificationExecutor<AlarmData>, CustomizedGroupCountRepository {
Fragment repository and its implementation:
public interface CustomizedGroupCountRepository {
Map<AlarmMsg.AlarmLevel, Long> groupAndCount(SingularAttribute singularAttribute, Specification where);
}
public class CustomizedGroupCountRepositoryImpl implements CustomizedGroupCountRepository {
private final EntityManager entityManager;
public CustomizedGroupCountRepositoryImpl(EntityManager entityManager) {
Assert.notNull(entityManager, "EntityManager must not be null!");
this.entityManager = entityManager;
}
#Override
public Map<AlarmMsg.AlarmLevel, Long> groupAndCount(SingularAttribute singularAttribute, Specification where) {
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
final Root<AlarmData> root = query.from(AlarmData.class);
final Path<AlarmMsg.AlarmLevel> expression = root.get(singularAttribute);
query.multiselect(expression, criteriaBuilder.count(root));
query.select(criteriaBuilder.tuple(expression, criteriaBuilder.count(root)));
query.where(where.toPredicate(root, query, criteriaBuilder));
query.groupBy(expression);
final List<Tuple> resultList = entityManager.createQuery(query).getResultList();
return resultList.stream()
.collect(toMap(
t -> t.get(0, AlarmMsg.AlarmLevel.class),
t -> t.get(1, Long.class))
);
}
}
The main difference between one-for-all-repository and one-for-single-repository is, in one-for-single-repository case, it can access the real entity class, like User in spring reference document. So that you don't need to use generic types to refer an any-typed entity, while in one-for-all-repository case, the implementation of the customized method uses generic types, and its class information could (or must) be gained from an injected JpaEntityInformation as stated in both section 3.6.
[1]: https://docs.spring.io/spring-data/jpa/docs/1.8.0.RELEASE/reference/html/#repositories.single-repository-behaviour
[2]: https://docs.spring.io/spring-data/jpa/docs/2.0.5.RELEASE/reference/html/#repositories.single-repository-behavior
[3]: https://jeroenbellen.com/spring-data-extending-the-jpa-specification-executor/
Specification doesn't support groupBy.
SimpleJpaRepository replaced query.select/multiselect by query.select(root)