[Spring] 정리(4)

박찬영·2024년 7월 2일

Spring

목록 보기
42/42

1. JDBC

JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API이다. JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다.

자바는 이렇게 표준 인터페이스를 정의해두었다. 이제부터 개발자는 이 표준 인터페이스만 사용해서 개발하면 된다. 그런데 인터페이스만 있다고해서 기능이 동작하지는 않는다. 이 JDBC 인터페이스를 각각의 DB 회사에서 자신의 DB에 맞도록 구현해서 라이브러리로 제공하는데, 이것을 JDBC 드라이버라 한다.

  • MySQL : MySQL JDBC 드라이버

1.1 SQL Mapper

  • 장점 : JDBC를 편리하게 사용하도록 도와준다.
    • SQL 응답 결과를 객체로 편리하게 변환해준다.
    • JDBC의 반복 코드를 제거해준다.
  • 단점 : 개발자가 SQL을 직접 작성해야한다.
  • 대표 기술 : 스프링 JdbcTemplate, MyBatis

1.2 ORM 기술

  • 객체를 관계형 데이터베이스 테이블과 매핑해주는 기술이다. 이 기술 덕분에 개발자는 반복적인 SQL을 직접 작성하지 않고, ORM 기술이 개발자 대신에 SQL을 동적으로 만들어 실행해준다. 추가로 각각의 데이터베이스마다 다른 SQL을 사용하는 문제도 중간에서 해결해준다.
  • 대표 기술 : JPA, 하이버네이트, 이클립스링크

DriverManager : JDBC가 제공하며 라이브러리에 등록된 DB 드라이버들을 관리하고, 커넥션을 획득하는 기능을 제공한다.

2. 커넥션 풀 & DataSource

2.1 커넥션 풀

  • 커넥션 풀은 TCP/IP 커넥션을 새로 생성하기 위한 리소스를 매번 사용하는 것을 방지한다.
  • 대표적인 커넥션 풀 오픈소스 : HikariCP

2.2 DataSource

  • DataSource는 커넥션을 획득하는 방법을 추상화하는 인터페이스이다.
@Test
void dataSourceDriverManager() throws SQLException {
	//DriverManagerDataSource - 항상 새로운 커넥션 획득
	DriverManagerDataSource dataSource = 
    		new DriverManagerDataSource(URL, USERNAME, PASSWORD);
	useDataSource(dataSource);
}


@Test
void dataSourceConnectionPool() throws SQLException,
									InterruptedException {

	//커넥션 풀링: HikariProxyConnection(Proxy) -> JdbcConnection(Target)
	HikariDataSource dataSource = new HikariDataSource();
	dataSource.setJdbcUrl(URL);
	dataSource.setUsername(USERNAME); 
	dataSource.setPassword(PASSWORD); 
	dataSource.setMaximumPoolSize(10); 
	dataSource.setPoolName("MyPool");	//커넥션 풀 최대 사이즈
   
	useDataSource(dataSource);
	Thread.sleep(1000); //커넥션 풀에서 커넥션 생성 시간 대기
}
  • DriverManager는 커넥션을 획득할때 마다 URL, USERNAME, PASSWORD 같은 파라미터를 계속 전달해야한다. 반면에 DataSource를 사용하는 방식은 처음 객체를 생성할 때만 필요한 파라미터를 넘겨두고, 커넥션을 회득할 때는 단순히 dataSource.getConnection()만 호출하면 된다.

설정과 사용의 분리

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

3. 트랜잭션 & DB락

3.1 데이터베이스 연결 구조와 DB 세션

  • 사용자는 웹 애플리케이션 서버(WAS)나 DB 접근 툴 같은 클라이언트를 사용해서 데이터베이스 서버에 접근할 수 있다. 클라이언트는 데이터베이스 서버에 연결을 요청하고 커넥션을 맺게 된다. 이때 데이터베이스 서버는 내부에 세션이라는 것을 만든다. 그리고 앞으로 해당 커넥션을 통한 모든 요청은 이 세션을 통해서 실행하게 된다.
  • 사용자가 커넥션을 닫거나, 또는 DBA가 세션을 강제로 종료하면 세션은 종료된다.
set autocommit false;	//트랜잭션 시작

3.2 DB락

4. 문제점들

4.1 애플리케이션 구조

  • 프레젠테이션 계층
    • UI와 관련된 처리 담당
    • 웹 요청과 응답
    • 사용자 요청을 검증
    • 주 사용 기술 : 서블릿과 HTTP 같은 웹 기술, 스프링 MVC
  • 서비스 계층
    • 비즈니스 로직을 담당
    • 주 사용 기술 : 가급적 특정 기술에 의존하지 않고, 순수 자바 코드로 작성
  • 데이터 접근 계층
    • 실제 데이터베이스에 접근하는 코드
    • 주 사용 기술 : JDBC, JPA

4.2 문제 정리

@Slf4j
@RequiredArgsConstructor
public class MemberServiceV2 {
        
	private final DataSource dataSource;
    private final MemberRepositoryV2 memberRepository;
    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);
		} 
	}
    
    
    private void bizLogic(Connection con, String fromId, String toId, int money) 
    	throws SQLException {
        Member fromMember = memberRepository.findById(con, fromId);
        Member toMember = memberRepository.findById(con, toId);
        memberRepository.update(con, fromId, fromMember.getMoney() - money);
        memberRepository.update(con, toId, toMember.getMoney() + money);
    }
}
  • 트랜잭션 문제
  • JDBC 구현 기술이 서비스 계층에 누수되는 문제
  • 동기화 문제
  • 트랜잭션 적용 반복 문제
  • 예외 누수
  • JDBC 반복 문제
4.2.1 트랜잭션 & 동기화 문제


PlatformTransactionManager 으로 해결

PlatformTransactionManager
package org.springframework.transaction;

public interface PlatformTransactionManager extends TransactionManager {
     
     TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
            
     void commit(TransactionStatus status) throws TransactionException;
     void rollback(TransactionStatus status) throws TransactionException;
}

  • PlatformTransactionManager는 DataSource를 주입 받는다.

트랜잭션 동기화 매니저

  • 스프링은 트랜잭션 동기화 매니저를 제공한다. 이것은 쓰레드 로컬(ThreadLocal)을 사용해서 커넥션을 동기화해준다. 트랜잭션 매니저는 내부에서 이 트랜잭션 동기화 매니저를 사용한다.
  • 트랜잭션 동기화 매니저는 쓰레드 로컬을 사용하기 때문에 멀티쓰레드 상황에 안전하게 커넥션을 동기화할 수 있다. 따라서 커넥션이 필요하면 트랜잭션 동기화 매니저를 통해 커넥션을 획득하면 된다. 따라서 이전처럼 파라미터로 커넥션을 전달하지 않아도 된다.
4.2.2 트랜잭션 적용 반복 문제

트랜잭션 템플릿으로 해결

public class TransactionTemplate {
     private PlatformTransactionManager transactionManager;
     
     public <T> T execute(TransactionCallback<T> action){..}
     void executeWithoutResult(Consumer<TransactionStatus> action){..}
}
  • TransactionTemplate을 사용하려면 PlatformTransactionManager가 필요하다. 생성자에서 PlatformTransactionManager을 주입 받으면서 TransactionTemplate을 생성하면 된다.

4.2.3 순수하지 않은 비즈니스 로직

@Transactional(트랜잭션 AOP) 애노테이션으로 해결

@Transactional
public void accountTransfer(String fromId, String toId, int money) 
												throws SQLException {
	bizLogic(fromId, toId, money);
}
  • @SpringBootTest : 스프링 AOP를 적용하려면 스프링 컨테이너가 필요하다. 이 애노테이션이 있으면 테스트 시 스프링 부트를 통해 스프링 컨테이너를 생성한다. 그리고 테스트에서 @Autowired 등을 통해 스프링 컨테이 너가 관리하는 빈들을 사용할 수 있다.
  • @TestConfiguration` : 테스트 안에서 내부 설정 클래스를 만들어서 사용하면서 이 에노테이션을 붙이면, 스 프링 부트가 자동으로 만들어주는 빈들에 추가로 필요한 스프링 빈들을 등록하고 테스트를 수행할 수 있다.
  • TestConfig
    • DataSource 스프링에서 기본으로 사용할 데이터소스를 스프링 빈으로 등록한다. 추가로 트랜잭션 매니저에서도 사용한다.

    • DataSourceTransactionManager 트랜잭션 매니저를 스프링 빈으로 등록한다.

      • 스프링이 제공하는 트랜잭션 AOP는 스프링 빈에 등록된 트랜잭션 매니저를 찾아서 사용하기 때문에 트랜잭션 매니저를 스프링 빈으로 등록해두어야 한다.

      스프링 부트의 자동 리소스 등록
      스프링 부트는 DataSource와 PlatformTransactionManager을 자동으로 스프링 빈에 등록한다.

5. 자바 예외 이해


  • 체크 예외 : 무조건 처리하거나 던져야한다.
  • 언체크 예외, 런타임 예외 : 신경 안써도된다.

6. 예외 문제

6.1 체크 예외를 런타임 예외로 변환

6.2 SQLExceptionTranslator

SQLExceptionTranslator exTranslator = new
SQLErrorCodeSQLExceptionTranslator(dataSource);
DataAccessException resultEx = exTranslator.translate("select", sql, e);
  • 스프링은 데이터 접근 계층에 대한 수십 가지 예외를 정리해서 일관된 예외 계층을 제공한다.
  • 각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있다. 따라서 서비스 계층에서도 스프링이 제공하는 예외를 사용하면 된다. 예를 들어서 JDBC 기술을 사용하든, JPA 기술을 사용하든 스프링이 제공하는 예외를 사용하면 된다.
  • JDBC나 JPA를 사용할 때 발생하는 예외를 스프링이 제공하는 예외로 변환해주는 역할도 스프링이 제공한다.

7. JdbcTemplate

@Slf4j
public class MemberRepositoryV5 implements MemberRepository {
    private final JdbcTemplate template;
    public MemberRepositoryV5(DataSource dataSource) {
        template = new JdbcTemplate(dataSource);
	}
    @Override
    public Member save(Member member) {
        String sql = "insert into member(member_id, money) values(?, ?)";
        template.update(sql, member.getMemberId(), member.getMoney());
        return member;
	}
    @Override
    public Member findById(String memberId) {
        String sql = "select * from member where member_id = ?";
        return template.queryForObject(sql, memberRowMapper(), memberId);
    }
    @Override
    public void update(String memberId, int money) {
        String sql = "update member set money=? where member_id=?";
        template.update(sql, money, memberId);
    }
    @Override
    public void delete(String memberId) {
	String sql = "delete from member where member_id=?";
          template.update(sql, memberId);
    }
    private RowMapper<Member> memberRowMapper() {
        return (rs, rowNum) -> {
             Member member = new Member();
             member.setMemberId(rs.getString("member_id"));
             member.setMoney(rs.getInt("money"));
             return member;
	};
}

JdbcTemplate는 JDBC로 개발할 때 발생하는 반복을 대부분 해결해준다. 그 뿐만 아니라 지금까지 학습했던, 트랜잭션을 위한 커넥션 동기화는 물론이고 예외 발생 시 스프링 예외 변환기도 자동으로 실행해준다.

profile
블로그 이전했습니다 -> https://young-code.tistory.com

0개의 댓글