240806 TIL

송형근·2024년 8월 6일

TIL

목록 보기
7/43
post-thumbnail

스파르타 Java 단기 심화 과정


코드카타


프로그래머스 12901 2016년

https://school.programmers.co.kr/learn/courses/30/lessons/12901

— 문제 설명

2016년 1월 1일은 금요일입니다. 2016년 a월 b일은 무슨 요일일까요? 두 수 a ,b를 입력받아 2016년 a월 b일이 무슨 요일인지 리턴하는 함수, solution을 완성하세요. 요일의 이름은 일요일부터 토요일까지 각각 SUN,MON,TUE,WED,THU,FRI,SAT

입니다. 예를 들어 a=5, b=24라면 5월 24일은 화요일이므로 문자열 "TUE"를 반환하세요.

— 제한 조건

  • 2016년은 윤년입니다.
  • 2016년 a월 b일은 실제로 있는 날입니다. (13월 26일이나 2월 45일같은 날짜는 주어지지 않습니다)

— 입출력 예

abresult
524"TUE"

— 문제 풀이

import java.time.*;

class Solution {
    public String solution(int a, int b) {
        LocalDate start = LocalDate.of(2016,1,1);
        LocalDate end = LocalDate.of(2016,a,b);
        long diff = Duration.between(start.atStartOfDay(),end.atStartOfDay()).toDays();
        Week[] days = Week.values();
        return days[(5+((int)diff%7))%7].name();
    }
    public enum Week {
        SUN,MON,TUE,WED,THU,FRI,SAT 
    }
}

프로그래머스 159994

https://school.programmers.co.kr/learn/courses/30/lessons/159994

— 문제 설명

코니는 영어 단어가 적힌 카드 뭉치 두 개를 선물로 받았습니다. 코니는 다음과 같은 규칙으로 카드에 적힌 단어들을 사용해 원하는 순서의 단어 배열을 만들 수 있는지 알고 싶습니다.

  • 원하는 카드 뭉치에서 카드를 순서대로 한 장씩 사용합니다.
  • 한 번 사용한 카드는 다시 사용할 수 없습니다.
  • 카드를 사용하지 않고 다음 카드로 넘어갈 수 없습니다.
  • 기존에 주어진 카드 뭉치의 단어 순서는 바꿀 수 없습니다.

예를 들어 첫 번째 카드 뭉치에 순서대로 ["i", "drink", "water"], 두 번째 카드 뭉치에 순서대로 ["want", "to"]가 적혀있을 때 ["i", "want", "to", "drink", "water"] 순서의 단어 배열을 만들려고 한다면 첫 번째 카드 뭉치에서 "i"를 사용한 후 두 번째 카드 뭉치에서 "want"와 "to"를 사용하고 첫 번째 카드뭉치에 "drink"와 "water"를 차례대로 사용하면 원하는 순서의 단어 배열을 만들 수 있습니다.

문자열로 이루어진 배열 cards1cards2와 원하는 단어 배열 goal이 매개변수로 주어질 때, cards1과 cards2에 적힌 단어들로 goal를 만들 있다면 "Yes"를, 만들 수 없다면 "No"를 return하는 solution 함수를 완성해주세요.

— 제한 조건

  • 1 ≤ cards1의 길이, cards2의 길이 ≤ 10
    • 1 ≤ cards1[i]의 길이, cards2[i]의 길이 ≤ 10
    • cards1과 cards2에는 서로 다른 단어만 존재합니다.
  • 2 ≤ goal의 길이 ≤ cards1의 길이 + cards2의 길이
    • 1 ≤ goal[i]의 길이 ≤ 10
    • goal의 원소는 cards1과 cards2의 원소들로만 이루어져 있습니다.
  • cards1cards2goal의 문자열들은 모두 알파벳 소문자로만 이루어져 있습니다.

— 입출력 예

cards1cards2goalresult
["i", "drink", "water"]["want", "to"]["i", "want", "to", "drink", "water"]"Yes"
["i", "water", "drink"]["want", "to"]["i", "want", "to", "drink", "water"]"No"

입출력 예 #1

본문과 같습니다.

입출력 예 #2

cards1에서 "i"를 사용하고 cards2에서 "want"와 "to"를 사용하여 "i want to"까지는 만들 수 있지만 "water"가 "drink"보다 먼저 사용되어야 하기 때문에 해당 문장을 완성시킬 수 없습니다. 따라서 "No"를 반환합니다.

— 문제 풀이

class Solution {
    public boolean[] selected1, selected2;
    public String solution(String[] cards1, String[] cards2, String[] goal) {
        selected1 = new boolean[cards1.length];
        selected2 = new boolean[cards2.length];
        
        String[] cards = new String[goal.length];
        
        if(cal(cards1, cards2, goal, cards, 0))return "Yes";
        return "No";
    }
    
    public boolean cal(String[] cards1, String[] cards2, String[] goal, String[] cards, int cnt){
        if(cnt==goal.length) return true;
        for(int i=0;i<cards1.length;i++){
            if(i>0&&!selected1[i-1]) break;
            if(!selected1[i] && goal[cnt].equals(cards1[i])) {
                selected1[i] = true;
                if(cal(cards1, cards2, goal, cards, cnt+1)) return true;
            }
        }
        for(int i=0;i<cards2.length;i++){
            if(i>0&&!selected2[i-1]) break;
            if(!selected2[i] && goal[cnt].equals(cards2[i])) {
                selected2[i] = true;
                if(cal(cards1, cards2, goal, cards, cnt+1)) return true;
            }
        }
        return false;
    }
}

인메모리 저장소 및 캐싱 전략 강의

Redis

  • 인 메모리 DB
  • 데이터를 변경하는 게 RDB에 비해 상대적으로 빠름
  • NoSQL
  • 일시적인 데이터, 변경이 잦은 데이터를 다뤄야 되는 상황에서 많이 활용
  • Key - Value

Redis의 데이터 타입

  • String
    • 가장 기본적인 자료형
    • Java의 Map<String, String> 처럼 동작한다고 생각하면 이해하기 쉬움
    • 저장할 수 있는 최대 크기 : 512MB
    • GET, SET
      • SET : key에 value 문자열 데이터를 저장, “으로 공백 구분
      • GET : key에 저장된 문자열 반환
    • INCR, DECR
      • 만약 저장된 데이터가 정수 데이터라면 데이터 증감 가능
      • INCR key : key에 저장된 데이터를 1 증가
      • DECR key : key에 저장된 데이터를 1 감소
    • MSET, MGET
      • MSET [key value][key value] … : key value 형태로 주어진 여러 데이터들을 한번에 저장할 수 있음
      • MGET [key][key] … : 모든 key들에 해당하는 데이터를 반환
  • List
    • 여러 문자열 데이터를 Linked List의 형태로 보관하는 자료형. 스택 또는 큐처럼 사용할 수 있음
    • PUSH 혹은 POP을 L, R과 조합해서 왼쪽 혹은 오른쪽에 데이터를 추가 / 제거 가능
    • LPUSH, RPUSH, LPOP, RPOP
    • LLEN, LRANGE
      • LLEN은 없는 Key일 경우 0, 다른 자료형일 경우 오류 발생
      • LRANGE
        • end가 실제 길이를 벗어나도 오류 발생 X
        • start > end 일 경우 빈 결과가 반환
        • 음수의 경우 뒤에서부터 데이터를 가져옴
  • SET
    • 문자열의 집합. 중복값을 제거하고, 순서가 존재하지 않음
    • SADD, SREM, SMEMBERS, SISMEMBER, SCARD
      • SADD key value - 추가
      • SREM key value - 제거
      • SMEMBERS key - key에 저장된 모든 원소 반환
      • SISMEMBER key value : key의 집합에 value가 존재하는지 반환
      • SCARD key : key의 집합의 크기를 반환
    • SINTER, SUNION, SINTERCARD
      • SINTER key1 key : key1과 key2의 교집합 원소들을 반환
      • SUNION key1 key2 : key1과 key2의 합집합 원소들을 반환
      • SINTERCARD number key1 key2 … keyN : number개의 key 집합들의 교집합의 크기를 반환
  • Hash
    • Field - Value 자료형. Hash 데이터를 가져오기 위해 key를 사용하고, 이후에 key에 저장된 Hash 데이터에 Field - Value 쌍을 넣어주는 형식으로 동작
    • HSET, HGET, HMGET, HGETALL, HKEYS, HLEN
      • HEST key field value field value …. : key의 Hash 데이터에 field-value를 넣음. 한번에 여러 쌍 넣을 수 있음
      • HGET key field : key에 저장된 Hash의 field에 저장된 value를 반환. 없는 field는 null
      • HMGET key field field … : key에 저장된 Hash에서 복수의 field에 저장된 value를 반환
      • HGETALL key : key에 저장된 Hash의 모든 field - value를 반환
      • HKEYS key : key에 저장된 Hash의 모든 field를 반환
      • HLEN key : key에 저장된 Hash의 field 갯수를 반환
  • Sorted Set
    • 정렬된 집합
    • 유일한 값과 score라는 실수를 함께 보관
    • 데이터를 가져올 때, score를 바탕으로 정렬
    • ZADD, ZINCRBY, ZRANK, ZRANGE, ZREVRANK, ZREVRANGE
      • ZADD key score member score member … : key의 Sorted Set에 score의 점수를 가진 member를 추가. 이미 있는 member의 경우 score 업데이트
      • ZRANK key member : key의 Sorted Set의 member 순위를 오름차순 기준으로 0에서 부터 세서 반환
      • ZRANGE key start stop : key의 Sorted Set의 member들을 start 부터 stop 순위까지 오름차순 기준으로 반환
      • ZREVRANK key member : key의 Sorted Set의 member 순위를 내림차순 기준으로 0에서 부터 세서 반환
      • ZREVRANGE key start stop : key의 Sorted Set의 member들을 start부터 stop 순위까지 내림차순 기준으로 반환
      • ZINCRBY key icrement member : key의 Sorted Set의 member의 score를 increment 만큼 증가 (음수 전달 시 감소)
  • 공통 명령어
    • DEL, EXPIRE, EXPIRETIME
      • DEL key : key 제거 - value도 같이 제거됨
      • EXPIRE key seconds : key의 TTL(유효시간)을 seconds로 설정
      • EXPIRETIME key : key가 만료되는 시각을 Unix Timestamp로 반환
    • FLUSHDB : 모든 key 제거

Redis 실습

  • 의존성
    • build.gradle
      dependencies {
      	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
      	implementation 'org.springframework.boot:spring-boot-starter-web'
      	compileOnly 'org.projectlombok:lombok'
      	annotationProcessor 'org.projectlombok:lombok'
      	testImplementation 'org.springframework.boot:spring-boot-starter-test'
      	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
      }
  • application.yml
    spring:
      application:
        name: redis
      data:
        redis:
          host: localhost
          port: 6379
          password: {설정한 password 없으면 X}
  • Item Class
    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    //Entity 대신 RedisHash
    @RedisHash("item")
    public class Item implements Serializable {
        @Id
        private Long id;
        private String name;
        private String description;
        private Integer price;
    }

CrudRepository

  • ItemRepository
    public interface ItemRepository extends CrudRepository<Item, Long> {
    }
  • 테스트 코드 작성
    @SpringBootTest
    public class RedisRepositoryTests {
    
        @Autowired
        private ItemRepository itemRepository;
    
        @Test
        @DisplayName("Redis 생성 테스트")
        public void createTest(){
            Item item = Item.builder()
                    .id(1L)
                    .name("keyboard")
                    .description("Very Expensive Keyboard")
                    .price(1000000)
                    .build();
    
            itemRepository.save(item);
        }
    
        @Test
        @DisplayName("Redis 단건 조회 테스트")
        public void findOneTest(){
            Item item = itemRepository.findById(1L)
                    .orElseThrow(RuntimeException::new);
            System.out.println(item.getDescription());
    
        }
    
        @Test
        @DisplayName("Redis 업데이트 테스트")
        public void updateTest(){
            Item item = itemRepository.findById(1L)
                    .orElseThrow(RuntimeException::new);
            item.setDescription("On Sales!!!");
            item = itemRepository.save(item);
            System.out.println(item.getDescription());
        }
    
        @Test
        @DisplayName("Redis 삭제 테스트")
        public void deleteTest(){
            itemRepository.deleteById(1L);
            assertThrows(NoSuchElementException.class, () -> {
                Item item = itemRepository.findById(1L).orElseThrow();
            });
        }
        
    }

RedisTemplate

  • Item 수정
    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    //Entity 대신 RedisHash
    @RedisHash("item")
    public class Item implements Serializable {
        @Id
        private String id;
        private String name;
        private String description;
        private Integer price;
    }
    
  • Item Repository 수정
    public interface ItemRepository extends CrudRepository<Item, String> {
    }
  • ItemDto 생성
    @Getter
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class ItemDto implements Serializable {
        private String name;
        private String description;
        private Integer price;
    }
    
  • RedisConfig 생성
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, ItemDto> itemRedisTemplate(
                RedisConnectionFactory redisConnectionFactory
        ){
            RedisTemplate<String, ItemDto> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            template.setKeySerializer(RedisSerializer.string());
            template.setValueSerializer(RedisSerializer.json());
            return template;
        }
    }
    
  • 테스트 코드 작성
    @SpringBootTest
    public class RedisTemplateTests {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void stringOpsTest(){
            //문자열 조작을 위한 클래스
            ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
            ops.set("simplekey", "simplevalue");
            System.out.println(ops.get("simplekey"));
    
            // 집합을 조작하기 위한 클래스
            SetOperations<String, String> setOps = stringRedisTemplate.opsForSet();
            setOps.add("hobbies", "games");
            setOps.add("hobbies", "coding", "alchohol", "games");
            System.out.println(setOps.size("hobbies"));
    
            stringRedisTemplate.expire("hobbies", 10, TimeUnit.SECONDS);
            stringRedisTemplate.delete("simplekey");
        }
    
        @Autowired
        private RedisTemplate<String, ItemDto> itemRedisTemplate;
    
        @Test
        public void itemRedisTemplateTest(){
            ValueOperations<String, ItemDto> ops
                    = itemRedisTemplate.opsForValue();
            ops.set("my:keyboard", ItemDto.builder()
                    .name("Mechanical Keyboard")
                    .price(250000)
                    .description("OMG")
                    .build());
            System.out.println(ops.get("my:keyboard"));
    
        }
    }
    

Redis CLI 명령어 실습

  • 블로그 글 별 조회수를 Redis로 확인하려함
    • 블로그 URL Path 는 /articles/{id}

    • 로그인 여부와 상관없이 새로고침 될때마다 조회수가 하나 증가

      INCR articles:id
      GET articles:id
      # incr articles:1
      # get articles:1
  • 블로그에 로그인한 사람들의 조회수와 가장 많은 조회수를 기록한 글을 Redis로 확인
    • 블로그 URL Path 는 /articles/{id}

    • 로그인 한 사람들의 계정은 영문으로만 이뤄져 있음

      -- 로그인한 사람들이 오늘 조회한 수
      sadd articles:2:20240806 alex
      scard articles:2:20240806
      
      -- 가장 많은 조회수를 기록한 글
      zincrby articles:ranks 1 articles:1
      zrange articles:ranks 0 -1 withscores
      zrevrange articles:ranks 0 -1 withscores

Spring Boot 프로젝트 Redis 실습

  • 주문 ID, 판매 물품, 갯수, 총액, 결제 여부에 대한 데이터를 지정하기 위한 ItemOrder 클래스를 RedisHash 로 만들고
  • 주문에 대한 CRUD 구현
  • Order
    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @RedisHash("order")
    public class Order {
        @Id
        private String id;
        private String item;
        private Integer count;
        private Long totalPrice;
        private String status;
    }
    
  • OrderRepository
    public interface OrderRepository extends CrudRepository<Order, String> {
    }
  • OrderController
    @RestController
    @RequestMapping("/orders")
    public class OrderController {
    
        private final OrderRepository orderRepository;
    
        public OrderController(OrderRepository orderRepository) {
            this.orderRepository = orderRepository;
        }
    
        @PostMapping
        public Order createOrder(@RequestBody Order request){
            return orderRepository.save(request);
        }
    
        @GetMapping
        public List<Order> getAllOrders(){
            List<Order> orders = new ArrayList<>();
            orderRepository.findAll()
                    .forEach(orders::add);
            return orders;
        }
    
        @PutMapping("/{id}")
        Order updateOrder(@PathVariable String id, @RequestBody Order request){
            Order order = orderRepository.findById(id)
                    .orElseThrow(()-> {
                        return new HttpClientErrorException(HttpStatus.NOT_FOUND, "주문 정보가 없습니다.");
                    });
            order.setItem(request.getItem());
            order.setCount(request.getCount());
            order.setTotalPrice(request.getTotalPrice());
            order.setStatus(request.getStatus());
            return orderRepository.save(order);
        }
    
        @DeleteMapping("/{id}")
        String deleteOrder(@PathVariable String id){
            orderRepository.deleteById(id);
            return id + " Order is deleted.";
        }
    }
    
  • 블로그 게시글 Spring Boot로 구현
    • RedisConfig에 Bean 두개 추가
      @Bean
          public RedisTemplate<String, Integer> articleTemplate(
                  RedisConnectionFactory redisConnectionFactory
          ) {
              RedisTemplate<String, Integer> template = new RedisTemplate<>();
              template.setConnectionFactory(redisConnectionFactory);
              template.setKeySerializer(RedisSerializer.string());
              template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
              return template;
          }
      
          @Bean
          public RedisTemplate<String, String> articleUserTemplate(
                  RedisConnectionFactory redisConnectionFactory
          ) {
              RedisTemplate<String, String> template = new RedisTemplate<>();
              template.setConnectionFactory(redisConnectionFactory);
              template.setKeySerializer(RedisSerializer.string());
              template.setValueSerializer(RedisSerializer.string());
              return template;
          }
    • ArticleController
      @RestController
      @RequestMapping("/articles")
      public class ArticleController {
      
          private final ValueOperations<String, Integer> ops;
          private final ZSetOperations<String, String> zOps;
      
          public ArticleController(RedisTemplate<String, Integer> articleTemplate,
          RedisTemplate<String,String> articleUserTemplate) {
              ops = articleTemplate.opsForValue();
              zOps = articleUserTemplate.opsForZSet();
          }
      
          @GetMapping("{id}")
          @ResponseStatus(HttpStatus.NO_CONTENT)
          public void read(@PathVariable Long id) {
              ops.increment("articles:%d".formatted(id));
              zOps.incrementScore("articles:rank", "articles:%d".formatted(id), 1);
          }
      
          @GetMapping("/rank")
          public Set<ZSetOperations.TypedTuple<String>> getRanks(){
              return zOps.reverseRangeWithScores("articles:rank", 0, -1);
          }
      }

MSA 실습 프로젝트

Spring Boot 개발 환경 Profile 분리

  • 개발 환경별로 달라지는 부분을 application-{환경}.yml 파일 개발환경 별로 분리
  • devprod 로 분리
  • application.yml ( 예시 : Auth 서비스 )
    spring:
      profiles:
        default: dev # 실행 옵션에 profile 지정이 없으면 dev로 실행
      application:
        name: auth-service
      jpa:
        hibernate:
          ddl-auto: update
        show-sql: true
        properties:
          hibernate:
            format_sql: true
            show_sql: true
            use_sql_comments: true
            highlight_sql: true
    server:
      port: 19095
    service:
      jwt:
        access-expiration: 3600000
        secret-key: "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"
    
  • application-dev.yml
    spring:
      config:
        activate:
          on-profile: dev
      datasource:
        url: jdbc:mysql://localhost:3306/msa_exam
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:19090/eureka/
  • application-prod.yml
    spring:
      config:
        activate:
          on-profile: prod
      datasource:
        url: jdbc:mysql://{RDS URL}:3306/{RDS DB명}
        username: admin
        password: # VM Option으로 Password 입력
        driver-class-name: com.mysql.cj.jdbc.Driver
    eureka:
      client:
        service-url:
          defaultZone: # VM Option으로 eureka 서버 url 입력

Custom Response Header 추가

  • Product 서버를 이중화할 예정이기 때문에 로드밸런싱이 잘 이루어지고 있는지 확인을 위해 Response Header에 Server Port 추가 필요
  • ResponseEntity를 만드는 곳에서 ( Controller or Service ) Value import
    • org.springframework.beans.factory.annotation.Value
  • Server port 값 가져오기
    @Value("${server.port}")
    private int serverPort;
  • ResponseEntity에 Custom Header 추가
    ResponseEntity.status(HttpStatus.OK)
                    .header("Server-Port", String.valueOf(serverPort))
                    .body(res);
profile
기록을 남겨보자

0개의 댓글