Skip to content

Latest commit

 

History

History
471 lines (288 loc) · 18.2 KB

TIL220412.md

File metadata and controls

471 lines (288 loc) · 18.2 KB

Daily to do list

Java


Spring


CS


알고리즘


오늘의 회고

220412 Spring DB

JDBC의 이해

JDBC의 이해 —————————————— JDBC 등장 이유 : 애플리케이션을 개발할 떄 중요한 데이터는 대부분 DB에 보관

옛날에는 DB를 여러개 사용하였다.

그래서 JDBC가 등장

  • JDBC 표준 인터페이스 JDBC는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트 하는 방법을 제공.

 자바는 이렇게 표준 인터페이스를 정의해두었다. 즉, 인터페이스를 각각의 DB 회사에서 자기네 DB에 맞도록 구현해서 라이브러리로 제공. 이것이 JDBC 드라이버

  • 정리 JDBC의 등장으로 다음 2가지 문제가 해결되었다.
  1. 데이터페이스를 다른 종류의 데이터베이스로 변경하면 애플리케이션 서버의 데이터베이스 사용 코드도 함께 변경해야하는 문제.
  2. 개발자가 각각의 데이터베이스마다 커넥션 연결, SQL 전달 그리고 결과 응답을 새로 학습해야 하는 문제.

표준화의 한계 : 각각의 데이터베이스마다 SQL, 데이터타입 등의 일부 사용법이 다르다.. 대표적으로 실무에서 기본으로 사용하는 페이징 SQL은 각각의 데이터베이스마다 사용법이 다르다…

결국 데이터베이스를 변경하면 JDBC 코드는 변경하지 않아도 되지만 SQL은 DB에 맞게 변경해야 한다.

참고로 JPA를 사용하면 이렇게 각각의 데이터베이스마다 다른 SQL을 정의해야 하는 문제도 많은 부분 해결할 수 있다.

————————————————————— JDBC와 최신 데이터 접근 기술

JDBC는 1997년 출시… 오래된 기술;; 사용 방법도 복잡. 그래서 최근에는 JDBC를 직접 사용하지 않고 편리하게 사용하느 기술이 존재 (SQL Mapper, ORM 기술)

ORM 기술 

sql mapper VS ORM 기술

각각 장단점이 있다. 쉽게 설명하면 SQL Mapper는 SQL만 직접 작성하면 나머지 번거로운 일은 SQL Mapper가 대신 해결 해준다. ORM 기술은 SQL 자체를 작성하지 않아도 되지만 개발 생산성이 높아진다. 편리한 반면 쉬운 기술이 아니여서.. 학습이 많이 필요

**** 중요 이런 기술들도 결국 내부에서는 JDBC를 사용한다!! 즉 JDBC를 직접 사용하지 않더라도 JDBC가 어떻게 동작하는지 기본 원리를 알아두어야 한다. 그래야지 깊이 있게 이해하고 문제가 생겼을 떄 근본적인 문제를 찾아서 해결할 수 있다. “JDBC는 자바 개발자라면 꼭 알아두어야 하는 필수 기본 기술!!”

———————————————— 데이터베이스 연결

 DriverManager.getConnection을 통해서 JDBC와 연결 함, 드라이버를 찾는다. 즉, MySQL 드라이버, Oracle드라이버 이런 식으를 찾음

 이와 같이 실행 결과에서는 예제에서 쓰는 h2드라이버를 찾아옴

 DriverManager는 라이버르리에 등록된 DB드라이버들을 관리하고 커넥션을 획득하는 기능 여기서 각각의 드라이버는 URL 정보를 체크해서 본인이 처리할 수 있는 요청인지 확인. 따라서 H2드라이버는 본인이 처리할 수 있으므로 실제 DB와 연결해서 커넥션을 획득하고 클라이언트에 반환.(반면에 Mysql 드라이버가 먼저 실행되면 본인이 처리할 수 없다고하고 다음 드라이버로 순서가 넘어감)

——————————————— JDBC 개발 - 등록

 finally에서 인셉션이 터지면.. 안되므로  이렇게 따로 try catch를 또 만들어 줘야 한다.

save() SQL 전달 커넥션을 연결하고 sql문을 넣는다.

 executeUpdate() Statememt를 통해 준비된 SQL을 커넥션을 통해 실제 DB에 전달. 반환되는 값은 영향 받은 DB row 수

리소스 정리.. finally에서 정의(항상 실행되도록) 만약 커넥션을 끊어지지 않고 계쏙 우지 되면 리소스 누수, 커넥션 부족으로 장애 발생

PreparedStatement 는 Statement의 자식 타입 ?를 통해 바인딩 제공 (파라미터 바인딩을 하면 데이터 취급되기 떄문에 SQL Injection에서도 안전)

 테스트 코드, 2번 실행하면 기본키가 겹쳐서 에러..

—————————————————— JDBC 개발 - 조회

  원ㄹ findMember, member는 다른 객체 하지만 assertThat에선 같음

*** EqualsAndHashCode equals 두 객체의 내용이 같은지 동등성을 비교 HashCode 두 객체가 같은 객체인지 동일성을 비교 자바 bean에서 동등성 비교를 위해 equals와 hashcode 메소드를 오버라이딩해서 사용 @EqualsAnndHashCode 애노테이션이 있음

실행 결과가 ToString으로 잘보임

*** ResultSet 데이터 구조, select쿼리의 결과가 순서대로 나옴. 예를 들어서 select member_id, money 라고 하면 member_id, money라는 이름으로 데이터가 저장. 참고로 select *을 사용하면 테이블 모든 컬럼을 다 지정

ResultSet내부에 있는 커서를 이동해서 다음 데이터를 조회할 수 있다. rs.next() << 이것을 사용하면 커서가 다음으로 이동, 최초의 커서는 데이터를 가리키고 있지 않음 그래서 rs.next()를 최초에 한번은 호출해야 데이터를 조회. rs.next()결과가 true이면 커서의 이동 결과 데이터가 있다는 뜻.

rs.getString(“member_id) 현재 커서가 가리키고 있느 ㄴ위치의 member_id 데이터를 String타입으로 반환.

——————————— JDBC 개발 - 수정 삭제

 수정

 삭제

크게 변화가 있는 것은 없고.. 반복이 많이 된다. sql문만 다르다

커넥션풀 과 데이터 이해

——————————————————

커넥션풀

  1. 애플리케이션 로직은 DB 드라이버를 통해 커넥션 조회
  2. DB드라이버는 DB와 TCP/IP 커넥션을 연결, 여기서 TCP/IP 연결을 위한 네트워크 동작 발생
  3. DB드라이버는 TCPIP커넥션이 연결되면 ID, PW와 기타 부가정보를 DB에 전달
  4. DB는 ID, PW를 통해 내부 인증을 완료, 내부에 DB 세션을 생성
  5. DB는 커넥션이 생성 완료되었다는 응답 보냄
  6. DB 드라이버는 커넥션 객체를 생성해서 클라에 반환

이렇게 커넥션을 새로 만드는 과정은 복잡하고 시간도 많이 소모 되는 일 진짜 문제는 고객이 애플리케이션을 사용할 떄 SQL을 실행하는 시간 뿐만 아니라 커넥션을 새로 만드는 시간이 추가되기 떄문에 응답 속도에 영향을 준다.

참고 : 데이터베이스마다 커넥션을 생성하는 시간이 다름ㅁ

이런 문제를 한번에 해결하는 아이디어가 바로 커넥션을 미리 생성해두고 사용하는 커넥션 풀이라는 방법, 이름 그대로 커넥션을 관리하는 풀(수영장. 풀…을 상상)

 

애플리케이션 로직에서 이제는 DB드라이버를 통해서 새로운 커넥션을 획득하는 것이 아닌 커넥션 풀을 통해 이미 생성되어 있는 커넥션을 객체 참조로 가져다 쓰면 됨.

반환할 떄도 커넥션이 살아 있는 상태로 커넥션 풀을 반환해야 한다.

  • 정리 적절한 커넥션 풀 숫자는 서버 여러가질 고려 커넥션 풀은 서버당 최대 커넥션 수를 제한할 수 있다. 따라서 DB에 무한정 연결이 생성되는 것을 막아주어서 DB를 보호하는 효과도 있다. “실무에서는 항상 기본으로 사용” 스프링부트 2.0부터 기본 커넥션 풀로 hikariCP를 제공

—————————————— 데이터 소스

커넥션을 획득하는 방법을 추상화!

기능의 발전은 추상화와,, 표준화의 연속

정리

  • 대부분의 커넥션 풀은 Datasource인터페이스를 이미 구현해두었다.
따라서 개발자는 DBCP2 커넥션풀, HikariCP 커넥션풀의 코드를 직접 의존이 아닌 DataSource 인터페이스에만 의존하도록 애플리케이션 로직을 작성
  • 커넥션 풀 구현 기술을 변경하고 싶으면 해당 구현체로 갈아끼우면 된다.
  • DriverManager는 DataSource 인터페이스를 사용하지 않는다. 따라서 직접 사용.. 즉 ㅅ커넥션 풀을 위해서는 소스를 다 바꿔야함 이런 문제를 해결하기 위해서 DriverManagerDataSource라는 데이터소스를 구현한 클래스를 제공
  • 자바는 DataSource를 통해 커넥션을 획득하는 방법을 추상화, 이제 애플리케이션 로직은 DataSource 인터페이스에만 의존하면 됨.
  • 덕분에 로직을 변경하지 않아도 됨

—————————————— DataSource 예제 - DriverManager

DriverManager는 커넥션을 획득할 때마다 파라미터를 계쏙 전달해야 한다. 하지만 DataSource를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파라미터를 넘겨두고, 커넥션을 획득할 때는 단순히 getConnection()만 호출하면 된다.

  • 설정과 사용의 분리 설정 : DataSource를 만들고 필요한 속성들을 사용해서 URL 등 입력하는 것을 말함. 이렇게 설정과 관련된 속성들을 한 곳에 있는 것이 향후 변경에 더유연하게 대처 가능 사용 : 설정은 신경쓰지 않고, getConnection() 만 호출해서 사용하면 된다.

이 분리가 큰 차이다. 외존하지 않아도 되고 호출만 하면 된다. 쉽게 리포지토리는 DataSOurce만 의존하고 이런 속성을 몰라도 된다.

———————————— DataSource 예제 2 - 커넥션 풀 

커넥션 풀은 별도의 쓰레드에서 커넥션 풀을 채운다. 그렇다면 왜 별도의 쓰레드를 사용해서 커넥션 풀에 커넥션을 채우는가? (오래 걸리는 작업이므로 비동기식으로 풀을 채운다)

 이렇게 데이터소스로 그냥 커넥션을 만들면  쿼리마다 커넥션을 새로 생성해준다!

 이렇게 히카리CP 커넥션 풀리을 쓰면  하나의 커넥션으로 해준다. 그 이유는 커넥션 풀에서 close를 할 경우 닫는게 아니라 반납을 하는 것으로 반납했다가 쓰고를 반복하는 것이다.

  • DI 관점 DriverManagerDataSource 에서 HikariDataSOurce로 변경해도 Repository의 코드는 전혀 변경하지 않아도 된다. MemberRepositoryV1은 DataSource 인터페이스에만 의존하기 때문이다. 이것이 DataSource의 장점이다. DI+OCP

——————————————————— 트랜잭션

트랜잭션은 이름 그대로 거래라는 뜻 데이터베이스에서 트랜잭션은 하나의 거래를 안전하게 처리하도록 보장해주는 것을 뜻한다.

모든 작업이 성공해서 데이터베이스에 정상 반영되는 것을 커밋(commit) 작업 중 하나라도 실패해서 거래 이전으로 되돌리는 것을 롤백(rollback)

  • 트랜잭션 ACID

원자성 : 트랜잭션 내의 작업은 모두 성공하거나 모두 실패해야 한다. 일관성 : 모든 트랜잭션은 일관성 있는 상태를 유지, 무결성 제약 조건을 항상 만족 격리성 : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치면 안된다 지속성 : 트랜잭션이 성공적으로 끝내면 그 결과가 항상 기록되어야 한다. 중간에 문제가 발생해도 로그 등을 사용해서 내용을 복구해야 한다.

문제는.. 격리성인데 트랜잭션 간에 격리성을 완벽히 보장하려면 트랜잭션을 거의 순서대로 실행해야 한다. 이렇게 하면 동시 처리 성능이 나빠진다. 이런 문제로 인해 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의했다.

  • 트랜잭션 격리 수준 READ UNCOMMITED (커밋되지 않은 읽기) READ COMMITED (커밋된 읽기)
REPEATABLE READ (반복 가능한 읽기)
SERIALIZABLE (직렬화 가능)

참고 : 일반적으로 많이 사용하는 것은 READ COMMITED커밋된 읽기 —————————————————————— 데이터베이스 연결 구조와 DB 세션

커넥션 풀이 10개의 커넥션을 생성하면 세션도 10개가 만들어진다.

연결을 하면 DB내부에는 DB세션을 만들고 트랜잭션을 시작하고 SQL을 실행하고 한다.

커넥션 풀이 생성되면 그에 맞는 세션이 생성된다.

———————————————————— 트랜잭션 - DB 예제 - 개념 이해

  데이터 정합성 : 어떤 데이터들의 값이 서로 일치하는 상태, 정합성이 맞다고 표현

커밋하지 않은 데이터 를 다른 곳에서 조회할 수 있다면 데이터 정합성에 문제가 발생한다.

 이제 커밋을 하면 볼 수 있다.

 롤백을 하게 되면 “트랜잭션”을 시작하기 직전의 상태로 복구하게 된다!

—————————————————— 트랜잭션 DB 예제, 자동 커밋, 수동 커밋

 자,수동으로 커밋을 하는 설정이 있다.

커밋이나 롤백을 직접 호출하지 않아도 되는 편리함이 있다. 하지만 쿼리를 하나하나 실행할 때 마다 자동으로 커밋되어버리기 때문에 우리가 원하는 트랜잭션 기능을 제대로 사용할 수 없다.

수동 커밋은 꼭 마지막에 commit 명령어 실행

관례상 수동 커밋모드로 변경하는 것을 트랜잭션을 시작한다라고도 한다.

———————————————— 트랜잭션 DB 예제 트랜잭션 실습

 이렇게 된 것을 창을 두개(즉 세션을 두개 해서 실습)

 수동 커밋하면 이렇게 차이남

커밋을 하고 rollback을 하면 newId1,newId2가 사라지며 커밋 이전으로 복구됨

—————————————————— 트랜잭션 DB 예제 계좌이체

계좌이체 정상, 계좌이체 비정상 이지만 커밋, 계좌이체 비정상 롤백

 이것과 같이 한 트랜잭션에서 하나의 쿼리는 성공하고 하나는 실패할 경우가 발생할 수 있다.

이런 경우를 방지하기 위함.  이렇게 돈이 빠지기만 하고… 큰일남

이상태로 커밋하면 돈만 빠져나간 상태가 반영됨

이런 경우 rollback 해야 함

  • 정리 원자성 : 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공 하거나 모두 실패해야 한다!

오토 커밋 : 만약 오토 커밋이 위 상황에서 발생한다면? 복구가 안된다.

트랜잭션 시작 : 따라서 이런 종류의 작업은 꼭 수동 커밋 모드 사용!! 보통 이렇게 자동 커밋 모드에서 수동 커밋 모드로 전환 하는 것을 트랜잭션을 시작한다고 표현!

———————————————————— DB 락 - 개념

 만약 이와 같이 세션 1이 트랜잭션을 시작하고 데이터를 수정하는 동안 아직 커밋하지 않았는데 세션 2에서 동시에 같은 데이터를 수정하게 되면 문제가 발생한다.. (원자성이 깨짐) 그래서 이런 문제를 해결하기 위해 “락”을 걸어 넣는다.

 이렇게 락을 획득 한다! 이 상태에서는 다른 세션에서 사용 할 수 없도록 한다. 만약 다른 세션에서 요청이 오면 락을 획득할 때까지 기다린다. 참고로 무한정 대기는 아니고 락 대기 시간을 넘기면 락 타임아웃 오류가 발생.  커밋이 수행되면 락이 돌아간다. 그 다음 세션 2가 락을 획득하여 트랜잭션을 수행한다.

—————————————— DB 락 - 변경

 세션 1에서 락을 걸어놓은 상태에서 세션 2가 이런식으로 접근을 한다면 세션 1이 완료가 되기를 기다렸다가 실행되게 된다.(락 타임아웃시간 만큼 기다림, 설정 안해도 기본 값이 있음)

——————————————— DB 락 - 조회

** 일반적인 조회는 락을 사용하지 않는다.

데이터를 조회할 때도 락을 획득하고 싶을 떄가 있다. 이럴 때는

select for update 구문을 사용하면 된다.

조회 시점에 락이 필요한 경우는 트랜잭션 종료 시점까지 해당 데이터를 다른 곳에서 변경하지 못하도록 강제로 막아야 할 때 사용한다. 예를 들어서 애플리케이션 로직에서 memberA 의 금액을 조회한 다음에 이 금액 정보로 애플리케이션에서 어떤 계산을 수행한다. 그런데 이 계산이 돈과 관련된 매우 중요한 계산이어서 계산을 완료할 때 까지 memberA 의 금액을 다른곳에서 변경하면 안된다. 이럴 때 조회 시점에 락을 획득하면 된다

————————————————— 트랜잭션 적용

(트랜잭션 없이 비즈니스 로직만)

 fromID의 회원을 조회해서 money만큼 깍고 toId의 회원을 조회해서 money만큼 돈을 더한다.

———————————————————— 트랜잭션 적용

(트랜잭션 적용) 커밋, 롤백. 을 어디다가 적용할지..?!

논리적 서비스 단위로  트랜잭션은 서비스계층에서 시작해야 한다. 비즈니스 로직이 잘못되면 해당 비즈니스 로직으로 인해 문제가 되는 부분을 함께 롤백해야 하기 때문.

트랜잭션을 사용하는 동안 같은 커넥션을 유지!

 예를 들어서 sql문이 여러개인데 세션, 커넥션이 달라지면 커밋 단위가 달라짐 즉 트랜잭션이 깨짐.

 즉 이전과는 다르게 이렇게 커넥션을 파라미터로 받아서 유지

주의 !

  1. 파라미터로 넘어온 커넥션을 써서 커넥션을 유지한다
  2. 커넥션을 닫으면 안된다. 커넥션을 전달 받은 리포지토리 뿐만 아니라 이후에도 커넥션을 계속 이어서 사용하기 때문에 닫으면 안된다.

 이렇게 보면… 트랜잭션의 시작은 위에서 처럼 커밋을 수동으로 변경하는 것으로 시작이다. 커넥션을 트랜잭션 시작 전에 불러온다. 그리고 트랜잭션 후 수동 커밋을 자동으로 변경하고 트랜잭션을 종료해준다!

—————————————————