dears.
Currently, I am working with JpaSpecificationExecutor API, cause I need to create dynamic queries based on a lot of optional search criteria, for this I am starting with a root entity, which is included in the generic type in the JpaSpecificationExecutor interface.
The code to make the build in the Specification (which is included in the findAll method that belongs to repository interface) is the following:
public Specification<T> build(){
return (root, query, criteriaBuilder) -> {
Predicate filters = null;
//createPredicate is a method to create predicates (it does not matter in this code)
filters = createPredicate(root, criteriaBuilder, searchCriterias);
return filters;
};
}
Now each is working fine, but there is a little detail, in the query I am using joins to make relation with other entities, but when I get the response, like in this code:
List<EXAMPLE> examples = repository.findAll(filters.build());
I am not able to see the filter data in other entities, just I can see the field in the root entity (It is EXAMPLE entity) because these ones are appearing in the query formed. so, I would like to include in the SELECT the other entities, in order to see the filter data by means of the JOINs, but until now I have not been able to do it.
I have seen in any inputs, that I can use these codes in my build code, I have tried, but the new files are not appearing in the generated query:
query.select, or query.multiselect
Ref:
Ref. 1
Also, I have found that this is not working with JpaSpecificationExecutor, because the list of attributes in the SELECT is assigned by the entity Ref. 2
My question is, does someone know if exists another way to change the field in the SELECT with JpaSpecificationExecutor.
Regards.
Related
I have a rather big system with a specification that is built by several methods on a child entity. So I have a User with a OneToMany on pets, as in this question. My relation is bidirectional, so Pet also has a ManyToOne relationship on User, and I'm struggling in transforming the specification on child entity to apply on parent entity instead.
Most questions I looked up showed how to apply a specification on a different entity, OR to get a different entity once the specification was executed. This is NOT what i'm looking for.
I'm trying to write a method like this :
public static Specification<User> extendsPetSpecToUser(
Specification<Pet> petSpec) {
// ???
}
but I don't know how to write it (I tried using Join, but didn't manage to "say" to JPA to combine the join and the specification to query with a different root but similar constraints)
Given the specification is big AND also used in other parts to query directly for Pet, rewriting it from a different root isn't really an option.
Thank you for your time, and sorry if this is a duplicate, I really didn't see another question matching my needs.
First, it feels like this problem hides a weird construction of the queries you use.
Why would you build a Specification<Pet> if you need Specification<User> at the end ?
There might be a code architecture to think about.
Anyway, to achieve the mentioned goal, have you tried using subqueries ?
public class TransformToUser implements Specification<User> {
private final Specification<Pet> specification;
public TransformToUser (Specification<Pet> specification) {
this.specification = specification;
}
#Override
public Predicate toPredicate(
Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Subquery<Pet> subqueryPet = criteriaQuery.subquery(Pet.class);
Root<Pet> fromPet = subqueryDeclaration.from(Pet.class);
subqueryPet.select(fromPet);
subqueryPet.where(
specification.toPredicate(fromPet, criteriaQuery, criteriaBuilder));
return root.get(User_.pets).in(subqueryPet);
}
}
Suppose an entity model where an Employee has a Supervisor who has an id. Using hibernate-jpamodelgen to generate the meta model for the entities, how can I query a nested field?
For instance, "get all employees whose supervisor has id 4", using JpaSpecificationExecutor:
Page<Employee> getEmployeesBySupervisorId(int id) {
return findAll((root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get(Employee_.supervisor.id), id);
});
}
Note that Employee_ is the model meta class for Employee (and was generated by Hibernate).
This code will produce an error because the id symbol cannot be found on type SingularAttribute<Employee, Supervisor>. I get that, but it seems like these should somehow be chainable. I can't find great examples of how to do this cleanly.
In order to navigate to related entities, you must use From#join() join method, which works well with MetaModel:
CriteriaQuery<Employee> cq = criteriaBuilder.createQuery(Employee.class);
Root<Employee> from = cq.from(Employee.class);
Predicate p = criteriaBuilder.equal(from.join(Employee_.supervisor).get(Supervisor_.id), id);
See also
Oracle's Java EE Tutorial - Using the Criteria API and Metamodel API to Create Basic Typesafe Queries
Yes, I also stumbled upon this problem that the Metamodel classes are not offering deeper visibility to relationships > 1.
While accessing A.b is possible, A.b.c is not.
But there is another possibility besides Joins:
Just concatenate by using several getter(). For this you will need a root element (= CriteriaQuery & CriteriaBuilder).
return criteriaBuilder.equal(root.get(Employee_.supervisor).get(Supervisor_.id), id);
While this still ensures type safety, the whole path should be correct as it is not validated until runtime.
Also for sorting a resultset using the Metamodel there is a similar solution. Say you want to sort by the Supervisor's id:
Use JpaSort and JpaSort.Path
JpaSort.of(JpaSort.Direction.ASC, JpaSort.path(Employee_.supervisor).dot(Supervisor_.id));
I'm new to JPA and got stuck with a rather straight forward use case. All I want is to add some conditions to my criteria based on certain filter user passes to my application. User passes the information to application using key, value pairs and we'll decode it to see what type each parameter is.
Eg:
1) key=id and value=20. We'll interpret it as search by id=20, where id is integer
2) key=name and value='test'. We'll interpret it as search by name='test', where name is string
This is achieved by a method like this
public <T> T getSearchValue(Object inputValue) {
if (condition) {
return (T) inputValue.toString();
} else {
return (T) Integer.parseInt(inputValue.toString);
}
}
I was thinking that I could easily add the condition using criteria builder. Something like this
cb.equal(getSearchKey(), getSearchValue(inputValue));
cb.gt(getSearchKey(), getSearchValue(inputValue));
Here cb.equal works fine. But cb.gt or any other operation like lessThan, like etc. are all giving compilation error that there are 2 method matches and hence ambiguous
First method is gt(Expression<? extends Number>, Expression<? extends Number>)
and second one is gt(Expression<? extends Number>, Number)
So how do I resolve this and make sure it can resolve to Number as the second parameter for example? Problem here is I don't know the type in advance as it is added based on what key user want to search with. Is it possible to dynamically cast to a class somehow or is there any other better approach here?
Idea is to supply the restrictions to the where clause of the "SQL statement".
SELECT * FROM table_name WHERE key=value
Lots of people like to write JPQL queries similar to SQL because it is easier to understand when reading. Nevertheless, there are some advantages about using this Criteria API (dynamic, less error prone, less attention for security, easier to refactor).
The first step is getzing the CriteriaBuilder and then, creating a CriteriaQuery with it. Afterwards, you could go on to define from which class you want to build the Root and use this to get the columns. Joins could also follow.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<YourClass> cq = cb.createQuery(YourClass.class);
Root<YourClass> root = cq.from(YourClass.class);
As a next step, you want to define the where clause with its restrictions (Predicates). As in a normal SQL statement, there could be only one where. If you have multiple restrictions, they have to be combined with criteriaBuilder.and().
There are several ways of applying the whole process. I have one way how I do this (and some others might do it like this as well). Usually, I create a List with all restrictions I want to use and create an array out of this which gets then combined with the cb.and().
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("key1"), "value1"));
predicates.add(cb.gt(root.get("key2"), 100));
cq.select(root).where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
Following, a whole example for such a DAO method.
public List<Foo>(String state, int size, String column, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
Root<Foo> root = cq.from(Foo.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get(Foo_.bla), state));
predicates.add(cb.gt(root.get(Foo_.blub), size));
predicates.add(cb.equal(root.get(column), value));
cq.select(root).where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
return entityManager.createQuery(cq).getResultList();
}
For getting the column names (or singular attributes), I used hibernate-jpamodelgen. Have a look for this plugin which generates classes with underscores automatically (e.g. Foo_) which makes it more typesafe when using column names.
Update: If you do not know the names of the columns for your restrictions in the where clause beforehand, it is simple to adjust it (see third restriction above).
I'm learning Spring MVC and I want find a car via an id but get in return the name.
In my service class I call a generic method getXXXById. This is something JPA gives me by nature.
I know that I get the whole entity but how can I just receive the corresponding name to the id.
Example: I call getCarById(2) and it gives me back Tesla.
My Table:
id | Name
----------
1 | Ford
2 | Tesla
My Service:
class CarService {
// code ...
public Optional<CarEntity> getCarById(int id) {
return carRepository.findById(id);
}
There are two options to do that.
Making your own query
You could write your own query in JQPL to retrive only names.
For example you could create method like that in your repository.
#Query("select t.name from CarEntity where id = ?1")
public String findNameById(Integer id);
more information on this feature of Spring Data Jpa HERE
Projections
Second option is to make projection. As it is written in documentation
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to rather project on certain attributes of those types. Spring Data allows to model dedicated return types to more selectively retrieve partial views onto the managed aggregates.
In simple words, it allows you to aggregate your results form queries in some limited set of attributes rather then whole entity.
Specifically for your needs I'd suggest to use first approch, but it is worth to know both.
What is the best practice for creating JPA Repositories?
Right now I have two tables: Media and Ratings.
To find media that is similar a query has to be made to the Rating table to find the interconnections between the different media. This query then returns a list of Media objects.
Should this query be placed in the Rating repository (as it queries the Rating table),
or in the Media repository (as it returns a collection of Media objects with the IDs set)?
I have tried searching for best-practices for this particular use-case but haven't found anything relevant.
Update:
The SQL query is defined like this:
#Query(value=[SQL query with several joins],nativeQuery=true)
List<Media> mySQLQuery()
it returns a list of mediaId's which can be returned from the function as Media objects.
Well, probably neither.
You see when you implement a Repository and do for example findAll() you will get a List of all Objects from the Entity used in the Repository creation.
interface MyRepo implements<Media, Long>....
myRepo.findAll() will return a List of Media objects.
What you are trying to do is out of scope from a Repository as it acts on only that particular Entity with a finit operations on that particular Entity.
Also it seems to me that Media and Ratings are connected with a OneToMany or ManyToMany, then this definitely should go to a separate DAO method.
I solved it by creating a custom implementation of the repository, implementing a custom interface.
First I had to declare the interface:
public interface CustomQuery {
public List<Integer>myCustomQuery(int id)
}
Then I implemented it in my custom repository. The name is important, it has to begin with the name of the repository interface it is extending. My repo is named MediaRepository so I named the custom implementation MediaRepositoryImpl:
#Component
public class MediaRepositoryImpl implements CustomQuery {
#PersistenceContext
EntityManager manager;
public List<Integer>myCustomQuery(int id){
Query q = manager.createNativeQuery(SQL_QUERY_GOES_HERE);
List<Integer> ids = new ArratList<Integer>();
#SuppressWarnings("unchecked")
List<Integer> result = q.getResultList();
for(Integer o : result){
//process the results and add them to the list
}
return list;
}
}
This way, you can do custom native queries while still keeping the regular Repositories clean. This approach is also easier to test.