개발 환경 설정 및 툴 사용법

[spring boot + redis] 설치 및 간단 실습

letsDoDev 2024. 11. 24. 22:59

redis 란?
- Remote Dictionary Server 의 약자
- 데이터를 메모리에 저장
- 메모리에 저장하기 떄문에 데이터의 빠른 읽기 및 쓰기 가능
- 큐 시스템, 세션관리, 실시간 분석 등에 사용 됨
- 조회가 많은 데이터들은 redis를 통해 캐시로 관리하면 빠른 조회가 가능
 
- 컴퓨터 캐시와 redis 캐시는 다른 개념이다.
- 따라서 pc의 캐시 (브라우저 캐시, 운영체제 캐시, 애플리케이션의 데이터 캐시)를 삭제한다고 해서
- redis 의 캐시가 삭제되지 않는다.
- 단, redis를 종료시키거나 데이터를 삭제하는 경우 redis 캐시가 삭제된다.,
- 따라서 운영할 애플리케이션에  적용할 경우 중요할 수 있는 redis 캐시데이터는 주기적으로
- RDB로 이관을 시켜주어야 한다.
 
 [redis 데이터가 삭제되는 경우]
 

  • Redis 서버를 수동으로 종료하거나 재시작한 경우 (Persistence가 설정되지 않은 상태에서)
  • 특정 키를 수동으로 삭제한 경우 (DEL, FLUSHDB, FLUSHALL 명령어 사용)
  • 설정된 TTL(Time-To-Live, 만료 시간)이 지나 데이터를 자동 삭제한 경우
  • Redis 메모리가 부족해 LRU 정책(Least Recently Used) 등으로 데이터를 삭제한 경우

[redis 설치 : 윈도우]
설치 파일 다운로드 경로 : https://github.com/microsoftarchive/redis/releases

 
 

- C:\Program Files\Redis 이동
- redis-server.exe 실행(redis 실행)

- redis-cli.exe 실행 (redis 명령어 사용하기 실행)

- redis-cli.exe 로 redis cli 접근 가능
 
- PING 이라고 명령어 입력 시 'PONG' 으로 응답받은 거면
- redis 실행 중이라는 것

- INFO 입력 시 현재 실행 중인 redis 의 정보를 확인 가능

- 추후 redis의 포트 변경이나 password 설정은 'redis.windows.conf' 로 가능하다.

 
- 6379 대신 다른 값 넣으면 해당 포트로 사용
- ★ 포트 지정 시 이미 사용 중인 포트인지 아닌지 확인해보자
 

- 위 "# requiredpass foobared" 에서 # 을 지워 주석해제 하고 footbared 자리에 원하는 패스워드 입력하면
- 해당 입력 값으로 패스워드 사용 가능
- 위 같은 방식으로 (구글링 해서 더 찾아보자)

bind [허용할 ip] // 외부 접근 가능한 ip 지정
logfile [내가 지정할 파일명.log] // redis log 파일 생성 및 지정

 
이렇게도 사용 가능하다.
 

# redis-cli.exe 명령어 정리
SHUTDOWN # 종료
redis-server # redis 재실행 (단 redis-server.exe 파일이 같은 경로에 있어야 함)

 
 


 
[실습 프로젝트 생성]
스프링 이니셜라이저 사용 : https://start.spring.io/
- 깜빡하고 lombok 안 넣어서 build.gradle 추가 작성 진행했음;;

- build.gradle에 추가로 작성한 것 

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
	implementation 'org.springframework.boot:spring-boot-starter-web'
    // lombok 추가한 부분------------------------------------------------------------
	implementation 'org.projectlombok:lombok' // Lombok 의존성 추가
	compileOnly 'org.projectlombok:lombok' // Lombok을 컴파일 전용으로 설정
	annotationProcessor 'org.projectlombok:lombok' // Lombok의 애너테이션 프로세서 설정
    // ------------------------------------------------------------------------------
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

[코드]
목표 : id, userEmail, customToken 값을
id로 redis 에서 관리하게 할 것이다.
key로 사용되는 것은 id 이며 key 값은 RedisUser로 정하였다.
 
# applicaion.properties (redis 관련 설정)

spring.application.name=try-redis

# [redis 설정]
# redis-host
spring.data.redis.host=localhost
# redis-port
spring.data.redis.port=6379
# redis-password 없으면 비워두자
spring.data.redis.password=

 
# RedisConfig.java (Redis 사용을 위한 config 파일)

package com.study.try_redis.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.StringRedisSerializer;

@EnableCaching
@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String host;

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

    @Value("${spring.data.redis.password}")
    private String password;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port) {{
            setPassword(password);
        }});
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

 
 
# RedisUser.java (redis 에 담길 데이터 객체 : 직렬화)

package com.study.try_redis;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.redis.core.RedisHash;

import java.io.Serializable;

@Getter
@NoArgsConstructor
@RedisHash("RedisUser") // key 가 RedisUser 이고 값이 아래 변수명들을 가지는 객체가 된다는 의미
public class RedisUser implements Serializable {

    // redis 에서는 GeneratedValue 지원 안 해서
    private String id; // key 가 될 값 // 별도 어노테이션 지원이 없어 서비스 로직 서 구분해주어야 한다.
    private String userEmail; // 유저이메일 
    private String customToken; // 임의로 값 넣어줄 거임

    @Builder
    public RedisUser(String id, String userEmail, String customToken) {
        this.id = id;
        this.userEmail = userEmail;
        this.customToken = customToken;
    }

    public String getId() {
        return id;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public String getCustomToken() {
        return customToken;
    }
}

 
 
# RedisController.java (redis 값 crud 매핑될 uri)
 - 여기 예제에서는 CR만 수행 UD 는 별도로 학습하도록 하자

package com.study.try_redis.controller;

import com.study.try_redis.RedisUser;
import com.study.try_redis.service.RedisService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/redis-api")
public class RedisController {
    
    // service 빈 주입
    private final RedisService redisService;
    public RedisController(RedisService redisService) {
        this.redisService = redisService;
    }

    @GetMapping("/getAllList")
    public List<RedisUser> getAllList() {
        return redisService.getAllList();
    };

    @GetMapping("/getRedisUserByEmail")
    public RedisUser getRedisUserByEmail(@RequestParam String userEmail) {
        return redisService.getRedisUserByEmail(userEmail);
    }

    @GetMapping("/getRedisUserById")
    public RedisUser getRedisUserById(@RequestParam String id) {
        return redisService.getRedisUserById(id);
    };

    @PostMapping("/setRedisUser")
    public void setRedisUser(@RequestBody RedisUser redisUser) {
        redisService.setRedisUser(redisUser);
    };
}

 
 
# RedisService.java (인터페이스)

package com.study.try_redis.service;

import com.study.try_redis.RedisUser;

import java.util.List;

public interface RedisService {

    public List<RedisUser> getAllList();
    public RedisUser getRedisUserByEmail(String userEmail);
    public RedisUser getRedisUserById(String id);
    public void setRedisUser(RedisUser redisUser);
}

 
# RedisServiceImpl.java (구현체)

package com.study.try_redis.service;

import com.study.try_redis.RedisUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Service
public class RedisServiceImpl implements RedisService{


    //빈 주입
    private RedisTemplate<String, RedisUser> redisTemplate;

    public RedisServiceImpl(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * ※ 주석처리한 opsForValue는 데이터가 객체가 아닌 string 타입으로 저장될 때 사용하기 좋은 메소드이다.
     * 이번 테스트에서는 객체를 직렬화하여 넣었기 떄문에 주석처리 해두었다. -> opsForHash를 사용하였다.
     */

    @Override
    public List<RedisUser> getAllList() {
        // Set<String> keys = redisTemplate.keys("RedisUser:*");
        // List<RedisUser> userList =  redisTemplate.opsForValue().multiGet(new ArrayList<>(keys));
        // Redis에서 "RedisUser" 해시의 모든 키 (id들)을 조회

        // key 값을 먼저 가져온 후 조회
        Set<String> keys = redisTemplate.opsForHash().keys("RedisUser").stream()
                .map(objectKey -> (String) objectKey).collect(Collectors.toSet());
        List<RedisUser> userList = redisTemplate.opsForHash().multiGet("RedisUser", new ArrayList<>(keys)).stream()
                .map(objectUser -> (RedisUser) objectUser).collect(Collectors.toList());
        if (userList.isEmpty()) {
            throw new RuntimeException("not exsist any users");
        }
        return userList;
    }

    @Override
    public RedisUser getRedisUserByEmail(String userEmail) {
        // userEmail 은 id가 아니므로 전체를 조회한 후 그 중에서 찾는다.
        List<RedisUser> allUsers = getAllList();
        RedisUser findUser = null;
        for (RedisUser targetUser : allUsers) {
            if (targetUser.getUserEmail().equals(userEmail)) {
                findUser = targetUser;
                break;
            }
        }
        if (findUser == null) throw new RuntimeException("not found user : " + userEmail);
        return findUser;
    }

    @Override
    public RedisUser getRedisUserById(String id) {
        RedisUser findUser = null;
        // findUser = redisTemplate.opsForValue().get("RedisUser:"+id);
        // findUser = redisTemplate.opsForValue().get(id); // id만 키로 등록시키기로 한 경우 이렇게도 가능
        findUser = (RedisUser) redisTemplate.opsForHash().get("RedisUser", id);

        if (findUser == null) throw new RuntimeException("not found user by id : " + id);
        return findUser;
    }

    @Override
    public void setRedisUser(RedisUser redisUser) {
        try {
            // redisTemplate.opsForValue().set(redisUser.getId(), redisUser);
            redisTemplate.opsForHash().put("RedisUser", redisUser.getId(), redisUser);
        } catch (Exception e) {
            log.error("RedisUser Set ERROR by : ");
            log.error(e.getLocalizedMessage());
            throw new RuntimeException("ERROR - [set RedisUser]");
        }
    }
}

 
 


[실행 결과]
#1 입력

#2 id (key) 값으로 조회

 
#3 userEmail(nonKey) 값으로 조회

 
#4 모든 user 조회

 
- 제대로 저장되고 조회된다.
- 이번에 학습한 redis의 기능들을 이용하여 애플리케이션의 ACCESS_TOKEN, REFRESH_TOKEN 과
- 관련된 기능 개발에 적용 가능하며 정책 도입에 응용할 수 있다
 


아래는 너무 헷갈려서 gpt를 통해 정리한 것이다
참조하자!