jpa and native queries - jpa

I faced with a strange thing with native queries:
If I'm trying to use named native query like the following
#NamedNativeQuery(name = "native_count_avg_guest_quizzes", resultClass = java.math.BigDecimal.class, query = "select avg(c) as r from ((select count(*) as c from user_quiz uq join user u on (uq.user_id = u.id) where u.status = 'temporary' group by u.id) as res)")
The application cannot run and I have
org.hibernate.HibernateException: Errors in named queries: native_count_avg_guest_quizzes_
But the same query works fine if I do not use NamedNativeQuery and merely create a dynamic native query like the following:
entityManager.createNativeQuery(
"select avg(c) as r from ((select count(*) as c from user_quiz uq join user u on (uq.user_id = u.id) where u.status = 'temporary' group by u.id) as res)")
.getSingleResult()
Why? What I'm doing wrong with NamedNativeQuery? Thanks
Update:
Entity class is as following
#Entity
#Table(name = "user_quiz")
#NamedNativeQueries({
#NamedNativeQuery(name = "native_count_avg_guest_quizzes", resultClass = java.math.BigDecimal.class, query = "select avg(c) as r from ((select count(*) as c from user_quiz uq join user u on (uq.user_id = u.id) where u.status = 'temporary' group by u.id) as res)")
})
#NamedQueries({
#NamedQuery(name = "list_clients_quizzes", query = "select uq from UserQuiz uq where uq.quiz.client.id = :clientId"),
.......
})
public class UserQuiz extends Measurable {
.......
}

createNativeQuery is used for native SQL query language that mean DB can understand and execute that query (Eg, select * from some_table where id = '0001');
It may cause DB dependency. Now you are using JPQL language, that's why, use createQuery() or createNameQuery() with #NameQuery annotation sing.
Example :
#NameQuery
#NamedQuery(name = "findAllEmployee", query = "select e from Employee e")
#Entity
public class Employee {
#Id int id;
String name;
// ...
}
Query query = em.createNamedQuery("findAllEmployee");
query.getResultList()
Dynamic
Query query = em.createQuery("select e from Employee e");
query.getResultList()?
*Native
Query query = em.createNativeQuery("SELECT * FROM EMPLOYEE_TBL");
query.getResultList()?

Delete resultClass from #NamedNativeQuery.
See https://stackoverflow.com/a/9763489
For NamedNativeQueries you can only use resultClass when the result actually maps to an Entity.

Related

Bulk update over spring data jpa with join

I'm having troubles doing a bulk update (JPA 2.1) with spring-data-jpa.
According to docs:
5.3.8. Modifying queries
All the sections above describe how to declare queries to access a given entity or collection of entities. Of course you can add custom modifying behaviour by using facilities described in Custom implementations for Spring Data repositories. As this approach is feasible for comprehensive custom functionality, you can achieve the execution of modifying queries that actually only need parameter binding by annotating the query method with #Modifying:
#Modifying
#Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
But the entity I need update has a #ManyToOne relationship with another entity.
#Entity
#Table(name = "usuarios", indexes = {
#Index(name = "idx_usuarios_rut", columnList = "rut")
,#Index(name = "idx_usuarios_email", columnList = "email")})
public class User extends BaseEntity implements UserDetails {
...
#NotNull(message = "Debe seleccionar un nivel")
#ManyToOne(fetch = FetchType.LAZY)
public Role getRole() {
return role;
}
}
So my UPDATE is:
#Modifying
#Transactional
#Query("update User u set u.password = ?2 where u.company = ?1 and u.role.name not in ('ROLE_ADMIN', 'ROLE_ROOT')")
int updatePasswordForAll(Company company, String password);
The resulting native query is update usuarios cross join set password=? where company_id=? and (nombre not in ('ROLE_ADMIN' , 'ROLE_ROOT')) so I get com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual ...
What am I doing wrong ?
I tried with
#Query("update User u set u.password = ?2 join u.role r where u.company = ?1 and r.name not in ('ROLE_ADMIN', 'ROLE_ROOT')")
But this one is a bad formed update sentence org.hibernate.QueryException: node to traverse cannot be null! [update cl.arvisoft.mantenimiento.jpa.User u set u.password = ?2 join u.role r where u.company = ?1 and r.name not in ('ROLE_ADMIN', 'ROLE_ROOT')].
In JPQL you cannot join entities when doing update operation (see reference).
To workaround this situation try to use a sub-select, something like this (not tested):
update
User u
set
u.password = ?2
where
u.company = ?1 and
u.role in (select r from Role r where r.name not in ('ROLE_ADMIN', 'ROLE_ROOT'))

How can I write an EXISTS predicate on a collection attribute in Criteria API?

I have these classes:
#Entity
public class Customer {
#Id long id;
String name;
#OneToMany List<Customer> related;
}
and I'm using this JPQL query:
select c from Customer c where c.name = 'ACME'
or exists( select 1 from c.related r where r.name = 'ACME' )
How can I write the same query with the Criteria API? I need to use exists with a subquery, like the JPQL, but I don't know how to create a subquery from a collection attribute in the Criteria API.
Something like this would give EXISTS (subquery)
Subquery<Long> sq = cq.subquery(Long.class);
Root<Customer> customerSub = sq.correlate(customer);
Join<Customer,Customer> related = customerSub.join(Customer_.related);
... extra config of subquery
Predicate existsCustomer = cb.exists(sq);
where cq is the CriteriaQuery, and cb is CriteriaBuilder. This comes from an example in the JPA 2.1 spec p323 Example 4

JPQL SELECT ElementCollection

I have an entity "Post" with this property:
#ElementCollection
#CollectionTable(name ="tags")
private List<String> tags = new ArrayList<>();
Then i have a native select query with a group by. The problem is now how can i select the property tags?
My select query:
Query query = em.createQuery("SELECT p.id,MAX(p.createdAt),MAX(p.value) FROM Post p JOIN p.tags t WHERE t IN (?1,?2,?3) GROUP BY p.id ORDER BY COUNT(p) DESC");
/* ...
query.setFirstResult(startIndex);
query.setMaxResults(maxResults);
List<Object[]> results = query.getResultList();
List<Post> posts = new ArrayList<>();
for (Object[] result : results) {
Post newPost = new Post();
newPost.setId(((Number) result[0]).longValue());
newPost.setCreatedAt((Date) result[1]);
newPost.setValue((String) result[2]);
posts.add(newPost);
}
return posts;
how to select the property tags?
Don't know if it will help, but in JPA2.1 Spec, part 4.4.6 Collection Member Declarations, you can do that:
SELECT DISTINCT o
FROM Order o, IN(o.lineItems) l
WHERE l.product.productType = ‘office_supplies’
So I guess with your case, you could try:
SELECT p.id,MAX(p.createdAt),MAX(p.value), t
FROM Post p, IN(p.tags) t
WHERE t IN (?1,?2,?3)
GROUP BY p.id, t
ORDER BY COUNT(p) DESC
Note: I added t to the GROUP BY, since it would not work with the query without using an aggregate function.

eclipselink JPQL Cascade query probleam

I have a problem in JPQL cascade query with eclipselink2.5
please see code
Entity code
public class Category{
...
#JoinColumn(name = "parent_category", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER,optional = true)
private Category parentCategory;
...
}
JPQL code
String jpql = "select o from Category o order by o.parentCategory.sort ASC";
problem
the problem is this JPQL return list does not include 'o' if 'o.parentCategory' is null.
please see this table http://i.stack.imgur.com/xsXvk.jpg
the return list only rows id is 2,3,4 .
because the column parent_category is null, I lost rows 1,5,6
the correct result should be return all rows
Looking forward to your help!
Using o.parentCategory.sort in the order by clause forces an inner join which filters nulls. If you want nulls included, you will need to use an explicit outer join in the query:
"select o from Category o outer join o.parentCategory parentCategory order by parentCategory.sort ASC"

How do I count the number of rows returned by subquery?

I want to do something like this:
select count(*) from (select ...)
(As it would be in SQL), but in JPA.
Any ideas on how I would do it?
I stumbled upon this issue as well. I would ultimately like to execute the following JPQL:
SELECT COUNT(u)
FROM (
SELECT DISTINCT u
FROM User u
JOIN u.roles r
WHERE r.id IN (1)
)
But this wasn't possible, also not with criteria API. Research taught that this was just a design limitation in JPA. The JPA spec states that subqueries are only supported in WHERE and HAVING clauses (and thus not in the FROM).
Rewriting the query in the following JPQL form:
SELECT COUNT(u)
FROM User u
WHERE u IN (
SELECT DISTINCT u
FROM User u
JOIN u.roles r
WHERE r.id IN (1)
)
using the JPA Criteria API like as follows:
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<User> u = query.from(User.class);
Subquery<User> subquery = query.subquery(User.class);
Root<User> u_ = subquery.from(User.class);
subquery.select(u_).distinct(true).where(u_.join("roles").get("id").in(Arrays.asList(1L)));
query.select(cb.count(u)).where(cb.in(u).value(subquery));
Long count = entityManager.createQuery(query).getSingleResult();
// ...
has solved the functional requirement for me. This should also give you sufficient insight into solving your particular functional requirement.
This should do the trick (If you want to use JPA criteria API):
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<Entity> root = query.from(Entity.class);
//Selecting the count
query.select(cb.count(root));
//Create your search criteria
Criteria criteria = ...
//Adding search criteria
query.where(criteria);
Long count = getEntityManager().createQuery(query).getSingleResult();
On the other hand, if you want to use JP-QL, the following code should do the trick:
//Add the where condition to the end of the query
Query query = getEntityManager().createQuery("select count(*) from Entity entity where...")
Long count = query.getSingleResult();
Use the following snippet to count rows for a given Criteria Query:
public static Query createNativeCountQuery(EntityManager em, CriteriaQuery<?> criteriaQuery) {
org.hibernate.query.Query<?> hibernateQuery = em.createQuery(criteriaQuery).unwrap(org.hibernate.query.Query.class);
String hqlQuery = hibernateQuery.getQueryString();
QueryTranslatorFactory queryTranslatorFactory = new ASTQueryTranslatorFactory();
QueryTranslator queryTranslator = queryTranslatorFactory.createQueryTranslator(
hqlQuery,
hqlQuery,
Collections.emptyMap(),
em.getEntityManagerFactory().unwrap(SessionFactoryImplementor.class),
null
);
queryTranslator.compile(Collections.emptyMap(), false);
String sqlCountQueryTemplate = "select count(*) from (%s)";
String sqlCountQuery = String.format(sqlCountQueryTemplate, queryTranslator.getSQLString());
Query nativeCountQuery = em.createNativeQuery(sqlCountQuery);
Map<Integer, Object> positionalParamBindings = getPositionalParamBindingsFromNamedParams(hibernateQuery);
positionalParamBindings.forEach(nativeCountQuery::setParameter);
return nativeCountQuery;
}
private static Map<Integer, Object> getPositionalParamBindingsFromNamedParams(org.hibernate.query.Query<?> hibernateQuery) {
Map<Integer, Object> bindings = new HashMap<>();
for (var namedParam : hibernateQuery.getParameterMetadata().getNamedParameters()) {
for (int location : namedParam.getSourceLocations()) {
bindings.put(location + 1, hibernateQuery.getParameterValue(namedParam.getName()));
}
}
return bindings;
}