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));
Related
I have to execute a SQL query in my repository:
public interface UserRequestResponseRepository extends JpaRepository<UserRequestResponse, Integer> {
//public static final String FIND_QUERY = "select user.u_httpstatus ,user.u_queryparam from UserRequestResponse user";
public static final String FIND_QUERY =
"select new com.abc.datacollection.entity.UserRequestResponse(user.u_httpstatus ,user.u_queryparam) from UserRequestResponse user";
#Query(value = FIND_QUERY)
public List<UserProjection> getAllRequestResponseRecords();
}
where UserProjection is a projection that I defined:
public interface UserProjection {
String getU_httpstatus();
String getU_queryparam();
}
The class userRequestResponse has more fields than u_httpstatus and u_queryparam, but I want only these 2 fields in my response.
public #ResponseBody List<UserRequestResponse> getAllRequestResponseRecords() {
return userRequestResponseRepository.findAll() ;
}
how do I modify the above code (findAll()) to get results from my custom query and not the results from the default CrudRepository findAll() (which returns all the fields).
First you don't need to add a #Query to make projections work. Just having the UserProjection as the return type of the method in repository should be enough. More about this here
Secondly, you can just have the following method in your repository as a projection-based findAll method;
public List<UserProjection> findAllProjectedBy();
If i have the next code
/**
* Spring Data JPA repository for the Event entity.
*/
#Repository
public interface EventRepository extends JpaRepository<Event, Long>{
#Query("SELECT id, name FROM event WHERE id IN (:ids)")
List<EventItem> findEvents(#Param("ids") Long[] ids);
}
And want to use it
Long[] ids = new Long[3];
ids[0] = new Long(1);
ids[1] = new Long(2);
ids[2] = new Long(3);
eventRepository.findEvents(ids);
How to use correctly. I'm a beginner user in Spring-framework. I want to get some records with particulary id-s in the same time.
use the JPA #NamedQuery i.e.
Event Entity
#Entity
#Table(name = "event")
#NamedQuery(name = "Event.fetchEventItem",
query = "SELECT id, name FROM event WHERE id IN (:ids)"
)
public class Event {
....
}
Your Interface
#Repository
public interface EventRepository extends JpaRepository<Event, Long>{
List<EventItem> findEvents(Long[] ids);
}
Interface implements class
#Repository
#Transactional(readOnly = true)
public class EventRepositoryImpl implements EventRepository {
#PersistenceContext
EntityManager entityManager;
#Override
public List<EventItem> findEvents(Long[] ids) {
List<Event> list = new ArrayList<Event>();
Query query = em.createNamedQuery("SELECT c FROM Country c");
Query query = entityManager.createNamedQuery("Event.fetchEventItem", Event.class);
query.setParameter(1, ids);
list = query.getResultList();
// Here You can Prapared List<EventItem>
}
}
JPA provides you keywords inside method names to do this, so in your EventRepository class, add a method by following name:
List<Event> findByIdIn(List<Long> ids);
You can find several keywords inside method names which are wrapped just calling by its format that JPA provides.
REFERENCES
Table 2.3 Supported keywords inside method names ->
https://docs.spring.io/spring-data/jpa/docs/1.6.0.RELEASE/reference/html/jpa.repositories.html
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());
I do have this hibernate filter in my repository:
#Entity
#Audited
#DiscriminatorValue(value = "GROUP")
#FilterDef(name = "groupACL", parameters = #ParamDef(name = "userId", type = "long"))
#Filters({
#Filter(name = "groupACL", condition = "app_group_id IN (SELECT DISTINCT APP_GROUP_ID FROM APP_GROUP START WITH APP_GROUP_ID IN (SELECT UG.APP_GROUP_ID FROM USER_GROUP UG JOIN APP_USER AU ON AU.APP_USER_ID = UG.APP_USER_ID WHERE USER_ID=:userId) CONNECT BY PARENT_APP_GROUP_ID = PRIOR APP_GROUP_ID)", deduceAliasInjectionPoints = false) })
public class Group extends AbstractGroup {
It is triggered using Spring AOP with the following class:
#Component
#Aspect
public class ACLFilterAspect {
private static final String GROUP_ACL = "groupACL";
#Autowired
private EntityManager em;
#Before("execution(* com.trendshift.kyn.pug.data.GroupRepository.*(..))")
public void forceFilter() {
Session hibernateSession = em.unwrap(Session.class);
....
hibernateSession.enableFilter(GROUP_ACL).setParameter("userId", userId);
}
}
}
I finally have the following service:
#Service
#Transactional(propagation = Propagation.REQUIRED)
public class GroupServiceImpl implements GroupService {
#Autowired
GroupRepository groupRepository;
#Override
public Group findGroupById(long id) {
Group group = groupRepository.findById(id);
return group;
}
}
and these repositories:
#RepositoryRestResource(exported = false)
public interface AbstractGroupRepository<T extends AbstractGroup>
extends JpaRepository<T, Long>, QueryDslPredicateExecutor<T> {
List<T> findByNameIgnoreCase(String name);
List<T> findByNameAndTypeOrderByNameAsc(String name, String type);
List<T> findByIdOrderByNameAsc(Long id);
AbstractGroup findById(Long id);
}
public interface GroupRepository
extends AbstractGroupRepository<Group>, GroupRepositoryExtras {
List<Group> findByNameAndCustomerId(String name, Long customerId);
Iterable<Group> findByCustomerIdAndTypeIn(Long id, List<String> types);
Group findById(long id);
}
The issue is that when I use groupRepository.findById(id) the filter is correctly applied.
If I use a CRUD core query groupRepository.findOne(id) the filter is not applied even after processing the Aspect hibernateSession.enableFilter(GROUP_ACL).setParameter("userId", userId);
Even if Java enables the filter, the log file doesn't show any trace of the filter in the hibernate query.
The problem seem to be only with the .findOne. findAll is working fine.
Is there something in the Hibernate doc that says that you cannot applied a filter to findOne methods?
I used filters to restrict user access to some information based on entity attributes. This was why I wanted even the findOne to respect the filters.
This was the prettiest way that I found to solve this "problem".
ClassPool pool = ClassPool.getDefault();
try {
CtClass cl = pool.get("org.hibernate.loader.plan.exec.internal.EntityLoadQueryDetails");
CtMethod me = cl.getDeclaredMethod("applyRootReturnFilterRestrictions");
String s = "{final org.hibernate.persister.entity.Queryable rootQueryable = (org.hibernate.persister.entity.Queryable) getRootEntityReturn().getEntityPersister();" +
"$1.appendRestrictions(" +
"rootQueryable.filterFragment(" +
"entityReferenceAliases.getTableAlias()," +
"getQueryBuildingParameters().getQueryInfluencers().getEnabledFilters()" +
"));}";
me.setBody(s);
cl.toClass();
} catch (Exception e) {}
To answer the actual Question:
Is there something in the Hibernate doc that says that you cannot applied a filter to findOne methods?
Yes, there is. From the Hibernate docs
Filters apply to entity queries, but not to direct fetching.
Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context.
entityManager
.unwrap( Session.class )
.enableFilter( "activeAccount" )
.setParameter( "active", true);
Account account = entityManager.find( Account.class, 2L );
assertFalse( account.isActive() );
The implementation of for e.g SimpleJpaRepository.java in Spring uses em.find under the hood. Therefore the request is not filtered.
BUT if you override the implementation somehow (e.g. by using a projection or by writing a custom query), so that a query is generated, the request will be filtered.
This behaviour can be pretty confusing.
I ended up listening for any access to the CRUDRepository class. Not sure if that's the best way but that solves my issue.
#Component
#Aspect
public class ACLFilterAspect {
private static final String GROUP_ACL = "groupACL";
#Autowired
private EntityManager em;
#Before("||execution(* *(..)) && this(com.trendshift.kyn.pug.data.GroupRepository)"
+ "||execution(* *(..)) && this(org.springframework.data.repository.CrudRepository)")
Just override findById and use getById instead
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer>, SupportedRepositoryOperation<Customer> {
default Optional<Customer> findById(Long aLong) {
throw new OperationFindByIdNotAllowedException();
}
Optional<Customer> getById(Long id);
}
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)