updateQuery returns 1 but it's not getting reflected in db (spring data jpa) - spring-data-jpa

But updation works with below code snippet
Department department= departmentRepository.findOne(300L);
department.setName("rajiv");
departmentRepository.saveAndFlush(department);
but then this scenario being onetone mapping it could be a case where i would end up hitting 3 queries which include 2 select queries and one one update query .
To optimize the way i update it i'm trying to use this approach which is not getting updated thought executeUpdate() returns affected row as 1 .

There is a small mistake in your query.
You have used the = operator instead you should have used the like operator
You can also take help of #NamedNativeQueries
Example
DepartmentRepository.java
#Query(nativeQuery = true)
public List<Department> update(Long id);
Department.java
#SqlResultSetMapping(name="updateResult", columns = { #ColumnResult(name = "count")})
#NamedNativeQueries({
#NamedNativeQuery(
name = "Department.update",
query = "UPDATE departmemnt SET name like 'rajiv' WHERE id = ?",
resultSetMapping = "updateResult")
})
NativeQueries works more faster than normal Hibernate Queries

Yeah may this would help , in case of spring data i found a way to make it updatable and since they insist to use #transactional and extend the functionality of jparepository and now it hits only one line of statement
#Modifying(clearAutomatically = true)
#Transactional(readOnly=false)
#Query("update Department d set d.name =:name where d.id=:id")
int update(#Param("name")String name,#Param("id")Long id);
in case if i choose to for nativeQuery
#Modifying(clearAutomatically = true)
#Transactional(readOnly=false)
#Query(value="update Department d set d.name =:name where d.id=:id",nativeQuery=true)
int update(#Param("name")String name,#Param("id")Long id);

Related

Rewrite query in JPA

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

How to avoiding AND condition if parameter is null in Spring Data JPA query

I am trying to get the result of one query using Spring Data JPA. Here I am sending some parameter and receiving result according to that.
My repository query is,
#Query("select u.username,p.pname from Users u join u.priviJoin p where u.username = :uname AND p.pname = :pname")
List<Users> findByUsername(#Param("uname") String uname , #Param("pname") String pname );
And calling from controller like the following,
#RequestMapping(value = "/joinResult", method = RequestMethod.GET)
public List<Users> joinResultShow()
{
return (List<Users>) userRepo.findByUsername("test_user","testRole");
}
Here we can see that if I am passing some value then only checking according to that parameter. Here I need to modify my query like if parameter is null, then not need to use AND condition in query.
How can I modify this query for avoiding AND condition if parameter is null? I am new to Spring Data JPA world.
Here are some possible options for you
1. Create multiple methods in your repository like
#Query("select u.username,p.pname from Users u join u.priviJoin p where u.username = :uname AND p.pname = :pname")
List<Users> findByusernamewithRole(#Param("uname") String uname , #Param("pname") String pname );
#Query("select u.username,p.pname from Users u join u.priviJoin p where u.username = :uname")
List<Users> findByUsernameWithoutRole(#Param("uname") String uname);
Write a custom respository and use EntityManager. With this you can create a dynamic queries based on your input using CriteriaBuilder and use this criteria in querying.
Last and the most preferred option in case of dynamic inputs(like you have) is Querydsl.
Some articles about querydsl
http://www.baeldung.com/querydsl-with-jpa-tutorial
http://www.querydsl.com/static/querydsl/latest/reference/html/ch02.html

Reduce number of queries for JPQL POJO containing an entity

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)

QueryDsl - OR statement not working

I have the following QueryDSL query:
QCustomer customer = QCustomer.customer;
BooleanBuilder builder = new BooleanBuilder();
builder.or(customer.person.name.containsIgnoreCase(query));
builder.or(customer.company.name.containsIgnoreCase(query));
return builder;
And I expect to get results from Persons that contains the name = query and/or Companies that contains the query parameter. But I get nothing.
This is my Customer class mapping:
#OneToOne(orphanRemoval = false, optional = true, cascade = CascadeType.ALL)
private Company company;
#OneToOne(orphanRemoval = false, optional = true, cascade = CascadeType.ALL)
private Person person;
Did someone knows what I'm missing here?
I expect to get a query like this:
select o
from Customer
where o.person.name like '%:name%' or o.company.name like '%:name%'
This is the generated query:
select
count(customer0_.uid) as col_0_0_
from
Customer customer0_
cross join
Person person1_
cross join
Company company2_
where
customer0_.person_uid=person1_.uid
and customer0_.company_uid = company2_.uid
and (lower(person1_.name) like ? escape '!' or lower(company2_.name) like ? escape '!') limit ?
It uses a count because it's the first query that Spring Data use to paginate the result.
The query looks ok. Most probably you get wrong results because the implicit property based joins make the joins inner joins.
Using left joins you might get the results you need.
QPerson person = QPerson.person;
QCompany company = QCompany.company;
BooleanBuilder builder = new BooleanBuilder();
builder.or(person.name.containsIgnoreCase(str));
builder.or(company.name.containsIgnoreCase(str));
query.from(customer)
.leftJoin(customer.person, person)
.leftJoin(customer.company, company)
.where(builder);

Passing List<Integer> in spring data jpa native query

Using spring data JPA, I am trying to make this sort of query (it is more complex, this is a simple case)
#Query(nativeQuery = true,
value = "SELECT * FROM events WHERE typeId IN (?1)")
List<Event> findEventsByType(List<Integer> types);
When I launch the query, an exception raises:
org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of java.util.ArrayList. Use setObject() with an explicit Types value to specify the type to use.
I have tried List < Integer >, Integer[], Object[] and String but it is not working...
Can't I pass list of values?
Which is the best approach to make this sort of queries?
Thanks!
Try taking away the #Query and make the method name:
public List<Event> findByTypeIn(List<Integer> types);
See table 2.2 in the link: http://docs.spring.io/spring-data/jpa/docs/1.2.0.RELEASE/reference/html/
I tried like below and it works for me.
#Query(value = "select * from events where type_id in :types", nativeQuery = true)
List<Event> findEventsByType(#Param("types") List<Integer> types);
#Query(value = "SELECT c from Company c where " +
"c.companyName IN (:company_names)")
List<Company> findCompaniesByName(#Param("company_names") List<String> companyNames);
This is the solution to your problem.
Here I am passing List which contains company names and I am querying DB and storing result in List.
Hope this hepls!
Use JPQL. A native query is or should be passed to the database exactly as you have created the SQL string, and unless your driver can take a serialized collection and understand that the single parameter needs to be interpreted as many, it just won't work. The collection you pass in needs the SQL expanded from (?) to (?, ?,...) based on the number of elements in a collection, and JDBC drivers just are not able to do this, and JPA providers are required to execute the string as is.
A JPQL query allows the JPA provider to create the SQL it needs dynamically based on the list passed in, so it can expand the collection for you.
Try this. It will work for Native Query in SpringBoot JPA:
#Query(value = "SELECT * FROM table WHERE columnName IN (:inputList)" ,
nativeQuery = true)
List<Object> findByObjectList(#Param("inputList") List<Object> inputList);
And also in case of JPA, try the below :
List<Object> findByObjectList(List<Object> inputList)
I know this is a little bit out of context (we use Update and not Select), but this can be usefull for others :
/**
* Update the state of list of entities using their ids
* #param ids request ids
* #param state new state
* #return
*/
#Modifying
#Query(value = "UPDATE AbstractRequest SET state = :state WHERE id IN (:ids)")
int updateStates(#Param("ids") List<Long> ids, #Param("state") InternalRequestStep state);
pass array this way inside IN Clause
#Query(value = "select name from teams where name in :names", nativeQuery = true)
List<String> getNames(#Param("names") String[] names);
Call this way
String[] names = {"testing team","development team"};
List<String> teamtest = teamRepository.getNames(names);
Remove brackets around (?1) parameter. Your query value should look like that:
#Query(value = "SELECT * FROM events WHERE typeId IN ?1")
try querying like this:
#Query(nativeQuery = true,
value = "SELECT * FROM events WHERE typeId = ?1")
List<Event> findEventsByType(List<Integer> types);
did it work?