Using COUNT in JPQL Query - jpa

I have the following JPQL query:
List<DestinationInfo> destinations = em.createQuery("SELECT NEW com.realdolmen.patuva.dto.DestinationInfo(d.name, d.continent, MIN(t.departureDate), MIN(t.pricePerDay), COUNT(t.id))" +
" FROM Destination d, Trip t" +
" WHERE d.continent = :continent " +
" GROUP BY d.name, d.continent").setParameter("continent", searchedContinent).getResultList();
If I run this I get the error:
javax.ejb.EJBTransactionRolledbackException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [com.realdolmen.patuva.dto.DestinationsList]
If I leave out the COUNT(t.id) and remove that parameter from my DestinationInfo constructor it works fine. Why can't I map the COUNT(t.id) to my DestinationInfo DTO.
This is my DestinationInfo class:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private Integer totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, Integer totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}

Apparently COUNT(t.id) returns a number of type long. Changing the DestinationInfo class to the following makes it work:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private long totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, long totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}

Related

How to convert Integer param which can be 'null' to '0' when selecting data with Spring Data JPA

Assume we have entity Animal. There are animals in DB with 'amount' = null, it's a valid case to save animal without the 'amount'.
Is there a way to convert field 'amount' to 0 in case it's null in query?
The simplest workaround seems to convert amount null to '0' earlier
when saving, but it's not allowed.
As another workaround we can do this mapping to '0' after fetching
it from the repository. When sorting by amount in asc order, null values will be at the beginning, in desc order they will be at the end. And after
converting to '0' everything will be at the right place. But it seems that can cause problems with pagination in future
What is the proper way to do it in Query?
Spring Data Jpa 2.2.9.RELEASE, Postgresql 42.2.16.
#Repository
public interface AnimalRepository extends JpaRepository<AnimalEntity, Long> {
#Query(value = "SELECT animal FROM AnimalEntity animal" +
" WHERE animal.ownerId = :ownerId" +
" and function('replace', upper(animal.name), '.', ' ') like function('replace', upper(concat('%', :name,'%')), '.', ' ') "
)
Page<AnimalEntity> findAllLikeNameAndOwnerSorted(String ownerId, String name, Pageable pageable);
}
#Entity
#Table(name = "animal")
public class AnimalEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer amount;
private String name;
private String ownerId;
}
UPDATE
Also important to mention. The solution I suggested with replacing nulls with zero is incorrect, because of the different null ordering in Postgresql and HSQLDB.
But it will work in tests, if you're using HSQLDB.
Animal entities in DB test sample: [
Animal(name=Cat, amount=599999.99),
Animal(name=Dog, amount=null),
Animal(name=John, amount=5000)
]
Hsqldb amount desc query result:
[
Animal(name=Cat, amount=599999.99),
Animal(name=John, amount=5000),
Animal(name=Dog, amount=null)
]
Postgresql amount desc query result:
[
Animal(name=Dog, amount=null)
Animal(name=Cat, amount=599999.99),
Animal(name=John, amount=5000)
]
The JPA supports the COALESCE function. Thus you can set up the desired value via this function.
SELECT COALESCE(amount,0) AS desiredAmount FROM AnimalEntity animal
The code should look like this:
#Entity
#Table(name = "animal")
public class AnimalEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer amount;
public AnimalEntity() {
}
public AnimalEntity(Integer amount, String name) {
this.amount = amount;
this.name = name;
}
public Long getId() {
return id;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And the repository:
#Repository
public interface AnimalRepository extends JpaRepository<AnimalEntity, Long> {
#Query(
value = "SELECT animal.id AS id, COALESCE(animal.amount,0) AS amount, UPPER(animal.name) AS name FROM animal animal WHERE animal.name = :name",
nativeQuery = true)
Page<AnimalEntity> findAllLikeNameAndOwnerSorted(String name, Pageable pageable);
}
Also I have prepared the test:
#SpringBootTest
class AnimalRepositoryTest {
#Autowired
private AnimalRepository animalRepository;
#Test
void findAllLikeNameAndOwnerSorted() {
AnimalEntity animalEntity = new AnimalEntity(null, "dog");
animalRepository.save(animalEntity);
AnimalEntity animalEntity2 = new AnimalEntity(1, "CAT");
animalRepository.save(animalEntity2);
System.out.println(animalEntity2.getId());
Pageable sortedByName = PageRequest.of(0, 3, Sort.by("id"));
Page<AnimalEntity> animals = animalRepository.findAllLikeNameAndOwnerSorted("dog", sortedByName);
animals.forEach(System.out::println);
}
}
You can check the commit: https://gitlab.com/chlupnoha/meth/-/commit/76abbc67c33b2369231ee89e0946cffda0460ec9 - it is experiment project.

method in ArangoRepository extension class using COLLECT in query annotation to group by and count not working

I have a simple node like this below
#Document("users")
public class User {
#Id // db document field: _key
private String id;
#ArangoId // db document field: _id
private String arangoId;
private String firstName;
private String lastName;
private String country;
public User() {
super();
}
public User(String id) {
this.id = id;
}
public User(String id, String country) {
this.id = id;
this.country = country;
}
// getter & setter
#Override
public String toString() {
return "User [id=" + id + ", name=" + firstName + ", surname=" + lastName + "]";
}
public String getId() {
return id;
}
}
here is the repository class but the method getListOfCountryAndNumUsers returns null even though i have inserted users with different countries into the database.
public interface UserRepository extends ArangoRepository<User, String> {
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN
{\"country\" : country, \"count\" : length }")
Iterable<CountryAndNumUsers> getListOfCountryAndNumUsers();
}
I think the problem could be with the the syntax of my query in the query annotation. I didnt see any direct example of using collect operation in the spring data arango db part of arangodb documentation here but I saw the collect operation in the section "high level operations" of arangoDb documentation here
Please Help. Thanks. !
So I discovered my error. It was in a class I didn't add in the question. That is the class for the return object of the method getListOfCountryAndNumUsers()
i.e class CountryAndNumUsers.
public class CountryAndNumUsers {
private String country;
private Integer numberOfUsers;
public CountryAndNumUsers(String country, Integer numberOfUsers) {
this.country = country;
this.numberOfUsers = numberOfUsers;
}
public String getCountry() {
return country;
}
public Integer getNumberOfUsers() {
return numberOfUsers;
}
}
so there was a mapping mismatch since the query returns an object with different field names. I changed the query to this below so that it matches
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN {\"country\" : country, \"numberOfUsers\" : length }")

How to use a #ConstructorResult with a Set<SomeEnum> field

I'm trying to create a #NamedNativeQuery with a #ConstructorResult for a class that has a field with a Set of enum values.
VeterinarianJPA.java:
#Entity
#Table(name = "veterinarians")
#Setter
#Getter
#NoArgsConstructor
#NamedNativeQueries({
#NamedNativeQuery(
name = VeterinarianJPA.FIND_ALL_VETS,
query = "SELECT v.id, v.name, vs.specialisations " +
"FROM veterinarians v " +
"JOIN veterinarian_specialisations vs ON v.id = vs.vet_id",
resultSetMapping = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER
)})
#SqlResultSetMappings({
#SqlResultSetMapping(
name = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER,
classes = #ConstructorResult(
targetClass = Veterinarian.class,
columns = {
#ColumnResult(name = "id", type = Long.class),
#ColumnResult(name = "name"),
#ColumnResult(name = "specialisations", type = Set.class)
}
)
)})
class VeterinarianJPA {
static final String FIND_ALL_VETS = "net.kemitix.naolo.gateway.data.jpa.findAllVets";
static final String VETERINARIAN_RESULT_MAPPER = "net.kemitix.naolo.gateway.data.jpa.Veterinarian";
#Id
#GeneratedValue
private Long id;
private String name;
#ElementCollection
#Enumerated(EnumType.STRING)
#CollectionTable(
name = "veterinarian_specialisations",
joinColumns = #JoinColumn(name = "vet_id")
)
private final Set<VetSpecialisation> specialisations = new HashSet<>();
}
Veterinarian.java:
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final Set<VetSpecialisation> specialisations) {
this.id = id;
this.name = name;
this.specialisations = new HashSet<>(specialisations);
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public Set<VetSpecialisation> getSpecialisations() {
return new HashSet<>(specialisations);
}
}
VetSpecialisation.java:
public enum VetSpecialisation {
RADIOLOGY,
DENTISTRY,
SURGERY
}
When I attempt to execute the named query:
entityManager.createNamedQuery(VeterinarianJPA.FIND_ALL_VETS, Veterinarian.class)
.getResultStream()
I get the following exception:
java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : net.kemitix.naolo.entities.Veterinarian
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.resolveConstructor(ConstructorResultColumnProcessor.java:92)
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.performDiscovery(ConstructorResultColumnProcessor.java:45)
at org.hibernate.loader.custom.CustomLoader.autoDiscoverTypes(CustomLoader.java:494)
at org.hibernate.loader.Loader.processResultSet(Loader.java:2213)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2169)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1930)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1892)
at org.hibernate.loader.Loader.scroll(Loader.java:2765)
at org.hibernate.loader.custom.CustomLoader.scroll(CustomLoader.java:383)
at org.hibernate.internal.SessionImpl.scrollCustomQuery(SessionImpl.java:2198)
at org.hibernate.internal.AbstractSharedSessionContract.scroll(AbstractSharedSessionContract.java:1058)
at org.hibernate.query.internal.NativeQueryImpl.doScroll(NativeQueryImpl.java:217)
at org.hibernate.query.internal.AbstractProducedQuery.scroll(AbstractProducedQuery.java:1462)
at org.hibernate.query.internal.AbstractProducedQuery.stream(AbstractProducedQuery.java:1486)
at org.hibernate.query.Query.getResultStream(Query.java:1110)
I expect that the SQL is returning multiple rows for a multi-valued Set rather than a single value, which is causing the constructor not to match. How do I change the SQL to produce the correct input to the constructor, or is there another configuration change I need to make?
Well, I'm not sure if that's even possible in the way you want to to this. But you can use LISTAGG function on specialisations table to inline the specialisations with veterinarians by using some kind of separator.
So the query should look like this:
SELECT v.id, v.name
(SELECT LISTAGG(vs.type, ';')
WITHIN GROUP (ORDER BY vs.type)
FROM veterinarian_specialisations vs
WHERE vs.vet_id = v.id) specialisations
FROM veterinarians v;
The query will return veterinarian and his semicolon separated specialisations:
1 NAME DENTISTRY;RADIOLOGY
And then in your Veterinarian class constructor you must remap String result back to Set of VetSpecialisation. I used Java 8 stream api just for convenience.
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final String specialisations) {
this.id = id;
this.name = name;
this.specialisations = Arrays.asList(specialisations.split(";"))
.stream()
.map(VetSpecialisation::valueOf) //Map string to VetSpecialisation enum.
.collect(Collectors.toSet());
}

Find an entity which use a class Id

To find an object from entity with primary key we use em.find(Person.class, <Id>).
I'm using JPA EclipseLink and I have a Person entity which has a composite primary key(#classId),
the Person entity:
#Entity
#IdClass(PersonId.class)
public class Person {
#Id
private int id;
#Id
private String name;
public String getName() {
return name;
}
// getters & setters
}
and the PersonID:
public class PersonId implements Serializable {
private static final long idVersionUID = 343L;
private int id;
private String name;
// must have a default construcot
public PersonId() {
}
public PersonId(int id, String name) {
this.id = id;
this.name = name;
}
//getters & setters
//hachCode & equals
}
How to use em.find to get a Person object?
I found the solution :
PersonId personeId = new PersonId(33, "Jhon");
Person persistedPerson = em.find(Person.class, personeId);
System.out.println(persistedPerson.getID() + " - " + persistedPerson.getName());

JPA with hibernate implementation is generating wrong named query

I configured JPA with spring. I am using spring 4.
I have an entity
#Entity
#NamedQueries({
#NamedQuery(name="PartnerCourseMapping.findByPartnerCourseIdAndHandlerName", query="select pm from PartnerCourseMapping pm where pm.partnerCourseId=:partnerCourseId and pm.handlerName=:handlerName")
})
#Table(name="PARTNER_COURSE_MAPPING")
public class PartnerCourseMapping implements Serializable {
private static final long serialVersionUID = 1L;
#Id
protected Long id;
#Column(name="COURSE_ID")
protected Long courseId;
#Column(name="PARTNER_COURSE_ID")
protected String partnerCourseId;
#Column(name="PARTNER_ID")
protected Integer partnerId;
#Column(name="PRODUCT_TYPE")
protected String productType;
#Column(name="HANDLER_NAME")
protected String handlerName;
//getters and setters
}
I have another entity which i defined like below
#Entity
#NamedNativeQueries({
#NamedNativeQuery(
name="ExternalCourse.findExternalCourseMappingByLearningSessionGuid",
query="SELECT PCM.*, LE.id AS LearnerEnrollmentId, LE.LEARNER_ID AS LearnerId "
+ "FROM LEARNINGSESSION LS "
+ "INNER JOIN LEARNERENROLLMENT LE ON LE.ID = LS.ENROLLMENT_ID "
+ "INNER JOIN PARTNER_COURSE_MAPPING PCM ON PCM.COURSE_ID = LE.COURSE_ID "
+ "WHERE LS.LEARNINGSESSIONGUID = :learningSessionGuid",
resultSetMapping="externalCourseMapping"
)
})
#SqlResultSetMappings({
#SqlResultSetMapping(
name="externalCourseMapping",
classes = {
#ConstructorResult(targetClass = ExternalCourse.class,
columns={
#ColumnResult(name = "ID", type=Long.class ),
// remaing ColumnResult
}
)
}
)
})
public class ExternalCourse extends PartnerCourseMapping /*implements Serializable*/ {
private Long learnerEnrollmentId;
private Long learnerId;
//default constructor
public ExternalCourse(Long id, Long courseId, String partnerCourseId, Integer partnerId, String productType,
String handlerName, Long learnerEnrollmentId, Long learnerId) {
this.id = id;
// remaing values
}
//getters and setters for learnerEnrollmentId and learnerId
}
Now I query PartnerCourseMapping.findByPartnerCourseIdAndHandlerName
TypedQuery<PartnerCourseMapping> query = entityManager.createNamedQuery("PartnerCourseMapping.findByPartnerCourseIdAndHandlerName", PartnerCourseMapping.class);
query.setParameter("partnerCourseId", paernerCourseId);
query.setParameter("handlerName", handlerName);
return getResult(query);
protected T getResult(TypedQuery<T> query) {
List<T> list = query.getResultList();
return CollectionUtils.isEmpty(list) ? null : list.get(0);
}
Hibernate is generating exception that
javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
...
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Invalid column name 'learnerEnrollmentId'.
When I debug then I saw hibernate is generating query like below
select partnercou0_.id as id2_13_,
partnercou0_.COURSE_ID as COURSE_I3_13_,
partnercou0_.HANDLER_NAME as HANDLER_4_13_,
partnercou0_.PARTNER_COURSE_ID as PARTNER_5_13_,
partnercou0_.PARTNER_ID as PARTNER_6_13_,
partnercou0_.PRODUCT_TYPE as PRODUCT_7_13_,
partnercou0_.learnerEnrollmentId as learnerE8_13_,
partnercou0_.learnerId as learnerI9_13_,
partnercou0_.DTYPE as DTYPE1_13_
from PARTNER_COURSE_MAPPING partnercou0_ where partnercou0_.PARTNER_COURSE_ID=? and partnercou0_.HANDLER_NAME=?
I want to ask that why hibernate is including learnerEnrollmentId and learnerId column? I am passing the query name and query. If I refactor my code like below then I get the correct result
#Entity
#NamedNativeQueries({
#NamedNativeQuery(
name="ExternalCourse.findExternalCourseMappingByLearningSessionGuid",
...
resultSetMapping="externalCourseMapping"
)
})
#SqlResultSetMappings({
#SqlResultSetMapping(
name="externalCourseMapping",
classes = {
..
}
)
})
public class ExternalCourse implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private Long courseId;
private String partnerCourseId;
private Integer partnerId;
private String productType;
private String handlerName;
private Long learnerEnrollmentId;
private Long learnerId;
//default constructor
//constructor with all parameters
//getters and setters
}
Why I am getting exception when I am extending class? I am passing the query name. Why ?
Thanks