Spring data Jpa using new keyword inside select query - spring-data

I have seen JPQL query like this
SELECT new path.to.dto.MyDto(e.id, e.otherProperty) FROM Student e WHERE e.id = ?1
can i write something in this way??
SELECT new path.to.dto.MyDto(e.id, new path.to.dto.OtherDTO) FROM Student e WHERE e.id = ?1

Perhaps you should use a query like this:
SELECT new path.to.dto.MyDto(e.id, e.otherDtoParam1, e.otherDtoParam2,...) FROM Student e
And implement MyDto's constructor like this:
public MyDto(Integer id, Object otherDtoParam1, Object otherDtoParam2, ...) {
this.id = id;
this.otherDto = new OtherDto(otherDtoParam1, otherDtoParam2, ...)
...
}

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.

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

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