Return entities with null property values when having nested objects - spring

I have following entities:
#Entity
#Table(name = "ENTITY_ONE")
public class EntityOne {
#Column(name = "PROPERTY_ID")
private Long propertyId;
#ManyToOne
#JoinColumn(name = "ENTITY_TWO_ID")
private EntityTwo entityTwo;
}
#Entity
#Table(name = "ENTITY_TWO")
public class EntityOne {
#Column(name = "PROPERTY_ID")
private Long propertyId;
#Column(name = "SOME_PROPERTY")
private String someProperty;
}
and I'm using JpaRepository with QuerydslPredicateExecutor.
I would like to query for EntityOne where condition would be like
where (EntityOne.EntityTwo.someProperty = 'test' or EntityOne.EntityTwo is NULL) order by EntityOne.EntityTwo.someProperty desc
So far after implementing Search by null with querydsl hint I'm ending up with CROSS JOIN (which I would like to change to LEFT JOIN):
FROM
ENTITY_ONE CROSS JOIN ENTITY_TWO
WHERE
ENTITY_ONE.ENTITY_TWO_ID = ENTITY_TWO.PROPERTY_ID
AND ( ENTITY_ONE.PROPERTY_ID IN ( 1, 2, 3, 4 ) OR ENTITY_ONE.ENTITY_TWO_ID IS NULL )
ORDER BY
ENTITY_TWO.SOME_PROPERTY DESC
Fetch enums doesn't do a thing for the generated query
#ManyToOne(fetch = FetchType.LAZY)
#Fetch(FetchMode.JOIN)
I am searching for some elegant, generic solution that could be implemented easily for other tables as well.

Related

Specification API/Criteria API - Group by data returned by existing specification object and findAll(specification,pageable)

Below are my entities:
Product
#Entity
#Table(name = "Product")
public class Product extends ReusableFields
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
Long productId;
#NonNull
#Column(name = "product_name")
String productName;
String measurementUnit;
//more fields and getters setters
}
Inward Outward List related to product:
#Entity
#Table(name = "inward_outward_entries")
public class InwardOutwardList extends ReusableFields
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long entryid;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "productId", nullable = false)
#JsonIgnoreProperties(
{ "hibernateLazyInitializer", "handler" })
Product product;
#JsonSerialize(using = DoubleTwoDigitDecimalSerializer.class)
Double quantity;
//more fields
}
Inward Inventory:
#Entity
#Table(name = "inward_inventory")
public class InwardInventory extends ReusableFields implements Cloneable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "inwardid")
Long inwardid;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "inwardinventory_entry", joinColumns =
{ #JoinColumn(name = "inwardid", referencedColumnName = "inwardid") }, inverseJoinColumns =
{ #JoinColumn(name = "entryId", referencedColumnName = "entryId") })
Set<InwardOutwardList> inwardOutwardList = new HashSet<>();
//more fields
}
Inward Inventory Repo:
#Repository
public interface InwardInventoryRepo extends extends JpaRepository<InwardInventory, Long>, JpaSpecificationExecutor<InwardInventory> ,PagingAndSortingRepository<InwardInventory, Long>
{}
Previously the requirement was only to filter data and show as pages based on filters selected by user. So I have a working code to create specification dynamically based on inputs. It is working fine. After creating the specification, I am using:
Page<T> findAll(#Nullable Specification<T> spec, Pageable pageable);
to generate required list of records.
But, now a new requirement has been added to show sum of quantities grouped on product name and measurement unit. i.e. whatever data is returned after filter should be grouped by. Since the filtration logic is already working fine, I do not want to touch it.
Can somehow help how to reuse existing specification object and group the data returned by findall(specification,pageable) method.
What I already tried.
Since specification directly do not support group by, I autowired entity manager and created own criteria query. But this is not giving correct results as all the tables are getting joined twice. Might be because they are joined first during specification object and again during grouping by:
#Service
#Transactional
public class GroupBySpecification {
#Autowired
EntityManager entityManager;
Logger log = LoggerFactory.getLogger(GroupBySpecification.class);
public List<ProductGroupedDAO> findDataByConfiguration(Specification<InwardInventory> spec) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductGroupedDAO> query = builder.createQuery(ProductGroupedDAO.class);
Root<T> root = query.from(InwardInventory.class);
Predicate p = spec.toPredicate(root, query, builder);
query.where(p);
Join< InwardInventory, InwardOutwardList> ioList = root.join(InwardInventory_.INWARD_OUTWARD_LIST);
Join<InwardOutwardList, Product> productList = ioList.join(InwardOutwardList_.PRODUCT);
query.multiselect(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT),
builder.sum(ioList.get(InwardOutwardList_.QUANTITY)));
query.groupBy(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT));
List<ProductGroupedDAO> groupedData = fetchData(query);
return groupedData;
}
Generated SQL - all the tables joined twice
SELECT DISTINCT product7_.product_name AS col_0_0_,
product10_.measurementunit AS col_1_0_,
Sum(inwardoutw12_.quantity) AS col_2_0_
FROM inward_inventory inwardinve0_
INNER JOIN inwardinventory_entry inwardoutw1_
ON inwardinve0_.inwardid = inwardoutw1_.inwardid
INNER JOIN inward_outward_entries inwardoutw2_
ON inwardoutw1_.entryid = inwardoutw2_.entryid
AND ( inwardoutw2_.is_deleted = 'false' )
INNER JOIN product product3_
ON inwardoutw2_.productid = product3_.productid
INNER JOIN warehouse warehouse4_
ON inwardinve0_.warehouse_id = warehouse4_.warehouse_id
INNER JOIN inwardinventory_entry inwardoutw5_
ON inwardinve0_.inwardid = inwardoutw5_.inwardid
INNER JOIN inward_outward_entries inwardoutw6_
ON inwardoutw5_.entryid = inwardoutw6_.entryid
AND ( inwardoutw6_.is_deleted = 'false' )
INNER JOIN product product7_
ON inwardoutw6_.productid = product7_.productid
INNER JOIN inwardinventory_entry inwardoutw8_
ON inwardinve0_.inwardid = inwardoutw8_.inwardid
INNER JOIN inward_outward_entries inwardoutw9_
ON inwardoutw8_.entryid = inwardoutw9_.entryid
AND ( inwardoutw9_.is_deleted = 'false' )
INNER JOIN product product10_
ON inwardoutw9_.productid = product10_.productid
INNER JOIN inwardinventory_entry inwardoutw11_
ON inwardinve0_.inwardid = inwardoutw11_.inwardid
INNER JOIN inward_outward_entries inwardoutw12_
ON inwardoutw11_.entryid = inwardoutw12_.entryid
AND ( inwardoutw12_.is_deleted = 'false' )
WHERE ( inwardinve0_.is_deleted = 'false' )
AND ( warehouse4_.warehousename LIKE ? )
AND ( product3_.product_name IN ( ?, ?, ?, ? ) )
GROUP BY product7_.product_name,
product10_.measurementunit
You will have to use the existing joins that are created by the specifications. You will probably have to do something like this:
public List<ProductGroupedDAO> findDataByConfiguration(Specification<InwardInventory> spec) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductGroupedDAO> query = builder.createQuery(ProductGroupedDAO.class);
Root<T> root = query.from(InwardInventory.class);
Predicate p = spec.toPredicate(root, query, builder);
query.where(p);
Join< InwardInventory, InwardOutwardList> ioList = getOrCreateJoin(root, InwardInventory_.INWARD_OUTWARD_LIST);
Join<InwardOutwardList, Product> productList = getOrCreateJoin(ioList, InwardOutwardList_.PRODUCT);
query.multiselect(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT),
builder.sum(ioList.get(InwardOutwardList_.QUANTITY)));
query.groupBy(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT));
List<ProductGroupedDAO> groupedData = fetchData(query);
return groupedData;
}
<X, T> Join<X, T> getOrCreateJoin(From<?, X> from, SingularAttribute<X, T> attr) {
return from.getJoins().stream().filter(j -> j.getAttribute() == attr).findFirst().orElse(() -> from.join(attr));
}
<X, T> Join<X, T> getOrCreateJoin(From<?, X> from, PluralAttribute<X, ?, T> attr) {
return from.getJoins().stream().filter(j -> j.getAttribute() == attr).findFirst().orElse(() -> from.join(attr));
}

Criteria API Specification - Filter records and return only the latest record for many to one mappings

I have two tables, Lead and LeadActivity. A lead can have many lead activities and mapping is done as #ManyToOne form LeadActivity to Lead.
Problem Statement -I want to to filter LeadActivity records such that, If there are more than one leadactivity records with same leadId, i should get only one record which is latest (have max primary key). Can anyone guide me on how to write specification or criteria API in such situations? I know this can be achieved through other ways but I have to use specification API. Below are the entity classes
Lead
#Entity
#Table(name = "customer_lead")
#Where(clause = ReusableFields.SOFT_DELETED_CLAUSE)
#Audited(withModifiedFlag = true)
#Data
public class Lead extends ReusableFields implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "lead_id", updatable = false, nullable = false)
Long leadId;
#Column(name = "name")
String customerName;
#Column(name = "primary_mobile")
String primaryMobile;
#Column(name = "secondary_mobile")
String secondaryMobile;
//more fields
}
Lead Activity
#Entity
#Table(name = "LeadActivity")
#Data
#Where(clause = ReusableFields.SOFT_DELETED_CLAUSE)
public class LeadActivity extends ReusableFields implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "leadactivity_id", updatable = false, nullable = false)
Long leadActivityId;
#Column(name = "activity_date_time", nullable = false)
#NonNull
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
Date activityDateTime;
#Column(name = "title")
#NonNull
String title;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "lead_id", nullable = false)
#JsonIgnoreProperties(
{ "hibernateLazyInitializer", "handler" })
#NotFound(action = NotFoundAction.IGNORE)
Lead lead;
//More fields
}
Expected Output - Suppose there are two records present with same leadId. I want to fetch only the latest among them based on their id. One with lower id should be ignored
Try this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<LeadActivity> cq = cb.createQuery(LeadActivity.class);
Root<LeadActivity> rootLeadActivity = cq.from(LeadActivity.class);
Join<LeadActivity,Lead> joinLead = rootLeadActivity.join(LeadActivity_.lead,JoinType.INNER);
/* If you dont use metamodel:
* Join<LeadActivity,Lead> joinLead = rootLeadActivity.join("lead",JoinType.INNER);
*/
// For performance, if you use JPA 2.1 set the leader id condition in the join
joinLead.on(cb.equal(joinLead.get(Lead_.leadId),LEAD_ID));
List<Predicate> predicatesList= new ArrayList<>();
/* if you use version 2.0 you will have to put it in the where
* predicatesList.add(cb.equal(joinLead.get(Lead_.leadId),LEAD_ID));
*/
Subquery<Long> sqMaxId = cq.subquery(Long.class);
Root<LeadActivity> sqRootActivity = sqMaxId.from(LeadActivity.class);
Join<LeadActivity,Lead> sqJoinLead = sqRootActivity.join(LeadActivity_.lead,JoinType.INNER);
sqMaxId.where(cb.equal(sqJoinLead.get(Lead_.leadId),joinLead.get(Lead_.leadId)));
sqMaxId.select(cb.max(sqRootActivity.get(LeadActivity_.leadActivityId)));
predicatesList.add(cb.equal(rootLeadActivity.get(LeadActivity_.leadActivityId),sqMaxId.getSelection()));
cq.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
cq.multiselect(rootLeadActivity);
The result query:
select a.* from lead_activity a
inner join lead l on a.lead_id = l.lead_id and l.lead_id = LEAD_ID
where a.lead_activity_id =
(select max(lead_activity_id) from lead_activity where lead_id = LEAD_ID)

Spring JPA Self Reference Issue

I created a table, Category in Postgres which holds both parent and child categories and references to itself (it's a self join table)
The table comprises of following columns: id, parent_id, start_date, end_date, status
I also have one row for root parent whose id = 0. So, any first level categories have root as its parent.
Example: Apparel > Women. Here Apparel(id=1) is a first level category whose parent_id = 0. Women is another category whose parent_id = 1.
I am using Spring JpaRepository findAll on my table and this is leading to infinite recursion.
POJO
#Table(name = "ofr_category")
#Getter
#Setter
#NoArgsConstructor
public class Category {
#Id
#Column(name = "cat_id", updatable = true, unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CATEGORY_SEQ")
#SequenceGenerator(sequenceName = "CATEGORY_ID_SEQ", allocationSize = 1, name = "CATEGORY_SEQ")
private Long id;
#Column(name = "cat_name")
private String name;
#Column(name = "cat_status")
private String status;
#Column(name = "start_date")
private LocalDate startDate;
#Column(name = "end_date")
private LocalDate endDate;
#Column(name = "parent_id")
private Long parentId;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", insertable = false, updatable = false)
private Category parentCategory;
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parentCategory")
private List<Category> childCategories;
public Category getParentCategory(){
return parentCategory;
}
}
Exception seen
"Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: java.util.ArrayList[0]->com.test.category.dataobject.Category[\"parentCategory\"]->com.test.category.dataobject.Category[\"parentCategory\"]->com.test.category.dataobject.Category[\"parentCategory\"])",
Maybe you can have a look into #JsonIdentityInfo, which solved a similar problem for me. You can check if this basic annotation works for you.:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Category {
...
}

Inner join on two tables in spring boot

I have 2 entities and want to perform an inner join on the ID of these two tables. How do I do that? After joining the tables, how do I get the values?
First entity: Employee.java
#Entity
#Table(name = "emp")
public class Employee {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "language", nullable = false)
private String language;
Second entity: Username.java
#Entity
#Table(name = "users")
public class Username {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "name", nullable = false)
private String name;
Thanks
I don't know it's helpful for your or not but,
You have to give relationship between those table first(Here i defined bidirectional relationship).
I suppose there is #OneToOne mapping. As like follow,
In Employee Table,
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "username_id")
private Username username;
#OneToOne(mappedBy = "employee")
private Employee employee;
Same way whenever you need those data base on requirement then Place Query as following way in your Employee Repository,
#Query(nativeQuery = true, value="<your-join-query>")
public Employee getEmployeeAllDetails();
For more brief detail follow this kind of tutorials which give you better idea regurding working mechenisum.
https://howtodoinjava.com/
https://www.baeldung.com/

How do I write this JPA query that requires a JOIN?

I'm using JPA 2.0, Hibernate 4.1.0.Final, and Spring 3.1.1.RELEASE. I have two entities:
#Entity
#Table(name = "user",
uniqueConstraints = { #UniqueConstraint(columnNames = { "USER_NAME" }) }
)
public class User implements Comparable<User>, Serializable
{
...
#Column(name = "first_name")
#NotNull
/* the first name of the User */
private String firstName;
and
#Entity
#Table(name="code_user",
uniqueConstraints = {
#UniqueConstraint(columnNames = { "SAMPLE_WORD_ID", "USER_ID" }) }
)
public class CodeUser
{
#Id
#NotNull
#GeneratedValue(generator = "uuid-strategy")
#Column(name = "ID")
private String id;
#ManyToOne
#JoinColumn(name = "CODE_ID", nullable = false, updatable = true)
private Code code;
#ManyToOne
#JoinColumn(name = "USER_ID", nullable = false, updatable = true)
private User user;
How do I write a JPA/CriteriaBuilder query to find all User objects who's first name matches "Dave" and who are tied to a Code record in which the code is "abode"?
I might have missed an HQL syntax element
SELECT user FROM CodeUser codeUser
JOIN FETCH codeUser.user user
JOIN FETCH codeUser.code code
WHERE user.firstName = 'Dave' AND code.value = 'abode'
assuming Code has a field value holding the value "abode". You might not need the FETCH.
You can always replace the literal values with a placeholder like ? or a named placeholder like :name and set their values from the Query object.

Resources