I have some already created org.springframework.data.jpa.domain.Specifications. Now I am creating a query in which I would like to use the specification on a table that I join to. But in order to use a Specification I need a Root, but joining gives me a Join object.
Is there a way of converting from a Join object to a Root? Or is there something analogous to Specification, but for Joins?
Tarwirdur Turon's solution does not fit my need so I managed to turn a Join into a Root by creating a Root<T> implementation that delegates all methods to a Join<?,T> instance. (Join and Root being children interfaces of From)
Although it works, it looks very dirty to me.
Tarwirdur Turon's solution doesn't work for me because I have an already built Specification<JoinedEntity> and I want to find all Entity for which the joinedEntity matches the specification without knowing what's 'inside' this specification.
public class JoinRoot<T> implements Root<T> {
private final Join<?, T> join;
public JoinRoot(Join<?, T> join) {
this.join = join;
}
// implements all Root methods, delegating them to 'this.join' (#boilerplate),
// cast when needed
#Override
public EntityType<T> getModel() {
// this one is the only one that cannot be delegated, although it's not used in my use case
throw new UnsupportedOperationException("getModel cannot be delegated to a JoinRoot");
}
}
Then use this class like follow :
Specification<JoinedEntity> joinedSpecs = ...
Specification<Entity> specs = (root, query, builder) -> {
// Convert Join into Root using above JoinRoot class
Root<JoinedEntity> r = new JoinRoot<>(root.join(Entity_.joinedEntity));
return joinedSpecs.toPredicate(r, query, builder);
}
Specification<Entity> where = Specifications.where(specs);
List<Entity> entities = entityRepository.findAll(where);
I really wonder why the Specification.toPredicatemethod takes a Root<X> as first argument instead of a From<Z,X>, this would ease all the thing ...
You don't need Root object. Join object is instance of Path and Expression interfaces. See example with working with join from Specification:
class JoinedSpecification extends Specification<JoinedEntity>() {
public Predicate pathPredicate(Path<JoinedEntity> joinedEntity, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.equal(joinedEnity.get(JoinedEntity_.value), 20L);
}
#Override
public Predicate toPredicate(Root<JoinedEntity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return pathPredicate(root, query, builder);
}
}
class MySpecification extends Specification<Entity>() {
private static JoinedSpecification joinedSpecification = new JoinedSpecification();
#Override
public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Join<T, JoinedEntity> join = root.join(Entity_.joinedEntity, JoinType.LEFT);
// Some join condition
Path<Long> someExpr = join.get(JoinedEntity_.someExpr);
Long someExprCriteria = 10L;
join = join.on(builder.equal(someExpr, someExprCriteria));
return joinedSpecification.pathPredicate(join, query, builder);
}
}
#Autowired
JpaSpecififcationExecutor<Entity> service;
Specification<Entity> spec = new MySpecification();
serivce.findAll(spec);
It will provide query like
SELECT e FROM Entity e LEFT JOIN e.joinedEntity j WITH j.someExpr=10 WHERE j.value = 20;
Related
I faced with a strange behaviour when entity framework core throwing away my Includes.
I need to make some generic methods to filter and combine my queries and those methods must receive some parameters and IQueryable filter subquery and return combined IQueryable result for further composition.
I simplified my code and made an example when you can see what I do mean:
public IQueryable<Tuple<TResult, TFilter>> Method1<TResult, TFilter>(IQueryable<TFilter> filters)
where TResult : ResultEntity
where TFilter : FilterEntity
{
var q = from state in _dbContext.Set<TResult>()
join f in filters on state.ID_Result equals f.ID
where ....
select new Tuple<TResult, TFilter>(state, f);
return q;
}
public void GetResult(int pageIndex, int pageSize)
{
IQueryable<Car> results = _dbContext.Cars.Include(x => x.Images);
// 1) All right, it has the images
var q1 = Method1<CarState, Car>(results).ToList();
// 2) Wrong, there is no images. And SQL query doesn't contain any joins to Images table
var q2 = Method1<CarState, Car>(results).Skip(pageIndex).Take(pageSize).ToList();
// 3) Without the method and with anonymous type it's ok.
var tmp = from state in _dbContext.Set<CarState>()
join f in results on state.ID_Result equals f.ID
where ....
select new { state, f };
var q3 = tmp.Skip(pageIndex).Take(pageSize).ToList();
}
What did I do wrong?
Do not use Builder methods or constructors if you want to use query later. It is because LINQ translator cannot trace back fields to generate correct SQL.
This part of query is problematic
new Tuple<TResult, TFilter>(state, f);
Better to create new class(es)
public class MTuple<T1, T2>
{
public T1 Item1 { get; set; }
public T2 Item2 { get; set; }
}
And use them in your methods (similar to anonymous classes usage)
public IQueryable<MTuple<TResult, TFilter>> Method1<TResult, TFilter>(IQueryable<TFilter> filters)
where TResult : ResultEntity
where TFilter : FilterEntity
{
var q = from state in _dbContext.Set<TResult>()
join f in filters on state.ID_Result equals f.ID
where ....
select new MTuple<TResult, TFilter>
{
Item1 = state,
Item2 = f
};
return q;
}
SUGGESTION
Such classes can be generated by T4 template, or just copy generated code
How could I write the following JPQL query using Criteria API?
" select a from A a left join fetch a.bs b where b.i like 'X%' "
A-to-B is a One-To-Many relationship where A and B is like following:
#Entity
public class A {
#OneToMany(cascade={CascadeType.PERSIST})
private Set<B> bs = new HashSet<B>();
//...
}
#Entity
public class B {
private String i;
//...
}
I tried the foolowing:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery( A.class );
Root<A> aRoot = cq.from( A.class );
Fetch<A, B> bs = aRoot.fetch(A_.bs, JoinType.LEFT);
cq.where(cb.like(what_do_i_do_here, "X%"));
cq.select(aRoot);
I need to get all those As along with its associated Bs where the i value of the associated Bs start with an "X".
EDIT:
If I try the method given at How to properly express JPQL "join fetch" with "where" clause as JPA 2 CriteriaQuery? I incorrectly get 2 As in result while I should get just 1 A whose associated B_.i has the value that starts with "X".
I got the issue resolved using the following code:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = builder.createQuery( A.class );
Root<A> root = cq.from( A.class );
Join<A, B> bs = (Join<A, B>) root.fetch(Guide_.students, JoinType.LEFT);
cq.where(builder.like(bs.get(B_.i), builder.parameter(String.class, "i")));
cq.select(root);
TypedQuery<A> query = em.createQuery(criteria).setParameter("i", "X%");
List<A> as= query.getResultList();
for (A a: as) {
System.out.println(a);
}
You can use Join<ParentTable,ChildTable> to get and filter child entities. Try below code
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery( A.class );
Root<A> aRoot = cq.from( A.class );
Join<A, B> bs = root.join(A_.B);
cq.where(cb.like(bs.get(B_.i), "X%"));
cq.select(aRoot);
Note - I didn't get a chance to test this code. You can refer to working code which I implemented for similar problem here https://github.com/bsridharpatnaik/SpecificationTest
Edit - Providing Detailed Answer
I have made new push to the repo. Please take latest pull.
I created exact same example that you mentioned.
Entity classes
public class A
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long aid;
#OneToMany(cascade={CascadeType.PERSIST})
private Set<B> bs = new HashSet<B>();
// get & set
}
public class B
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long bid;
String i;
//get & set
}
Now, as per your query, you want to filter all records from class A where corresponding B.i starts with x.
Below is service class. to make it easy to understand, I wrote criteroa query logic in separate class
#Service
public class Service1
{
#Autowired
aRepo repo;
#Autowired
ModelSpecification modelSpecification;
#Autowired
EntityManager entityManager;
public List<?> getResults() throws ParseException
{
//ModelSpecification modelSpecification = new ModelSpecification();
CriteriaQuery<A> query = modelSpecification.getSpecQuery();
TypedQuery<A> typedQuery = entityManager.createQuery(query);
List<A> resultList = typedQuery.getResultList();
return resultList;
}
}
Below is the criteria query logic
#Component
public class ModelSpecification
{
#Autowired
EntityManager entityManager;
public CriteriaQuery<A> getSpecQuery() throws ParseException
{
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<A> query = builder.createQuery(A.class);
Root<A> root = query.from(A.class);
Join<A,B> ab = root.join(A_.BS);
query.where(builder.like(ab.get(B_.I),"x"+"%"));
return query;
}
}
I am filtering all A records where B_.I starts with x.
Output
Class A has below entries for aid - 1,2
Class B has below entries for
join table a_bs has below entries
Now, if I hit API, I should get only A record with aid =1 as only it is associated with B.i starting with x.
I have created the object Person, I can deleted and modify it and I can also search for Person by his name or phonenumber... but I don't know for exemple how to search for a person by his ** home address**. Here is my code:
My entity Person.java:
public class Person{
private Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
private Address address;
....
}
My entity Address.java
public class Address{
...
private String streetName;
...
}
And here is the most interesting function that I am trying to modify to get what I want, I would like to search for Persons who live in xxx (streetName = xxx). Here is my function getByQuery:
public List<Person> getByQuery(PersonSearchQuery searchQuery) {
Map<String, String> criteriaQuery = new HashMap<String, String>();
if (searchQuery.getName() != null)
criteriaQuery.put("name",searchQuery.getName());
TypedQuery<Person> query = this.findByQuery(criteriaQuery);
return query.getResultList();
}
The object PersonSearchQuery contains just to attributes name (String) and streetName (String) and their getters.
Function findByQuery:
public TypedQuery<T> findByQuery(Map<String, String> criteriaQuery) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(this.entityClass);
Root<T> root = criteria.from(this.entityClass);
criteria.select(root);
Predicate predicate = builder.conjunction();
if (criteriaQuery.size() != 0) {
for (String key : criteriaQuery.keySet()) {
try{
predicate = builder.and(predicate, builder.equal(root.<String>get(key), criteriaQuery.get(key)));
}catch(IllegalArgumentException e){
continue;
}
}
}
criteria.where(predicate);
return this.em.createQuery(criteria);
}
So I can search for Persons by their names by I cannot search for them by streetName the problem is my function getByQuery I would like to do something like this:
if (searchQuery.getStreetName() != null)
criteriaQuery.put("Address.streetName",searchQuery.getStreetName());
The problem is I don't know how to define the key in this case. Thanks for your help
I only use CriteriaBuilder if I have several similar Entities which needs to be used/rendered in the same way, so if person is the only Entity with an Address reference I would just use JPQL, like this:
entityManager.createQuery(
"select p from Person p where p.address.streetName like :streetName", Person.class)
.setParameter("streetName", "xyz" + "%").getResultList()
The main reason I tend to avoid CriteriaBuilder, is because it has a rather steep learning curve, and you need to write a lot of code to express very simple concepts. In contrast any developer familiar with SQL can read and maintain JPQL code.
These days I always use frameworks, like DeltaSpike Data (for EE) and Spring Data, they both implements most of the basic DAO/Repository features, so If you don't mind an extra dependency (and some magic) it can save you a lot of boilerplate JPA code.
currently I am wrestling with being able to fetch only the data I need. The findAll() method needs to fetch data dependant on where its getting called.
I do not want to end up writing different methods for each entity graph.
Also, I would avoid calling entitymanagers and forming the (repetitive) queries myself.
Basicly I want to use the build in findAll method, but with the entity graph of my liking. Any chance?
#Entity
#Table(name="complaints")
#NamedEntityGraphs({
#NamedEntityGraph(name="allJoinsButMessages", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
}),
#NamedEntityGraph(name="allJoins", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre"),
#NamedAttributeNode("complaintMessages")
}),
#NamedEntityGraph(name="noJoins", attributeNodes = {
})
})
public class Complaint implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private long id;
private Timestamp date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer")
private User customer;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "handling_employee")
private User handling_employee;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="genre")
private Genre genre;
private boolean closed;
#OneToMany(mappedBy = "complaint", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ComplaintMessage> complaintMessages = new ArrayList<ComplaintMessage>();
//getters and setters
}
And my JPARepository
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
}
We ran into a similar problem and devised several prospective solutions but there doesn't seem to be an elegant solution for what seems to be a common problem.
1) Prefixes. Data jpa affords several prefixes (find, get, ...) for a method name. One possibility is to use different prefixes with different named graphs. This is the least work but hides the meaning of the method from the developer and has a great deal of potential to cause some non-obvious problems with the wrong entities loading.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#EntityGraph(value = "User.membershipYears", type = EntityGraphType.LOAD)
User readByUserId(int id);
}
2) CustomRepository. Another possible solutions is to create custom query methods and inject the EntityManager. This solution gives you the cleanest interface to your repository because you can name your methods something meaningful, but it is a significant amount of complexity to add to your code to provide the solution AND you are manually grabbing the entity manager instead of using Spring magic.
interface UserRepositoryCustom {
public User findUserWithMembershipYearsById(int id);
}
class UserRepositoryImpl implements UserRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public User findUserWithMembershipYearsById(int id) {
User result = null;
List<User> users = em.createQuery("SELECT u FROM users AS u WHERE u.id = :id", User.class)
.setParameter("id", id)
.setHint("javax.persistence.fetchgraph", em.getEntityGraph("User.membershipYears"))
.getResultList();
if(users.size() >= 0) {
result = users.get(0);
}
return result;
}
}
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
}
3) JPQL. Essentially this is just giving up on named entity graphs and using JPQL to handle your joins for you. Non-ideal in my opinion.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#Query("SELECT u FROM users WHERE u.id=:id JOIN??????????????????????????")
User findUserWithTags(#Param("id") final int id);
}
We went with option 1 because it is the simplest in implementation but this does mean when we use our repositories we have have to look at the fetch methods to make sure we are using the one with the correct entity graph. Good luck.
Sources:
JPA EntityGraph with different views using Spring
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
I don't have enough reputation to post all of my sources. Sorry :(
We had the same issue and built a Spring Data JPA extension to solve it :
https://github.com/Cosium/spring-data-jpa-entity-graph
This extension allows to pass named or dynamically built EntityGraph as an argument of any repository method.
With this extension, you would have this method immediatly available:
List<Complaint> findAll(Sort sort, EntityGraph entityGraph);
And be able to call it with an EntityGraph selected at runtime.
Use #EntityGraph together with #Query
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoinsButMessages();
#EntityGraph(value = "allJoins" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoin();
...
}
Using the #EntityGraph annotation on a derived query is possible, as I found out from This article. The article has the example:
#Repository
public interface ArticleRepository extends JpaRepository<Article,Long> {
#EntityGraph(attributePaths = "topics")
Article findOneWithTopicsById(Long id);
}
But I don't think there's anything special about "with" and you can actually have anything between find and By. I tried these and they work (this code is Kotlin, but the idea is the same):
interface UserRepository : PagingAndSortingRepository<UserModel, Long> {
#EntityGraph(attributePaths = arrayOf("address"))
fun findAnythingGoesHereById(id: Long): Optional<UserModel>
#EntityGraph(attributePaths = arrayOf("address"))
fun findAllAnythingGoesHereBy(pageable: Pageable): Page<UserModel>
}
The article had mentioned the caveat that you can't create a method similar to findAll which will query all records without having a By condition and uses findAllWithTopicsByIdNotNull() as an example. I found that just including By by itself at the end of the name was sufficient: findAllWithTopicsBy(). A little more terse but maybe a little more confusing to read. Using method names which end with just By without any condition may be in danger of breaking in future versions in Spring since it doesn't seem like an intended use of derived queries name.
It looks like the code for parsing derived query names in Spring is here on github. You can look there in case you're curious about what's possible for derived queries repository method names.
These are the spring docs for derived queries.
This was tested with spring-data-commons-2.2.3.RELEASE
EDIT: this doesn't actually work. Ended up having to go with https://github.com/Cosium/spring-data-jpa-entity-graph. The default method LOOKS correct, but doesn't successfully override the annotations.
Using JPA, what I found works is to use a default method, with a different EntityGraph annotation:
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee" }, type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee", "messages" }, type=EntityGraphType.FETCH)
default List<Complaint> queryAll(Sort sort){
return findAll(sort);
}
}
You don't have to do any of the re-implementation, and can customize the entity graph using the existing interface.
Can you try create EntiyGraph name with child that you will request and give same name to the find all method.
Ex:
#EntityGraph(value = "fetch.Profile.Address.record", type = EntityGraphType.LOAD)
Employee getProfileAddressRecordById(long id);
For your case:
#NamedEntityGraph(name="all.Customer.handling_employee.genre", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
})
method name in repository
#EntityGraph(value = "all.Customer.handling_employee.genre" , type=EntityGraphType.FETCH)
findAllCustomerHandlingEmployeeGenre
This way you can keep track of different findAll methods.
I'm getting started with JPA and I want to know the best way to achieve something like this:
I need to implement a service that returns a list of scripts, and each script has a list of parameters. I simplified the query, but it is something like this:
(SELECT
p.DESC
FROM
INPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
UNION
(SELECT
p.DESC
FROM
OUPUT_PARAMETERS p
INNER JOIN SCRIPT_PARAMS sp ON p.PARAM_ID = sc.PARAM_I
INNER JOIN SCRIPT s ON s.SCRIPT_ID = sc.SCRIPT_ID
WHERE
s.NAME = 'name')
And I want to return a list of POJO objects that are something like:
public class Script {
private String name;
private List<String> params;
public Script(){}
public String getName()
{
return name;
}
public void setName(String pName)
{
name = pName;
}
public List<String> getParams()
{
return params;
}
public void setParams(List<String> pParams)
{
params = pParams;
}
}
I want to know what is the best way to load the POJO object from a query. Is it best to build a JPQL query, or can I use a Native Named Query, and do I need to get a object[] and construct my POJOs manually, or can I use JPA to load the objects from the query?
You can only construct instances of Script manually. When querying with JPQL, reason is that constructor expression cannot take List as argument. Additionally JPQL does not have unions.
Also with SQL query you cannot construct Script directly, because result can be only scalars or entity.