Skip to content
This repository was archived by the owner on Jan 20, 2023. It is now read-only.

Commit 84ed582

Browse files
authored
Merge pull request #33 from k163377/conversion_service
Add ConversionService support.
2 parents a15c934 + 44061df commit 84ed582

File tree

9 files changed

+117
-46
lines changed

9 files changed

+117
-46
lines changed

README.ja.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ val dst: Dst = jdbcTemplate.query(query, KRowMapper(::Dst, /* 必要に応じた
9898

9999
また、`KRowMapper`はデフォルトでは引数名によってカラムとの対応を見るため、「引数がキャメルケースでカラムはスネークケース」というような場合、引数名を変換する関数も渡す必要が有ります。
100100

101+
必要に応じて値の変換のために`ConversionService`を渡すこともできます。
102+
渡さなかった場合、`DefaultConversionService.sharedInstance`がデフォルトとして利用されます。
103+
101104
### method reference(KFunction)からの初期化
102105
`KRowMapper``method reference`から初期化できます。
103106

@@ -230,13 +233,16 @@ val mapper: KRowMapper<Dst> = KRowMapper(::Dst, parameterNameConverter)
230233
ただし、よりプレーンな`Kotlin`に近い書き方をしたい場合にはこれらの機能を用いず、呼び出し対象メソッドで全ての初期化処理を書くことをお勧めします。
231234

232235
### 値のデシリアライズ
233-
`KRowMapper``java.sql.ResultSet`から値の取得を行うため、デフォルトではこの実装でサポートされていない型を取得することはできません。
234-
この問題に対応するため、`KRowMapper`ではデフォルトの変換機能に加え以下の3種類のデシリアライズ方法を提供しています。
236+
`KRowMapper``BeanPropertyRowMapper`同様`ConversionService`(デフォルトでは`DefaultConversionService.sharedInstance`)を用いたデシリアライズをサポートしています。
237+
238+
これに加え、より明示的で柔軟性の高いデシリアライズ方法として、`KRowMapper`では以下の3種類のデシリアライズ方法を提供しています。
235239

236240
1. `KColumnDeserializer`アノテーションを利用したデシリアライズ
237241
2. デシリアライズアノテーションを自作してのデシリアライズ
238242
3. 複数引数からのデシリアライズ
239243

244+
これらのデシリアライズ方法は`ConversionService`によるデシリアライズより優先的に適用されます。
245+
240246
#### KColumnDeserializerアノテーションを利用したデシリアライズ
241247
自作のクラスで、かつ単一引数から初期化できる場合、`KColumnDeserializer`アノテーションを用いたデシリアライズが利用できます。
242248
`KColumnDeserializer`アノテーションは、コンストラクタ、もしくは`companion object`に定義したファクトリーメソッドに対して付与できます。
@@ -470,6 +476,3 @@ class Foo(
470476
val description: String = ""
471477
)
472478
```
473-
474-
#### Enumをデシリアライズする
475-
DBに格納された値と`Enum::name`プロパティが一致する場合、特別な記述無しに`Enum`をデシリアライズすることができます。

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ Also, it supports the default arguments which are peculiar to `Kotlin`.
103103
Also, by default, `KRowMapper` compares argument names and column names to see if they correspond.
104104
Therefore, in the case of "argument name is `camelCase` and column name is `snake_case`", it is necessary to pass a function that appropriately converts the naming convention of the argument name.
105105

106+
You can also pass a `ConversionService` for value conversion if needed.
107+
If you don't pass it, `DefaultConversionService.sharedInstance` will be used as default.
108+
106109
### Initialization from method reference(KFunction)
107110
You can initialize `KRowMapper` from `method reference(KFunction)` as follows It is.
108111

@@ -235,13 +238,16 @@ By using the contents described so far, you can perform more flexible and safe m
235238
In addition, by making full use of the abundant functions provided by `KRowMapper`, further labor saving is possible.
236239

237240
### Deserialization
238-
Since `KRowMapper` gets the value from `java.sql.ResultSet`, by default it is not possible to get the type which is not supported by this implementation.
239-
To deal with this problem, `KRowMapper` provides the following three types of deserialization methods in addition to the default conversion function.
241+
`KRowMapper` supports deserialization using the `ConversionService` (`DefaultConversionService.sharedInstance` by default).
242+
243+
In addition to this, as a more explicit and flexible deserialization method, `KRowMapper` provides the following three deserialization methods.
240244

241245
1. Deserialization by using the `KColumnDeserializer` annotation.
242246
2. Deserialization by creating your own custom deserialization annotations.
243247
3. Deserialization from multiple arguments.
244248

249+
These deserialization methods take precedence over deserialization by `ConversionService`.
250+
245251
#### Deserialization by using the KColumnDeserializer annotation
246252
If it is a self-made class and can be initialized from a single argument, deserialization using the `KColumnDeserializer` annotation can be used.
247253
`KColumnDeserializer` annotation can be used to `constructor` or `factory method` defined in `companion object`.
@@ -481,6 +487,3 @@ class Foo(
481487
val description: String = ""
482488
)
483489
```
484-
485-
#### Deserialize Enum
486-
If the value stored in the DB and the `Enum::name` property of the map destination are the same, it will be automatically converted to You can deserialize the `Enum`.

src/main/kotlin/com/mapk/krowmapper/DummyConstructor.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package com.mapk.krowmapper
22

3+
import org.springframework.core.convert.ConversionService
34
import com.mapk.krowmapper.KRowMapper as Mapper
45

6+
@Suppress("FunctionName")
7+
inline fun <reified T : Any> KRowMapper(
8+
conversionService: ConversionService,
9+
noinline parameterNameConverter: ((String) -> String)? = null
10+
) = Mapper(T::class, conversionService, parameterNameConverter)
11+
512
@Suppress("FunctionName")
613
inline fun <reified T : Any> KRowMapper(
714
noinline parameterNameConverter: ((String) -> String)? = null

src/main/kotlin/com/mapk/krowmapper/KRowMapper.kt

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,42 @@ package com.mapk.krowmapper
22

33
import com.mapk.core.KFunctionForCall
44
import com.mapk.core.toKConstructor
5+
import org.springframework.core.convert.ConversionService
6+
import org.springframework.core.convert.support.DefaultConversionService
57
import org.springframework.jdbc.core.RowMapper
68
import java.sql.ResultSet
79
import kotlin.reflect.KClass
810
import kotlin.reflect.KFunction
911

10-
class KRowMapper<T : Any> private constructor(private val function: KFunctionForCall<T>) : RowMapper<T> {
11-
constructor(function: KFunction<T>, parameterNameConverter: ((String) -> String)? = null) : this(
12-
KFunctionForCall(function, parameterNameConverter)
13-
)
12+
class KRowMapper<T : Any> private constructor(
13+
private val function: KFunctionForCall<T>,
14+
conversionService: ConversionService?
15+
) : RowMapper<T> {
16+
constructor(
17+
function: KFunction<T>,
18+
conversionService: ConversionService,
19+
parameterNameConverter: ((String) -> String)? = null
20+
) : this(KFunctionForCall(function, parameterNameConverter), conversionService)
1421

15-
constructor(clazz: KClass<T>, parameterNameConverter: ((String) -> String)? = null) : this(
16-
clazz.toKConstructor(parameterNameConverter)
17-
)
22+
constructor(
23+
function: KFunction<T>,
24+
parameterNameConverter: ((String) -> String)? = null
25+
) : this(KFunctionForCall(function, parameterNameConverter), null)
1826

27+
constructor(
28+
clazz: KClass<T>,
29+
conversionService: ConversionService,
30+
parameterNameConverter: ((String) -> String)? = null
31+
) : this(clazz.toKConstructor(parameterNameConverter), conversionService)
32+
33+
constructor(
34+
clazz: KClass<T>,
35+
parameterNameConverter: ((String) -> String)? = null
36+
) : this(clazz.toKConstructor(parameterNameConverter), null)
37+
38+
private val conversionService: ConversionService = conversionService ?: DefaultConversionService.getSharedInstance()
1939
private val parameters: List<ParameterForMap<*, *>> =
20-
function.requiredParameters.map { ParameterForMap.newInstance(it) }
40+
function.requiredParameters.map { ParameterForMap.newInstance(it, this.conversionService) }
2141

2242
override fun mapRow(rs: ResultSet, rowNum: Int): T {
2343
val adaptor = function.getArgumentAdaptor()

src/main/kotlin/com/mapk/krowmapper/ParameterForMap.kt

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package com.mapk.krowmapper
22

33
import com.mapk.annotations.KColumnDeserializer
4-
import com.mapk.core.EnumMapper
54
import com.mapk.core.KFunctionWithInstance
65
import com.mapk.core.ValueParameter
76
import com.mapk.core.getAnnotatedFunctions
87
import com.mapk.core.getAnnotatedFunctionsFromCompanionObject
98
import com.mapk.core.getKClass
109
import com.mapk.deserialization.AbstractKColumnDeserializer
1110
import com.mapk.deserialization.KColumnDeserializeBy
11+
import org.springframework.core.convert.ConversionException
12+
import org.springframework.core.convert.ConversionService
1213
import java.lang.IllegalArgumentException
1314
import java.sql.ResultSet
1415
import kotlin.reflect.KClass
@@ -23,12 +24,21 @@ internal sealed class ParameterForMap<S, D> {
2324
abstract val name: String
2425
abstract fun getObject(rs: ResultSet): D?
2526

26-
private class Plain<T>(override val name: String, val requiredClazz: Class<T>) : ParameterForMap<T, T>() {
27-
override fun getObject(rs: ResultSet): T? = rs.getObject(name, requiredClazz)
28-
}
29-
30-
private class Enum<D>(override val name: String, val enumClazz: Class<D>) : ParameterForMap<String, D>() {
31-
override fun getObject(rs: ResultSet): D? = EnumMapper.getEnum(enumClazz, rs.getString(name))
27+
private class Default<D>(
28+
override val name: String,
29+
val requiredClazz: Class<D>,
30+
private val conversionService: ConversionService
31+
) : ParameterForMap<Any, D>() {
32+
override fun getObject(rs: ResultSet): D? = rs.getObject(name)?.let {
33+
if (requiredClazz.isInstance(it))
34+
@Suppress("UNCHECKED_CAST")
35+
it as D?
36+
else try {
37+
conversionService.convert(it, requiredClazz)
38+
} catch (ex: ConversionException) {
39+
throw IllegalStateException("Could not find a method to deserialize for '$name' parameter.", ex)
40+
}
41+
}
3242
}
3343

3444
private class Deserializer<S : Any, D>(
@@ -45,22 +55,20 @@ internal sealed class ParameterForMap<S, D> {
4555
}
4656

4757
companion object {
48-
fun <T : Any> newInstance(param: ValueParameter<T>): ParameterForMap<*, T> {
58+
fun <T : Any> newInstance(
59+
param: ValueParameter<T>,
60+
conversionService: ConversionService
61+
): ParameterForMap<*, T> {
4962
param.getDeserializer()?.let {
5063
return Deserializer(param.name, it)
5164
}
5265

5366
param.requiredClazz.getDeserializer()?.let {
54-
val targetClass = it.parameters.single().getKClass().javaObjectType
55-
return Deserializer(param.name, targetClass, it)
67+
val srcClass = it.parameters.single().getKClass().javaObjectType
68+
return Deserializer(param.name, srcClass, it)
5669
}
5770

58-
return param.requiredClazz.javaObjectType.let {
59-
when (it.isEnum) {
60-
true -> Enum(param.name, it)
61-
false -> Plain(param.name, it)
62-
}
63-
}
71+
return Default(param.name, param.requiredClazz.javaObjectType, conversionService)
6472
}
6573
}
6674
}

src/test/kotlin/com/mapk/krowmapper/DefaultValueTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ class DefaultValueTest {
1919
@DisplayName("デフォルト値を用いたマッピングテスト")
2020
fun test() {
2121
val resultSet = mockk<ResultSet>()
22-
every { resultSet.getObject("foo_id", any<Class<*>>()) } returns 1
22+
every { resultSet.getObject("foo_id") } returns 1
2323
every { resultSet.getObject("bar_value", any<Class<*>>()) } returns "From result set."
2424

2525
val result = KRowMapper(Dst::class, this::camelToSnake).mapRow(resultSet, 0)
2626

2727
Assertions.assertEquals(1, result.fooId)
2828
Assertions.assertEquals("default", result.barValue)
2929

30-
verify(exactly = 1) { resultSet.getObject("foo_id", Integer::class.java) }
30+
verify(exactly = 1) { resultSet.getObject("foo_id") }
3131
verify(exactly = 0) { resultSet.getObject("bar_value", String::class.java) }
3232
}
3333
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.mapk.krowmapper
2+
3+
import io.mockk.every
4+
import io.mockk.mockk
5+
import io.mockk.verify
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.DisplayName
8+
import org.junit.jupiter.api.Test
9+
import org.springframework.core.convert.ConversionService
10+
import java.sql.ResultSet
11+
import java.time.YearMonth
12+
13+
@DisplayName("ConversionServiceによるデシリアライズのテスト")
14+
private class DeserializeByConversionServiceTest {
15+
data class Dst(val yearMonth: YearMonth)
16+
17+
@Test
18+
fun test() {
19+
val conversionService = mockk<ConversionService> {
20+
every { convert(202101, YearMonth::class.java) } returns (YearMonth.of(2021, 1))
21+
}
22+
val mapper = KRowMapper<Dst>(conversionService)
23+
val resultSet = mockk<ResultSet> {
24+
every { getObject("yearMonth") } returns 202101
25+
}
26+
27+
assertEquals(Dst(YearMonth.of(2021, 1)), mapper.mapRow(resultSet, 0))
28+
verify(exactly = 1) { conversionService.convert(202101, YearMonth::class.java) }
29+
}
30+
}

src/test/kotlin/com/mapk/krowmapper/KParameterFlattenTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ class KParameterFlattenTest {
2828
@DisplayName("スネークケースsrc -> キャメルケースdst")
2929
fun test() {
3030
val resultSet = mockk<ResultSet>() {
31-
every { getObject("baz_baz_foo_foo", any<Class<*>>()) } returns 1
32-
every { getObject("baz_baz_bar_bar", any<Class<*>>()) } returns "str"
33-
every { getObject("qux_qux", any<Class<*>>()) } returns LocalDateTime.MIN
31+
every { getObject("baz_baz_foo_foo") } returns 1
32+
every { getObject("baz_baz_bar_bar") } returns "str"
33+
every { getObject("qux_qux") } returns LocalDateTime.MIN
3434
}
3535

3636
val result = KRowMapper<Dst>(this::camelToSnake).mapRow(resultSet, 0)
3737
assertEquals(expected, result)
3838

39-
verify(exactly = 1) { resultSet.getObject("baz_baz_foo_foo", Integer::class.java) }
40-
verify(exactly = 1) { resultSet.getObject("baz_baz_bar_bar", String::class.java) }
41-
verify(exactly = 1) { resultSet.getObject("qux_qux", LocalDateTime::class.java) }
39+
verify(exactly = 1) { resultSet.getObject("baz_baz_foo_foo") }
40+
verify(exactly = 1) { resultSet.getObject("baz_baz_bar_bar") }
41+
verify(exactly = 1) { resultSet.getObject("qux_qux") }
4242
}
4343
}

src/test/kotlin/com/mapk/krowmapper/SimpleMappingTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ class SimpleMappingTest {
1919
@DisplayName("スネークケースsrc -> キャメルケースdst")
2020
fun test() {
2121
val resultSet = mockk<ResultSet>()
22-
every { resultSet.getObject("foo_id", any<Class<*>>()) } returns 1
23-
every { resultSet.getObject("str_value", any<Class<*>>()) } returns "str"
22+
every { resultSet.getObject("foo_id") } returns 1
23+
every { resultSet.getObject("str_value") } returns "str"
2424

2525
val result = KRowMapper(::Dst, this::camelToSnake).mapRow(resultSet, 0)
2626

2727
assertEquals(1, result.fooId)
2828
assertEquals("str", result.strValue)
2929

30-
verify(exactly = 1) { resultSet.getObject("foo_id", Integer::class.java) }
31-
verify(exactly = 1) { resultSet.getObject("str_value", String::class.java) }
30+
verify(exactly = 1) { resultSet.getObject("foo_id") }
31+
verify(exactly = 1) { resultSet.getObject("str_value") }
3232
}
3333
}

0 commit comments

Comments
 (0)