JPA Criteria API - Accessing fields of One-to-Many collection association - jpa

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.

Related

Hibernate Criteriabuilder Query with part of a compound id

I have a class that I am attempting to query by "userid"
#Entity
#IdClass(CollectionPK.class)
#Table(name="collection", schema="mageduelsusers")
public class Collection{
#Id
#Column(name = "userid")
private int userId;
#Id
#Column(name = "cardid")
private int cardId;
...
Id class of
public class CollectionPK implements Serializable{
private int userId;
private int cardId;
public CollectionPK() {
}
...
Query code is
public List<Collection> readCollection(int id) {
List<Collection> collection = null;
Session session = factory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Collection> criteriaQuery = builder.createQuery(Collection.class);
Root<Collection> root = criteriaQuery.from(Collection.class);
ParameterExpression userIdParameter = builder.parameter(Collection.class);
criteriaQuery.where(builder.equal(root.get("userid"), userIdParameter));
Query<Collection> query = session.createQuery(criteriaQuery);
query.setParameter("userid", id);
collection = query.getResultList();
tx.commit();
}
...
Error is
Exception in thread "main" java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [userid] on this ManagedType [com.panda.userinfo.Collection]
Ideal query would be
Select * from collection where userid = 'userid';
How do I modify to make this work?
Pretty sure error is in the criteria builder section as session.save(). session.get(), and session.delete() all work properly
Update:
Did a little bit of testing and the cause of the issue is definitely root.get("userid") Is there any way to check what Attributes hibernate has for a class?
Update2:
Capitalizing the I in root.get("userId") fixes that error. However both forms still cause an error at query.setParameter("userId", id)
java.lang.IllegalArgumentException: Unable to locate parameter registered with that name [userId]
Update 3:
Figured it out or at least made it functional. Hibernate was renaming things in the background. Solved by printing everything to find the correct parameter name.
for(Parameter<?> p:query.getParameters()) {
System.out.println(p.getName());
}
System.out.println(query.getParameters().size());
Try to correct your query in this way:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Collection> criteriaQuery = builder.createQuery(Collection.class);
Root<Collection> root = criteriaQuery.from(Collection.class);
ParameterExpression<Integer> userIdParameter = builder.parameter(Integer.class);
criteriaQuery.where(builder.equal(root.get("userid"), userIdParameter));
List<Collection> collection = session.createQuery(criteriaQuery)
.setParameter("userid", id)
.getResultList();
See also this section of the documentation.

TypedQuery is not working as expected

Iam running into below issue
String queryString = SELECT
a.field1 as f1,a.field2 as f2,b.field3 as f3,c.field4 as f4
FROM
Entity1 a, Entity2 b, Entity3 c
WHERE
a.cf1 = b.cf2
AND a.cf4 = c.cf3
AND a.fieldn = ?1
Defined below entity to hold above ResultSet
public class Entity1 {
public String f1,f2,f3;
Getters & Setters
}
List<Entity1> list = new ArrayList<>();
TypedQuery<Entity1> typedQuery = bowbEntityManager.createQuery(queryString, Entity1.class);
typedQuery.setParameter("fieldn", "blah");
list = typedQuery.getResultList();
Iam getting ClassCastException.
Above "list" returned as object[] for each fields
i can access values by obj[0], obj[1],....
Can anyone point out the mistake here? I want to retrieve the resultset as List.

JPA spring data specification on joins

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;

How can I query specific columns from 2 tables inside my objects using JPA 2.0?

I am looking for a way to request specific columns and have the foreign object present in the root object using CriteriaBuilder. Here is the context:
I have EntityA
#Entity
#Table(name = "ENTITY_A")
public class EntityA {
int id;
int entityBKey;
EntityBObject entityBObject;
int AColumn1;
int AColumn2;
#Basic
public Long getEntityBKey() {
return entityBKey;
}
#ManyToOne
#JoinColumn(name = "ENTITY_B_FK")
public EntityBObject getProgramType() {
return entityBObject;
}
#Basic
#Column(name = "COLUMN_1")
public String getAColumn1() {
return AColumn1;
}
...
}
Then I have EntityB
public class EntityB {
int id;
int BColumn1;
int BColumn2;
...
}
Now, I want to request column AColumn1 from EntityA and column BColumn1 from EntityB, while having the object EntityB inside the EntityA. How can I achieve this ?
How can I modify the following to get a partial EntityA with an EntityB inside ?
public List<EntityA> findAll() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EntityA> criteria = cb.createQuery(EntityA.class);
Root<EntityA> root = criteria.from(EntityA.class);
criteria.select(root);
return em.createQuery(criteria).getResultList();
}
Thanks !
Edit
#Tassos Bassoukos Yes, that's what I ended up doing, but it would get really messy when the request gets more complex. Ex.: Pull customers with their orders, with items for each orders. There would be so much java to achieve this, I though it could be automated so my object are automatically populated.
public List<EntityA> findAll() {
ArrayList<EntityA> result = new ArrayList<>();
Query q = em.createQuery("select eA, eB, from EntityA eA, EntityB eB where eA.key = eB.key");
#SuppressWarnings("unchecked")
List<Object[]> abc = q.getResultList();
for (Object[] array : abc) {
EntityA eA = (EntityA) array[0];
EntityB eB = (EntityB) array[1];
eA.setEntityB(eB);
result.add(pe);
}
return result;
}
First, why do you want a partial entity? That does not make sense from an OO perspective. Is there an actual, specific requirement for this?
Secondly, do you want entities or columns of entities? You can do both with CriteriaBuilder, but you need to be clear on a) what you want to achieve, b) why you want to achieve it.
Thirdly, there's JOIN FETCH.

Creating an "IN" query with JPA 2.0 Criteria api

I am using tje JPA criteria API to create an "IN" query. I want to select Courses that are in certain Categories. The Categories are supposed to end up in the IN part of the query.
This is the Course entity. It has a reference to a Category entity, because each Course is in one Category.
#Entity
public class Course implements DomainObject {
private Long id;
private Integer version;
private String name;
private Category category;
#Override
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
In my service I want to select Courses that are belong to certain (a list) of Categories.
public List<Course> findCourses(CourseFilter filter) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> criteriaQuery = criteriaBuilder.createQuery(Course.class);
Root<Course> root = criteriaQuery.from(Course.class);
List<Predicate> predicateList = new ArrayList<Predicate>();
if (!filter.getCategories().isEmpty()) {
Predicate predicate = root.get(Course_.category).in(filter.getCategories());
predicateList.add(predicate);
}
Predicate[] predicates = new Predicate[predicateList.size()];
predicateList.toArray(predicates);
criteriaQuery.where(predicates);
TypedQuery<Course> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getResultList();
}
When the query executes on the last line of the method it throws an error:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing:nl.codebasesoftware.produx.domain.Category;
nested exception is java.lang.IllegalStateException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing: nl.codebasesoftware.produx.domain.Category
I am not even sure I am using the right way to create an IN query. I think the criteria API is terribly complicated. But before I worry about the IN query I would like to know why Hibernate is throwing this TransientObjectException. The filter.getCategories() call results in actual categories, filled with a primary key id, etc.
Added:
Here is how I get the Category instance that I use to later fetch Courses with. This is also a DAO method that is called via a #Service from a #Controller method.
public Category findByName(String name) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Category> query = builder.createQuery(Category.class);
Root<Category> root = query.from(Category.class);
Predicate predicate = builder.equal(root.get(Category_.urlTitle), name);
query.where(predicate);
TypedQuery<Category> typedQuery = entityManager.createQuery(query);
return getSingleResult(typedQuery);
}
So, Hibernate is telling me I am using Category objects that somehow reference an unsaved entity, but I don't see how. The Category that is returned from this method is just a Category that if fetched by Hibernate. I am not doing anything with it before I send it to the method that fetches Courses.
Here is my the controller method:
#RequestMapping(method = RequestMethod.GET, value = "/{categoryUrlName}")
public String setup(#PathVariable("categoryUrlName") String categoryUrlName, Model model){
// Fetch the category
Category category = categoryService.findByName(categoryUrlName);
// if no category found, throw a 404
if(category == null){
throw new ResourceNotFoundException();
}
// Fetch courses in this category
List<Course> courses = courseService.findCourses(category);
model.addAttribute("courses", courses);
model.addAttribute("category", category);
model.addAttribute("mainContent", "content/category");
return "main";
}
Before executing a query, Hibernate flushes the changes you made to persistent entities in the session. This ensures that the query will search on the latest state of all the entities. Unfortunately, one of the dirty entities that Hibernate tries to flush references a transient entity, and thus can't be flushed, which causes the exception. The exception doesn't come from the query itself, but from the flush before the execution of the query.
You probably did something like the following before executing the query:
Cat cat = em.find(Cat.class, catId); // cat is a persistent persistent entity
cat.setMate(new Mouse()); // the mouse has not been persisted, and cat references it.