I want to write equivalent JPA query to the following mysql query
select active_package,sum(duration),sum(charge),count(*)
from User
where call_type="MO"
and start_date between '2012-02-01' and '2012-02-09'
group by active_package;
For JPA Query the corresponding Attributes are below.
activePackage,callType,duration,charge,startDate
Entity class is User.
I want to use the createQuery() of JPA.
Can any one tell me or give me the link where can i find the solution for this.
Try this one, it should work, if not, please comment, we will get it work :).
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);
Root<User> entity = cq.from(User.class);
cq.multiselect(entity.get("activePackage"),
cb.sum(entity.get("duration").as(Long.class)),
cb.sum(entity.get("charge").as(Double.class),
cb.count(entity).as(Long.class)));
cq.where(cb.equal(entity.get("callType"), "MO"),
cb.between(entity.get("startDate").as(Date.class),
new Date(), new Date()));
cq.groupBy(entity.get("activePackage"));
List<Tuple> resultList = entityManager.createQuery(cq).getResultList();
for (Tuple result : resultList) {
System.out.println(result.get(0) + " " + result.get(1)
+ " " + result.get(2) + " " + result.get(3));
}
Also if you want to filter only by date, but have timestamp in your model, you can check this Compare date's date part only with Timestamp in Hibernate answer.
Also JPA provides constructing result classes as return values, so you can group your columns. Read more.
Related
We got an Spring Boot 2 application using Spring Data JDBC. Its displaying a list of orders which needs to be filtered for many different aspects and combinations. Therefore, our Repository is growing more and more, because for new combinations find* methods are added or new parameters are applied.
public interface OrderJdbcRepository extends PagingAndSortingRepository<OrderEntity, Long> {
String ORDER_FILTER = "WHERE ..."; // big custom sql statement
String NOT_BILLED = "...";
Optional<OrderEntity> findById(String orderId);
#Query(
"SELECT o.* FROM `ORDER` AS o "
+ ORDER_FILTER
+ "ORDER BY o.ORDER_ID "
+ "LIMIT :limit OFFSET :offset"
)
List<OrderEntity> findFiltered(String customerId, LocalDate startDate,
LocalDate endDate, ...some other filter criterias, int limit, long offset);
#Query(
"SELECT o.* FROM `ORDER` AS o "
+ ORDER_FILTER + NOT_BILLED // additional filter for not billed orders
+ "ORDER BY o.ORDER_ID "
+ "LIMIT :limit OFFSET :offset"
)
List<OrderEntity> findFilteredAndNotBilled(String customerId, LocalDate startDate,
LocalDate endDate, ...some other filter criterias, int limit, long offset);
// many other methods which looks similar
I'm afraid of that this leads to unmaintainable code. Therefore I'm trying to use the composite specification pattern like its described in the link. For example i just need to combine CustomerIdSpecification, DateBetweenSpecification and NotBilledSpecification. However, the linked tutorial is for Spring Data JPA with Criteria API, which does not exist in Spring Data JDBC. So I'm thinking of how i could solve this problem in Spring Data JDBC.
I need a way to define the custom sql in each specification and pass it to my OrderJdbcRepository. An alternative I thought is to use JdbcTemplate for building a custom sql and accessing my data by hand. Are there other solutions, which I did not think of?
Thanks so far!
I want to rewrite this SQL query in JPA.
String hql = "SELECT date(created_at) AS cdate, sum(amount) AS amount, count(id) AS nooftransaction "
+ "FROM payment_transactions WHERE date(created_at)>=date(now()- interval 10 DAY) "
+ "AND date(created_at)<date(now()) GROUP BY date(created_at)";
TypedQuery<Merchants> query = entityManager.createQuery(hql, Merchants.class);
List<Merchants> merchants = query.getResultList();
Is there a way to rewrite the queries into JPA or I should use it as it is?
In situations like these, more often than not the best approach is to write a plain SQL view:
CREATE OR REPLACE VIEW payment_transactions_stats AS
SELECT date(created_at) AS cdate, sum(amount) AS amount, count(id) AS nooftransaction
FROM payment_transactions
WHERE date(created_at)>=date(now()- interval 10 DAY)
AND date(created_at)<date(now()) GROUP BY date(created_at);
And map it to an #Immutable entity. This approach works well when:
you have read only data
the view does not need parameters (in this case there are solutions as well which span from hacky to nice)
You provide no details about the classes and entities but it could be something like:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = builder.createTupleQuery();
From<PaymentTransaction> tx = query.from(PaymentTransaction.class);
Expression<Long> sumAmount = builder.sum(tx.get("amount"));
Expression<Long> count = builder.count(tx.get("id"));
Expression<Date> createdAt = tx.get("created_at");
query.multiselect(createdAt, sumAmount, count);
query.where(builder.greaterThanOrEqualTo(createdAt, builder.function("DATEADD", "DAY", new Date(), builder.literal(-10))),
builder.lessThan(createdAt, new Date()));
query.groupBy(createdAt);
entityManager.createQuery(query).getResultList().stream()
.map(t -> new Merchants(t.get(0, Date.class), t.get(1, Long.class), t.get(2, Long.class)))
.collect(Collectors.toList());
It is better not to use JPA for complex queries like this. JPA are usually used for simple queries.
Since the question is tagged with spring-data-jpa, you could try using a Spring CRUDRepository on top of your table. In the CRUDRepository, write a custom method with the #Query annotation.
It's hard for me to formulate the entire query because I don't know the members of your Merchants class.
Alternatively you can set the nativeQuery = true for the #Query annotation and use actual DB query to solve your problem.
You can use below code
CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = qb.createQuery();
Root paymentInstructionsRoot = cq.from(PaymentInstructions.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(qb.greaterThanOrEqualTo(path, fromDateRange));
predicates.add(qb.lessThanOrEqualTo(path, toDateRange));
Selection cdate = paymentInstructionsRoot.get(PaymentInstructions_.createdAt).alias("cdate");
Selection amount = qb.sum(paymentInstructionsRoot.get(PaymentInstructions_.amount))).alias("amount");
Selection nooftransaction = qb.count(paymentInstructionsRoot.get(PaymentInstructions_.id))).alias("nooftransaction");
Selection[] selectionExpression = {cdate, amount, nooftransaction};
Expression[] groupByExpression = {paymentInstructionsRoot.get(PaymentInstructions_.createdAt)};
cq.multiselect(selectionExpression).where(predicates.toArray(new Predicate[]{})).groupBy(groupByExpression).where(predicates.toArray(new Predicate[]{}));
List<PaymentInstructions> paymentInstructions = entityManager.createQuery(cq).getResultList();
In your Entity class that represents the 'payment_transactions' table, add the following:
#SqlResultSetMapping(
name = "PaymentTransaction.summaryMapping",
classes = {
#ConstructorResult(targetClass = PaymentTransactionSummary.class,
columns = {
#ColumnResult(name = "cdate")
, #ColumnResult(name = "amount")
, #ColumnResult(name = "nooftransaction")
})
}
)
Create a new pojo class named PaymentTransactionSummary (must match the name used above, or whatever name you choose, with member fields cdate, amount, and nooftransaction. Include a constructor that includes those three fields in the order listed above.
Then in your dao class, write this:
Query q = entityManager.createNativeQuery("your query string from above"
, "PaymentTransaction.summaryMapping");
List<PaymentTransactionSummary> results = q.getResultList();
I am trying to call a stored procedure, which is built in mysql, in my Spring boot app using JPA. My stored procedure returns the result which cant be contain in single model as it fetches data from combination of tables.
I can do this with "call " but i guess that is not JPA's way. COuld you please let me know what is the best way to do it?
In case you're using plain JPA you need to do a native query call. Something like below.
Query q = em.createNativeQuery("select my_store_pro(?, ?)");
List<Object[]> results = q.getResultList();
for (Object[] a : results) {
System.out.println("result " + a[0] + " " + a[1]);
}
If you're using Spring Data repositories then you want something like below.
#Query(nativeQuery = true, value = "select my_store_pro(?, ?)")
Date callMyStoreProc(int val1, int val2);
Entity relation: Transaction(#ManyToOne - eager by default) -> Account
String sql = "SELECT new com.test.Pojo(t.account, SUM(t.value)) FROM Transaction t GROUP BY t.account";
List list = entityManager.createQuery(sql).getResultList();
By default JPA using Hibernate implementation will generate 1 + n queries. The n queries are for lazy loading of the account entities.
How can I make this query eager and load everything with a single query? The sql equivalent would be something like
SELECT account.*, SUM(t.value) FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id
, a syntax that works well on PostgreSQL. From my findings Hibernate is generating a query that justifies the lazy loading.
SELECT account.id, SUM(t.value) FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id
Try marking the #ManyToOne field as lazy:
#ManyToOne(fetch = FetchType.LAZY)
private Account account;
And change your query using a JOIN FETCH of the account field to generate only one query with all you need, like this:
String sql = "SELECT new com.test.Pojo(acc, SUM(t.value)) "
+ "FROM Transaction t JOIN FETCH t.account acc GROUP BY acc";
UPDATE:
Sorry, you're right, the fetch attribute of #ManyToOne is not required because in Hibernate that is the default value. The JOIN FETCH isn't working, it's causing a QueryException: "Query specified join fetching, but the owner of the fetched association was not present".
I have tried with some other approaches, the most simple one that avoids doing n + 1 queries is to remove the creation of the Pojo object from your query and process the result list, manually creating the objects:
String hql = "SELECT acc, SUM(t.value)"
+ " FROM " + Transaction.class.getName() + " t"
+ " JOIN t.account acc"
+ " GROUP BY acc";
Query query = getEntityManager().createQuery(hql);
List<Pojo> pojoList = new ArrayList<>();
List<Object[]> list = query.getResultList();
for (Object[] result : list)
pojoList.add(new Pojo((Account)result[0], (BigDecimal)result[1]));
Well PostgreSQL (And any other SQL database too) will block you from using mentioned query: you have to group by all columns of account table, not by id. That is why Hibernate generates the query, grouping by ID of the account - That is what is intended to be, and then fetching the other parts. Because it cannot predict in general way, what else will be needed to be joined and grouped(!!!), and in general this could produce situation, when multiple entities with the same ID are fetched (just create a proper query and take a look at execution plan, this will be especially significant when you have OneToMany fields in your Account entity, or any other ManyToOne part of the Account entity) that is why Hibernate behaves this way.
Also, having accounts with mentioned IDs in First level cache, will force Hibernate to pick them up from that. Or IF they are rarely modified entities, you can put them in Second level cache, and hibernate will not make query to database, but rather pick them from Second level cache.
If you need to get those from database in single hint, but not use all the goodness of Hibernate, just go to pure JPA Approach based on Native queries, like this:
#NamedNativeQuery(
name = "Pojo.groupedInfo",
query = "SELECT account.*, SUM(t.value) as sum FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id, account.etc ...",
resultClass = Pojo.class,
resultSetMapping = "Pojo.groupedInfo")
#SqlResultSetMapping(
name = "Pojo.groupedInfo",
classes = {
#ConstructorResult(
targetClass = Pojo.class,
columns = {
#ColumnResult(name = "sum", type = BigDecimal.class),
/*
* Mappings for Account part of entity.
*/
}
)
}
)
public class Pojo implements Serializable {
private BigDecimal sum;
/* .... */
public Pojo(BigDecimal sum, ...) {}
/* .... */
}
For sure this will work for you well, unless you will use the Account, fetched by this query in other entities. This will make Hibernate "mad" - the "entity", but not fetched by Hibernate...
Interesting, the described behaviour is as if t instances are returned from the actual query and t.account association in the first argument of Pojo constructor is actually navigated on t instances when marshalling results of the query (when creating Pojo instances from the result rows of the query). I am not sure if this is a bug or intended feature for constructor expressions.
But the following form of the query should work (no t.account navigation in the constructor expression, and no join fetch without the owner of the fetched association because it does not make sense to eagerly initialize something that is not actually returned from the query):
SELECT new com.test.Pojo(acc, SUM(t.value))
FROM Transaction t JOIN t.account acc
GROUP BY acc
EDIT
Very good observation by Ilya Dyoshin about the group by clause; I completely oversaw it here. To stay in the HQL world, you could simply preload all accounts with transactions before executing the query with grouping:
SELECT acc FROM Account acc
WHERE acc.id in (SELECT t.account.id FROM Transaction t)
I've ported some of my Entity from JPA to document and now porting some of my queries.
here is the JPA query:
em.createQuery("select distinct c from CustomerImpl c left join fetch c.addresses ca where (:name is null or c.firstName LIKE :name or c.lastName LIKE :name) and (:ref is null or c.externalReference LIKE :ref) and (:city is null or ca.city LIKE :city) order by c.firstName").setParameter("name", name).setParameter("ref", customerRef).setParameter("city", city).getResultList();
below is my attempt :
Criteria orNameCriteria = new Criteria().orOperator(Criteria.where("firstName").is(null), Criteria.where("firstName").is(name), Criteria.where("lastName").is(name));
Criteria orCustomerRefCriteria = new Criteria().orOperator(Criteria.where("externalReference").is(null), Criteria.where("externalReference").regex(customerRef,"i"));
Criteria orAddress = new Criteria().orOperator(Criteria.where("addresses.city").is(null), Criteria.where("addresses.city").regex(city, "i"));
Query nameq = new Query(new Criteria().andOperator(orNameCriteria,orCustomerRefCriteria,orAddress));
this query return zero size arraylist. I've then changed the orNameCriteria to use is clause and making sure the data contained in name variable has / as suffix and prefix. That didn't work as well.
but queries from mongoVue and RockMongo clients :
{ firstName: /SAM/}
returns data.
Question 1: How do you write LIKE CLAUSE with spring-data-mongo Criteria?
Question 2 : is that the right way to use or and and clause with criteria
Thanks for reading
Criteria.where("field").regex(pattern) should work
Since I don't have the ability add comments...
If you do a static import on Criteria, it will make your where clauses look a lot better.
Criteria orAddress = new Criteria().orOperator(where("addresses.city").is(null), where("addresses.city").regex(city, "i"));