[Redis] Cache with Spring Boot

minholee_93·2020년 1월 13일
1

Redis

목록 보기
14/14
post-thumbnail

참고 자료 : [ https://www.youtube.com/watch?v=Yjm-izEb7TM , https://www.youtube.com/watch?v=6uWU3gDhZk4 ]


이번 글에서는 spring boot에서 redis를 활용한 cache를 구현해보겠습니다.

0. Cache란?

"오랜시간이 걸리는 작업" 혹은 "반복적으로 요청하는 작업"의 결과를 메모리에 저장해서 데이터 접근의 시간과 비용을 줄이는 기법을 의미합니다.

예를 들어 아래 그림에서, 어플리케이션은 필요한 데이터에 접근할 때 먼저 Cache에 접근해 확인합니다. 이후 Cache에 데이터가 없는경우에만 직접 Database에 접근하게 됩니다.

Cache는 일반적으로 Redis와 같은 인 메모리 DB를 사용하기 때문에, Databse보다 빠른속도로 데이터를 가져올 수 있습니다. 또한 Cache에는 Key-Value 형식으로 데이터를 저장하기 때문에, 동일한 key값의 데이터는 중복 저장되지 않습니다.

1. application.yml

이번 실습에서는 Cache 저장소로는 AWS에서 구동중인 Redis 서버를 사용하겠습니다. Database 저장소로는 MariaDB를 사용하며, ORM은 JPA를 사용합니다.

먼저 아래와 같이 Databse & JPA properties를 추가했습니다.

다음으로 redis cache를 properties를 추가합니다.

2. build.gradle

dependencies에는 아래와 같은 내용을 추가합니다. 이제 기본적인 설정은 끝났습니다. 참쉽죠? 😎

3. Application

자, 이제 Cache를 구현해보겠습니다. 가장먼저 Application에 Cache를 사용하겠다는 의미인 @EnableCaching 어노테이션을 선언합니다.

4. Entity

Cache를 사용할 entitiy를 아래와 같이 간단히 작성합니다.

JPA를 사용할 것이므로 @Data와 @Entity 어노테이션을 추가했습니다. 기본적인 생성자 및 Getter/Setter는 @Lombok으로 대체했습니다. 마지막으로 Redis에 저장할때는 Hash를 사용하므로, Serializable을 implements 했습니다.

5. Reposiory

Repository는 간단히 JpaRepository를 상속해서 만들 수 있습니다. JPA를 사용할 것이므로 반드시 @Repository 어노테이션을 추가합니다.

6. Service

서비스는 아래와 같이 구현합니다.

앞서 생성한 Repository를 @Autowired로 주입받아 사용하면 됩니다. 기본적인 CRUD 만 작성했습니다.

@Service
public class PersonService {

    @Autowired
    private PersonRepository personRepository;

    // create
    public Person create(String firstName, String lastName, int age){
        return personRepository.save(new Person(firstName,lastName, age));
    }

    // retrieve
    public List<Person> getAll(){
        return personRepository.findAll();
    }

    // get
    public Person getByFirstName(String firstName){
        return personRepository.findByFirstName(firstName);
    }

    // update
    public Person update(String firstName, String lastName, int age){
        Person p = personRepository.findByFirstName(firstName);
        p.setLastName(lastName);
        p.setAge(age);
        return personRepository.save(p);
    }

    // delete
    public void deleteAll(){
        personRepository.deleteAll();
    }

}

7. Controller

이제 마지막으로 위에서 작성한 Service를 사용할 Controller를 아래와 같이 작성합니다.

Spring boot에서는 아래와 같이 어노테이션을 통해 간단히 Cache를 구현할 수 있습니다. 정상적으로 데이터가 Redis에 Cache된 경우에는 메서드를 실행하지 않고, 동일한 key값의 return값을 Cache에서 가져옵니다.

@RestController
public class PersonController {

    @Autowired
    private PersonService personService;

    private  static final Logger log = LoggerFactory.getLogger(PersonController.class);

    @RequestMapping("/create")
    @ResponseBody
    @CachePut(value = "persons", key = "#firstName")
    public Person create(@RequestParam String firstName, @RequestParam String lastName, @RequestParam int age){
        log.info("create method call");
        Person p = personService.create(firstName, lastName, age);
        return p;
    }

    @RequestMapping("/get")
    @ResponseBody
    @Cacheable(value = "persons" , key = "#firstName")
    public Person getPerson(@RequestParam String firstName){
        log.info("get method call");
        return personService.getByFirstName(firstName);
    }

    @RequestMapping("/getAll")
    @ResponseBody
    @Cacheable(value = "persons")
    public List<Person> getAll(){
        log.info("getAll method call");
        return personService.getAll();
    }

    @RequestMapping("/update")
    @ResponseBody
    @CachePut(value = "persons", key = "#firstName")
    public Person update(@RequestParam String firstName, @RequestParam String lastName, @RequestParam int age){
        log.info("update method call");
        Person p = personService.update(firstName, lastName, age);
        return p;
    }

    @RequestMapping("/deleteAll")
    @CacheEvict(value = "persons", allEntries = true)
    public void deleteAll(){
        log.info("deleteAll method call");
        personService.deleteAll();
    }
}

위에서 각 메서드위에 @CachePut / @Cacheable / @CacheEvict 를 선언해 데이터를 Redis에 Cache합니다. 각각의 어노테이션의 의미는 아래와 같습니다.

• @CachePut : key값으로 Return된 결과값을 Cache에 저장합니다. 동일한 key값이 이미 Cache에 있는 경우 해당 값은 update 됩니다.

• @Cacheable : 동일 key값이 Cache에 있는경우 Cache에서 데이터를 Return합니다. 만약 동일 key값이 없을 경우 해당 메서드를 실행하고 반환된 Return 결과값을 Cache에 저장합니다.

• @CacheEvict : Cache에서 데이터를 삭제합니다.

8. Test

8-1) CREATE

이제 실제로 Cache가 정상적으로 실행되는지 테스트 해보겠습니다. 어플리케이션을 실행하고 /create 를 호출해 아래와 같이 Person을 저장합니다.

create에서 호출한 메서드에는 아래와 같이 @CachePut이 선언되어 있습니다. 따라서 key값인 firstName("Minho")으로 redis에 데이터가 Cache 됩니다.

  @RequestMapping("/create")
    @ResponseBody
    @CachePut(value = "persons", key = "#firstName")
    public Person create(@RequestParam String firstName, @RequestParam String lastName, @RequestParam int age){
        log.info("create method call");
        Person p = personService.create(firstName, lastName, age);
        return p;
    }

redis server에 아래와 같이 "persons::Minho"로 SET을 정상적으로 호출한것을 확인할 수 있습니다.

8-2) GET

이제 위에서 등록한 Person을 호출하면 아래와 같이 정상적으로 데이터를 가져옵니다.

get 메서드에는 아래와 같이 @Cacheable을 선언했으므로, key값인 "Minho"에 대한 데이터가 Cache에 이미 있을 경우 결과값을 Cache에서 찾아 반환하고 메서드는 실행하지 않습니다.

위에서 /create 메서드가 @CachePut을 통해 key값이 "Minho"인 데이터를 Cache에 이미 저장했기 때문에 아래의 메서드는 Cache에서 결과값을 찾아 return할 것입니다.

  @RequestMapping("/get")
    @ResponseBody
    @Cacheable(value = "persons" , key = "#firstName")
    public Person getPerson(@RequestParam String firstName){
        log.info("get method call");
        return personService.getByFirstName(firstName);
    }

IDE의 Log를 확인해보면 아래와 같이 아무것도 print되지 않았습니다. 정상적으로 메서드를 실행했으면 "get method call"이 print가 됩니다. 하지만 우리는 Cache에서 데이터를 가져왔기때문에 메서드는 실행되지 않았습니다.

Cache에서 데이터를 가져왔기 때문에 redis server에는 아래와 같이 GET Log가 찍혀있는 것을 확인할 수 있습니다.

위와 같이 Cache를 사용하면 동일한 key에 대한 결과값은 메서드를 실행하지 않아도 되므로 많은 자원을 아낄 수 있습니다. 😆

profile
Backend Developer at FLO 🎵

0개의 댓글