Compilation error using both HashKey and RangeKey in dynamoDB with Springboot - spring-boot

I am using dynamDb with my springboot application. I am trying to save data in the table which has hashKey and sortKey. Initially I tried to annotate my HaskKey and sortKey with #DynamoDBHashKey and #DynamoDBRangeKey respectively. However this was giving me compilation issues. Upon search I found this article https://github.com/derjust/spring-data-dynamodb/wiki/Use-Hash-Range-keys which explains how in springboot we need to create a composite key/id with annotation #id. This id will not be part of the table.
I implemented the same however I still get compilation error:
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'eventRepository' defined in com.accuity.kyc.hep.repository.EventRepository defined in #EnableDynamoDBRepositories declared on DynamoDBConfig: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/data/repository/core/support/ReflectionEntityInformation\r\n\tat org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)\r\n\tat org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)\r\n\tat org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)\r\n\tat org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)\r\n\tat org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)\r\n\tat org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)\r\n\tat org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)\r\n\tat org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)\r\n\tat org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)\r\n\tat org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730)\r\n\tat org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412)\r\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:302)\r\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)\r\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:1290)\r\n\tat com.accuity.kyc.hep.HepApplication.main(HepApplication.java:16)\r\nCaused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'historyEventRepository' defined in com.accuity.kyc.hep.repository.HistoryEventRepository defined in #EnableDynamoDBRepositories declared on DynamoDBConfig: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/data/repository/core/support/ReflectionEntityInformation\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)\r\n\tat org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)\r\n\tat org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)\r\n\tat org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)\r\n\tat org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)\r\n\tat org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)\r\n\tat org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)\r\n\tat org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)\r\n\t... 19 common frames omitted\r\nCaused by: java.lang.NoClassDefFoundError: org/springframework/data/repository/core/support/ReflectionEntityInformation\r\n\tat java.base/java.lang.ClassLoader.defineClass1(Native Method)\r\n\tat java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)\r\n\tat java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)\r\n\tat java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:800)\r\n\tat java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698)\r\n\tat java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621)\r\n\tat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579)\r\n\tat java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)\r\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)\r\n\tat org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBEntityMetadataSupport.getEntityInformation(DynamoDBEntityMetadataSupport.java:125)\r\n\tat org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBRepositoryFactory.getEntityInformation(DynamoDBRepositoryFactory.java:104)\r\n\tat org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBRepositoryFactory.getDynamoDBRepository(DynamoDBRepositoryFactory.java:128)\r\n\tat org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBRepositoryFactory.getTargetRepository(DynamoDBRepositoryFactory.java:150)\r\n\tat org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:324)\r\n\tat org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:322)\r\n\tat org.springframework.data.util.Lazy.getNullable(Lazy.java:230)\r\n\tat org.springframework.data.util.Lazy.get(Lazy.java:114)\r\n\tat org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:328)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863)\r\n\tat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)\r\n\t... 30 common frames omitted\r\nCaused by: java.lang.ClassNotFoundException: org.springframework.data.repository.core.support.ReflectionEntityInformation\r\n\tat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)\r\n\tat java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)\r\n\tat java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)\r\n\t... 50 common frames omitted\r\n"}
My entity class looks like this:
#DynamoDBTable(tableName = "Event")
#NoArgsConstructor
public class Event {
#Id
private EventId eventId;
#DynamoDBHashKey(attributeName = "id")
#DynamoDBAutoGeneratedKey
public String getId() {
return eventId != null ? eventId.getId() : null;
}
public void setId(String id) {
if (eventId == null) {
eventId = new eventId();
}
eventId.setId(id);
}
#DynamoDBRangeKey(attributeName = "eventDate")
public Date getEventDate() {
return eventId != null ? eventId.getEventDate() : null;
}
public void setEventDate(Date eventDate) {
if (eventId == null) {
eventId = new eventId();
}
eventId.setEventDate(eventDate);
}
}
Composite key class:
#NoArgsConstructor
#AllArgsConstructor
public class EventId {
private String id;
private Date eventDate;
#DynamoDBHashKey(attributeName = "id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#DynamoDBRangeKey(attributeName = "eventDate")
public Date getEventDate() {
return eventDate;
}
public void setEventDate(Date eventDate) {
this.eventDate = eventDate;
}
}
Repository:
#EnableScan
#Repository
public interface EventRepository extends CrudRepository<Event, EventId> {
}
For dependency I am using:
id 'org.springframework.boot' version '2.6.1' and
com.github.derjust:spring-data-dynamodb:5.1.0
I have followed the similar example from couple of other sources. I can not see what am I missing.
Please help.

There is an open issue for this, https://github.com/derjust/spring-data-dynamodb/issues/279. It appears the library is not compatible with Spring Boot 2.2. Per one of the commenters there is a fork available that may support this, but I haven't found this yet.
EDIT: The repository where this supposedly works now is at https://github.com/boostchicken/spring-data-dynamodb.

Related

Spring Boot JPA EntityListener query causes "don't flush the Session after an exception occurs"

Problem:
I create object A with an EntityListener with #PostPersist-method that will create object B, this works like a charm!
I need to introduce some logic before creating object B, I need to query the database and see if a similar B object already exists in the database. But when I run my query
#Query("select case when count(n) > 0 then true else false end from Notification n where student = :student and initiator = :initiator and entityType = :entityType and entityId = :entityId")
boolean alreadyNotified(#Param("student") Student student, #Param("initiator") Student initiator, #Param("entityType") EntityType entityType, #Param("entityId") Long entityId);
I get the following error:
ERROR org.hibernate.AssertionFailure.<init>(31) - HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in se.hitract.model.Likes entry (don't flush the Session after an exception occurs)
org.hibernate.AssertionFailure: null id in se.hitract.model.Likes entry (don't flush the Session after an exception occurs)
Background:
I have a Spring Boot project with Hibernate and MySql DB and I'm building a simple social media platform where students can upload posts/images and other user can like/comments.
When someone like/comment an object a notification should be sent to the other user. The like object:
#SuppressWarnings("serial")
#Entity
#Table(uniqueConstraints=#UniqueConstraint(columnNames = {"entityType", "entityId", "studentId"}))
#EntityListeners(LikeListener.class)
public class Likes extends CommonEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long likeId;
#NotNull
#Enumerated(EnumType.STRING)
private EntityType entityType;
private Long entityId;
...
}
The LikeListener:
#Component
public class LikeListener {
#PostPersist
public void doThis(Likes like) {
NotificationService notificationService = BeanUtil.getBean(NotificationService.class);
if(like.getEntityType().equals(EntityType.INSPIRATION)) {
InspirationService inspirationService = BeanUtil.getBean(InspirationService.class);
Inspiration inspiration = inspirationService.get(like.getEntityId());
notificationService.createLikeNotification(inspiration.getStudent(), like.getStudent(), EntityType.INSPIRATION, inspiration.getId());
}
if(like.getEntityType().equals(EntityType.COMMENT)) {
CommentService commentService = BeanUtil.getBean(CommentService.class);
Comment comment = commentService.get(like.getEntityId());
notificationService.createLikeNotification(comment.getStudent(), like.getStudent(), EntityType.COMMENT, comment.getId());
}
}
}
and the problem:
public Notification createLikeNotification(Student student, Student initiator, EntityType entityType, Long entityId) {
if(student.equals(initiator) || alreadyNotified(student, initiator, entityType, entityId)) {
return null;
}
Notification notification = createNotification(student,
initiator,
NOTIFICATION_TYPE.LIKE,
entityType,
entityId,
null);
return repository.save(notification);
}
public boolean alreadyNotified(Student student, Student initiator, EntityType entityType, Long entityId) {
return repository.alreadyNotified(student, initiator, entityType, entityId);
}
If I remove the alreadyNotified-call no error is thrown. What am I missing?
It seems that Hibernate flushes the Likes-save before my query is run but then it fails. Do I need to do some manual flush/refresh? I think Hibernate should solve this for me.

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

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

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 Data Rest - resource becomes unavailable when subclassed

After having debugged my head off, I am out of ideas and would need some help. What really intrigues me badly is the simplicity of what I am trying to do and still the impossibility of achieving it...
I am trying to make a small demo of spring rest data, by creating some entities, exposing them as rest resources and persisting them in an in-memory h2 database.
The following code works:
#Entity
#Table(name="info")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="INFO_TYPE", discriminatorType=DiscriminatorType.STRING)
public class ItemInfo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
public long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
#RepositoryRestResource(collectionResourceRel="itemInfos", path="itemInfos")
public interface ItemInfoRepository extends PagingAndSortingRepository<ItemInfo, Long> {
}
When I issue a curl request for curl http://localhost:8080/itemInfos I get the following correct response:
{
"_embedded" : {
"itemInfos" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/itemInfos"
},
"profile" : {
"href" : "http://localhost:8080/profile/itemInfos"
}
},
"page" : {
"size" : 20,
"totalElements" : 0,
"totalPages" : 0,
"number" : 0
}
}
However, as soon as I add a subclass entity of ItemInfo, the /itemInfos resource will not be available anymore.
So adding the class:
#Entity
#Table(name="info")
#DiscriminatorValue(value="COMMENT")
public class UserComment extends ItemInfo {
private String from;
private String comment;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
Will result in the same curl command as earlier to produce an error:
{"timestamp":1488282548770,"status":500,"error":"Internal Server Error","exception":"org.springframework.dao.InvalidDataAccessResourceUsageException","message":"could not prepare statement; SQL [select iteminfo0_.id as id2_0_, iteminfo0_.comment as comment3_0_, iteminfo0_.from as from4_0_, iteminfo0_.info_type as info_typ1_0_ from info iteminfo0_ limit ?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement","path":"/itemInfos"}
Furthermore, trying to add a new itemInfo causes a similar error:
{"timestamp":1488282718547,"status":500,"error":"Internal Server Error","exception":"org.springframework.dao.InvalidDataAccessResourceUsageException","message":"could not prepare statement; SQL [insert into info (id, info_type) values (null, 'ItemInfo')]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement","path":"/itemInfos"}
Adding a UserCommentRepository also doesn't solve the problem in any way:
public interface UserCommentRepository extends PagingAndSortingRepository<UserComment, Long> { }
In this case, if i try to add a new user comment:
curl -i -X POST -H "Content-Type:application/json" -d "{ \"from\" : \"Ana\", \"comment\" : \"some comment\" }" http://localhost:8080/userComments
I get a further error:
{"timestamp":1488282968879,"status":500,"error":"Internal Server Error","exception":"org.springframework.dao.InvalidDataAccessResourceUsageException","message":"could not prepare statement; SQL [insert into info (id, comment, from, info_type) values (null, ?, ?, 'COMMENT')]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement","path":"/userComments"}
It seems that adding inheritance for my entities completely ruins my resources. Did anyone of you had a similar problem?!
Ok...I figured it out (mostly by mistake) and it is even more frustrating.
The problem was the use of the variable name "from". It seems that spring data rest uses this as a keyword. As soon as I refactored the name of this variable, everything worked as expected. Unfortunately the exceptions and error messages didn't help at all.
Hopefully others will not go through the same debugging hell...

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