Searching by Value in Redis in Spring Boot and Kotlin - spring

Let's consider we are in Spring Boot with Kotlin. For Redis queries, we're using a RedisTemplate dependency.
Considering the following data class:
data class Person (
val id: String,
val name: String,
val birthDate: ZonedDateTime
)
For saving we'll use:
fun save(person: Person) {
redisTemplate.opsForValue().set(person.id, person)
}
Then, is it possible to search by name, which is a property of the Person class - in order to retrieve a collection of Person's with the same name? And if yes, how can this be done with RedisTemplate?
Thank you in advance!

Related

FindById doesn't work with UUID in Mongodb SpringData

I try to use Mongodb with Spring Data.
I wanted to use UUID instead of ObjectId. I have followed this tutorial: https://www.baeldung.com/java-mongodb-uuid (some differences might exist because I use Kotlin). I took path 2 where I added Entity callback. When I create a new entity it is saved to the database with UUID as I wanted. If I use mongo console I can type:
db.home.find({_id: UUID("18aafcf9-0c5a-46f3-84ff-1c25b00dd1ab")})
And I will find my entity by id.
However, when I try to do it by code it doesn't work as it should. It will always throw here DataOperationException(NOT_FOUND) because findById returns null.
fun findHomeById(id: String): Home {
val home = homeRepository.findById(id)
return home.unwrap() ?: throw DataOperationException(NOT_FOUND)
}
Here is repository
#Repository
interface HomeRepository: MongoRepository<Home, String>
Abstraction with id.
abstract class UuidIdentifiedEntity {
#Id
var id: UUID? = null // I tried use UUID type and String with the same result
}
And my home class
class Home(
var address: String,
var rooms: Int,
): UuidIdentifiedEntity()
Not sure if this will help, but see if changing your #Id annotation to #MongoId fixes this. Under certain circumstances, Mongo needs to know more about a field being used for an id. #MongoId should give you more control on how the field is stored too.
What is use of #MongoId in Spring Data MongoDB over #Id?

Neo4jRepository saves empty node from Kotlin data class

I have a Kotlin data class node for Neo4j nodes:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) val name: String,
val description: String
)
and a Spring repository:
interface MyNodesRepository : Neo4jRepository<MyNode, Long>
Then, when I save a node into the DB via this repository it is empty, without any properties:
val node = MyNode(null, "name 1", "lorem ipsum")
myNodesRepository.save(node)
after the save(node) call, the node.dbId is set to the Neo4j's internal id i.e. it is null before save() and has a value afterwards. I can also see the node in the Neo4j browser, but it does not have name and description properties.
After that, when I try to load all nodes the call crashes with InvalidDataAccessApiUsageException because it cannot deserialize/map the nodes with null/missing name and description:
val allNodes = myNodesRepository.findAll()
If I add a custom save method to my repository, where I manually create the node with CQL query, then everything works.
interface MyNodesRepository : Neo4jRepository<MyNode, Long> {
#Query(
"MERGE (mn:MyNode {name:{name}})\n" +
"ON CREATE SET m += {description:{description}}"
)
fun customSave(#Param("name") name: String, #Param("description") description: String)
}
Now the findAll() loads my newly created and/or updated nodes.
I am using org.springframework.boot:spring-boot-starter-data-neo4j:2.1.6.RELEASE and this is inside a Spring Boot CLI application so no web server and RestControllers.
What am I doing wrong?
EDIT: This is now solved in Neo4j OGM 3.1.13
EDIT: This is now solved in Neo4j OGM 3.1.13 and the workaround below is not needed anymore.
ORIGINAL ANSWER
After few days of debugging it looks like there is a bug in Neo4j OGM for Kotlin data classes where it does not save val properties -- both in data classes and normal classes. So change from val properties:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) val name: String,
val description: String
)
to all var properties:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) var name: String,
var description: String
)
works. Now both saving and loading works, but it is not idiomatic Kotlin.
So at least we have a workaround.

Spring Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

I can't understand, what's wrong with my Service. I receive org.hibernate.StaleObjectStateException trying to run this method:
fun updateNameForPhone(phone: String, name: String): Client {
val res = clientRepository.findByPhone(phone) ?: throw ClientNotFoundException(phone)
res.name = name
return clientRepository.save(res)
}
ClientRepository:
#Repository
interface ClientRepository : JpaRepository<Client, UUID> {
fun findByPhone(phone: String): Client?
}
Client entity:
#Entity
data class Client(
var name: String = "",
var phone: String = "",
#Id #GeneratedValue(strategy = GenerationType.AUTO)
val uuid: UUID = defaultUuid()
)
Exception:
Object of class [com.app.modules.client.domain.Client] with identifier
[12647903-7773-4f07-87a8-e9f86e99aab3]: optimistic locking failed;
nested exception is org.hibernate.StaleObjectStateException: Row was
updated or deleted by another transaction (or unsaved-value mapping
was incorrect) :
[com.app.modules.client.domain.Client#12647903-7773-4f07-87a8-e9f86e99aab3]"
What is the reason?
I'm using Kotlin 1.3.11, Spring Boot 2.1.1, MySql. I don't run it in different threads, just trying with single request.
Well, finally I've found a solution. Better say workaround.
The problem is in the way spring uses UUID as entity identifier.
So there are two workarounds, solving this issue:
first, you can change your id field type to other one, such as Long, for example, if it's possible to you;
or you can add this annotation to your uuid field: #Column(columnDefinition = "BINARY(16)").
The last solution I've found from this question.

spring validation in composition

I have the following data classes:
data class User (
#field:NotEmpty
val firstName: String?
#field:NotEmpty
val lastName: String?
)
data class Expert (
#field:NotEmpty
val name: String?
#field:NotNull
val contact: User?
)
And I would like to use my rest API endpoint to create an expert with spring validation:
#RestController
#RequestMapping("/api/experts")
class ExpertController(private val expertService: ExpertService) {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
fun create(#Valid #RequestBody dto: Expert) = expertService.create(dto)
}
Validation on name and contact fields works fine. But validation on firstName and lastName fields (User class) doesn't work. Is it a normal behaviour? I can't use validation on composition? Why? Or am I missing something?
In order for the User to be validated if it is contained within an Expert, you will need to add the #Valid annotation to it, so Spring's Validator knows to keep checking, otherwise it will stop.
Try this (untested):
data class Expert (
#field:NotEmpty
val name: String?
#field:NotNull
#field:Valid
val contact: User?
)

How to configure Spring MVC to validate constructor parameters of controller method arguments

I'm writing a project in Kotlin and have this in a controller:
#PostMapping("/token")
fun generateToken(#RequestBody #Valid credentials: Credentials) { /* something */ }
data class Credentials(#Email #NotBlank val email: String,
#NotBlank val password: String)
By default #Valid annotation tells Spring to validate object fields. But Kotlin places constraint annotations on the constructor parameters, so validation doesn't work. To make it work I have to define use-site targets for annotations:
data class Credentials(#field:Email #field:NotBlank val email: String,
#field:NotBlank val password: String)
which is annoying and adds visual garbage. Is it possible to configure Spring to validate constructor parameters?
There isn't a whole lot you can do. You can make it look a little better by combining annotations for each field, e.g.:
data class Credentials(#field:[Email NotBlank] val email: String,
#field:NotBlank val password: String)
Other than that, your only other options are:
Manually configured Spring validation classes
Validating the data within your code body

Resources