전문 해석기 배치 LineMapper - BeanWrapperFieldSetMapper와 RecordFieldSetMapper의 차이
LineMapper는 크게 Tokenizer와 FieldSetMapper로 이루어진다. 전문 특성상 LineMapper로는PatternMatchingCompositeLineMapper 를 Tokeinizer로는 FixedLengthTokenizer를 사용하면 되는데
FieldSetMapper로는 세 가지 선택지가 있다.
- BeanWrapperFieldSetMapper
- RecordFieldSetMapper
- 직접 구현
이 중 SpringBoot에서 기본 제공하는 1, 2에 대해 비교해보았다.
1
2
3
4
5
6
class SampleModel() {
var field1: String? = null
var field2: Int? = null
var field3: Double? = null
var field4: BigDecimal? = null
}
1
2
3
4
5
6
data class SampleRecord(
val field1: String,
val field2: Int,
val field3: Double,
val field4: BigDecimal
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class FieldSetMappersTest {
fun tokenize(): FieldSet {
val line = " abcde 000001234500.8000.51"
val tokenizer = FixedLengthTokenizer().apply {
setNames("field1", "field2", "field3", "field4")
setColumns(Range(1, 10), Range(11, 20), Range(21, 25), Range(26, 30))
}
return tokenizer.tokenize(line)
}
@Test
fun tokenizerTest() {
val fs = tokenize()
assert(fs.names[0] == "field1")
assert(fs.names[1] == "field2")
assert(fs.names[2] == "field3")
assert(fs.names[3] == "field4")
assert(fs.values[0] == " abcde ")
assert(fs.values[1] == "0000012345")
assert(fs.values[2] == "00.80")
assert(fs.values[3] == "00.51")
}
@Test
fun beanWrapperFieldSetMapperTest() {
val fs = tokenize()
val beanWrapperFieldSetMapper = BeanWrapperFieldSetMapper<SampleModel>().apply {
setTargetType(SampleModel::class.java)
}
// BeanWrapperFieldSetMapper를 쓰려면 SampleModel에 반드시 NoArgsConstructor가 필요하다.
val sampleModel = beanWrapperFieldSetMapper.mapFieldSet(fs)
assert(sampleModel.field1 == "abcde") // 앞뒤 trim이 자동으로 들어간다. 왜?
assert(sampleModel.field2 == 12345)
assert(sampleModel.field3 == 0.8)
assert(sampleModel.field4 == "0.51".toBigDecimal())
/**
* 전문 field 앞이나 뒤 한쪽에 의도적으로 공백을 넣는 경우가 있을까? 그런 경우가 있다면 대응이 안될 것 같다.
* BeanWrapperFieldSetMapper도 깊숙히 들어가면 ConversionService를 쓰는 것 같긴 한데...
* 아무튼 ConversionService를 직접 컨트롤 할 수 있는 RecordFieldSetMapper가 더 유연해보인다.
* 프로토타입 빈을 쓸 필요가 없는 대부분의 경우 Record쪽이 더 낫지 않을까?
*/
}
@Test
fun recordFieldSetMapperTest() {
val fs = tokenize()
/**
* ConversionService를 명시하지 않는다면 DefaultConversionService가 설정된다.
* SampleRecord는 반드시 java record 일 필요는 없다. kotlin data class와도 잘 호환 된다.
* 생성자 기반 매핑이기 때문에 SampleRecord는 반드시 생성자에 매핑 대상 필드들을 명시해야 한다.
* fieldSet names와 생성자 파라미터의 파라미터 명 전체가 정확히 일치하지 않으면 IllegalArgumentException 발생한다. (good!)
*/
val recordFieldSetMapper = RecordFieldSetMapper(SampleRecord::class.java)
val sampleRecord = recordFieldSetMapper.mapFieldSet(fs)
assert(sampleRecord.field1 == " abcde ") // 앞뒤 trim 자동으로 들어가지 않는다.
assert(sampleRecord.field2 == 12345)
assert(sampleRecord.field3 == 0.8)
assert(sampleRecord.field4 == "0.51".toBigDecimal())
}
}
BeanWrapperFieldSetMapper에서 자동으로 trim이 되는 이유?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DefaultFieldSet implements FieldSet {
@Override
public Properties getProperties() {
if (names == null) {
throw new IllegalStateException("Cannot create properties without meta data");
}
Properties props = new Properties();
for (int i = 0; i < tokens.length; i++) {
String value = readAndTrim(i);
if (value != null) {
props.setProperty(names.get(i), value);
}
}
return props;
}
}
내부에서 property 변환하면서 앞뒤 trim 한다.
ConversionService Hierarch
그렇다면 전문 변환 시 사용할만한 기본 제공 FieldSetMapper는?
- trim이나 align에 대한 세부적인 처리가 필요하지 않은 경우 -> RecordFieldSetMapper
- 전문 해석기 배치 LineMapper - TelegramFieldSetMapper
- 대부분의 경우 이 케이스로 커버된다.
- trim이나 align에 대한 세부적인 처리가 필요한 경우 -> FieldSetMapper 직접 구현 필요.
This post is licensed under CC BY 4.0 by the author.