Spring data mongo using CoroutineCrudRepository - kotlin

I am trying to use new Spring data reactive approach using CoroutineCrudRepository in a Kotlin project.
Github repository link: https://github.com/cristianprofile/spring-data-CoroutineCrudRepository-test
This is my application's code:
#SpringBootApplication
class DemoApplication
data class PersonDto(val age: Int, val name : String)
#RestController
#RequestMapping("/persons")
class PersonController (val personService: PersonService)
{
#GetMapping
suspend fun getPersons() = personService.getAll()
#PostMapping
suspend fun savePerson(#RequestBody personDto: PersonDto) = personService.save(personDto)
}
interface PersonService {
suspend fun getAll(): Flow<Person>
suspend fun getById(id: String): Person?
suspend fun save(personDto: PersonDto): Person
}
#Service
class PersonServiceImpl (private val personRepository: PersonRepository ) : PersonService
{
override suspend fun getAll(): Flow<Person> {
return personRepository.findAll()
}
override suspend fun getById(id: String): Person? {
return personRepository.findById(id)
}
override suspend fun save(personDto: PersonDto): Person
{
return personRepository.save(personDto.convertToPerson())
}
}
fun PersonDto.convertToPerson() =
Person(
name = name,
age = age
)
#Document
data class Person (#Id val id: String? = null, val age: Int, val name : String)
#Repository
interface PersonRepository: CoroutineCrudRepository<Person, String>
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
When I try to start my Spring boot project
Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.lang.Object org.springframework.data.repository.kotlin.CoroutineCrudRepository.count(kotlin.coroutines.Continuation); Reason: No property 'count' found for type 'Person'; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property 'count' found for type 'Person'
at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) ~[spring-data-commons-2.7.3.jar:2.7.3]
I don't know why "No property 'count' found for type" error is thrown. Which would be the way to be able to use CoroutineCrudRepository using Spring data mongo repository?

Related

how can I convert my room entity to my data class in kotlin?

I have a data class that I pull from internet and I want to save room database but there is a problem like that.
It always gives an error like this, how can I overcome this problem?
my room entity class
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "base_code") val base_code: String,
#ColumnInfo(name = "conversion_rates") val conversion_rates: ConversionRates,
#ColumnInfo(name = "result") val result: String,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
my dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET base_code=:base_code,conversion_rates=:conversion_rates , result=:result")
suspend fun update(base_code:String,conversion_rates:ConversionRates,result:String)
}
my exchange data class
#Serializable
data class Exchange(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
) {
fun toEntity() = ExchangeEntity(
base_code = base_code,
conversion_rates = conversion_rates,
result = result
)
}
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
I cant use toEntity function in getAll()
exchangeRepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll())
}
}
override suspend fun update(exchange: Exchange) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates)
}
}
my exchange converter
class ExchangeConverter {
#TypeConverter
fun fromSource(conversionRates: ConversionRates) : String{
val gson = Gson()
return gson.toJson(conversionRates)
}
#TypeConverter
fun toSource(json: String): ConversionRates {
val gson = Gson()
val typeToken = object : TypeToken<List<ConversionRates>>() {}.type
return Gson().fromJson(json, typeToken)
}
}
I wrote a converter like this, but it might not be correct, I'm not so sure. How can I solve this problem?
Inside flow you have created call map function the call to toEntity() eg
flow{
emit (dao.getAll().map{it.toEntity()})
}
Well your flow returns a flow of
List<Exchange>
and your repo returns
List<ExchangeEntity>
and there's nothing in your code to map an ExchangeEntity to an Exchange.
So you need something like:
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll().map{Exchange(base_code = it.baseCode)})// add in other fields on exchange constructor
}
}

typealias for global kotlinx.serialization not work

First I'll post the code
#OptIn(ExperimentalSerializationApi::class)
#Serializer(forClass = UUID::class)
object UUIDserializer : KSerializer<UUID> {
override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: UUID) {
encoder.encodeString(value.toString())
}
}
typealias SID = #Serializable(with = UUIDserializer::class) UUID
fun randomSid() = UUID.randomUUID() as SID
#Serializable
data class Example(val id:SID = randomSid())
class SerializeId {
#Test
fun nestedTypeUsage() {
val example = Example()
val string = Json.encodeToString(example)
println(string)
}
#Test
fun directTypeUsage () {
val hi = randomSid()
val string = Json.encodeToString(hi)
println(string)
}
}
nestedTypeUsage run and passes, but directTypeUsage fails.
Serializer for class 'UUID' is not found.
Mark the class as #Serializable or provide the serializer explicitly.
kotlinx.serialization.SerializationException: Serializer for class 'UUID' is not found
I can't apply the #Serializable annotation directly to a val or a function parameter.
almost immediately after posting this. I realized I can
#Test
fun directTypeUsage () {
val hi = randomSid()
val string = hi.toString()
println(string)
}

How to write generic functions in Kotlin interfaces's implementations

I am trying to implement a generic HttpClient like this one:
interface HttpClient {
fun <T: Any> get(url: String): T?
}
implemented by a class like this:
class HttpClientImpl #Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String): T? = execute(url)
private inline fun <reified T: Any> execute(url: String): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBodySuccess()
}
}
private inline fun <reified T: Any> ResponseBody?.parseBody(): T? {
val type = objectMapper.typeFactory.constructType(T::class.java)
return this?.let { objectMapper.readValue(it.string(), type) }
}
}
Now, I would like to be able to call such GET method in this way:
data class MyEntity(...)
class MyService #Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get<MyEntity>(url)
}
However this is not allowed and the compiler throws an error referring to the line of code
override fun <T : Any> get(endpoint: String): T? = execute(endpoint)
flagging that : Cannot use 'T' as reified type parameter. Use a class instead.
I have been trying to re-write the line as
override inline fun <reified T : Any> get(endpoint: String): T? = execute(endpoint)
however, despite having to make the other two inline functions "non private" the compiler still won't compile because in this last way of writing the overriding function, it says:
Override by a function with reified type parameter
How can I achieve such generic function?
I ended up doing something like this:
interface HttpClient {
fun <T: Any> get(url: String, type: Class<T>): T?
}
implemented as:
class HttpClientImpl #Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String, type: Class<T>): T? = execute(url, type)
private fun <T: Any> execute(url: String, type: Class<T>): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBody(type)
}
}
private fun <T: Any> ResponseBody?.parseBody(type: Class<T>): T? {
val dataType = objectMapper.typeFactory.constructType(type)
return this?.let { objectMapper.readValue(it.string(), dataType) }
}
}
that I can call in this way:
data class MyEntity(...)
class MyService #Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get(url, MyEntity::class.java)
}
I would have preferred to pass the Type directly as an actual type like
client.get<MyEntity>(url)
rather than passing the Class as a parameter, however, just for now it works...
If anyone can suggest a better way of doing this, please let me know.
Updated
As suggested by Pawel, I have created an extra inline extension function to the HttpClient interface
inline fun <reified T:Any> HttpClient.get (url: String) = get(url, T::class.java)
And I'm now able to call the function the way I wanted.

Deserialize generic object using Kotlin Serialization

I am trying to replace Gson library by kotlin serialization to handle JSON serialization/deserialization.
I am facing some issues to deserialize generic objects I have setup a simple example of what I am trying to achieve:
#Serializable
data class ContentMessageDto<T>(
val method: String,
val content: T
)
#Serializable
private data class DummyObjectNonNullProperties(
val value: Int,
#SerialName("aaa") val someProp: String,
val bbb: Boolean,
val ccc: Double
)
interface MyParser {
fun <T> parseContentMessage(
json: String
): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString<ContentMessageDto<T>>(json)
}
}
fun main() {
println("start processing...")
val jsonToParse = """
{
"method":"get",
"content":{
"value":345,
"aaa": "some string",
"bbb": true,
"ccc": 23.4
}
}""".trimIndent()
val parser:MyParser = MyParserImpl(Json)
val result = parser.parseContentMessage<DummyObjectNonNullProperties>(jsonToParse)
println("result -> $result")
}
But when I run the main method, I get the following error:
Exception in thread "main" java.lang.IllegalStateException: Only KClass supported as classifier, got T
at kotlinx.serialization.internal.Platform_commonKt.kclass(Platform.common.kt:102)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:52)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at kotlinx.serialization.SerializersKt__SerializersKt.builtinSerializerOrNull$SerializersKt__SerializersKt(Serializers.kt:79)
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:69)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:54)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
But I am not sure why. Can someone provide me an explanation and if possible some tips on how I can implement this?
It would have worked if you've simply done:
val result = Json.decodeFromString<ContentMessageDto<DummyObjectNonNullProperties>>(jsonToParse)
But with all this wrapping, type information about T was lost. The problem is that you can't simply use reified generics here, cause inline functions can't be non-final.
Possible workarounds:
Define parseContentMessage as extension function so that it could have inline modifier (and T could be reified):
interface MyParser {
val jsonSerialization: Json
}
inline fun<reified T> MyParser.parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(json)
}
class MyParserImpl(override val jsonSerialization: Json) : MyParser
//Usage will be the same
Manually pass serializer for T into parseContentMessage:
interface MyParser {
fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(ContentMessageDto.serializer(contentSerializer), json)
}
}
//Usage:
val result = parser.parseContentMessage(jsonToParse, DummyObjectNonNullProperties.serializer())

Android Room + Kotlin pattern

the Android Room documentation says that we should follow the singleton design pattern when instantiating an AppDatabase object.
I was thinking about it, and I would like to know if its recommended to put the AppDatabase class inside my Application class. Or if I can use the Kotlin singleton for that.
Let's say I have a DAO called CarroDAO and class CarrosDatabase that is a RoomDatabase.
Is it ok to create a DatabaseManager class using a Kotlin object/singleton ?
object DatabaseManager {
private var dbInstance: CarrosDatabase
init {
val appContext = MyApplication.getInstance().applicationContext
dbInstance = Room.databaseBuilder(
appContext,
CarrosDatabase::class.java,
"mybd.sqlite")
.build()
}
fun getCarroDAO(): CarroDAO {
return dbInstance.carroDAO()
}
}
So I can get the DAO class like this:
val dao = DatabaseManager.getCarroDAO()
According to Android documentation, we can create a database instance using the singleton design pattern as follows
Create a room database entity
#Entity
data class User(
#PrimaryKey var uid: Int,
#ColumnInfo(name = "first_name") var firstName: String?,
#ColumnInfo(name = "last_name") var lastName: String?
)
Create DAO class
#Dao
interface UserDao {
#Query("SELECT * FROM user")
fun getAll(): List<User>
#Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
#Query("SELECT * FROM user WHERE first_name LIKE: first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
#Insert
fun insertAll(vararg users: User)
#Delete
fun delete(user: User)
}
Create database with singleton pattern
#Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var instance: AppDatabase? = null
fun getInstance(
context: Context
): AppDatabase = instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"database-name"
).build()
}
}
}
You can get database instance by following code
var databaseInstance=AppDatabase.getInstance(context)