How to get a specific element in a collection field of a JPA entity? - spring-data-jpa

I'm using Criteria API to retrieve entities from the database. I have two entities as following:
public class EntityA {
String primaryKey;
#JoinColumn(
updatable = false,
insertable = false,
name = "primaryKey",
referencedColumnName = "primaryKeyOne")
List<EntityB> list;
}
public class EntityB {
String primaryKeyOne;
String primaryKeyTwo;
String name;
Integer value;
}
Now I want to create a Spring JPA Specification using these two entities.
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> cr, CriteriaBuilder cb);
}
After I fetched EntityA, I can get the whole list of EntityB. But how do I filter out a specific instance of EntityB and create a predicate using it? My intention is as the following:
cb.gt(root.get("list").stream().filter(e -> "abc".equals(e.getName())).get("value"), 100)

Usually you should perform a join between the two entities and do the filtering based on the join.
Given your example, the specification will be something like the following:
public class EntityASpecifications {
public static Specification<EntityA> toSpecification(String name, Integer value){
return (root, criteriaQuery, criteriaBuilder) -> {
Join<EntityA, EntityB> entityBJoin = root.join("list");
return criteriaBuilder.and(
criteriaBuilder.equal(entityBJoin.get("name"), name),
criteriaBuilder.gt(entityBJoin.get("value"), value)
);
};
}
}
You will then use it with the repository of EntityA like so,
entityARepository.findAll(EntityASpecifications.toSpecification("abc", 100));

Related

No results returned when using JPA Specifications in(Collection<?>) method

I'm trying to implement the equivalent of this SQL query using Spring Data JPA Specifications:
SELECT * FROM product WHERE category_id IN (....)
The two entities involved in this query have a OneToMany and ManyToOne relationship:
ProductEntity:
#Data
#NoArgsConstructor
#Entity
#Table(name = "PRODUCT")
public class ProductEntity extends AbstractAuditableEntity {
// skipped other properties for simplicity sake
#ManyToOne
private CategoryEntity categoryEntity;
}
CategoryEntity:
#Entity
#Table(name = "PRODUCT_CATEGORY")
#Data
public class CategoryEntity extends AbstractBaseEntity {
// skipped other properties for simplicity sake
#OneToMany(mappedBy = "categoryEntity")
private List<ProductEntity> productEntities;
}
My JPA Specifications query compiles and runs without any error, but doesn't return any result:
Specification definition:
public static Specification<ProductEntity> inCategories(List<Long> categories) {
return (root, query, builder) -> {
if (categories != null && !categories.isEmpty()) {
final Path<CategoryEntity> category = root.get("categoryEntity");
return category.get("id").in(categories);
} else {
// always-true predicate, means that no filtering would be applied
return builder.and();
}
};
}
Client code:
Page<ProductEntity> productEntityPage = productRepository.findAll(Specification
.where(ProductSpecifications.inCategories(filterCriteria.getCategories()))
, pageRequest);
Why doesn't it work? I get results when querying the database using SQL statements, so there must be something wrong with either my JPA Specifications query or my entities mapping.
What am I missing?
I think you should use join here
Join<ProductEntity, CategoryEntity> categoryJoin = root.join("categoryEntity");
return categoryJoin.get("id").in(categories);
instead of
Path<CategoryEntity> category = root.get("categoryEntity");
return category.get("id").in(categories);
since root.get("categoryEntity").get("id"); will give you nothing as no such path (product.categoryEntity.id) exists in product table.

Searching by query for attributes of complex objects in Java EE

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.

QueryDslPredicateExecutor: Predicate from two tables

I have this QueryDSL Predicate:
public class Predicates {
public static Predicate get() {
QUser qUser = QUser.user;
QUserCompany qUserCompany = QUserCompany.userCompany;
return qUser.id.eq(qUserCompany.userId);
}
}
as you see, I need two tables to query users (User and UserCompany), so I get an error when I declare my repository like this:
public interface UserRepository QueryDslPredicateExecutor<User>
{
}
because internally UserCompany table is not included in the query (QueryDslPredicateExecutor<User>).
List<User> users = userRepository.findAll(Predicates.get());
How can I build a Predicate using two tables and use it with QueryDslPredicateExecutor?
Solved! I have mapped the one-to-many relationship in the User entity to UserCompany and then:
public class Predicates {
public static Predicate get() {
QUser qUser = QUser.user;
return qUser.userCompanies.any().userId.eq(qUser.id);
}
}

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.