2023. 1. 26. 17:29ㆍspring/db
1/25
* jdbc
- jdbc 표준 인터페이스만 알면 모든 데이터베이스에 동일하게 적용이 가능하다.
- 하지만 sql은 각각 데이터베이스에 맞게 바꾸어야 한다.
* SQL Mapper ( Jdbc Template, MyBatis )
- sql응답 결과를 객체로 편리하게 변환해준다.
- jdbc 반복 코드를 제거해준다.
* JPA( 하이버네이트 ,이클립스링크 )
======================================================================================
1/26
1. 애플리케이션 로직은 DB드라이버를 통해 커넥션을 조회한다.
2. DB드라이버는 DB와 *TCP/IP* 커넥션을 연결한다. 이 과정에서 3 way handshake와 같은 TCP/IP 연결을 위한 네트워크 동작이 발생한다.
3. DB 드라이버는 TCP/IP 커넥션이 연결되면 ID, PW와 기타 부가정보를 DB에 전달한다.
4. DB는 ID, PW를 통해 내부 인증을 완료하고, 내부에 DB 세션을 생성한다.
5. DB는 커넥션 생성이 완료되었다는 응답을 보낸다.
6. DB 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환한다.
* IP
- IP 프로토컬 -> 지정한 IP주소에 패킷이라는 통신 단위로 데이터 전달
- 받을 대상이 없거나 서비스 불능 상태여도 패킷 전송 (비연결성)
- 순서 X , 중간에 사라질 가능성 O (비신뢰성)
- 같은 IP를 사용하는 서버에서 통신하는 애플리케이션이 둘 이상이라면 ?
* TCP (전송 제어 프로토콜)
- TCP 3 way handshake(가상 연결)
- 데이터 전달 보증
- 순서보장
* TCP/IP 패킷
* 커넥션 풀
- 필요한 만큼 커넥션을 미리 확보해서 풀에 보관 (기본 10개)
- 애플리케이션 요청시 풀에서 꺼내서 객체로 반환
- 모두 사용하고나서 종료하는 것이 아니고 다시 풀에 반환
- 최근에는 hikariCP 를 사용
* DataSource
public interface DataSource {
Connection getConnection() throws SQLException;
}
- 커넥션 풀은 DataSource 인터페이스를 구현해두었다.
void dataSourceDriverManager() throws SQLException {
//DriverManagerDataSource - 항상 새로운 커넥션을 획득한다.
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
useDataSource(dataSource);
}
private void useDataSource(DataSource dataSource) throws SQLException {
Connection con1 = dataSource.getConnection();
Connection con2 = dataSource.getConnection();
log.info("connection={}, class={}", con1, con1.getClass());
log.info("connection={}, class={}", con2, con2.getClass());
}
- DriverManager은 DataSource를 구현하지 않아서 코드를 변경하려면 복잡하다.
- 스프링은 이런 문제를 해결하고자 DataSource를 구현하고 있는 DriverManagerDataSource를 제공한다.
* 커넥션 풀 사용 예제
void dataSourceConnectionPool() throws SQLException, InterruptedException {
//커넥션 풀링
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
dataSource.setMaximumPoolSize(10);
dataSource.setPoolName("MyPool");
useDataSource(dataSource);
// Thread.sleep(1000);
}
@BeforeEach
void beforeEach(){
//기본 DriverManager - 항상 새로운 커넥션을 획득
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
repository = new MemberRepositoryV1(dataSource);
}
11:50:56.257 [main] INFO hello.jdbc.repository.MemberRepositoryV1 - get connection=conn0: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class org.h2.jdbc.JdbcConnection
11:50:56.276 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:h2:tcp://localhost/~/test]
11:50:56.280 [main] INFO hello.jdbc.repository.MemberRepositoryV1 - get connection=conn1: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class org.h2.jdbc.JdbcConnection
11:50:56.287 [main] INFO hello.jdbc.repository.MemberRepositoryV1Test - findMember=Member(memberId=memberV2, money=10000)
11:50:56.408 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:h2:tcp://localhost/~/test]
11:50:56.414 [main] INFO hello.jdbc.repository.MemberRepositoryV1 - get connection=conn2: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class org.h2.jdbc.JdbcConnection
11:50:56.416 [main] INFO hello.jdbc.repository.MemberRepositoryV1 - resultSize=1
11:50:56.418 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:h2:tcp://localhost/~/test]
11:50:56.423 [main] INFO hello.jdbc.repository.MemberRepositoryV1 - get connection=conn3: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class org.h2.jdbc.JdbcConnection
11:50:56.428 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:h2:tcp://localhost/~/test]
11:50:56.432 [main] INFO hello.jdbc.repository.MemberRepositoryV1 - get connection=conn4: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class org.h2.jdbc.JdbcConnection
11:50:56.436 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:h2:tcp://localhost/~/test]
11:50:56.441 [main] INFO hello.jdbc.repository.MemberRepositoryV1 - get connection=conn5: url=jdbc:h2:tcp://localhost/~/test user=SA, class=class org.h2.jdbc.JdbcConnection
- DriverManagerDataSoure -> 항상 새로운 커넥션을 획득
@BeforeEach
void beforeEach(){
//기본 DriverManager - 항상 새로운 커넥션을 획득
// DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
//커넥션 풀링
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setPassword(PASSWORD);
dataSource.setUsername(USERNAME);
repository = new MemberRepositoryV1(dataSource);
}
get connection=HikariProxyConnection@xxxxxxxx1 wrapping conn0: url=jdbc:h2:... user=SA get connection=HikariProxyConnection@xxxxxxxx2 wrapping conn0: url=jdbc:h2:... user=SA get connection=HikariProxyConnection@xxxxxxxx3 wrapping conn0: url=jdbc:h2:... user=SA get connection=HikariProxyConnection@xxxxxxxx4 wrapping conn0: url=jdbc:h2:... user=SA get connection=HikariProxyConnection@xxxxxxxx5 wrapping conn0: url=jdbc:h2:... user=SA get connection=HikariProxyConnection@xxxxxxxx6 wrapping conn0: url=jdbc:h2:... user=SA
- HikariDataSource -> conn0 으로 계속 재사용
* DriverManagerDataSource를 사용하나 HikariDataSoucre를 사용하나 MemberRepositoryV1의 코드는
전혀 변경하지 않았다.
==> DataSource 인터페이스에만 의존하므로 (DI + OCP)
====================================================================================
* 트랜잭션
* ACID
- - 원자성 : 모든 작업이 하나인 것처럼 모두 성공 or 모두 실패
- - 일관성 : 일관성 있는 데이터베이스 상태를 유지해야 한다.
- - 격리성 : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야 한다.
- - 지속성 : 성공적으로 끝내면 그 결과가 항상 기록되어야 한다. 중간에 문제가 발생해도 성공한 트랜잭션 내용을 복구해야 한다.
* 트랜잭션 격리 수준
- - READ UNCOMMITED (커밋되지 않은 읽기)
- - READ COMMITTED (커밋된 읽기) v
- - REPEATABLE READ (반복 가능한 읽기) v
- - SERIALIZABLE (직렬화 가능)
* sql 커밋 설정
- - set autocommit true; // 자동 커밋 설정 (기본)
- - set autocommit false; // 수동 커밋 설정
- - 수동 커밋 -> 트랜잭션 시작
- - commit / rollback
* DB 락
- set autocommit false -> 수동 커밋 설정과 동시에 락을 가져옴
- commit or rollback으로 수동 커밋이 종료되기 전까진 다른 곳에서 락을 가져갈 수 없다.
- 대기 상태로 있게 된다.
- select * from member where member_id='memberA' for update 조회하면서 락을 가져온다.
* WAS에 트랜잭션 적용
- 한 비지니스 로직에서 사용하는 메서드는 트랜잭션을 적용하기 위해서 파라미터로 같은 Connection을 공유해야 한다.
그러므로 파라미터에 Connection이 들어간 메서드를 재정의해야 한다.
- 이렇게 만들어진 메서드는 Connection을 close하면 안된다.
- 비지니스 로직이 끝났을 때 예외없이 잘 수행이 되었으면 commit을 호출하고 반납한다.
- 예외 발생시 rollback한 후 반납한다.
- 커넥션 풀 이용시에는 그냥 반납하면 set autoCommit(false) 상태이기 때문에 autoCommit(true)적용후 반납한다.
* 트랜잭션 코드
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
Connection con = dataSource.getConnection();
try{
con.setAutoCommit(false); //트랜잭션 시작
//비지니스 로직
bizLogic(con, fromId, toId, money);
con.commit(); //성공시 커밋
} catch(Exception e){
con.rollback(); //실패시 롤백
throw new IllegalStateException(e);
} finally {
release(con);
}
}
'spring > db' 카테고리의 다른 글
스프링 db2 - MyBatis (0) | 2023.02.01 |
---|---|
스프링 db2 - JdbcTemplate 사용 정보 (0) | 2023.01.31 |
스프링 db1 - JdbcTemplate (0) | 2023.01.30 |
스프링 db - 예외 처리 반복 해결 (0) | 2023.01.30 |
스프링 DB1 - 스프링 트랜잭션 (0) | 2023.01.27 |