JPA Criteria select from another query - java

I am trying to select information from another query, like:
SELECT user.user_id, product.first_time
FROM USER AS user
INNER JOIN (SELECT min(product.first_time) as first_time,
product.user_id
FROM PRODUCT AS product
GROUP BY (product.user_id)
) product
ON user.user_id = product.user_id
but i don't know how to create a Criteria Query to execute it.
Can I execute it with Criteria API?

try using this:
https://docs.jboss.org/hibernate/orm/4.3/topical/html/metamodelgen/MetamodelGenerator.html
need to create metaclasses your's entities.
example, your's service class:
#Service("jpaCustomerService")
#Repository
#Transactional
public class CustomerServiceImpl implements CustomerService {
#PersistenceContext
private EntityManager em;
#Transactional(readOnly = true)
public List<CustomersEntity> findByCriteriaQuery(String name){
CriteriaBuilder cb = em.getCriteriaBuilder();
javax.persistence.criteria.CriteriaQuery<CustomersEntity> criteriaQuery = cb.createQuery(CustomersEntity.class);
Root<CustomersEntity> customersEntityRoot = criteriaQuery.from(CustomersEntity.class);
customersEntityRoot.fetch(CustomersEntity_.animal, JoinType.LEFT);
criteriaQuery.select(customersEntityRoot).distinct(true);
Predicate criteria = cb.conjunction();
if (!name.isEmpty()){
Predicate p = cb.equal(customersEntityRoot.get(CustomersEntity_.name), name);
criteria = cb.and(criteria, p);
}
criteriaQuery.where(criteria);
List<CustomersEntity> result = em.createQuery(criteriaQuery).getResultList();
return result;
}
}
and your's controller or your's main:
public static void criteriaExample(GenericXmlApplicationContext ctx){
CustomerService service = ctx.getBean("jpaCustomerService", CustomerService.class);
List<CustomersEntity> creteriaResult = service.findByCriteriaQuery("NCI-3");
for (CustomersEntity customer : creteriaResult){
System.out.println(customer);
if (!customer.getAnimal().isEmpty())
for (AnimalsEntity animal : customer.getAnimal()){
System.out.println(animal);
}
}
}
CustomersEntity_ - metamodel class. No needs SQL-query.
and your's infs is:
public interface CustomerService {
List<CustomersEntity> findByCriteriaQuery(String name);}

Related

How execute native SQL queries using Spring in runtime in Java?

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));

java spring Long[] (array) type

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

Hibernate Filter is not applied for FindOne CRUD operation

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);
}

Accessing multiple entity join query resultset with Spring JPA

I have below 2 entity classes 1.student 2. resluts and I have to return a resultset by executing the below customized query
select s.roll_no , s.first_name, s.age ,r.subject_name , r.marks from student s , results r where s.roll_no= : rollNo and r.marks >70
which gives the result set with combination of both student and result entity. In such scenario how do i write my implementation. I have tried below two approaches
Approach 1 :
public interface GetStudentDetail extends CrudRepository<Student, String> {
#Transactional(readOnly=true)
#Query("select s.roll_no , s.first_name, s.age ,r.subject_name , r.marks from student s , results r where s.roll_no= : rollNo and r.marks >70")
public List<student> getStudentDetails(#Param("rollNo")String rollNo);
}
With the above i was able to get only the student entity values from the resultset and results entity objects were not visible here.
Approach 2:
public interface GetStudentDetail extends CrudRepository<Student, String> {
#Transactional(readOnly=true)
#Query("select s.roll_no , s.first_name, s.age ,r.subject_name , r.marks from student s , results r where s.roll_no= : rollNo and r.marks >70")
public List<Object[]> getStudentDetails(#Param("rollNo")String rollNo);
}
By this, i created one more VO class with both the entity variable and i manually set to those object by accessing to it position like below
List<StudentResultVo>studtVoObjList = new ArrayList<StudentResultVo>();
for (Object[] resObj : resultSetList) {
StudentResultVo studtVO = new StudentResultVo();
if (resObj[0] != null) {
studtVO.setRollNo(resObj[0].toString().trim());
}
//.First name
//.Age
if (resObj[3] != null) {
studtVO.setSubName(resObj[3].toString().trim());
}
//.Marks
studtVoObjList.add(studtVO);
}
i knew the above approach is not a good coding practice. Is there any way to achieve solution to this problem.
Thanks.
first create new interface and declare your method
public interface GetStudentDetailCustom {
public List<Object[]> getStudentDetails(String rollNo);
}
and second create class GetStudentDetailImpl and implement the interface
public class GetStudentDetailImpl implements GetStudentDetailCustom {
#PersistenceContext
private EntityManager entitymanager; // I use entity manager also you want to use inject SessionFactory etc..
#Override
public List<Object[]> getStudentDetails(String rollNo) {
String queryString = "SELECT s.roll_no , s.first_name, s.age ,r.subject_name , r.marks FROM student s "
+ ", results r WHERE s.roll_no= :rollNo and r.marks > 70";
Query query = entitymanager.createQuery(queryString);
query.setParameter("rollNo", rollNo);
return query.getResultList();
}
}
and refactor your repository like this
public interface GetStudentDetail extends CrudRepository<Student, String>, GetStudentDetailCustom {}
and finaly use inject GetStudentDetail in your service class and call getStudentDetails method
Example use in your service layer :
#Autowired
private GetStudentDetail getStudentDetail;
getStudentDetail.getStudentDetails(String rollNo);
reference answer : How to add custom method to Spring Data JPA
and spring reference : http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.single-repository-behaviour

Spring Data JPA Specification groupBy

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&ltDataflow&gt root, CriteriaQuery&lt?&gt 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)

Categories

Resources