SpringFramework/Spring

Redis 연동

lovineff 2020. 11. 17. 10:20

build.gradle 파일 의존성 추가

// Redis
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis'

// Data Redis (Redis를 DB화하여 사용하는 경우)
compile "org.springframework.boot:spring-boot-starter-data-redis"
compile "redis.clients:jedis:2.9.0"
compile "org.springframework.session:spring-session-data-redis"

 

application.yml 파일 설정 추가

spring:
  redis:
    host: 127.0.0.1
    port: 6379

 

redis config 빈 설정

방법 1.

@Configuration
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = null;

        try {
            RedisStandaloneConfiguration conn = new RedisStandaloneConfiguration();
            conn.setHostName(host);
            conn.setPort(port);

            jedisConnectionFactory = new JedisConnectionFactory(conn);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return jedisConnectionFactory;
    }

    @Bean(name="redisTemplate")
    public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        final RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericToStringSerializer<>(Object.class));
        redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Object.class));

        redisTemplate.setConnectionFactory(jedisConnectionFactory);

        return redisTemplate;
    }
    
    @Primary
    @Bean("cacheManager")
    public RedisCacheManager cacheManager() {
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(jedisConnectionFactory);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(5L));  // 기본 캐시 5분 처리

        // 캐시 value에 맞춰 아래 ttl을 적용한다.
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        cacheConfigurations.put("ttl1minute", configuration.entryTtl(Duration.ofMinutes(1L)));
        cacheConfigurations.put("ttl5minute", configuration.entryTtl(Duration.ofMinutes(5L)));
        cacheConfigurations.put("ttl10minute", configuration.entryTtl(Duration.ofMinutes(10L)));
        cacheConfigurations.put("article", configuration.entryTtl(Duration.ofMinutes(10L)));

        return builder.cacheDefaults(configuration).withInitialCacheConfigurations(cacheConfigurations).build();
    }
}

방법 .2

RedisConfig 빈 등록

@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {

    /**
     * Cache Key Generator
     * 함수명을 캐시 키로 자동 생성해준다.
     * @return
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (o, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(".");
            sb.append(o.getClass().getSimpleName());
            sb.append(".");
            sb.append(method.getName());
            sb.append("(");
            int index = 0;
            for (Object obj : objects) {
                if (index > 0) {
                    sb.append(",");
                }
                if (o.getClass().isArray()) {
                    sb.append(Arrays.deepToString((Object[]) obj));
                } else {
                    sb.append(obj.toString());
                }
                index++;
            }
            sb.append(")");
            return sb.toString();
        };
    }

    /**
     * String Redis Serializer
     * @return
     */
    @Bean
    public StringRedisSerializer stringRedisSerializer() {
        return new StringRedisSerializer();
    }

    /**
     * Redis Template
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(stringRedisSerializer());
        return redisTemplate;
    }

    /**
     * Redis CacheManager
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer()));

        return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration)
                .withInitialCacheConfigurations(configurationMap())
                .build();
    }

    /**
     * Cache Config Map
     * @return
     */
    private Map<String, RedisCacheConfiguration> configurationMap() {
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        cacheConfigurations.put("cache.day"         , createCacheConfiguration("cache.day", (24 * 60 * 60)));   // 1일
        cacheConfigurations.put("cache.hour"        , createCacheConfiguration("cache.hour", (60 * 60)));       // 1시간
        cacheConfigurations.put("cache.default"     , createCacheConfiguration("cache.default", (5 * 60)));     // 기본 5분
        return cacheConfigurations;
    }

    /**
     * Create Cache Configuration
     * @param cacheKey
     * @param timeoutInSeconds
     * @return
     */
    private static RedisCacheConfiguration createCacheConfiguration(String cacheKey, long timeoutInSeconds) {
//        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
//        String timestamp = format.format(new Timestamp(System.currentTimeMillis()));

        return RedisCacheConfiguration.defaultCacheConfig()
                .prefixKeysWith(cacheKey)
                .entryTtl(Duration.ofSeconds(timeoutInSeconds));
    }
}

 

연동 (Redis Template)

캐시 조회 컨트롤러 생성

@RequiredArgsConstructor
@RestController
@RequestMapping("/caches")
public class CacheController {
    private final CacheService cacheService;

    @GetMapping("")
    public List<String> lists(HttpServletRequest request){
        return cacheService.listKey();
    }

    @GetMapping("/{key}")
    public String listsKey(HttpServletRequest request, @PathVariable(name = "key") String key){
        return cacheService.getCacheValues(key);
    }

    @GetMapping("/key}/clear")
    public String listsKeyClear(HttpServletRequest request, @PathVariable(name = "key") String key){
        cacheService.clear(key);
        return "clear";
    }
}

캐시 조회 서비스 생성

@RequiredArgsConstructor
@Service
public class CacheService {
    private final RedisTemplate<String, Object> redisTemplate;

    // 캐시키 제거
    public void clear(String key){
        redisTemplate.delete(key);
    }

    // 모든 키를 조회
    public List<String> listKey(){
        Set<String> keys = redisTemplate.keys("*");

        if(keys != null){
            List<String> keyList = new ArrayList<>();
            keyList.addAll(keys);
            return keyList;
        }

        return new ArrayList<>();
    }

    // 캐시 키로 조회한 값 조회
    public String getCacheValues(String cacheName){
        return redisTemplate.opsForValue().get(cacheName).toString();
    }
}

캐시 데이터 자동 생성 방법

@RequiredArgsConstructor
@Service
public class AdminIpPermissionService {
    private final AdminIpPermissionMapper adminIpPermissionMapper;

    /**
     * 관리자 허용 IP 리스트 정보 조회
     * @param status
     * @return
     */
    @Cacheable(value = "cache.hour") // 1시간 유지 캐시 설정
    public List<AdminIpPermission> getAdminIpPermissionList(String status) {
        return adminIpPermissionMapper.getAdminIpPermissionList(status);
    }
}

 

 

연동 (Redis Data)

repository 생성

public interface RedisNoticeRepository extends CrudRepository<Notice, Long> {
}

entity 생성

@ToString	// 테스트용
@Builder
@Getter
@RedisHash("notice")
public class Notice {
    @Id
    private Long id;
    private String content;

    public Notice changeContent(String newContent){
        this.content = newContent;
        return this;
    }
}

테스트 코드 작성

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class RedisNoticeRepositoryTest {
    @Autowired
    private RedisNoticeRepository redisNoticeRepository;

    @Test
    public void save(){
        Notice notice = Notice.builder().content("test content").build();
        redisNoticeRepository.save(notice);
    }

    @Test
    public void selectAll(){
        // 출력 테스트
        redisNoticeRepository.findAll().forEach(notice ->{
            System.out.println(notice.toString());
        });
    }

    @Test
    public void selectOne(){
    	// 키값은 redis-cli에서 직접 확인 필요
        Notice notice = redisNoticeRepository.findById().get();
        assertEquals("test content", notice.getContent());
    }

    @Test
    public void update(){
    	// 키값은 redis-cli에서 직접 확인 필요
        Notice notice = redisNoticeRepository.findById().get();
        notice.changeContent("change content");

        redisNoticeRepository.save(notice);
        
        // 키값은 redis-cli에서 직접 확인 필요
        Notice updatedNotice = redisNoticeRepository.findById().get();

        assertEquals(updatedNotice.getContent(), "change content");
    }
}

redis 값 확인 방법

 

 

Cacheable TTL 적용 예시

위에 있는 RedisConfig 설정에 선언한 ttl 적용

@RequiredArgsConstructor
@Service
public class MemberRestService {
    private final MemberRepository memberRepository;
    private final OrdersRepository ordersRepository;
    private final ModelMapper modelMapper;

    @Cacheable(value = "article")
    public List<MemberDto> getAllMembers(){
        return memberRepository.findAll()
                .stream()
                .map(member -> modelMapper.map(member, MemberDto.class))
                .collect(Collectors.toList());
    }

    // article 형태를 가진 모든 키를 제거
    @CacheEvict(cacheNames = {"article"}, allEntries = true)
    @Transactional
    public void updateMember(Long id, int age){
        memberRepository.findById(id).ifPresent(member -> member.changeAge(age));
    }

    public List<OrdersDto> getListOrders(Long id){
        return ordersRepository.findByMember_Id(id)
                .stream()
                .map(orders -> modelMapper.map(orders, OrdersDto.class))
                .collect(Collectors.toList());
    }
}

'SpringFramework > Spring' 카테고리의 다른 글

jQuery DataTable Paging Model Mapping  (0) 2021.03.09
Enum 객체 활용 방안  (0) 2021.03.09
ModelMapper 사용 법  (0) 2020.11.16
Swagger2 설정 및 사용  (0) 2020.11.13
class 파일내 DataSource 설정  (0) 2020.11.11