SqlResultSetMapping to POJO class from a NamedNativeQuery throwing 'could not locate appropriate constructor' - spring

I made a #NamedNativeQuery and attached it to an entity 'Doctor', on the same entity I attached a #SqlResultSetMapping which takes the columns of the query's result and maps them to a constructor of a specifically made POJO class. This query is also connected to a JPA method, which resides in the repository of the same entity.
However I keep getting an error that the appropriate constructor could not be located, as if the #SqlResultSetMapping or the POJO constructors are not in sync. (stack trace is at the bottom)
My entity, #NamedNativeQuery and #SqlResultSetMapping:
I tried the query directly on the DB and it gave the expected result, so I am just writing the select clause
#Entity
#NamedNativeQuery(
name =
"Doctor.findFreeExaminationTimes", // name of the JPA method in entity's repository (definition below)
query =
"SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " +
"...",
resultSetMapping = "freeTimesByDoctorId" // name of the mapping below
)
#SqlResultSetMapping(
name = "freeTimesByDoctorId", // result set mapping name
classes = #ConstructorResult(
targetClass = DoctorAvailabilityResponse.class, // my POJO class (definition below)
columns = { // order and types are the same as in the select clause above and the POJO constructor below
#ColumnResult(name = "onDate", type = java.sql.Date.class),
#ColumnResult(name = "fromTime", type = java.sql.Time.class),
#ColumnResult(name = "toTime",type = java.sql.Time.class)
}
)
)
public class Doctor extends User {...}
The POJO class which I mention in the #ConstructorResult under 'targetClass' has a constructor with the exact order, number and type, of arguments, specified under 'columns'
My POJO class which should be mapped to the query's result:
public class DoctorAvailabilityResponse {
final private java.sql.Date onDate;
final private java.sql.Time fromTime;
final private java.sql.Time toTime;
public DoctorAvailabilityResponse(java.sql.Date onDate, java.sql.Time fromTime, java.sql.Time toTime) {
this.onDate = onDate;
this.fromTime = fromTime;
this.toTime = toTime;
}
// getters
}
My repository:
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
// JPA method mapped to the named native query above
List<DoctorAvailabilityResponse> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
However when testing this JPA method I get an exception with the message 'could not locate appropriate constructor'.
My test:
#SpringBootTest
public class DoctorTests {
#Autowired
private DoctorRepository doctorRepository;
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Test
public void shouldReturnDoctorAvailability() {
// Exception thrown here
List<DoctorAvailabilityResponse> freeTimes = doctorRepository.findFreeExaminationTimes(4L);
LOGGER.info(freeTimes.toString());
}
}
I cannot understand why is this happening. Is there a way to manually map this result set to the POJO while maintaining the JPA repository method?
Stack trace:
org.springframework.dao.InvalidDataAccessApiUsageException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse; nested exception is java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:256)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
...
...
Caused by: java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse
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:2333)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2289)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2045)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2007)
at org.hibernate.loader.Loader.doQuery(Loader.java:953)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354)
at org.hibernate.loader.Loader.doList(Loader.java:2810)
at org.hibernate.loader.Loader.doList(Loader.java:2792)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2624)
at org.hibernate.loader.Loader.list(Loader.java:2619)
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338)
at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2137)
at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1134)
at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:173)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1526)
at org.hibernate.query.Query.getResultList(Query.java:165)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:409)
at com.sun.proxy.$Proxy212.getResultList(Unknown Source)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:126)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:353)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 73 more
Sanity check and alternative approach
To make a sanity check I deleted the #SqlResultSetMapping, in which case the query is supposed to return a list of 'Object[]' values, and then I tested each individual value in that array to check its type, it showed me that the types are what I assumed them to be 'java.sql.Date' and 'java.sql.Time' twice, and all three of them were in the expected order, (Date, Time, Time), which matches the order of the constructor parameters of my POJO class.
My entity and namedNativeQuery:
#Entity
#NamedNativeQuery(
name =
"Doctor.findFreeExaminationTimes",
query =
"SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " +
"..."
)
public class Doctor extends User {...}
My repository with a new return type:
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
List<Object[]> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
My test:
#SpringBootTest
public class DoctorTests {
#Autowired
private DoctorRepository doctorRepository;
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Test
public void shouldReturnDoctorAvailability() {
// Exception thrown here
List<DoctorAvailabilityResponse> freeTimes = doctorRepository.findFreeExaminationTimes(4L);
freeTimes.stream().forEach((ft) -> {
// Values are in expected order and of expected types
String classNameOnDate = ft[0].getClass().toString(); // java.sql.Date
String classNameFromTime = ft[1].getClass().toString(); // java.sql.Time
String classNameToTime = ft[1].getClass().toString(); // java.sql.Time
// I suppose the mapping mechanism is supposed to do something like this, but fails for some reason
DoctorAvailabilityResponse dar = new DoctorAvailabilityResponse((Date)ft[0], (Time)ft[1], (Time)ft[2]);
});
LOGGER.info(freeTimes.toString());
}
}
Running this test works perfectly which supposedly shows that the problem is in the #SqlResultSetMapping or in POJO class.
I would appreciate any feedback. Thanks!
SOLUTION
I had to change the types in the #SqlResultSetMapping and in the constructor of my POJO class.
Changed #SqlResultSetMapping
#SqlResultSetMapping(
name = "freeTimesByDoctorId",
classes = #ConstructorResult(
targetClass = DoctorAvailabilityResponse.class,
columns = {
#ColumnResult(name = "onDate", type = String.class),
#ColumnResult(name = "fromTime", type = String.class),
#ColumnResult(name = "toTime",type = String.class)
}
)
)
Changed POJO class constructor
public DoctorAvailabilityResponse(String onDate, String fromTime, String toTime) {
this.onDate = Date.valueOf(onDate);
this.fromTime = Time.valueOf(fromTime);
this.toTime = Time.valueOf(toTime);
}
This alone did not solve my problem however as I got a hibernate exception as mentioned and solved in this SO question. According to this answer I also changed my repository and added an additional annotation.
Changed repository
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
#Query(nativeQuery = true) // This is added
List<DoctorAvailabilityResponse> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
Now everything works, though the question remains why didn't #SqlResultSetMapping map java.sql.* types to the constructor in the first place.

#ConstructorResult does not work very well with java.sql.Date.class or java.sql.Time.class type. A way to fix your issue is to use String.class instead and then convert the String values to Date/Time in DoctorAvailabilityResponse's constructor

Related

How to bind #PathVariable value to #Where clause in Spring boot rest API

I am reading #PathVaraible in Controller and using #Where annotation in Entity. If I pass hardcoded value to #Where clause in Entity it is working fine. But I want to pass run time value which is passed in #PathVariable to #Where in Entity.
#GetMapping("/api/v1/course/details/{courseId}")
public List<CourseDto> getcourseDetails(#Valid #PathVariable final String courseId) {
// lines of code
}
#Entity
#Where(clause="course_id=:courseId")
public class CourseEntity{
#NotBlank(message = "Please provide courseId)
#Getter
#Setter
#Column(columnDefinition = "nvarchar(50)",length = 50)
private String courseId;
}
#Repository
public interface MyRepo extends JpaRepository<Course, Integer> {
}
With this code I am getting error as
Incorrect syntax near ':'.
How to fix this error. Is there any other solution ?
You cannot change a #Where clause at runtime. If you want to set a parameter you can use #Filter:
#Entity
#FilterDef(
name="courseFilter",
parameters=#ParamDef(
name="courseId",
type="int"
)
)
#Filter(
name="firstAccounts",
condition="course_id=:courseId"
)
public class CourseEntity{
...
}
Now with the Hibernate ORM session or entity manager you can set a parameter the following way:
entityManager
.unwrap( Session.class )
.enableFilter( "courseFilter" )
.setParameter( "courseId", ...);
List<CourseEntity> courses = entityManager
.createQuery("from CourseEntity c", CourseEntity.class)
.getResultList();
The Hibernate ORM documentation has more details about filtering entities using #Filter or #Where.

Facing issue while deserialization of JSON to object in spring boot

I am facing issue while deserialization the JSON to object in my springboot application.
I am having code as below :-
public interface IStatus{
}
#JsonTypeName("internal")
public enum InternalStatus implements IStatus{
INTERNAL_1,
INTERNAL_2
}
#JsonTypeName("external")
public enum ExternalStatus implements IStatus{
EXTERNAL_1,
EXTERNAL_2
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = As.PROPERTY,
property = "status")
#JsonSubTypes({
#JsonSubTypes.Type(value = InternalStatus.class, name = "internal"),
#JsonSubTypes.Type(value = ExternalStatus.class, name = "external")
})
#Setter
#Getter
public class Response{
IStatus status;
}
and my json corresponding to Response class is {status:"EXTERNAL_1"}
error details as below:-
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class IStatus]: missing type id property 'status'

Impossible to return a list / collection of projection

I made a spring-boot project to access to my datas.
My main class is Patient.java:
#Entity
public class Patient {
private Long id;
private String numeroSs;
private String profession;
// lot of stuff...., getters, setters, ...
}
In order to improve my requests, I've made a very simple projection of my class Patient. I've called this projection PatientCustom :
public interface PatientCustom {
String getNumeroSs();
Timestamp getDateProchainRdv();
}
And in my repository "PatientRepo.java", I created two custom methods :
#Transactional
#RepositoryRestResource(collectionResourceRel = "patient", path = "patient")
public interface PatientRepo extends JpaRepository<Patient, Long> {
PatientCustom findOneByNumeroSs(String numeroSs);
Collection<PatientCustom> findByNumeroSs(String numeroSs);
}
When I use the first method, no problem : it returns a PatientCustom :
{
numeroSs: "150505617017002",
dateProchainRdv: null
}
But When I want a list or a collection, it sends me an error :
{
cause: null,
message: "Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy165!"
}
And the traces in my terminal :
2019-03-12 10:32:34.671 ERROR 2072 --- [nio-8090-exec-2] o.s.d.r.w.RepositoryRestExceptionHandler : Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy165!
java.lang.IllegalArgumentException: Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy165!
at org.springframework.data.mapping.context.PersistentEntities.lambda$getRequiredPersistentEntity$2(PersistentEntities.java:96) ~[spring-data-commons-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at java.util.Optional.orElseThrow(Optional.java:290) ~[na:1.8.0_181]
at org.springframework.data.mapping.context.PersistentEntities.getRequiredPersistentEntity(PersistentEntities.java:95) ~[spring-data-commons-2.1.5.RELEASE.jar:2.1.5.RELEASE]
I understand very well that spring wants to add a proxy class to my interface, but when it want to add this proxy to a list or a collection, it's unhappy because list and collection are not interfaces but classses.
What can I do to solve this ?

Jackson java.util.Optional serialization does not include type ID

I got the following classes:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
...
}
public class MySubEntity extends Entity {
...
}
Now when I serialize that MySubEntity wrapped in an Optional then JSON does not contain the clazz attribute containing the type ID. Bug? When I serialize to List<MySubEntity> or just to MySubEntity it works fine.
Setup: jackson-databind 2.9.4, jackson-datatype-jdk8 2.9.4, serialization is done in Spring Boot application providing a RESTful web service.
EDIT: Here is the Spring REST method that returns the Optional:
#RequestMapping(method = RequestMethod.GET, value = "/{uuid}", produces = "application/json")
public Optional<MySubEntity> findByUuid(#PathVariable("uuid") String uuid) {
...
}
EDIT:
I made a SSCCE with a simple Spring REST controller and two tests. The first test is using ObjectMapper directly which is successful in deserialization although the clazz is missing. The second test calls the REST controller and fails with an error because clazz is missing:
Error while extracting response for type [class com.example.demo.MySubEntity] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'
This, indeed, looks like a bug. There is one workaround that I can suggest for this case, is to use JsonTypeInfo.As.EXISTING_PROPERTY and add field clazz to your Entity. There only one case with this approach is that the clazz must be set in java code manually. However this is easy to overcome.
Here is the full code for suggested workaround:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY, //field must be present in the POJO
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
#JsonProperty
private String uuid;
//Here we have to initialize this field manually.
//Here is the simple workaround to initialize in automatically
#JsonProperty
private String clazz = this.getClass().getSimpleName();
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
}

Spring Cassandra Custom Repository for TTL Save

I'm trying use a custom repository to save an entity with a TTL (Time to Live) value. I've have done a lot of searching and read the docs online, but I'm still getting an exception.
Any help gratefully appreciated.
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property saveWithTTL found for type Task!
The snippets are as follows:
Task (the entity):
#Table
public class Task {
#PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String uuid;
private Type type;
private Status status;
private String parentId;
private String body;
}
CassandraDbConfig:
#Configuration
#PropertySource(value = "classpath:cassandra.properties")
#EnableCassandraRepositories(repositoryBaseClass = TTLRepositoryCustomImpl.class)
public class CassandraDbConfig extends DefaultCassandraConfig {
}
TTLRepositoryCustom:
#NoRepositoryBean
public interface TTLRepositoryCustom<T> extends CassandraRepository<T> {
T saveWithTTL(T entity, Integer ttl);
}
TTLRepositoryCustomImpl:
public class TTLRepositoryCustomImpl<T> extends SimpleCassandraRepository<T, MapId>implements TTLRepositoryCustom<T> {
public TTLRepositoryCustomImpl(final CassandraEntityInformation<T, MapId> metadata,
final CassandraOperations operations) {
super(metadata, operations);
}
#Override
public T saveWithTTL(T entity, Integer ttl) {
WriteOptions options = new WriteOptions();
options.setTtl(ttl);
return operations.insert(entity, options);
}
}
TaskDbRepository:
#Repository
public interface TaskDbRepository extends TTLRepositoryCustom<Task> {
}
Full stack trace:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property saveWithTTL found for type Task!
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:77)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:329)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:309)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:272)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:243)
at org.springframework.data.repository.query.parser.Part.<init>(Part.java:76)
at org.springframework.data.repository.query.parser.PartTree$OrPart.<init>(PartTree.java:247)
at org.springframework.data.repository.query.parser.PartTree$Predicate.buildTree(PartTree.java:398)
at org.springframework.data.repository.query.parser.PartTree$Predicate.<init>(PartTree.java:378)
at org.springframework.data.repository.query.parser.PartTree.<init>(PartTree.java:86)
at org.springframework.data.cassandra.repository.query.PartTreeCassandraQuery.<init>(PartTreeCassandraQuery.java:47)
at org.springframework.data.cassandra.repository.support.CassandraRepositoryFactory$CassandraQueryLookupStrategy.resolveQuery(CassandraRepositoryFactory.java:163)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:436)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:221)
You can use something like this in a spring boot application inside a service class (with other 'normal' cassandra repo in place in the application), TTL will be in seconds, I know you are asking for a complete TTL-repo implementation, but this can be handy if you want just to save that with TTL.
#Autowired
private CassandraOperations cassandraOperations;
private void saveWithTTL(Task task)
{
String cql = "insert into task (uuid, body) values ('"+task.getUuid()+"', "+task.getBody()+") USING TTL 86400;";
cassandraOperations.execute(cql);
}

Resources