Spring Data Redis - UUID id - ConverterNotFoundException - spring

I would like to store my entities in Redis with a UUID key:
#RedisHash("order")
public class Order {
#Id
private UUID id;
...
}
However, I get the following exception with this setup:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.util.UUID] to type [byte[]]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.data.redis.core.convert.MappingRedisConverter.toBytes(MappingRedisConverter.java:948) ~[spring-data-redis-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.redis.core.convert.MappingRedisConverter.lambda$writeInternal$2(MappingRedisConverter.java:592) ~[spring-data-redis-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:328) ~[spring-data-commons-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.redis.core.convert.MappingRedisConverter.writeInternal(MappingRedisConverter.java:584) ~[spring-data-redis-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.redis.core.convert.MappingRedisConverter.write(MappingRedisConverter.java:396) ~[spring-data-redis-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.redis.core.convert.MappingRedisConverter.write(MappingRedisConverter.java:122) ~[spring-data-redis-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.redis.core.RedisKeyValueAdapter.put(RedisKeyValueAdapter.java:208) ~[spring-data-redis-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.keyvalue.core.KeyValueTemplate.lambda$update$1(KeyValueTemplate.java:204) ~[spring-data-keyvalue-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.keyvalue.core.KeyValueTemplate.execute(KeyValueTemplate.java:343) ~[spring-data-keyvalue-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.keyvalue.core.KeyValueTemplate.update(KeyValueTemplate.java:204) ~[spring-data-keyvalue-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository.save(SimpleKeyValueRepository.java:103) ~[spring-data-keyvalue-2.0.6.RELEASE.jar:2.0.6.RELEASE]
...
When using String instead of UUID, no such exception appears.
How can I use UUID as ID type?

EDIT:
To be more precise you have to register a CustomConversion bean in your context with the name redisCustomConversions. See this post: Redis - How to configure custom conversions
So in your case it would be something like
#Bean
public CustomConversion redisCustomConversions(){
return new CustomConversions(
Arrays.asList(new UUIDToStringConverter(), new StringToUUIDConverter()))))
}
Original:
I think the easiest way to fix this is to write a type converter for uuid.
Something like a
class UUIDConverter implements Converter<UUID, String>
Or when needed
class UUIDConverter implements Converter<UUID, byte[]>
You have to register this as a bean in your context.

Related

How to get column field only with spring-data-jpa?

How can I make spring-data-jpa return a String column only, instead of a bean?
The following should return the bookingNumber String from a Booking #Entity:
public interface BookingRepository extends CrudRepository<Booking, Long> {
String findDistinctBookingNumberByLastname(String lastname);
}
If the booking exists, I'm getting the following exception instead of the bookingnumber string only:
java.lang.ClassCastException: class Booking cannot be cast to class java.lang.String
at com.sun.proxy.$Proxy198.findDistinctBookingNumberByLastname(Unknown Source) ~[?:?]
Sidenote: I know I could write a native #Query on the interface methods, but I'd prefer a query by method name only.

How to resolve No converter found capable of converting from type TupleBackedMap to type [com.example.dto.ExampleDto]

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.dto.ExampleDto]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.data.repository.query.ResultProcessor$ProjectingConverter.convert(ResultProcessor.java:293) ~[spring-data-commons-2.1.5.RELEASE.jar:2.1.5.RELEASE]
The above error is being thrown when I have a query that returns 2 values in a native JPA query is being used. I'm capturing the query response in the DTO below:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class ExampleDto {
#Id
private String name1;
private int nameFlag;
}
And in a DAO class, I'm calling the native query as below. The query works in SQL Developer and returns 2 records. But when called as below , it throws the above error.
List<ExampleDto> getExampleDto = myJPARepository.
.findNameObject(uuid);
There is something wrong in the DTO class, which i need to change. Annotations? I'm not sure what is missing here, and try as I might , putting in #Entity annotation, #Data annotation , I'm not able to resolve this error when the query is called.
UPDATE: The native query associated with this is
#Query(value = "select name1, nameFlag from NameTable",
nativeQuery = true, name = "findNameObject where namekey = ?")
List<ExampleDto> findNameObject(
#Param("nameKey") UUID nameKey);
This is a bug: https://jira.spring.io/browse/DATAJPA-1714
You can either use JPQL with a constructor expression, an interface projection or a custom method implementation as a workaround.
Make sure, your Repository class is using same class, as you are using for extracting the records from native query. Example shown below for XYZDTO
#Repository
public interface XYZRepository extends JpaRepository <XYZDTO, Long> {
}

Spring Data JPA native query result binding

Entity class:
#Entity
#SqlResultSetMapping(
name="hourMapping",
classes=#ConstructorResult(
targetClass=Representation.class,
columns={
#ColumnResult(name="hour", type=BigDecimal.class),
#ColumnResult(name="transactions", type=BigDecimal.class)
}
)
)
#NamedNativeQuery(name="MyEntity.reportByHour", query="SELECT hour,SUM(tran_per_hour) AS transactions FROM MY_ENTITY GROUP BY hour ORDER BY hour"
,resultSetMapping="hourMapping")
#Table(name="MY_ENTITY")
public class MyEntity implements Serializable {
Pojo class:
#Data //Lombok
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class Representation {
public Representation(BigDecimal hour, BigDecimal transactions) {
this.hour = hour;
this.transactions = transactions;
}
private BigDecimal hour;
private BigDecimal transactions;
Repository interface:
public interface MyEntityRepository extends JpaRepository<MyEntity, MyEntityPK> {
List<Representation> reportByHour();
}
When I run the endpoint which invokes the native query, I get exception:
Failed to convert from type [java.lang.Object[]] to type [com.representation.Representation] for value '{0, 198}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.math.BigDecimal] to type [com.representation.Representation]
Now if I just have "hour" field returned from my native query (and relevant changes to POJO constructor etc) it works fine.
Any help appreciated.
Ok, false alarm. My hibernate dependencies were all messed up and causing conflicts so resulting in the above exception.
After fixing these dependency issues, works great!!
Long story short: let spring-boot-* handle most hibernate dependencies instead of overriding or managing your own.

Spring Boot 2.0.0M2 Thymeleaf 3 failed to convert field

After upgrading to spring-boot-2-m2 (thymeleaf 3) I am getting failed conversion errors for fields that correspond to JPA relationships.
Failed to convert from type [#javax.persistence.ManyToOne #javax.persistence.JoinColumn com.pps2....entities.FormType] to type [java.lang.String] for value 'com.pps2.....FormType#819841af'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.util.Optional<?>] to type [java.lang.String]
JPA Entity
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_form_type")
private FormType type;
public FormType getType() {
return type;
}
The code in the template is:
<select th:field="*{type}" class="col-xs-12">
Throws similar failed conversion error.
Of course when putting direct reference it works, but in that case it breaks a lot of templates in the project. And generates name as type.id and not type.
Working example
<select th:field="*{type.id}" class="col-xs-12">
Question - Why did they change the API? And is there a way to resolve it without rechecking all the templates (e.g. writing converter?)?
The solution is to write your own Optional<T> to String converter. I do not know why it was excluded from Spring Boot 2 M2
Converter code
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.Optional;
#Component
final class OptionalToString implements Converter<Optional<?>, String> {
public String convert(Optional<?> source) {
return Objects.toString(source.get(),"");
}
}
Another option
Is to specify directly the column (like id)
Working example <select th:field="*{type.id}" class="col-xs-12">

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