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
Related
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 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);
}
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);}
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
I'm using Hibernate in a Spring Boot app. I'm making a new CrudRepository for all my Model objects, to do basic CRUD tasks. They look like this:
#Repository
public interface FoobarCrudRepo extends CrudRepository<Foobar, Long> {
}
But then I always need to do some additional things, like custom search queries with inequalities and such. I follow a pattern like this:
#Repository
public class FoobarDao {
#PersistenceContext
EntityManager em;
public List<Foobar> findFoobarsByDate(Date date) {
String sql = "select fb from Foobar fb where createdDate > :date";
...
return query.getResultList();
}
}
My question is, can I combine these two concepts into a single class? I tried making it an abstract class, like so:
#Repository
public abstract class FoobarCrudRepo extends CrudRepository<Foobar, Long> {
#PersistenceContext
EntityManager em;
public List<Foobar> findFoobarsByDate(Date date) {
String sql = "select fb from Foobar fb where createdDate > :date";
...
return query.getResultList();
}
}
But then Spring didn't create a bean for it.
How can I accomplish this?
Thanks!
There are lots of ways you could probably accomplish this. If you really need absolute control try this
interface FoobarRepositoryCustom{
List<Foobar> findFoobarsByDate(Date date);
}
interface FoobarRepository extends CrudRepository<Foobar, Long>, FoobarRepositoryCustom
public class FoobarRespoitoryImpl implements FoobarRepositoryCustom{
#PersistenceContext private EntityManager em;
public List<Foobar> findFoobarsByDate(Date date) {
String sql = "select fb from Foobar fb where createdDate > :date";
...
return query.getResultList();
}
}
There is also the possibility to go a simpler route and the query can be auto generated for you based on the method name. In your example you could just add this to your FoobarCrudRepo and Spring should do the rest assuming Foobar has a property named CreatedDate
List<Foobar> findByCreatedDateGreaterThan(Date date);
For reference on how Spring can generate queries based on the method name see this http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
Completely new to Spring Data, but having searched a bit it is my impression that you do not have to leave the interface to create custom logic - rather you would create either an annotated interface method, an interface method that follows a special naming scheme or a default interface method with custom logic:
Screenshot from Baeldung: Introduction to Spring.
Here is a link to the documentation. Notice "table 4. Supported keywords inside method names" which can be used to create interface methods, whose name conveys information to the code generator about which query to create (See part of table below).
The problem here is abstract keyword.
#Repository
public abstract class FoobarCrudRepo extends CrudRepository<Foobar, Long>
Spring will not create a bean for a class unless it is a concrete class.
That's why you are getting a bean for it.
This is what worked for me...
#SpringBootApplication(scanBasePackages = { "com.myproject" })
#EnableJpaRepositories(basePackages="com.myproject.sprinbootapp.repository")
#EntityScan("com.myproject.sprinbootapp.model")
public class SpringbootAppWithDatabaseApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAppWithDatabaseApplication.class, args);
}
}
#Service
public class TopicService {
#Autowired
private TopicRepository topicRepository;
private List<Topics> topics = new ArrayList<Topics>();
public List<Topics> getAllTopics(){
List<Topics> listOfTopics = new ArrayList<Topics>();
topicRepository.findAll().forEach(listOfTopics::add);;
return listOfTopics;
}
}
#Entity
public class Topics {
#Id
private String id;
private String name;
public Topics(){
}
getters and setters...
}
public interface TopicRepository extends CrudRepository<Topics, String> {
}
we can use the JPA EntityManager for direct sql actions:
public interface VerificationsRepository extends
CrudRepository<Verification, Integer>,
DAOAccess
{ }
interface DAOAccess {
List findByEmail(String email);
}
class DAOAccessImpl implements DAOAccess {
#PersistenceContext private EntityManager em;
public List findByEmail(String email) {
String sql =
"select * from verifications where email = ?";
Query query = em.createNativeQuery(sql, Verification.class)
.setParameter(1, email);
return query.getResultList();
}
}