Skip to content
This repository has been archived by the owner on Nov 21, 2023. It is now read-only.

Commit

Permalink
[#40] Collection converter 추가 (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
chanhyeong authored Dec 3, 2021
1 parent 539d424 commit 87ff694
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.sns.commons.config

import com.sns.commons.oauth.Role
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Profile
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.sns.user.component.user.domains

import java.time.Instant
import javax.validation.constraints.Max
import javax.validation.constraints.NotNull
import org.hibernate.validator.constraints.URL
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.annotation.Transient
import org.springframework.data.domain.Persistable
import org.springframework.data.relational.core.mapping.MappedCollection
import java.time.Instant
import javax.validation.constraints.Max
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size

/**
* 사용자 프로필 도메인 객체(VO)
Expand All @@ -32,9 +30,7 @@ data class Profile(
@Max(200)
val intro: String?, // 소개, 약력

@Size(max = 5)
@MappedCollection
val hobbies: List<Hobby>?, // 취미 목록
val hobbies: MutableList<String>?, // 취미 목록

@LastModifiedDate
var updatedAt: Instant = Instant.MIN,
Expand All @@ -52,7 +48,7 @@ data class Profile(
nickName = nickName,
iconImageUrl = iconImageUrl,
intro = intro,
hobbies = hobbies?.map { e -> Hobby(e) }?.toList(),
hobbies = hobbies?.toMutableList(),
).apply { new = true }
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,77 @@
package com.sns.user.core.config.db.converter

import com.sns.user.component.user.domains.UserId
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.ConditionalGenericConverter
import org.springframework.core.convert.converter.Converter
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
import org.springframework.core.convert.support.DefaultConversionService
import org.springframework.data.convert.ReadingConverter
import org.springframework.data.convert.WritingConverter
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory
import org.springframework.data.jdbc.core.convert.JdbcConverter
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions
import org.springframework.data.jdbc.core.convert.RelationResolver
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration
import org.springframework.data.relational.core.dialect.Dialect
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations
import org.springframework.lang.Nullable
import org.springframework.util.ClassUtils

/**
* @author Hyounglin Jun
*/
@Configuration
class JdbcConfiguration : AbstractJdbcConfiguration() {
@Bean
override fun jdbcConverter(
mappingContext: JdbcMappingContext,
operations: NamedParameterJdbcOperations,
relationResolver: RelationResolver,
conversions: JdbcCustomConversions,
dialect: Dialect
): JdbcConverter {
val jdbcTypeFactory = DefaultJdbcTypeFactory(operations.jdbcOperations)

val baseConverter = BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory, dialect.identifierProcessing)
val jdbcConverter = object : JdbcConverter by baseConverter {
/**
* 기존 collection 을 array 로 변환 (List<String> -> String[])
* 변경: collection 을 내부 클래스로 변환 (List<String> -> String)
*/
override fun getColumnType(property: RelationalPersistentProperty): Class<*> {
return if (property.isCollectionLike && !property.isEntity) {
ClassUtils.resolvePrimitiveIfNecessary(property.actualType)
} else {
baseConverter.getColumnType(property)
}
}
}

// 기본으로 들어가는 Collection -> String, Collection -> Object, String -> Object converter 제거
val conversionService = jdbcConverter.conversionService as DefaultConversionService
conversionService.removeConvertible(String::class.java, Collection::class.java)
conversionService.removeConvertible(Collection::class.java, String::class.java)
conversionService.removeConvertible(Collection::class.java, Object::class.java)

// 별도 추가한 converter 추가
conversionService.addConverter(CollectionToStringConverter())
conversionService.addConverter(StringToCollectionConverter())

return jdbcConverter
}

override fun jdbcCustomConversions(): JdbcCustomConversions {
return JdbcCustomConversions(
listOf(UserIdToStringConverter(), StringToUserIdConverter()),
listOf(
UserIdToStringConverter(),
StringToUserIdConverter(),
),
)
}
}
Expand All @@ -33,3 +89,45 @@ class StringToUserIdConverter : Converter<String, UserId> {
return UserId(source)
}
}

@ReadingConverter
class StringToCollectionConverter : ConditionalGenericConverter {
override fun getConvertibleTypes(): Set<ConvertiblePair> {
return setOf(ConvertiblePair(String::class.java, MutableCollection::class.java))
}

override fun matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean {
return sourceType.type == String::class.java && MutableCollection::class.java.isAssignableFrom(targetType.type)
}

@Nullable
override fun convert(@Nullable source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any? {
if (source == null) {
return null
}
val string = source as String

return string.split(":").toList()
}
}

@WritingConverter
class CollectionToStringConverter : ConditionalGenericConverter {
override fun getConvertibleTypes(): Set<ConvertiblePair> {
return setOf(ConvertiblePair(MutableCollection::class.java, String::class.java))
}

override fun matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean {
return MutableCollection::class.java.isAssignableFrom(sourceType.type) && targetType.type == String::class.java
}

@Nullable
override fun convert(@Nullable source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any? {
if (source == null) {
return null
}
val collection = source as Collection<*>

return collection.joinToString(":")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ data class ProfileResponse(
nickName = profile.nickName,
iconImageUrl = profile.getServiceIconImageUrl(),
intro = profile.intro,
hobbies = profile.hobbies?.map { e -> e.name }?.toList(),
hobbies = profile.hobbies,
)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.sns.user.component.user.repositories

import com.sns.user.component.user.domains.Hobby
import com.sns.user.component.user.domains.Profile
import com.sns.user.hasValueSatisfying
import com.sns.user.isEqualTo
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.transaction.annotation.Transactional

/**
* @author Hyounglin Jun
Expand All @@ -16,15 +16,15 @@ internal class ProfileRepositoryTest {
@Autowired
lateinit var profileRepository: ProfileRepository

@Transactional
@Test
fun save() {
val id = "[email protected]"
val nickName = "닉네임"
val inputHobbies = listOf("밥먹기", "운동하기")
val outputHobbies = listOf(Hobby("밥먹기"), Hobby("운동하기"))
val hobbies = listOf("밥먹기", "운동하기")
val profile = Profile.create(
userId = id, nickName = nickName,
hobbies = inputHobbies,
hobbies = hobbies,
)

profileRepository.save(profile)
Expand All @@ -34,7 +34,7 @@ internal class ProfileRepositoryTest {
savedUser.nickName isEqualTo nickName
savedUser.iconImageUrl isEqualTo null
savedUser.intro isEqualTo null
savedUser.hobbies isEqualTo outputHobbies
savedUser.hobbies isEqualTo hobbies
}
}
}
Expand Down
21 changes: 7 additions & 14 deletions user-api/src/test/resources/db/users/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,14 @@ CREATE TABLE IF NOT EXISTS `user`
updated_at DATETIME NOT NULL COMMENT '마지막 수정 시간'
);

DROP TABLE IF EXISTS `profile`;
CREATE TABLE IF NOT EXISTS `profile` (
user_id VARCHAR(50) NOT NULL PRIMARY KEY COMMENT '아이디 (이메일)',
nick_name VARCHAR(50) COMMENT '닉네임',
CREATE TABLE IF NOT EXISTS `profile`
(
user_id VARCHAR(50) NOT NULL PRIMARY KEY COMMENT '아이디 (이메일)',
nick_name VARCHAR(50) COMMENT '닉네임',
icon_image_url VARCHAR(100) COMMENT '아이콘 이미지 URL',
intro VARCHAR(200) COMMENT '소개, 약력',
hobbies JSON COMMENT '취미 목록',
updated_at DATETIME NOT NULL COMMENT '마지막 수정 시간'
);

DROP TABLE IF EXISTS `hobby`;
CREATE TABLE IF NOT EXISTS `hobby` (
profile_key VARCHAR(50) NOT NULL COMMENT '리스트 순서 번호',
profile VARCHAR(50) NOT NULL COMMENT 'profile의 id',
name VARCHAR(20) NOT NULL COMMENT '취미 이름'
intro VARCHAR(200) COMMENT '소개, 약력',
hobbies VARCHAR(100) COMMENT '취미 목록',
updated_at DATETIME NOT NULL COMMENT '마지막 수정 시간'
);

DROP TABLE IF EXISTS `auth_code`;
Expand Down

0 comments on commit 87ff694

Please sign in to comment.