Distinct on specific column using JPA Specification - spring-boot

I'm trying to write the following query using JPA Specification.
select distinct name from hcp where area = 'Dhaka';
select distinct name from hcp where area = 'Dhaka';
The hcp entity looks as following
#Entity
public class HCP implements Serializable {
#Id
#Column
private String id;
#Column
private String name;
#Column
private String area;
}
The table would look like this
I tried to convert the above query using jpa-specification as following. It's selecting 'name' field and I've set distinct as true
List<HCP> result = hcpRepository.findAll(new Specification<HCP>() {
#Override
public Predicate toPredicate(Root<HCP> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
query.select(root.get("name")).distinct(true);
return criteriaBuilder.equal(root.get("area"), "Dhaka");
}
});
But it's not working. It's only applying district on the field that has #Id annotation. I want to apply distinct on area field.
Generated Hibernate Query looks like this:
select
distinct hcp0_.id as id1_0_,
hcp0_.area as area2_0_,
hcp0_.name as name3_0_
from
hcp hcp0_
where
hcp0_.area=?
How can I get generated query like the following?
select
distinct hcp0_.name as name3_0_
from
hcp hcp0_
where
hcp0_.area= 'Dhaka'
The desired result will list distinct names based on area.
How can I apply distinct on a specific field using JPA Specification?
Desired query and output:

You can't use Specifications because you want to return a List of Strings.
So you could use JPQL
#Query("select distinct h.name from Hcp h where area = 'Dhaka'")
List<String> findDistinctName();

Related

Fetch specific columns dynamically

I have the following User entity:
public class User extends PanacheEntityBase{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DataIdGenerator")
#Column(name = "id")
public Long id;
public String name;
public String location;
public int age;
}
I also have the following endpoint: '/user', with a 'select' query parameter where you provide the column names you want to receive. It should be possible to select any combination of columns like: /user?select=id,name, /user?select=id,age, /user?select=name,age, /user?select=age,name
Based on the 'select' query I want to use a projection to get the selected columns only. Currently I'm using the query to create the following query fe: /user?select=id,name to SELECT d.id, d.name FROM User d, however I need the DTO to be dynamic based on the columns provided too.
Currently I have the following projection where UserDTO is a class with id and name attributes. This works fine, but if I change any parameter I need a different DTO.
// This variable is dynamically created based on query parameters
String query = 'SELECT d.id, d.name FROM User d'
return User.find(query).project(UserDTO.class).list();
Is it possible to make this projection DTO class more dynamic, so it supports all combinations?
I suspect the Panache API is not flexible enough at the moment to do what you are asking.
But you could use the Hibernate Reactive API without Panache:
#Inject
Mutiny.SessionFactory sf;
public Uni<List<Tuple>> find(String query) {
return sf.withSession(session ->
session.createQuery(query, Tuple.class).getResultList()
);
}
Once you have the Tuple, you can convert it to the type you prefer.

Select latest record for each id spring jpa

I have an entity like this:
#Entity
class Point{
#EmbeddedId
private PointIdentity pointIdentity;
private float latitude;
private float longitude;
#Embeddable
public static class PointIdentity implements Serializable {
private Long id;
private ZonedDateTime timestamp;
}
}
There is EmbeddedId, so in "id" column can be multiple records with the same ids.
And I need to get latest record for each id, using CriteriaQuery and JPA specifications I think, but don't know how.
In SQL, this would be something like this:
SELECT id, MAX(timestamp)
FROM geodata
GROUP BY id
Is there any way to do it?
Any help, thanks.
You can easily write a JPQL query:
TypedQuery<Object[]> query = entityManager.createQuery(
"select p.pointIdentity.id, max(p.pointIdentity.timestamp) from Point p group by p.pointIdentity.id",
Object[].class);
List<Object[]> results = query.getResultList();
which translates to:
select
point0_.id as col_0_0_,
max(point0_.timestamp) as col_1_0_
from
point point0_
group by
point0_.id
Alternatively, you can use criteria query:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<Point> point = query.from(Point.class);
query.groupBy(point.get("pointIdentity").get("id"));
query.multiselect(
point.get("pointIdentity").get("id"),
criteriaBuilder.max(point.get("pointIdentity").get("timestamp"))
);
TypedQuery<Object[]> typedQuery = entityManager.createQuery(query);
List<Object[]> results = typedQuery.getResultList();
which produces identical SQL.

Spring Data JPA Projection with select distinct

I have a database table which holds Metadata for documents. My task now is to get a list with documenttypes. The documenttypes are not unique in the database table but of course I want them to be in my list. The sql is very simple:
SELECT DISTINCT groupname, group_displayorder
FROM t_doc_metadata
ORDER BY group_displayorder;
I have learned that I can use projections to get a subset of fields from my entity DocMetadata. I solved this as follows. My Entity:
#Entity
#Table(name="T_DOC_METADATA")
#Data
public class DocMetadata {
..............
#Column(nullable=false)
private String displayname;
#Column(nullable=false)
private Integer displayorder;
#Column(nullable=false)
private String groupname;
#Column(name="GROUP_DISPLAYORDER",
nullable=false)
private Integer groupDisplayorder;
#Column(name="METADATA_CHANGED_TS",
nullable=false,
columnDefinition="char")
private String metadataChangedTimestamp;
..........
}
My inteface for projection:
public interface GroupnameAndOrder {
String getGroupname();
Integer getGroupDisplayorder();
void setGroupname(String name);
void setGroupDisplayorder(int order);
}
Now I thought I'd be extraordinary clever by adding these lines to my repository:
#Query("select distinct d.groupname, d.groupDisplayorder from DocMetadata d order by d.groupDisplayorder")
public List<GroupnameAndOrder> findSortedGroupnames();
Sadly, when iterating over the result list and calling getGroupname() the result is null.
So I changed the lines in my repository according to the documentation:
public List<GroupnameAndOrder> findBy();
Now I get the groupnames but of course they are not unique now. So it doesn't solve my problem.
Is there any way to receive a ordered list with unique groupnames?
You are trying to be too clever. Instead just write the proper find method and return the GroupnameAndOrder. Spring Data JPA will then only retrieve what is needed for the projection.
Something like this should do the trick.
List<GroupnameAndOrder> findDistinctByOrderByGroupDisplayorder();

Select fews columns (DTO) with specification JPA

I am using spring-data-jpa version 1.5.1.RELEASE .
My domain is :
public class MyDomain{
....
....
private String prop1;
private String prop2;
......
......
}
My JPA Specification is:
public final class MyDomainSpecs {
public static Specification<MyDomain> search(final String prop1,final String prop2) {
return new Specification<MyDomain>() {
public Predicate toPredicate(Root<MyDomain> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// Some tests if prop1 exist .....
Predicate predicate1 = cb.equal(root.get("prop1"), prop1);
Predicate predicate2 = cb.equal(root.get("prop2"), prop2);
return cb.and(predicate1, predicate2);
}
};
}
}
My Repository :
public interface MyDomainRepository extends JpaRepository<MyDomain, Long>, JpaSpecificationExecutor<MyDomain> {
List<MyDomain> findAll(Specification<MyDomain> spec);
}
All is Working .
But my need (For performance DB tunning) is to not return and select all fields of MyDomain from DB .
I need to select only for example tree properties (prop1, prop2, prop3) , idealy in a DTO Object .
I don't want to convert My List<MyDomain> to List<MyDto> because i am tunning DB request .
So , I don't find any way to do that with spring-data-Jpa and Specification .
Any Idea ?
Thanks
This is not possible as for now. There is a ticket for this but no idea if it will be ever implmented: https://jira.spring.io/browse/DATAJPA-51
Create a special version of MyDomain (e.g. MyDomainSummary or LightMyDomain) that only includes the fields you want to map.
Basic example
Borrowed from the excellent JPA WikiBook.
Assume a JPA entity (i.e. domain class) like so:
#Entity
#Table(name="EMPLOYEE")
public class BasicEmployee {
#Column(name="ID")
private long id;
#Column(name="F_NAME")
private String firstName;
#Column(name="L_NAME")
private String lastName;
// Any un-mapped field will be automatically mapped as basic and column name defaulted.
private BigDecimal salary;
}
The SQL query generated will be similar to
SELECT ID, F_NAME, L_NAME, SALARY FROM EMPLOYEE
if no conditions (where clause) are defined. So, to generalize the basic case one can say that the number of queried columns is equal to the number of mapped fields in your entity. Therefore, the fewer fields your entity, the fewer columns included in the SQL query.
You can have an Employee entity with e.g. 20 fields and a BasicEmployee as above with only 4 fields. Then you create different repositories or different repository methods for both.
Performance considerations
However, I seriously doubt you'll see noticeable performance improvements unless the fields you want to omit represent relationships to other entities. Before you start tweaking here log the SQL that is currently issued against the data base, then remove the columns you want to omit from that SQL, run it again and analyze what you gained.

Is it possible to use an Array object as a parameter in Spring Repository #Query annotation?

Is it possible to use an Array object as a parameter in Spring Repository #Query annotation?
I'm trying to retrieve all rows in a table whose column node is present in an String array. Is it possible to do it at a time using the #Query annotation in Spring repository?
Here is my Location Entity:
#Entity
#Table(name = "LOCATIONS")
public class Location extends Measurement{
private String latitude;
private String nsIndicator;
private String longitude;
private String ewIndicator;
#ManyToOne
#JoinColumn(name="node")
private Node node;
}
Where node references the Node class, and it is mapped in the database as a BIGINT.
I have a repository like this:
public interface LocationRepository extends CrudRepository<Location, Long>{
#Query(value=
"SELECT l1.node, l1.id, l1.latitude, l1.longitude " +
"FROM LOCATIONS l1 WHERE l1.node IN (:ids)",
nativeQuery=true)
List<Location> findMeasureByIds(#Param("ids") String[] ids);
}
There you can see the query that I'm trying to execute, but it's not working. I don't know if it's possible to use an array there, or parameters must be just Strings and/or Integers, I couldn't find it anywhere.
I've tried several combinations like using a simple String with the right format or a long array.. but nothing has worked so far.
Thanks in advance.
SOLUTION:
#Query(value="SELECT * FROM LOCATIONS l1 " +
"INNER JOIN (SELECT node, MAX(id) AS id FROM LOCATIONS GROUP BY node) l2 " +
"ON l1.node = l2.node AND l1.id = l2.id " +
"WHERE l1.node IN :ids", nativeQuery=true)
List<Location> findLastLocationByIds(#Param("ids") Set<Long> ids);
I've added more functionality to the query because I needed to retrieve the last row inserted for each node identifier. So there's the MAX function and the INNER JOIN to do that work.
Use a collection instead of an array (Set<String>), and make sure it's not empty (otherwise the query will be invalid.
Also, there's no reason to use a native query for that, and you shouldn't have parentheses around the parameter:
#Query("SELECT l1 FROM Location l1 WHERE l1.node.id IN :ids")
List<Location> findLocationsByNodeIds(#Param("ids") Set<String> ids);

Resources