How to Map a Complex native Query Result Set to a Pojo Class in Spring Data JPA - spring

My Repository
public interface ProductRepository extends JpaRepository<Product,Integer> {
#Query(value = "select mp.*,u.name,p.name, p.ext_id from merchant_product mp join campaign c on c.product_id = mp.id and c.status in (4, 8)join product p on p.id = mp.product_id join user u on u.id = mp.user_id where mp.status = 4 and mp.availability = 'Y';", nativeQuery = true)
List<Object> getAllProduct();
}
This is my Query in Spring Boot i am using Spring data JPA . I need to map this to a Pojo class . So that i can use it for further processing .
can anyone help me with this.
My pojo
#Data
#AllArgsConstructor
public class Product {
#Id
//data of merchant product table
//data of user table
private int id;
private String name;
private String ext_id;
}

#Query(nativeQuery = true, name = "test", value = "select mp.* ...")
#SqlResultSetMapping(name="test", classes = {
#ConstructorResult(targetClass = Product.class,
columns = {#ColumnResult(name="name"), #ColumnResult(name="id")}, #ColumnResult(name="ext_id")})
})
Add the SqlResultSetMapping to initialize mappings which columns from the query corresponding to the Pojo fields

Id create a constructor for the params you want on Product and call -
public interface ProductRepository extends JpaRepository<Product,Integer> {
#Query(value = "select new Product(u.name,p.name, p.ext_id) from merchant_product mp join campaign c on c.product_id = mp.id and c.status in (4, 8)join product p on p.id = mp.product_id join user u on u.id = mp.user_id where mp.status = 4 and mp.availability = 'Y';", nativeQuery = true)
List<Product> getAllProduct();
}
You will need to define the params from the wildcard mp.* in your object for mapping

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));
}

Can't solve MultipleBagFetchException

I'm getting MultipleBagFetchException error. I tried to follow this tutorial, but it didn't helped me. https://vladmihalcea.com/hibernate-multiplebagfetchexception/
I have an entity Commodity (simplified)
#Entity
public class Commodity {
#ElementCollection
#CollectionTable
private List<Ingredient> ingredients; // Embeddable
#ManyToMany
private List<CommodityType> commodityTypes;
#OneToMany(mappedBy = "commodity", cascade = CascadeType.ALL)
#MapKey(name = "localizedId.locale")
private Map<Locale, LocalizedCommodity> localizations;
}
I want to load Commodity with ingredients and commodityTypes loaded.
That's my repository:
public interface CommodityRepository extends JpaRepository<Commodity, Long> {
#Query("select e.id from Commodity e")
List<Long> findAllIds(Pageable pageable);
#QueryHints(value = #QueryHint(name = "hibernate.query.passDistinctThrough", value = "false"), forCounting = false)
#Query("select distinct c from Commodity c left join fetch c.ingredients left join fetch c.localizations where c.id in :ids")
List<Commodity> findAllByIdIn(#Param("ids") List<Long> ids);
#QueryHints(value = #QueryHint(name = "hibernate.query.passDistinctThrough", value = "false"), forCounting = false)
#Query("select distinct c from Commodity c left join fetch c.commodityTypes t where c in :com")
List<Commodity> findWithCommodityTypes(#Param("com") List<Commodity> com);
default List<Commodity> findAllFullyLoaded(Pageable pageable) {
return findWithCommodityTypes(findAllByIdIn((findAllIds(pageable))));
}
}
After findWithCommodityTypes call, Commodity i got is not filled with c.ingredients and c.localizations. I'm getting failed to lazily initialize a collection of role Commodity.localizations error.
How do i get Commodity with c.ingredients, c.localizations and c.commodityTypes?

Spring Data: Get Count With Additional Fields

I want to return a count of one field plus another field. But I cant transform it into POJO. I can get it only as List of object array. What to I have:
public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long> {
#Query(value = "select count(c.is_read), c.chat_id from chat_message c where (c.sender_id = :userId or c.receiver_id = :userId) group by c.chat_id", nativeQuery = true)
List<Object[]> findAllByUserId(long userId);
But I need something like this:
#Query(value = "select count(c.is_read), c.chat_id from chat_message c where (c.sender_id = :userId or c.receiver_id = :userId) group by c.chat_id", nativeQuery = true)
List<POJO> findAllByUserId(long userId);
#Data
public static class POJO {
private long count;
private long id;
}
Found the answer:
public interface POJO {
Long getCount();
Long getId();
}
#Query(value = "select count(c.isRead) as count,c.chat.chatId as id from ChatMessage c where (c.senderId = :userId or c.receiverId= :userId) group by c.chat.chatId ")
List<POJO> findAllByUserId(long userId);

HSQL query using limit in join part

I'm new in Spring Boot. I tried to find an answer for my question on SO and google, but I can't find an exact answer for it.
I'm trying to create a function in my Spring Boot JpaRepository class which returns a Customer by id with a limited number of ascending ordered AccountingLogs related to the Customer.
My line of code in JpaRepository:
#Query("select c from Customer left outer join AccountingLog a on a.customer.id = c.id where c.id= :id")
Customer getWithLimitedNumberOfLastTransactions(#Param("id") Long id, #Param("limit") int limit);
The Customer class only with the relevant code:
#Entity
public class Customer {
//...
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Column(nullable = true)
#JsonManagedReference
private Set<AccountingLog> accountingLogs;
//...
}
The customer class only with the relevant code:
#Entity
public class AccountingLog {
//...
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "customer_id")
#JsonBackReference
private Customer customer;
//...
}
So I'm looking for an HSQL query which selects one customer by id with a specified number (variable named limit) of accounting logs in ascending order (last in first) related to the customer.
Thank you for your help!
You can write a native query in #Query if you are using MySql:
#Query(value="select c from Customer left outer join AccountingLog a on a.customer.id = c.id where c.id= :id limit :limit", nativeQuery = true)
or Oracle:
#Query(value="select c from Customer left outer join AccountingLog a on a.customer.id = c.id where c.id= :id and ROWNUM < :limit", nativeQuery = true)

JPA Join Custom query with OneToMany

I would like to use #Query annotation to create left join query from a entity with a #OneToMany relationship.
Parent entity is :
#Entity
#Table(name="Registration")
public class Registration {
#Column(nullable = false)
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate effect;
#OneToMany(targetEntity=Payment.class, cascade=CascadeType.ALL,fetch = FetchType.LAZY)
#JoinColumn(name="uuid")
private List<Payment> payment;
}
Child :
#Entity
#Table(name="Payment")
public class Payment {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name="uuid", strategy = "uuid2")
#Column(columnDefinition = "BINARY(16)")
private UUID uuid;
}
For DAO, I do like below :
#Query("SELECT p FROM Registration r JOIN r.payment p WHERE r.effect = :effect")
Iterable<Payment> find(#Param("effect") LocalDate effect);
Obviously, its wrong because generated query is :
select payment1_.uuid as uuid1_9_, payment1_.amount as amount2_9_ from registration registrati0_ inner join payment payment1_ on registrati0_.uuid=payment1_.uuid where registrati0_.effect=?
while the relation table has been generated:
For me, the correct query should be something like this :
select p.* from registration r join registration_payment rp on rp.registration = r.uuid join payment p on p.uuid = rp.payment where r.effect = '2015-10-16'
What is the good query syntax please ? Actual query return en empty array.
Finally, I found a solution.
You must describe relation table with #JoinTable :
#JoinTable(
name="registration_payment",
joinColumns = #JoinColumn(name="registration"),
inverseJoinColumns = #JoinColumn(name = "payment")
)
private List<Payment> payment;
Modify #Query not needed :
#Query("SELECT p FROM Registration r JOIN r.payment p WHERE r.effect = :effect")
Generated query is :
select payment2_.uuid as uuid1_9_, payment2_.amount as amount2_9_
from registration registrati0_
inner join registration_payment payment1_ on registrati0_.uuid=payment1_.registration
inner join payment payment2_ on payment1_.payment=payment2_.uuid
where registrati0_.effect=?

Resources