Spring Boot With Redis CRUD Example

DongHyun Kim·2023년 8월 31일
1
post-custom-banner

왜 Redis를 사용하는가?

  • NoSQL 스토리지 시스템은, 수평적 확장성과 속도를 위해 기존 RDBMS에 대한 대안을 제공.

  • 그 중 Redis는 Spring Data 가 지원하는 Key-Value 저장소 중 하나이다. 그리고 다른 NoSQL 들이 disk 나 SSD 에 저장하는 것과 달리, Redis 는 in-memory 에 저장한다.

  • in-memory 저장 덕분에, Redis 는 데이터 접근 속도가 매우 매우 빠르다 (ms 단위라고 함).

Memory SnapShot

메모리에 있는 데이터를 스냅샷을 찍어 통째로 디스크에 옮겨준다. 그리고 필요할 때 다시 메모리에 올려준다.


Redis 설정

Redis 설치 완료된 상태에서

(Redis Window 설치 - https://aljjabaegi.tistory.com/616)

  • build.gradle 설정
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
  • Redis에 연결

Redis와 Spring을 사용할 때 첫 번째 작업은, IoC 컨테이너를 통해 저장소를 연결한다.

RedisConnection 은 RedisConnectionFactory 를 통해 생성된다.

RedisConnectionFactory 는 크게 2가지 방식으로 구현된다. 하나는 Jedis , 나머지 하나는 Lettuce Redice Clients 이다. 이 중 Jedis 는 Redis 공식 문서에 Java 추천 방법으로 나와있다.

하지만 Lettuce 가 SPring Data Redis 를 구현하고, thread-safe , asynchronous 하기 때문에 reactive applications 에서 쓰기 좋다.

  • Redis Configuration

다음은 Lettuce 연결 팩토리를 생성하는 방법이다

@Configuration
class AppConfig {

	private String redisHost = ...;
	private int redisPort = ...;

	// Creating Connection with Redis
  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort));
  }

	// Creating RedisTemplate for Entity 'Employee'
	@Bean
  public RedisTemplate<String, Employee> redisTemplate() {
      RedisTemplate<String, Employee> redisTemplate = new RedisTemplate<>();
      redisTemplate.setConnectionFactory(redisConnectionFactory());
      return redisTemplate;
  }
}

Employee 를 Entity 로 사용할 것이므로 다음과 같이 RedisTemplate 를 명시해주자. (Employee 대신 Object 로 할 경우도 상관 X)

Redis 를 이용한 CRUD 예제

redis 를 이용해 CRUD 할 Entity 를 먼저 만들자

@Data
@NoArgsConstructor
@AllArgsConstructor
// No @Entity concept here
public class Employee implements Serializable {

    private static final long serialVersionUID = -7817224776021728682L;

    private Integer empId;
    private String empName;
    private Double empSalary;
}

Redis 를 사용하기 때문에 @Entity 를 사용하지 않는다. 하지만 Redis 의 엔티티로 사용하려면 Serializable 인터페이스를 꼭 구현해야한다. (Employee 데이터를 하나의 Employee 테이블 같이 묶기 위해서 필요하다.)

Serializable 을 안 쓴다면 Key 값에 Employee 들을 구분하기 위해 Employee:id 이렇게 넣어야한다.


public interface EmployeeRepository {

    void saveEmployee(Employee emp);
    Employee getOneEmployee(Integer id);
    void updateEmployee(Employee emp);
    Map<Integer, Employee> getAllEmployees();
    void deleteEmployee(Integer id);
    void saveAllEmployees(Map<Integer, Employee> map);
}

Employee 에서 사용할 DB 기능들을 정리해 놓은 인터페이스


@Repository
public class EmployeeRepositoryImpl implements EmployeeRepository{

    private final String hashReference = "Employee";

    @Resource(name = "redisTemplate") // 빨간 줄 무시
    private HashOperations<String, Integer, Employee> hashOperations;

    @Override
    public void saveEmployee(Employee emp) {
        hashOperations.putIfAbsent(hashReference, emp.getEmpId(), emp);
    }

    @Override
    public void saveAllEmployees(Map<Integer, Employee> map) {
        hashOperations.putAll(hashReference, map);
    }

    @Override
    public Employee getOneEmployee(Integer id) {
        return hashOperations.get(hashReference, id);
    }

    @Override
    public void updateEmployee(Employee emp) {
        hashOperations.put(hashReference, emp.getEmpId(), emp);
    }

    @Override
    public Map<Integer, Employee> getAllEmployees() {
        return hashOperations.entries(hashReference);
    }

    @Override
    public void deleteEmployee(Integer id) {
        hashOperations.delete(hashReference, id);
    }
}

@Resource 를 이용해서 생성한 redisTemplate 빈을 이름으로 찾는다. (@Autowired 와의 차이점) 그리고 HashOperations 에 주입시켜준다. (원래는 redisTemplate.opsForHash() 로 생성 가능)


@Component
public class RedisOperationsRunner implements CommandLineRunner {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public void run(String... args) throws Exception {

        //saving one employee
        employeeRepository.saveEmployee(new Employee(500, "Emp0", 2150.0));

        //saving multiple employees
        employeeRepository.saveAllEmployees(
                Map.of( 501, new Employee(501, "Emp1", 2396.0),
                        502, new Employee(502, "Emp2", 2499.5),
                        503, new Employee(503, "Emp4", 2324.75)
                )
        );

        //modifying employee with empId 503
        employeeRepository.updateEmployee(new Employee(503, "Emp3", 2325.25));

        //deleting employee with empID 500
        employeeRepository.deleteEmployee(500);

        //retrieving all employees
        employeeRepository.getAllEmployees().forEach((k,v)-> System.out.println(k +" : "+v));

        //retrieving employee with empID 501
        System.out.println("Emp details for 501 : "+employeeRepository.getOneEmployee(501));
    }
}

CommandLineRunner 를 구현하면 Spring Application 에 포함되는 순간 자동 실해될 수 있도록 해준다.

순서는 @Ordered 어노테이션을 이용해 지정할 수 있다.

🥳실행 결과

지금까지 Redis 를 스프링 부트에서 사용하는 방법에 대해 알아봤다.

이를 이용해 JWT 토큰 관리를 Redis 로 하도록 바꿔보는 토이 프로젝트를 진행해보자

더 다양한 예제 - https://blog.kingbbode.com/25

Redis cli 명령어 정리

출처 - https://freeblogger.tistory.com/10

공식 문서 - https://redis.io/docs/ui/cli/

마주했던 오류들

  • Reason: Failed to determine a suitable driver class

Spring Boot 는 프로젝트 시작 시 연결할 수 있는 DB 가 있나 체크한다. 그러므로 아무 DB라도 연결해놓아야 한다

  • Unauthorized: 401

Spring Security 를 포함해서 빌드했는데 Spring Boot 를 실행할 때 자동으로 실행돼서 요청 URL 에 대해 인증이 없던 상태라 발생한 오류였다.

참고 :

https://javatechonline.com/spring-boot-redis-crud-example/#Jedis_Connector_JedisConnectionFactory

https://www.woolog.dev/backend/spring-boot/spring-boot-redis-dessert-simple/

Redis 공식 - https://redis.io/docs/clients/java/

profile
do programming yourself
post-custom-banner

0개의 댓글