<MySQL & Oracle>

SELECT A.FOOD_TYPE
     , A.REST_ID
     , A.REST_NAME
     , A.FAVORITES
  FROM REST_INFO A
 INNER JOIN (
              SELECT FOOD_TYPE
                   , MAX(FAVORITES) AS FAVORITES
                FROM REST_INFO
            GROUP BY FOOD_TYPE
            ) B
    ON A.FOOD_TYPE = B.FOOD_TYPE
   AND A.FAVORITES = B.FAVORITES
 ORDER BY A.FOOD_TYPE DESC

프로그래머스 즐겨찾기가 가장 많은 식당 정보 출력하기 SQL

알고리즘 4문제, SQL 1문제, 제한 시간 210분

알고리즘 3문제, SQL 1문제, 제한 시간 120분

public class ExceptionOccurred {
	
	// Checked Exception : RuntimeException을 상속받지 않은 클래스
	// Unchecked Exception : RuntimeException을 상속받은 클래스
	// throw : 강제로 예외를 발생시킬 수 있음 단, 발생시킨 예외를 catch문에서 잡을 수 있는 처리가 되어있지 않으면 오류 발생(catch문의 Exception은 모든 예외를 잡을 수 있음)
	// throws : 예외 발생 시 자신을 호출한 상위 메소드로 예외를 던짐(특정 상위 메소드에서 예외를 한 번에 처리하는 경우가 있을 수 있음, 계속 throws로 던질 경우 최종적으로 JVM이 처리를 하게 되지만, 권장하지 않음)
	// 중요 : RuntimeException을 상속받은 Unchecked Exception의 경우 throws는 아무 의미 없음, throws는 Checked Exception의 처리 방법 중 하나
	
	public void method1() throws Exception {
		// 이 메소드를 호출한 부분의 catch문에 Exception에 대한 에외 처리가 있어야 함
		throw new Exception("강제로 예외 발생"); // 모든 예외를 잡을 수 있는 Exception의 경우 throws Exception 필수
	}
	
	// RuntimeException을 상속받은 NullPointerException
	public void method2() {
		// 이 메소드를 호출한 부분의 catch문에 Exception 또는 NullPointerException에 대한 예외 처리가 있어야 함
		throw new NullPointerException("강제로 NullPointerException 발생"); // NullPointerException 발생
	}
	
	// RuntimeException을 상속받은 ArithmeticException
	public void method3() {
		// 이 메소드를 호출한 부분의 catch문에 Exception 또는 ArithmeticException에 대한 예외 처리가 있어야 함
		System.out.println(3 / 0); // ArithmeticException 발생
	}
	
	// RuntimeException을 상속받은 NullPointerException 발생이므로 throws는 아무 의미 없음
	public void method4() throws ArithmeticException {
		throw new NullPointerException("강제로 NullPointerException 발생"); // NullPointerException 발생
	}
	
	// RuntimeException을 상속받은 NullPointerException 발생이므로 throws는 아무 의미 없음
	public void method5() throws ArithmeticException {
		String name = null;
		System.out.println(name.length());
	}
	
	// ArithmeticException : 어떤 수를 0으로 나눌 때 발생(RuntimeException 상속)
	// NullPointerException : NULL 객체를 참조할 때 발생(RuntimeException 상속)
	// ClassCastException : 적절하지 못하게 클래스를 형 변환하는 경우 발생(RuntimeException 상속)
	// NegativeArraySizeException : 배열의 크기가 음수 값인 경우 발생(RuntimeException 상속)
	// IndexOutOfBoundsException : 리스트형 객체에서 선언되지 않은 요소를 가져오려고 할 때 발생(RuntimeException 상속)
}
public class ExceptionTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String name = null;
		
		ExceptionOccurred exceptionOccurred = new ExceptionOccurred();
		
		try {
			int nameLength = name.length(); // 1. NullPointerException에 걸리고 finally 실행
			int num = 3 / 0; // 2. 코드 1이 없다면 ArithmeticException에 걸리고 finally 실행
			exceptionOccurred.method2(); // 3. 코드 1, 2가 없다면 NullPointerException에 걸리고 finally 실행
			exceptionOccurred.method3(); // 4. 코드 1, 2, 3이 없다면 ArithmeticException에 걸리고 finally 실행
		} catch (NullPointerException e) {
			System.out.println("NullPointerException : " + e.getMessage());
		} catch (ArithmeticException e1) {
			System.out.println("ArithmeticException : " + e1.getMessage());
		} catch (Exception e) { // try문 실행에서 발생하는 예외 중 NullPointerException, ArithmeticException 외의 예외는 이곳에 걸림
			System.out.println("Exception : 모든 예외를 잡을 수 있음");
		} finally { // 무조건 실행시킬 코드를 적는 부분
			System.out.println("무조건 실행되는 부분");
		}
		
		try {	
			int nameLength = name.length(); // 1. Exception에 걸리고 finally 실행
			int num = 3 / 0; // 2. 코드 1이 없다면 Exception에 걸리고 finally 실행
			exceptionOccurred.method1(); // 3. 코드 1, 2가 없다면 Exception에 걸리고 finally 실행
			throw new Exception("강제 예외 발생"); // 4. 코드 1, 2, 3이 없다면 Exception에 걸리고 finally 실행
//			exceptionOccurred.method2(); // 5. 코드 1, 2, 3, 4가 없다면 Exception에 걸리고 finally 실행
//			exceptionOccurred.method3(); // 6. 코드 1, 2, 3, 4, 5가 없다면 Exception에 걸리고 finally 실행
		} catch (Exception e) { // try문 실행에서 발생하는 모든 예외를 잡을 수 있지만, 어떤 문제로 발생하는 예외인지 확인이 불가하다는 단점이 있다.
			System.out.println("Exception : 모든 예외를 잡을 수 있음");
		} finally { // 무조건 실행시킬 코드를 적는 부분
			System.out.println("무조건 실행되는 부분");
		}
	}
}

Checked Exception : RuntimeException을 상속받지 않은 클래스

Unchecked Exception : RuntimeException을 상속받은 클래스

throw : 강제로 예외를 발생시킬 수 있음 단, 발생시킨 예외를 catch문에서 잡을 수 있는 처리가 되어있지 않으면 오류 발생(catch문의 Exception은 모든 예외를 잡을 수 있음)

throws : 예외 발생 시 자신을 호출한 상위 메소드로 예외를 던짐(특정 상위 메소드에서 예외를 한 번에 처리하는 경우가 있을 수 있음, 계속 throws로 던질 경우 최종적으로 JVM이 처리를 하게 되지만, 권장하지 않음)

 

중요 : RuntimeException을 상속받은 Unchecked Exception의 경우 throws는 아무 의미 없음, throws는 Checked Exception의 처리 방법 중 하나

 

 

'Java > 참고자료' 카테고리의 다른 글

[Java] Equals & HashCode  (0) 2022.11.21
[Java] Comparable & Comparator  (0) 2022.09.15
[Java] Stack, Queue, Deque  (0) 2022.09.05
[Java] 참고자료  (0) 2022.08.31

<추가 또는 변경한 파일>

BadRequestException : (추가) 400 예외 처리를 위해

GeneralExceptionHandler : (변경) 400 예외 처리를 위해

JdbcOrderRepository : (추가) orders 테이블의 데이터 조회, 변경을 위해

JdbcProductRepository : (변경) products 테이블의 데이터 변경을 위해

JdbcReviewRepository : (추가) reviews 테이블의 데이터 조회, 추가를 위해

Order : (추가) Order Entity

OrderDto : (추가) Order Dto

OrderRepository : (추가) JdbcOrderRepository 추가를 위해

OrderRestController : (추가) 6개의 메소드

OrderRestControllerTest : (변경) 정상적인 테스트를 위해 어노테이션 추가

OrderService : (추가) 6개의 메소드

ProductRepository : (변경) JdbcProductRepository 변경을 위해

ProductRestController : (변경) 2개의 메소드

ProductService : (변경) 1개의 메소드 추가

Review : (추가) Review Entity

ReviewDto : (추가) Review Dto

ReviewRepository : (추가) JdbcReviewRepository 추가를 위해

ReviewRestController : (추가) 1개의 메소드

ReviewRestControllerTest : (변경) 정상적인 테스트를 위해 어노테이션 추가

ReviewService : (추가) 2개의 메소드

의존성 주입 방식

 

1. @Autowired (필드 주입 : Field Injection)

참고 : 필드를 final로 선언 불가

@Service
public class ArticleService {

  @Autowired
  private ArticleRepository articleRepository;
}

 

2. private final (생성자 주입 : Constructor Injection)

@Service
public class ArticleService {

  private final ArticleRepository articleRepository;

  public ArticleService(ArticleRepository articleRepository) {
    this.articleRepository = articleRepository;
  }
}

 

private final 방식이 더 좋은 이유

1. 순환 참조를 방지할 수 있다. (순환 참조 발생 시, Application이 구동되지 않는다.)

2. 테스트에 용이하다.

3. final 선언이 가능해 불변성이 보장된다.

4. 코드의 품질을 높일 수 있다.

5. 오류를 방지할 수 있다.

Spring Boot에서 JWT를 활용한 인증 구현

세션 VS 토큰

 

[세션]

1. 클라이언트가 로그인 요청을 하고, 로그인 정보가 일치하면 서버는 세션을 생성/유지 (클라이언트마다 하나씩)

(세션에는 사용자 ID, 로그인 시간, IP 등을 저장)

2. 서버는 로그인 응답 (세션을 찾을 수 있는 세션 ID를 클라이언트에 전달 (보통 쿠키로 전달))

(세션은 서버에 저장되는 값, 쿠키는 클라이언트에 저장되는 값)

3. 클라이언트가 세션 ID와 함께 서비스 요청을 하면 서버는 세션 ID로 세션을 찾아 인증된 유저임을 확인

4. 서버는 서비스 응답

서버가 세션을 들고있다.

 

서버가 1대라면 세션을 사용해도 괜찮지만, 서버는 여러 대가 존재할 것이다. (모두 세션을 가져야 함 & 동기화)

세션 클러스터링 등 작업이 복잡하며 DB에도 부담을 줄 수 있다.

 

[토큰]

1. 클라이언트가 로그인 요청을 하면 서버는 토큰을 생성 (클라이언트마다 하나씩)

2. 서버는 토큰과 함께 로그인 응답

3. 클라이언트가 토큰과 함께 서비스 요청을 하면 서버는 토큰으로 인증된 유저임을 확인

4. 서버는 토큰과 함께 서비스 응답

클라이언트와 서버가 토큰을 주고 받는다.

 

build.gradle의 dependencies에 아래의 코드 추가

implementation 'javax.xml.bind:jaxb-api:'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

SecurityController

package com.example.firstproject.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;

@RestController
@RequestMapping("/security")
public class SecurityController {

    @Autowired
    private SecurityService securityService;

    @GetMapping("/create/token")
    public Map<String, Object> createToken(@RequestParam(value = "subject") String subject) { // 일반적으로 subject를 ID 값으로, PW를 key 값으로 같이 보내는 POST 방식이 맞지만 여기서는 확인을 위해 GET 방식으로 한다.
        String token = securityService.createToken(subject, (2 * 1000 * 60)); // 2분
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("result", token);
        return map;
    }

    @GetMapping("/get/subject")
    public Map<String, Object> getSubject(@RequestParam(value = "token") String token) {
        String subject = securityService.getSubject(token);
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("result", subject);
        return map;
    }
}

SecurityService

package com.example.firstproject.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;

@Service
public class SecurityService {
    private static final String SECRET_KEY = "qweqiwehqhruhqwiejqiwejqiwheuqfjnqweojqwiejqwuequwe";

    // 로그인 서비스 보낼 때 같이
    public String createToken(String subject, long expTime) {

        if (expTime <= 0) {
            throw new RuntimeException("만료 시간은 0보다 커야합니다.");
        }

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);

        Key signingKey = new SecretKeySpec(secretKeyBytes, signatureAlgorithm.getJcaName());

        return Jwts.builder()
                   .setSubject(subject)
                   .signWith(signingKey, signatureAlgorithm)
                   .setExpiration(new Date(System.currentTimeMillis() + expTime))
                   .compact();
    }

    // 실제로 사용할 때는 안에 있는 로직을 이용해 boolean 타입을 리턴하는 토큰 검증 메소드를 만들고 토큰 검증 로직에서 이 메소드를 호출해서 사용
    public String getSubject(String token) {
        Claims claims = Jwts.parserBuilder()
                            .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY))
                            .build()
                            .parseClaimsJws(token)
                            .getBody();

        return claims.getSubject();
    }
}

POSTMAN 테스트

1. /create/token

2. /get/subject

3. /get/subject 토큰 유지 시간으로 설정한 2분이 지나면

import java.util.LinkedList;

public class Solution {
	
	// [1차] 캐시
	// LRU(Least Recently Used) : 가장 오랫동안 참조되지 않은 페이지를 교체하는 방식
	// 캐시가 가득 찼을 때, 가장 오랫동안 참조되지 않은 페이지를 찾아서 없애는 과정이 필요
    
	public static int solution(int cacheSize, String[] cities) {
		int answer = 0;
		String city = "";
		
		LinkedList<String> cache = new LinkedList<>();
		
		if (cacheSize == 0) {
			return cities.length * 5;
		}
		
		for (int i = 0; i < cities.length; i++) {
			
			city = cities[i].toLowerCase();
			
			if (cache.remove(city)) { // cache hit
				cache.addFirst(city); // 제거 후 다시 추가하여 최신화
				answer += 1;
			} else { // cache miss
				
				if (cache.size() == cacheSize) {
					cache.removeLast(); // 가장 오래된 것 제거
				}
				
				cache.addFirst(city);
				answer += 5;
			}
		}
		
		return answer;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int cacheSize = 3;
		String[] cities = {"Jeju", "Pangyo", "Seoul", "Jeju", "Pangyo", "Seoul", "Jeju", "Pangyo", "Seoul"};
		
		System.out.println(solution(cacheSize, cities));
	}
}

문제 해결을 위해 사용한 LinkedList에 대해 조금 더 알아보자

public void linkedListTest() {

	// add, remove(실패 시 Exception 발생), element
	// offer, poll(실패 시 null 리턴), peek
	// LinkedList의 경우 add, remove, offer, poll을 (A)로 사용해 (A), (A)First, (A)Last가 가능하다.
	// remove의 경우 remove("A")와 같이 지정 삭제가 가능하지만, poll의 경우 poll("A")와 같이 지정 삭제가 불가능하다.
		
	// 1. add & remove & element
	LinkedList<String> linkedList1 = new LinkedList<>();
		
	linkedList1.add("A"); // linkedList1.element() => A
	linkedList1.add("B"); // linkedList1.element() => A
	linkedList1.add("C"); // linkedList1.element() => A
	linkedList1.add("D"); // linkedList1.element() => A
		
	System.out.println(linkedList1.element()); // A
		
	linkedList1.remove(); // A
		
	System.out.println(linkedList1.element()); // B
		
	linkedList1.remove("C"); // C
		
	System.out.println(linkedList1.element()); // B
		
	// 2. add, addFirst, addLast & remove, removeFirst, removeLast & element
	LinkedList<String> linkedList2 = new LinkedList<>();
		
	linkedList2.add("A"); // linkedList2.element() => A
	linkedList2.addFirst("B"); // linkedList2.element() => B
	linkedList2.add("C"); // linkedList2.element() => B
	linkedList2.addLast("D"); // linkedList2.element() => B
	linkedList2.add("E"); // linkedList2.element() => B
		
	System.out.println(linkedList2.element()); // B
		
	linkedList2.removeFirst(); // B
		
	System.out.println(linkedList2.element()); // A
		
	linkedList2.removeLast(); // E
		
	linkedList2.remove("C"); // C
		
	System.out.println(linkedList2.element()); // A
		
	// 3. offer & poll & peek
	LinkedList<String> linkedList3 = new LinkedList<>();
		
	linkedList3.offer("A"); // linkedList3.peek() => A
	linkedList3.offer("B"); // linkedList3.peek() => A
	linkedList3.offer("C"); // linkedList3.peek() => A
	linkedList3.offer("D"); // linkedList3.peek() => A
		
	System.out.println(linkedList3.peek()); // A
		
	linkedList3.poll(); // A
		
	System.out.println(linkedList3.peek()); // B
		
//	linkedList3.poll("C"); // 지정 삭제 불가
		
	// 4. offer, offerFirst, offerLast & poll, pollFirst, pollLast & peek
	LinkedList<String> linkedList4 = new LinkedList<>();
		
	linkedList4.offer("A"); // linkedList4.peek() => A
	linkedList4.offerFirst("B"); // linkedList4.peek() => B
	linkedList4.offer("C"); // linkedList4.peek() => B
	linkedList4.offerLast("D"); // linkedList4.peek() => B
	linkedList4.offer("E"); // linkedList4.peek() => B
		
	System.out.println(linkedList4.peek()); // B
		
	linkedList4.pollFirst(); // B
		
	System.out.println(linkedList4.peek()); // A
		
	linkedList4.pollLast(); // E
		
//	linkedList4.poll("C"); // 지정 삭제 불가
		
	System.out.println(linkedList4.peek()); // A
}

프로그래머스 1차 캐시 문제 풀이 Java 소스 코드

+ Recent posts