본문 바로가기
Spring

Redis와 SpringBoot 연동

by 혀눅짱 2023. 11. 20.

 

Redis란?

  •    Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 DBMS.
  •   인메모리 방식의 데이터 저장소로, 일반적인 DB에 비해 속도가 빠르다.
  •   String, Set, Sorted Set, Hash, List와 같이 다양한 데이터 타입을 지원한다.
  •   Single Thread 구조이기 때문에 처리 시간이 긴 요청이 들어올 경우 해당 요청을 처리할 때 까지 다른 요청도 응답을 받을 수 없다.
  •    Master Redis 서버의 데이터를 Slave Redis 서버에 복제할 수 있다.

 

 

로컬에서 Redis는 Docker환경에 구축하기로 하였다.

 

docker run --name redis_server -it -d -p 6379:6379 redis

 

실행후

 

docker logs -f redis_server

 

로그확인

 

Redis가 정상적으로 실행됨을 확인 할 수 있다.

 

다음은 Redis와 연동하기 위한 SpringBoot 설정이다.

 

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

    

Build.gradle에 라이브러리를 추가한다.

 

spring:
  cache:
    type: redis
  redis:
    host: 127.0.0.1
    port: 6379

 

redis 접속정보와 이번 예제에선 redis에 데이터가 저장된것을 확인하고 해당 데이터를 캐시로 사용할것이기 때문에 캐시타입을 레디스로  application.yml에 명시한다.

 

 

이제 스프링부트에 redisconfig파일에 redis와 연동할 빈을 등록한다.

@Configuration
@RequiredArgsConstructor
@EnableCaching
public class RedisConfig {

    private final RedisProperties redisProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        return new LettuceConnectionFactory(redisProperties.getHost(),redisProperties.getPort());
    }

    @Bean
    public CacheManager cacheManager() {
        RedisCacheManagerBuilder builder =
                RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory());

        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
                .disableCachingNullValues()
                .entryTtl(Duration.ofMinutes(30L));

        builder.cacheDefaults(configuration);

        return builder.build();
    }

    @Bean
    public RedisTemplate<String,Object> redisTemplate(){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());   // Key: String
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));  // Value: 직렬화에 사용할 Object 사용하기
        return redisTemplate;

    }
}

 

 

Redis 구현체로는 Jedis와 Lettuce가 있는데, Lettuce가 성능이 더 좋아 많이 사용된다고 한다.

@EnableCaching 어노테이션을 통해 캐싱 기능 사용 등록하고 레디스캐시매니저를 등록한다.

예제이후 자료구조형태로 레디스에 데이터가 저장되는지 테스트하기위해 레디스템플릿역시 bean으로 등록해둔다.

 

테스트를 하기위해 컨트롤러와 서비스에 예제소스를 구성한다.

 

@GetMapping("/member/{memberId}")
public ResponseEntity<?> getMemberInfo(@PathVariable("memberId") Long memberId) {
    return ResponseEntity.ok(redisMemberService.getMemberInfo(memberId));
}

@PostMapping("/join")
public ResponseEntity<?> joinMember(@RequestBody Map<String, String> memberInfo) {
    Member member = new Member();
    member.setName(memberInfo.get("name"));
    redisMemberService.join(member);
    return ResponseEntity.ok("가입 완료");
}

@PutMapping("/update")
public ResponseEntity<?> updateMember(@RequestBody Map<String, String> memberInfo) {
    System.out.println(memberInfo);
    Member member = new Member();
    member.setId(Long.parseLong(memberInfo.get("id")));
    member.setName(memberInfo.get("name") + "update");
    redisMemberService.update(member);
    return ResponseEntity.ok("수정 완료");
}

@DeleteMapping("/member/{memberId}")
public ResponseEntity<?> deleteMember(@PathVariable("memberId") Long memberId) {
    redisMemberService.removeMember(memberId);
    return ResponseEntity.ok("삭제 완료");
}

 

간단한 CRUD작업이다.

 

@Service
@RequiredArgsConstructor
@Transactional
public class RedisMemberService {

    private final RedisMemberRepository redisMemberRepository;

    public void join(Member member){
        redisMemberRepository.save(member);
    }

    @CachePut(value = "Member", key = "#member.id", cacheManager = "cacheManager")
    public Member update(Member member){
        redisMemberRepository.save(member);
        return redisMemberRepository.findById(member.getId()).get();
    }

    @Cacheable(value = "Member", key = "#memberId", cacheManager = "cacheManager",unless = "#result == null")
    public Member getMemberInfo(Long memberId){
        return redisMemberRepository.findById(memberId).get();
    }

    @CacheEvict(value = "Member", key = "#memberId", cacheManager = "cacheManager")
    public void removeMember(Long memberId){
        redisMemberRepository.delete(redisMemberRepository.findById(memberId).get());
    }

}

 

서비스 레이어의 경우  레디스캐시매니저를 이용해 캐싱한다.

@Entity
@Getter
@Setter
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String name;
}

 

간단한 Member Entitiy를 만들어 data jpa로 테스트하도록한다.

 

 

먼저 간단히 회원가입을 하고

 

 

 

Get메서드로 해당 사용자의 정보를 불러온다.

 

 

해당 SELECT 쿼리가 db로 날아간걸 콘솔로 확인 할 수 있다.

 

여기서 재차 사용자정보를 조회하게되면

 

쿼리가 날아가지 않는다 이유는 레디스에 캐싱이 적용되었기 때문이다 (서비스 레이어에서)

그렇기 때문에 처음 조회한 이후에 레디스에 해당 사용자의 키로 데이터가 저장되어야한다.

Redlis-cli로 확인해보자

 

docker exec -it redis_server redis-cli

명령어로 접속한다.

 

 

 

 

keys * 명령어로 저장된 키들을 확인 할 수 있다.

방금 조회한 사용자의id로 Member라는 캐시이름으로 데이터가 저장되었다 해당 데이터의 상세정보는 get 명령어로 확인 할 수 있다.

 

사용자의 정보가 정상적으로 redis에 등록되었다.

 

사용자정보를 업데이트 할 경우

db뿐만아니라 redis에도 해당 업데이트가 적용된다.

 

 

삭제 또한 db뿐만아니라 redis에서도 삭제된다.

 

 

 

이번엔 객체를 redis에 저장하는게 아닌 자료구조를 저장하는 테스트를 구성해보았다.

 

@GetMapping("/templateTest")
public void templateTest(){
    ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    valueOperations.set("testKey","testValue");
}


@GetMapping("/templateListTest")
public void templateListTest(){
    ListOperations<String, Object> stringObjectListOperations = redisTemplate.opsForList();

    stringObjectListOperations.rightPush("listTest","h");
    stringObjectListOperations.rightPush("listTest","e");
    stringObjectListOperations.rightPush("listTest","l");
    stringObjectListOperations.rightPush("listTest","l");
    stringObjectListOperations.rightPush("listTest","o");

    stringObjectListOperations.rightPushAll("listTest"," ","w","o","r","l","d");

    String character_1 = (String) stringObjectListOperations.index("listTest", 1);

    System.out.println("character_1 = " + character_1);

    Long size = stringObjectListOperations.size("listTest");

    System.out.println("size = " + size);

    List<Object> ResultRange = stringObjectListOperations.range("listTest", 0, 10);

    System.out.println("ResultRange = " + Arrays.toString(ResultRange.toArray()));

}

@GetMapping("/templateHashTest")
public void templateHashTest(){
    String key = "testHash";

    HashOperations<String, Object, Object> stringObjectObjectHashOperations = redisTemplate.opsForHash();

    stringObjectObjectHashOperations.put(key, "Hello", "helloVal");
    stringObjectObjectHashOperations.put(key, "Hello2", "helloVal2");
    stringObjectObjectHashOperations.put(key, "Hello3", "helloVal3");

    Object hello = stringObjectObjectHashOperations.get(key, "Hello");

    System.out.println("hello = " + hello);

    Map<Object, Object> entries = stringObjectObjectHashOperations.entries(key);

    System.out.println("entries = " + entries.get("Hello2"));

    Long size = stringObjectObjectHashOperations.size(key);

    System.out.println("size = " + size);
}

 

 

일반적인 키밸류,리스트,맵 형태가 모두 잘 저장되고 불러올수 있다.

 

 

'Spring' 카테고리의 다른 글

@Conditional과 @AutoConfiguration  (0) 2023.11.06
SpringBoot AutoConfiguration  (0) 2023.11.06
SpringBoot와 웹서버  (0) 2023.10.31
인터셉터 설정  (0) 2023.10.05
전역예외처리  (1) 2023.10.05