From ee52ad2d921f231a9f0fc6a78c421a2ec1015e12 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Mon, 11 Nov 2019 16:31:46 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Redis=20Caching=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 캐싱을 위한 설정및 의존성 추가 - 주소 검색, 배달 가능지역 조회에 시범적으로 캐싱 적용 --- .../com/delfood/FoodDeliveryApplication.java | 2 ++ .../java/com/delfood/config/CacheKeys.java | 8 +++++ .../java/com/delfood/config/RedisConfig.java | 36 +++++++++++++++++++ .../controller/LocationController.java | 20 ++++------- .../reqeust/GetAddressesRequest.java | 4 +-- src/main/resources/mybatis/mapper/address.xml | 2 +- 6 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/delfood/config/CacheKeys.java diff --git a/src/main/java/com/delfood/FoodDeliveryApplication.java b/src/main/java/com/delfood/FoodDeliveryApplication.java index 8725678..edb5315 100644 --- a/src/main/java/com/delfood/FoodDeliveryApplication.java +++ b/src/main/java/com/delfood/FoodDeliveryApplication.java @@ -2,6 +2,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; @@ -20,6 +21,7 @@ * id값이 존재한다면 서버에서는 이 세션의 주인이 이 id를 가진 사용자라고 확인하게 되고 관련된 정보를 사용할 수 있도록 조회한다. */ @EnableAspectJAutoProxy // 최상위 클래스에 적용해야 AOP를 찾을 수 있도록 만들어준다. +@EnableCaching // Spring에서 Caching을 사용하겠다고 선언한다. public class FoodDeliveryApplication { public static void main(String[] args) { diff --git a/src/main/java/com/delfood/config/CacheKeys.java b/src/main/java/com/delfood/config/CacheKeys.java new file mode 100644 index 0000000..acffddb --- /dev/null +++ b/src/main/java/com/delfood/config/CacheKeys.java @@ -0,0 +1,8 @@ +package com.delfood.config; + +public class CacheKeys { + private CacheKeys() {} + + public static final String DELIVERY_LOCATION = "DELIVERY_LOCATION_CACHE_KEY"; + public static final String ADDRESS_SEARCH = "ADDRESS_SERCH_CACHE_KEY"; +} diff --git a/src/main/java/com/delfood/config/RedisConfig.java b/src/main/java/com/delfood/config/RedisConfig.java index d66c17f..7d9e821 100644 --- a/src/main/java/com/delfood/config/RedisConfig.java +++ b/src/main/java/com/delfood/config/RedisConfig.java @@ -4,14 +4,18 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.time.Duration; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @@ -25,6 +29,9 @@ public class RedisConfig { @Value("${spring.redis.password}") private String redisPwd; + @Value("${spring.redis.defaultExpireSecond}") + private long defaultExpireSecond; + /* * Class <=> Json간 변환을 담당한다. * @@ -82,4 +89,33 @@ public RedisTemplate redisTemplate(ObjectMapper objectMapper) { return redisTemplate; } + /** + * Redis Cache를 사용하기 위한 cache manager 등록.
+ * 커스텀 설정을 적용하기 위해 RedisCacheConfiguration을 먼저 생성한다.
+ * 이후 RadisCacheManager를 생성할 때 cacheDefaults의 인자로 configuration을 주면 해당 설정이 적용된다.
+ * RedisCacheConfiguration 설정
+ * disableCachingNullValues - null값이 캐싱될 수 없도록 설정한다. null값 캐싱이 시도될 경우 에러를 발생시킨다.
+ * entryTtl - 캐시의 TTL(Time To Live)를 설정한다. Duraction class로 설정할 수 있다.
+ * serializeKeysWith - 캐시 Key를 직렬화-역직렬화 하는데 사용하는 Pair를 지정한다.
+ * serializeValuesWith - 캐시 Value를 직렬화-역직렬화 하는데 사용하는 Pair를 지정한다. + * Value는 다양한 자료구조가 올 수 있기 때문에 GenericJackson2JsonRedisSerializer를 사용한다. + * + * @author jun + * @param redisConnectionFactory Redis와의 연결을 담당한다. + * @return + */ + @Bean + public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, + ObjectMapper objectMapper) { + RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig() + .disableCachingNullValues().entryTtl(Duration.ofSeconds(defaultExpireSecond)) + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair + .fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper))); + + return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(configuration).build(); + } + } diff --git a/src/main/java/com/delfood/controller/LocationController.java b/src/main/java/com/delfood/controller/LocationController.java index f4742b7..7d50743 100644 --- a/src/main/java/com/delfood/controller/LocationController.java +++ b/src/main/java/com/delfood/controller/LocationController.java @@ -1,23 +1,21 @@ package com.delfood.controller; import com.delfood.aop.OwnerShopCheck; +import com.delfood.config.CacheKeys; import com.delfood.controller.reqeust.GetAddressesRequest; import com.delfood.dto.AddressDTO; import com.delfood.dto.DeliveryLocationDTO; import com.delfood.service.AddressService; import com.delfood.service.ShopService; import java.util.List; -import java.util.Map; import java.util.Set; import javax.servlet.http.HttpSession; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NonNull; -import lombok.RequiredArgsConstructor; import lombok.Setter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -61,6 +59,7 @@ public void addDeliveryLocation( * @param shopId 배달가능 지역을 조회할 매장의 id * @return */ + @Cacheable(value = CacheKeys.DELIVERY_LOCATION, key = "#shopId") @GetMapping("deliveries/{shopId}/possibles") @OwnerShopCheck public List getDeliveryLocations( @@ -93,12 +92,12 @@ public void deleteDeliveryLocation( * @param requestInfo 검색할 주소 정보. * @return */ + @Cacheable(value = CacheKeys.ADDRESS_SEARCH, key = "#requestInfo") @GetMapping("address") - public ResponseEntity getAddressByZipInfo( + public List getAddressByZipInfo( GetAddressesRequest requestInfo) { List addresses = addressService.getAddressByZipAddress(requestInfo); - return new ResponseEntity( - new GetAddressesByZipInfo(addresses), HttpStatus.OK); + return addresses; } @@ -112,11 +111,4 @@ private static class AddDeliveryLocationRequest { } - // ---------------------- Response 객체 ---------------------- - - @Getter - @AllArgsConstructor - private static class GetAddressesByZipInfo { - private List addresses; - } } diff --git a/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java b/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java index f333618..3f6df4c 100644 --- a/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java +++ b/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java @@ -2,12 +2,12 @@ import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Getter @Setter +@ToString public class GetAddressesRequest { - private String cityName; - private String cityCountryName; private String townName; private Integer buildingNumber; private Integer buildingSideNumber; diff --git a/src/main/resources/mybatis/mapper/address.xml b/src/main/resources/mybatis/mapper/address.xml index f74b305..fd69556 100644 --- a/src/main/resources/mybatis/mapper/address.xml +++ b/src/main/resources/mybatis/mapper/address.xml @@ -55,7 +55,7 @@ WHERE 1 = 1 - AND town name LIKE CONCAT(#{townName}, '%') + AND town_name LIKE CONCAT(#{townName}, '%') AND road_name LIKE CONCAT(#{roadName}, '%') From 557512a9ebb0c05a438347a4f2616b509c347acc Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Thu, 14 Nov 2019 10:00:36 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20-?= =?UTF-8?q?=20=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20toString=20=EC=98=A4?= =?UTF-8?q?=EB=B2=84=EB=9D=BC=EC=9D=B4=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20-?= =?UTF-8?q?=20CacheKeys=20=EC=83=81=EC=88=98=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=9C=EA=B1=B0=20-=20=EA=B0=80=EB=8F=85?= =?UTF-8?q?=EC=84=B1=20=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EA=B0=9C=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/delfood/config/CacheKeys.java | 8 -------- src/main/java/com/delfood/config/RedisConfig.java | 9 ++++++--- .../java/com/delfood/controller/LocationController.java | 5 ++--- .../delfood/controller/reqeust/GetAddressesRequest.java | 1 - 4 files changed, 8 insertions(+), 15 deletions(-) delete mode 100644 src/main/java/com/delfood/config/CacheKeys.java diff --git a/src/main/java/com/delfood/config/CacheKeys.java b/src/main/java/com/delfood/config/CacheKeys.java deleted file mode 100644 index acffddb..0000000 --- a/src/main/java/com/delfood/config/CacheKeys.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.delfood.config; - -public class CacheKeys { - private CacheKeys() {} - - public static final String DELIVERY_LOCATION = "DELIVERY_LOCATION_CACHE_KEY"; - public static final String ADDRESS_SEARCH = "ADDRESS_SERCH_CACHE_KEY"; -} diff --git a/src/main/java/com/delfood/config/RedisConfig.java b/src/main/java/com/delfood/config/RedisConfig.java index 7d9e821..bce5bc0 100644 --- a/src/main/java/com/delfood/config/RedisConfig.java +++ b/src/main/java/com/delfood/config/RedisConfig.java @@ -108,10 +108,13 @@ public RedisTemplate redisTemplate(ObjectMapper objectMapper) { public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) { RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig() - .disableCachingNullValues().entryTtl(Duration.ofSeconds(defaultExpireSecond)) + .disableCachingNullValues() + .entryTtl(Duration.ofSeconds(defaultExpireSecond)) .serializeKeysWith( - RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) - .serializeValuesWith(RedisSerializationContext.SerializationPair + RedisSerializationContext.SerializationPair + .fromSerializer(new StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper))); return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory) diff --git a/src/main/java/com/delfood/controller/LocationController.java b/src/main/java/com/delfood/controller/LocationController.java index 7d50743..0b9e230 100644 --- a/src/main/java/com/delfood/controller/LocationController.java +++ b/src/main/java/com/delfood/controller/LocationController.java @@ -1,7 +1,6 @@ package com.delfood.controller; import com.delfood.aop.OwnerShopCheck; -import com.delfood.config.CacheKeys; import com.delfood.controller.reqeust.GetAddressesRequest; import com.delfood.dto.AddressDTO; import com.delfood.dto.DeliveryLocationDTO; @@ -59,7 +58,7 @@ public void addDeliveryLocation( * @param shopId 배달가능 지역을 조회할 매장의 id * @return */ - @Cacheable(value = CacheKeys.DELIVERY_LOCATION, key = "#shopId") + @Cacheable(value = "DELIVERY_LOCATION", key = "#shopId") @GetMapping("deliveries/{shopId}/possibles") @OwnerShopCheck public List getDeliveryLocations( @@ -92,7 +91,7 @@ public void deleteDeliveryLocation( * @param requestInfo 검색할 주소 정보. * @return */ - @Cacheable(value = CacheKeys.ADDRESS_SEARCH, key = "#requestInfo") + @Cacheable(value = "ADDRESS_SERCH", key = "#requestInfo") @GetMapping("address") public List getAddressByZipInfo( GetAddressesRequest requestInfo) { diff --git a/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java b/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java index 3f6df4c..856137f 100644 --- a/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java +++ b/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java @@ -6,7 +6,6 @@ @Getter @Setter -@ToString public class GetAddressesRequest { private String townName; private Integer buildingNumber; From ef57e77b8ec1e6bd44a36d4ce006bc3a1f51882d Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Mon, 18 Nov 2019 11:49:56 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Redis=20Connection=20Factory=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/delfood/config/RedisConfig.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/delfood/config/RedisConfig.java b/src/main/java/com/delfood/config/RedisConfig.java index bce5bc0..4a2c50b 100644 --- a/src/main/java/com/delfood/config/RedisConfig.java +++ b/src/main/java/com/delfood/config/RedisConfig.java @@ -31,6 +31,7 @@ public class RedisConfig { @Value("${spring.redis.defaultExpireSecond}") private long defaultExpireSecond; + /* * Class <=> Json간 변환을 담당한다. @@ -54,12 +55,16 @@ public ObjectMapper objectMapper() { } /* - * Redis Connection Factory library별 특징 1. Jedis - java의 표준 redis client library Connection Poll을 - * 적용하여 높은 TPS를 요구하면 Redis의 CPU 점유율이 높아져 문제가 발생할 수 있다. + * Redis Connection Factory library별 특징 + * 1. Jedis - 멀티쓰레드환경에서 쓰레드 안전을 보장하지 않는다. + * - Connection pool을 사용하여 개선이 가능하지만 Lettuce보다 상대젖ㄱ으로 하드웨어적인 자원이 많이 필요하다. + * - 비동기 기능을 제공하지 않는다. * - * 2. Lettuce - Netty 기반 redis client library 비동기로 요청하기 때문에 Jedis에 비해 높은 성능을 가지고 있다. + * 2. Lettuce - Netty 기반 redis client library + * - 비동기로 요청하기 때문에 Jedis에 비해 높은 성능을 가지고 있다. + * - TPS, 자원사용량 모두 Jedis에 비해 우수한 성능을 보인다는 테스트 사례가 있다. * - * Jedis와 Lettuce의 성능 비교 https://jojoldu.tistory.com/418 + * Jedis와 Lettuce의 성능 비교 https://jojoldu.tistory.com/418 */ @Bean public RedisConnectionFactory redisConnectionFactory() { @@ -79,7 +84,7 @@ public RedisTemplate redisTemplate(ObjectMapper objectMapper) { redisTemplate.setConnectionFactory(redisConnectionFactory()); // json 형식으로 데이터를 받을 때 // 값이 깨지지 않도록 직렬화한다. - // 저장할 클래스가 여러개일 경우 범용 JacsonJerializer인 GenericJackson2JsonRedisSerializer를 이용한다 + // 저장할 클래스가 여러개일 경우 범용 JacsonSerializer인 GenericJackson2JsonRedisSerializer를 이용한다 // 참고 https://somoly.tistory.com/134 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(serializer); From 73ac44e93c593222dac92601b1bb052159280f33 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Mon, 18 Nov 2019 11:51:19 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/delfood/config/RedisConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/delfood/config/RedisConfig.java b/src/main/java/com/delfood/config/RedisConfig.java index 4a2c50b..9670b07 100644 --- a/src/main/java/com/delfood/config/RedisConfig.java +++ b/src/main/java/com/delfood/config/RedisConfig.java @@ -57,7 +57,7 @@ public ObjectMapper objectMapper() { /* * Redis Connection Factory library별 특징 * 1. Jedis - 멀티쓰레드환경에서 쓰레드 안전을 보장하지 않는다. - * - Connection pool을 사용하여 개선이 가능하지만 Lettuce보다 상대젖ㄱ으로 하드웨어적인 자원이 많이 필요하다. + * - Connection pool을 사용하여 개선이 가능하지만 Lettuce보다 상대적으로 하드웨어적인 자원이 많이 필요하다. * - 비동기 기능을 제공하지 않는다. * * 2. Lettuce - Netty 기반 redis client library From 34e2533891c32ba97b40f8fe8a3f385240010167 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Thu, 21 Nov 2019 15:41:09 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=EC=BA=90=EC=8B=B1=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20-=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20=EA=B2=80=EC=83=89=20Request=EB=A5=BC=20=EB=8F=84?= =?UTF-8?q?=EB=A1=9C=EB=AA=85,=20=EC=A7=80=EB=B2=88=EA=B2=80=EC=83=89?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=82=98=EB=88=94=20-=20=EC=BA=90?= =?UTF-8?q?=EC=8B=B1=20=ED=82=A4=EB=A5=BC=20=EC=A0=9C=EC=9E=91=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EC=9E=91=20-=20?= =?UTF-8?q?=EC=BA=90=EC=8B=B1=EC=9D=84=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=EC=97=90=EC=84=9C=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주소 검색 쿼리 수정 - 통합 검색을 도로명, 지번 검색에 따른 쿼리로 나눔 - NULL값이 들어올 경우 공백으로 검색하도록 변경 --- .../java/com/delfood/config/RedisConfig.java | 4 +- .../controller/LocationController.java | 28 ++++++++++---- .../reqeust/GetAddressByZipRequest.java | 17 +++++++++ .../reqeust/GetAddressRequestBase.java | 28 ++++++++++++++ .../reqeust/GetAddressesByRoadRequest.java | 17 +++++++++ .../reqeust/GetAddressesRequest.java | 16 -------- .../com/delfood/mapper/AddressMapper.java | 7 +++- .../com/delfood/service/AddressService.java | 13 +++++-- .../com/delfood/service/OwnerService.java | 1 + .../java/com/delfood/service/ShopService.java | 2 + src/main/resources/mybatis/mapper/address.xml | 37 +++++++++---------- .../delfood/service/MemberServiceTest.java | 16 ++++++++ 12 files changed, 137 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/delfood/controller/reqeust/GetAddressByZipRequest.java create mode 100644 src/main/java/com/delfood/controller/reqeust/GetAddressRequestBase.java create mode 100644 src/main/java/com/delfood/controller/reqeust/GetAddressesByRoadRequest.java delete mode 100644 src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java create mode 100644 src/test/java/com/delfood/service/MemberServiceTest.java diff --git a/src/main/java/com/delfood/config/RedisConfig.java b/src/main/java/com/delfood/config/RedisConfig.java index 9670b07..ea9a8fe 100644 --- a/src/main/java/com/delfood/config/RedisConfig.java +++ b/src/main/java/com/delfood/config/RedisConfig.java @@ -57,7 +57,7 @@ public ObjectMapper objectMapper() { /* * Redis Connection Factory library별 특징 * 1. Jedis - 멀티쓰레드환경에서 쓰레드 안전을 보장하지 않는다. - * - Connection pool을 사용하여 개선이 가능하지만 Lettuce보다 상대적으로 하드웨어적인 자원이 많이 필요하다. + * - Connection pool을 사용하여 성능, 안정성 개선이 가능하지만 Lettuce보다 상대적으로 하드웨어적인 자원이 많이 필요하다. * - 비동기 기능을 제공하지 않는다. * * 2. Lettuce - Netty 기반 redis client library @@ -84,7 +84,7 @@ public RedisTemplate redisTemplate(ObjectMapper objectMapper) { redisTemplate.setConnectionFactory(redisConnectionFactory()); // json 형식으로 데이터를 받을 때 // 값이 깨지지 않도록 직렬화한다. - // 저장할 클래스가 여러개일 경우 범용 JacsonSerializer인 GenericJackson2JsonRedisSerializer를 이용한다 + // 저장할 클래스가 여러개일 경우 범용 JacksonSerializer인 GenericJackson2JsonRedisSerializer를 이용한다 // 참고 https://somoly.tistory.com/134 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(serializer); diff --git a/src/main/java/com/delfood/controller/LocationController.java b/src/main/java/com/delfood/controller/LocationController.java index 0b9e230..23c0dcb 100644 --- a/src/main/java/com/delfood/controller/LocationController.java +++ b/src/main/java/com/delfood/controller/LocationController.java @@ -1,7 +1,8 @@ package com.delfood.controller; import com.delfood.aop.OwnerShopCheck; -import com.delfood.controller.reqeust.GetAddressesRequest; +import com.delfood.controller.reqeust.GetAddressByZipRequest; +import com.delfood.controller.reqeust.GetAddressesByRoadRequest; import com.delfood.dto.AddressDTO; import com.delfood.dto.DeliveryLocationDTO; import com.delfood.service.AddressService; @@ -58,7 +59,6 @@ public void addDeliveryLocation( * @param shopId 배달가능 지역을 조회할 매장의 id * @return */ - @Cacheable(value = "DELIVERY_LOCATION", key = "#shopId") @GetMapping("deliveries/{shopId}/possibles") @OwnerShopCheck public List getDeliveryLocations( @@ -85,16 +85,30 @@ public void deleteDeliveryLocation( } /** - * 주소를 검색한다. + * 도로명 주소를 검색한다. * * @author jun - * @param requestInfo 검색할 주소 정보. + * @param requestInfo 검색할 도로명 주소 정보. * @return */ - @Cacheable(value = "ADDRESS_SERCH", key = "#requestInfo") - @GetMapping("address") + @GetMapping("address/road") + public List getAddressByRoadInfo( + GetAddressesByRoadRequest requestInfo) { + List addresses = addressService.getAddressByRoadName(requestInfo); + return addresses; + } + + + /** + * 지번 주소를 검색한다. + * + * @author jun + * @param requestInfo 검색할 지번 주소 정보. + * @return + */ + @GetMapping("address/zip") public List getAddressByZipInfo( - GetAddressesRequest requestInfo) { + GetAddressByZipRequest requestInfo) { List addresses = addressService.getAddressByZipAddress(requestInfo); return addresses; } diff --git a/src/main/java/com/delfood/controller/reqeust/GetAddressByZipRequest.java b/src/main/java/com/delfood/controller/reqeust/GetAddressByZipRequest.java new file mode 100644 index 0000000..07f6f84 --- /dev/null +++ b/src/main/java/com/delfood/controller/reqeust/GetAddressByZipRequest.java @@ -0,0 +1,17 @@ +package com.delfood.controller.reqeust; + +import javax.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GetAddressByZipRequest extends GetAddressRequestBase { + @NotNull + private String townName; + + + protected String generateKey() { + return "townName:" + this.townName; + } +} diff --git a/src/main/java/com/delfood/controller/reqeust/GetAddressRequestBase.java b/src/main/java/com/delfood/controller/reqeust/GetAddressRequestBase.java new file mode 100644 index 0000000..c66884f --- /dev/null +++ b/src/main/java/com/delfood/controller/reqeust/GetAddressRequestBase.java @@ -0,0 +1,28 @@ +package com.delfood.controller.reqeust; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public abstract class GetAddressRequestBase { + protected Integer buildingNumber; + protected Integer buildingSideNumber; + protected String buildingNameForCity; + protected String lastSearchBuildingManagementNumber; + + protected abstract String generateKey(); + + /** + * 캐싱을 진행할 키를 제작한다. + * @author jun + * @return + */ + public String getKey() { + return "buildingNumber:" + this.buildingNumber + "/" + + "buildingSideNumber:" + this.buildingSideNumber + "/" + + "buildingNameForCity" + this.buildingNameForCity + "/" + + "lastSearchBuildingManagementNumber" + this.lastSearchBuildingManagementNumber + "/" + + generateKey(); + } +} diff --git a/src/main/java/com/delfood/controller/reqeust/GetAddressesByRoadRequest.java b/src/main/java/com/delfood/controller/reqeust/GetAddressesByRoadRequest.java new file mode 100644 index 0000000..0c968e0 --- /dev/null +++ b/src/main/java/com/delfood/controller/reqeust/GetAddressesByRoadRequest.java @@ -0,0 +1,17 @@ +package com.delfood.controller.reqeust; + +import javax.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +public class GetAddressesByRoadRequest extends GetAddressRequestBase { + @NotNull + private String roadName; + + protected String generateKey() { + return "roadName" + roadName; + } +} diff --git a/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java b/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java deleted file mode 100644 index 856137f..0000000 --- a/src/main/java/com/delfood/controller/reqeust/GetAddressesRequest.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.delfood.controller.reqeust; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -@Getter -@Setter -public class GetAddressesRequest { - private String townName; - private Integer buildingNumber; - private Integer buildingSideNumber; - private String buildingNameForCity; - private String roadName; - private String lastSearchBuildingManagementNumber; -} diff --git a/src/main/java/com/delfood/mapper/AddressMapper.java b/src/main/java/com/delfood/mapper/AddressMapper.java index c356248..e95e718 100644 --- a/src/main/java/com/delfood/mapper/AddressMapper.java +++ b/src/main/java/com/delfood/mapper/AddressMapper.java @@ -1,6 +1,7 @@ package com.delfood.mapper; -import com.delfood.controller.reqeust.GetAddressesRequest; +import com.delfood.controller.reqeust.GetAddressByZipRequest; +import com.delfood.controller.reqeust.GetAddressesByRoadRequest; import com.delfood.dto.AddressDTO; import java.util.List; import org.springframework.stereotype.Repository; @@ -9,5 +10,7 @@ public interface AddressMapper { public List findByShopId(Long shopId); - public List findByZipAddress(GetAddressesRequest searchInfo); + public List findByZipName(GetAddressByZipRequest searchInfo); + + public List findByRoadName(GetAddressesByRoadRequest searchInfo); } diff --git a/src/main/java/com/delfood/service/AddressService.java b/src/main/java/com/delfood/service/AddressService.java index d097934..d7843a4 100644 --- a/src/main/java/com/delfood/service/AddressService.java +++ b/src/main/java/com/delfood/service/AddressService.java @@ -1,10 +1,12 @@ package com.delfood.service; -import com.delfood.controller.reqeust.GetAddressesRequest; +import com.delfood.controller.reqeust.GetAddressByZipRequest; +import com.delfood.controller.reqeust.GetAddressesByRoadRequest; import com.delfood.dto.AddressDTO; import com.delfood.mapper.AddressMapper; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service @@ -17,8 +19,13 @@ public List getTownInfoByShopId(Long shopId) { return addressMapper.findByShopId(shopId); } + @Cacheable(value = "ADDRESS_SERCH_ZIP", key = "#searchInfo.getKey()") + public List getAddressByZipAddress(GetAddressByZipRequest searchInfo) { + return addressMapper.findByZipName(searchInfo); + } - public List getAddressByZipAddress(GetAddressesRequest searchInfo) { - return addressMapper.findByZipAddress(searchInfo); + @Cacheable(value = "ADDRESS_SERCH_ROAD", key = "#searchInfo.getKey()") + public List getAddressByRoadName(GetAddressesByRoadRequest searchInfo) { + return addressMapper.findByRoadName(searchInfo); } } diff --git a/src/main/java/com/delfood/service/OwnerService.java b/src/main/java/com/delfood/service/OwnerService.java index 0ea8a2c..196583f 100644 --- a/src/main/java/com/delfood/service/OwnerService.java +++ b/src/main/java/com/delfood/service/OwnerService.java @@ -5,6 +5,7 @@ import com.delfood.utils.SHA256Util; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/delfood/service/ShopService.java b/src/main/java/com/delfood/service/ShopService.java index cf18405..17fcb5b 100644 --- a/src/main/java/com/delfood/service/ShopService.java +++ b/src/main/java/com/delfood/service/ShopService.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -220,6 +221,7 @@ public ShopDTO getShop(Long shopId) { * @param shopId 조회할 매장의 아이디 * @return */ + @Cacheable(value = "DELIVERY_LOCATION", key = "#shopId") public List getDeliveryLocations(Long shopId) { return deliveryLocateionMapper.findByShopId(shopId); } diff --git a/src/main/resources/mybatis/mapper/address.xml b/src/main/resources/mybatis/mapper/address.xml index fd69556..c9ab3bf 100644 --- a/src/main/resources/mybatis/mapper/address.xml +++ b/src/main/resources/mybatis/mapper/address.xml @@ -51,27 +51,26 @@ LIMIT 10 - WHERE 1 = 1 - - AND town_name LIKE CONCAT(#{townName}, '%') - - - AND road_name LIKE CONCAT(#{roadName}, '%') - - - AND building_number LIKE CONCAT(#{buildingNumber}, '%') - - - AND building_side_number LIKE CONCAT(#{buildingSideNumber}, '%') - - - AND building_name_for_city LIKE CONCAT(#{buildingNameForCity}, '%') - - - AND building_management_number > #{lastSearchBuildingManagementNumber} - + AND town_name LIKE CONCAT(IFNULL(#{townName}, ''), '%') + AND building_number LIKE CONCAT(IFNULL(#{buildingNumber}, ''), '%') + AND building_side_number LIKE CONCAT(IFNULL(#{buildingSideNumber}, ''), '%') + AND building_name_for_city LIKE CONCAT(IFNULL(#{buildingNameForCity}, ''), '%') + AND building_management_number > IFNULL(#{lastSearchBuildingManagementNumber}, '0') + ORDER BY building_management_number + LIMIT 10 + + + diff --git a/src/test/java/com/delfood/service/MemberServiceTest.java b/src/test/java/com/delfood/service/MemberServiceTest.java new file mode 100644 index 0000000..661c5d4 --- /dev/null +++ b/src/test/java/com/delfood/service/MemberServiceTest.java @@ -0,0 +1,16 @@ +package com.delfood.service; + +import static org.junit.Assert.*; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class MemberServiceTest { + + @Test + public void test() { + fail("Not yet implemented"); + } + +} From 07bed6347db654dac73976c9f16a1aba54b12d01 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Thu, 21 Nov 2019 17:53:27 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/delfood/service/MemberService.java | 3 +- .../delfood/service/MemberServiceTest.java | 145 +++++++++++++++++- 2 files changed, 141 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/delfood/service/MemberService.java b/src/main/java/com/delfood/service/MemberService.java index ef3ed7d..8694b5a 100644 --- a/src/main/java/com/delfood/service/MemberService.java +++ b/src/main/java/com/delfood/service/MemberService.java @@ -69,11 +69,10 @@ public boolean isDuplicatedId(String id) { public void updateMemberPassword(String id, String password) { String cryptoPassword = SHA256Util.encryptSHA256(password); int result = memberMapper.updateMemberPassword(id, cryptoPassword); - if (result == 1) { + if (result != 1) { log.error("update Member ERROR! id : {}, pw : {}", id, password); throw new RuntimeException("update Member Password ERROR!"); } - } /** diff --git a/src/test/java/com/delfood/service/MemberServiceTest.java b/src/test/java/com/delfood/service/MemberServiceTest.java index 661c5d4..94a964f 100644 --- a/src/test/java/com/delfood/service/MemberServiceTest.java +++ b/src/test/java/com/delfood/service/MemberServiceTest.java @@ -1,16 +1,151 @@ package com.delfood.service; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.delfood.dto.MemberDTO; +import com.delfood.mapper.MemberMapper; +import com.delfood.utils.SHA256Util; +import java.time.LocalDateTime; +import org.hamcrest.core.IsEqual; +import org.hamcrest.core.IsNull; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +// Mockito에서 사용하는 목 객체를 사용하기 위해 적용한다. @RunWith(MockitoJUnitRunner.class) public class MemberServiceTest { - + /* + * '@Mock'이 붙은 목 객체를 해당 어노테이션이 선언된 객체에 주입할 수 있다. + * Dao객체를 주입하기 위해서는 Dao에 '@Mock'을, Service에' @InjectMocks'를 붙여주어야한다. + */ + @InjectMocks + MemberService service; + + // Mock객체를 생성한다. + @Mock + MemberMapper mapper; + + // 새로운 멤버 객체를 생성하여 반환한다. + public MemberDTO generateMember() { + MemberDTO member = new MemberDTO(); + member.setId("testMemberId"); + member.setPassword("testMemberPassword"); + member.setName("testUserName"); + member.setTel("010-1111-2222"); + member.setMail("test@mail.com"); + member.setAddressCode("3023010100100090018000001"); + member.setAddressDetail("102호"); + member.setStatus(MemberDTO.Status.DEFAULT); + member.setCreatedAt(LocalDateTime.now()); + member.setUpdatedAt(LocalDateTime.now()); + + return member; + } + @Test - public void test() { - fail("Not yet implemented"); + public void insertMemberTest_고객_회원가입_성공() { + MemberDTO member = generateMember(); + given(mapper.insertMember(member)).willReturn(1); + service.insertMember(member); + } + + @Test(expected = RuntimeException.class) + public void insertMemberTest_고객_회원가입_실패() { + MemberDTO member = generateMember(); + given(mapper.insertMember(member)).willReturn(0); + service.insertMember(member); + } + + @Test + public void loginTest_고객_로그인_성공() { + MemberDTO member = generateMember(); + given(mapper.findByIdAndPassword("testMemberId", + SHA256Util.encryptSHA256("testMemberPassword"))) + .willReturn(member); + + assertThat(service.login("testMemberId", "testMemberPassword")).isEqualTo(member); + } + + @Test + public void loginTest_고객_로그인_실패_테스트_비밀번호_불일치() { + given(mapper.findByIdAndPassword("testMemberId", + SHA256Util.encryptSHA256("aaaa"))) + .willReturn(null); + + assertThat(service.login("testMemberId", "aaaa")).isNull(); + } + + @Test + public void updateMemberPasswordTest_고객_비밀번호_변경_성공() { + MemberDTO member = generateMember(); + given(mapper.updateMemberPassword(member.getId(), SHA256Util.encryptSHA256("1234"))) + .willReturn(1); + + service.updateMemberPassword(member.getId(), "1234"); + } + + @Test(expected = RuntimeException.class) + public void updateMemberPasswordTest_고객_비밀번호_변경_실패() { + MemberDTO member = generateMember(); + given(mapper.updateMemberPassword(member.getId(), SHA256Util.encryptSHA256("1234"))) + .willReturn(0); + + service.updateMemberPassword(member.getId(), "1234"); + } + + @Test + public void deleteMemberTest_고객_삭제_성공() { + MemberDTO member = generateMember(); + given(mapper.deleteMember(member.getId())) + .willReturn(1); + + service.deleteMember(member.getId()); + } + + @Test(expected = RuntimeException.class) + public void deleteMember_고객_삭제_실패() { + MemberDTO member = generateMember(); + given(mapper.deleteMember(member.getId())) + .willReturn(0); + + service.deleteMember(member.getId()); + } + + @Test + public void updateMemberAddress_고객_주소_변경_성공() { + MemberDTO member = generateMember(); + given(mapper.updateMemberAddress(member.getId(), "3023010100100090028000002", "1023호")) + .willReturn(1); + + service.updateMemberAddress(member.getId(), "3023010100100090028000002", "1023호"); + } + + @Test(expected = RuntimeException.class) + public void updateMemberAddress_고객_주소_변경_실패() { + MemberDTO member = generateMember(); + given(mapper.updateMemberAddress(member.getId(), "3023010100100090028000002", "1023호")) + .willReturn(0); + + service.updateMemberAddress(member.getId(), "3023010100100090028000002", "1023호"); + } + + @Test + public void getTownCode_고객_읍면동코드_조회() { + MemberDTO member = generateMember(); + given(mapper.findTownCodeById(member.getId())) + .willReturn(member.getAddressCode().substring(0, 10)); + + assertThat(service.getTownCode(member.getId())).isEqualTo("3023010100"); } - }