작심칠일러의 스프링 시작하기(7)-3

서은경·2022년 8월 17일
0

Spring

목록 보기
13/43

DB 연동 과정에서 발생 가능한 익셉션

익셉션은 에러로그를 잘 읽어보면 금방 원인을 찾을 수 있기 때문에 간단하게만 짚고 넘어가겠다!

  1. DB 연결 정보 에러
    java.sql.SQLException : Access denied for user 'spring5'@'localhost'(using password: YES)
  2. DBMS 미실행 (실습하면서 꼭 한번은 에러낸다 ,, 자꾸 db 실행시키는걸 까먹고 애플리케이션을 돌려서)
    Exception in thread "main" org.springframework.jdbc.CannotGetJdbcConnectionException
  3. 쿼리에러
    Exception in thread "main" org.springframework.jdbc.BadSqlGrammarException

스프링의 익셉션 변환 처리

SQL 문법이 잘못됐을 때 발생한 메시지를 보면
org.springframework.jdbc.BadSqlGrammarException
임을 확인할 수 있다.
BadSqlGrammarException 익셉션이 발생한 이유는 MySQLSyntaxErrorException이 발생했기 떄문이다.

JdbcTemplate의 update() 메서드는 DB 연동을 위해 JDBC API를 사용하는데, JDBC API를 사용하는 과정에서 SQLException이 발생하면 이 익셉션을 알맞은 DataAccessException으로 변환해서 발생한다.

🖐정리해보자면!
1. 잘못된 sql문으로 JdbcTemplate의 update() 메서드 사용
2. update() 메서드는 DB 연동을 위해 JDBC API 사용
3. JDBC 드라이버가 SQLException을 상속받은 MySQLSyntaxException 발생시킴
4. JdbcTemplate이 이 익셉션을 DataAccessException을 상속받은 BadSqlGrammarException으로 변환

🙋‍♀️ 스프링은 왜 SQLException을 그대로 전파하지 않고 DataAccessException으로 변환하나요?
💡 주된 이유는 연동 기술에 상관없이 동일하게 익셉션을 처리할 수 있도록 하기 위함이다.
스프링은 JDBC뿐만 아니라 JPA, 하이버네이트 등에 대한 연동을 지원하고 MyBatis는 자체적으로 스프링 연동 기능을 제공한다. 그런데 각각의 구현기술마다 익셉션을 다르게 처리해야한다면 개발자는 기술마다 익셉션 처리 코드를 작성해야 할 것이다.
각 연동 기술에 따라 발생하는 익셉션을 스프링이 제공하는 익셉션으로 변환함으로써 구현 기술에 상관없이 동일한 코드로 익셉션을 처리할 수 있게 된다!

‼️ DataAccessException : 스프링이 제공하는 익셉션 타입으로 데이터 연결에 문제가 있을 때 스프링 모듈이 발생시킴

DataAccessException은 RuntimeException으로 필요한 경우에만 익셉션을 처리하면 된다.(=uncheckedException! 잘 모르겠으면 자바 시리즈에 포스팅 해두었으니 다시 확인하기~)

JDBC를 직접 이용하면 try~catch를 이용해서 필요한 경우에 따라 익셉션을 처리한다! (SQLException을 반드시 알맞게 처리해주어야 함)

트랜잭션 처리

아주아주아주 중요한 트랜잭션! 트랜잭션은 DB 상태변경을 위해 수행하는 작업의 논리적 단위이다. 실무에서 트랜잭션의 중요성에 대해서는 아주 잘~ 배웠기 때문에 이론은 이정도로 넘어가겠다.

JDBCSMS Connection의 setAutoCommit(false)를 이용하여 트랜잭션을 시작하고 commit()과 rollback()을 이용해서 트랜잭션을 반영하거나 취소한다.

하지만 이 방식은 코드 누락의 위험이 있으며 구조적인 중복이 반복되는 문제가 있다. 스프링은 이런 부분을 보완하기 위해 트랜잭션 기능을 제공한다.

@Transactional을 이용한 트랜잭션 처리

@Transactional 어노테이션을 사용하면 트랜잭션 범위를 매우 쉽게 지정할 수 있다. @Transactional 어노테이션이 제대로 동작하려면 다음의 두 가지 내용을 스프링 설정에 추가해야 한다.

  • 플랫폼 프랜잭션 매니저 빈 설정
  • @Transactional 어노테이션 활성화 설정
@Configuration
@EnableTransactionManagement
public class AppCtx {

	//..생략..
    
    // 플랫폼 트랜잭션 매니저(PlatformTransactionManager) 빈 설정
    @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager tm = new DataSourceTransactionManager();
        tm.setDataSource(dataSource());
        return tm;
    }
    
    //..생략..

PlatformTransactionManager는 스프링이 제공하는 트랜잭션 매니터 인터페이스이다. JDBC는 DataSourceTransactionManager 클래스를 PlatformTransactionManager로 사용한다.

@EnableTransactionManagement 어노테이션은 @Transactional 어노테이션이 붙은 메서드를 트랜잭션 범위에서 실행하는 기능을 활성화한다.

트랜잭션 처리를 위한 설정을 완료하면 트랜잭션 범위에서 실행하고 싶은 스프링 빈 객체의 메서드에 @Transactional 어노테이션을 붙이면 된다.

  	@Transactional
    //(value = "test") 이 속성 없으면 PlatformTransactionManager 타입으로 찾아서 사용
    public void changePassword(String email, String oldPwd, String newPwd) {
        Member member = memberDao.selectByEmail(email);
        if(member == null)
            throw new MemberNotFoundException();

        member.changePassword(oldPwd, newPwd);

        memberDao.update(member);
    }

❗️❕ 트랜잭션이 시작되고 커밋되는지 확인하기 위해서는 스프링이 출력하는 로그 메시지를 보면 된다. 트랜잭션과 관련 로그 메시지를 추가로 출력하기 위해 Logback을 사용한다.❕❗️


    // Logback 모듈 : 트랜잭션과 관련 로그 메시지 추가로 출력한다
    implementation 'org.slf4j:jcl-over-slf4j'
    implementation 'ch.qos.logback:logback-classic'

@Transactional과 프록시

AOP의 대표예시 중 하나가 바로 Transaction이다.

AOP에 대해 다시 한번 정리!
관점 지향 프로그래밍이란 뜻으로 여러 빈 객체에 공통으로 적용되는 기능을 구현하는 방법을 말한다.
따로 코드 밖에서 개발을 해두고 프록시개념으로 메서드가 실행되기전, 실행된 직후, 실행시점에 따라 따로 기능을 적용 시키는 것이다.

스프링은 @Transactional 어노테이션을 이용해서 트랜잭션을 처리하기 위해 내부적으로 AOP를 사용한다.
@EnableTransactionManagement 태그를 사용하면 스프링은 @Transactional 어노테이션이 적용된 빈 객체를 찾아 알맞은 프록시 객체를 생성한다.

1.설정파일 읽음 -> 2.@Transactional 어노테이션이 붙은 빈 찾음 -> 3.해당 객체의 프록시 객체를 생성 -> 4.트랜잭션 시작 -> 5.실제 객체의 메서드 실행 -> 6.성공적이면 커밋

@Transactional 적용 메서드의 롤백 처리

커밋과 동일하게 프록시 객체를 통해 처리된다. 프록시 객체는 원본 객체의 메서드를 실행하는 과정에서 RuntimeException이 발생하면 트랜잭션을 롤백한다.

별도 설정을 추가하지 않으면 발생한 익셉션이 RuntimeException일 때 트랜잭션을 롤백하는데, SQLException은 RuntimeException을 상속하고 있지 않으므로 트랜잭션을 롤백하지 않는다. 이 경우 롤백하고 싶다면

@Transactional(rollbackFor = SQLException.class)
// 여러 익셉션 지정하고 싶을 땐
@Transactional(rollbackFor = {SQLException.class, IOException.class})
public void someMethod() {
	// 기능..
}

이렇게 rollbackFor 속성을 설정해주면 된다. 이와 반대되는 속성은 noRollbackFor 속성이다.

@Transactional 주요 속성

  • value : String
    트랜잭션을 관리할 때 사용할 PlatformTransactionManager 빈의 이름을 지정한다. 기본값은 ''이다.
	// 트랜잭션적용파일 (ChangePasswordService.java)
    @Transactional(value = "test
    public void changePassword(String email, String oldPwd, String newPwd) {
    
    
    // 설정파일 (AppCtx.java)
    @Bean
    public PlatformTransactionManager test() {
    
  • propagation : Propagation
    트랜잭션 전파 타입을 지정한다. 기본값은 Propagation.REQUIRED이다.
    - 주요값 : REQUIRED, MANDATORY, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, NEVER, NESTED
  • isolation : Isolation
    트랜잭션 격리 레벨을 지정한다. 기본값은 Isolation.DEFAULT이다.
    - 주요값 : DEFAULT, READ_UNCOMMITED, READ_COMMITED, REPEATBLE_READ, SERIALIZABLE
  • timeout : int
    트랜잭션 제한 시간을 지정한다. 기본값은 -1로 이 경우 데이터베이스의 타임아웃 시간을 사용한다. 초 단위로 지정한다.

@EnableTransactionManagement 어노테이션의 주요 속성

  • proxyTargetClass : 클래스를 이용해서 프록시를 생성할지 여부를 지정한다. 기본값은 false로서 인터페이스를 이용해서 프록시를 생성한다.
  • order : AOP 적용 순서를 지정한다. 기본값은 가장 낮은 우선순위에 해당하는 int의 최댓값이다.

@트랜잭션 전파

  • REQUIRED
    메서드를 수행하는 데 트랜잭션이 필요하다는 것을 의미한다. 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 사용한다. 존재하지 않으면 새로운 트랜잭션을 생성한다.
  • MANDATORY
    메서드를 수행하는 데 트랜잭션이 필요하다는 것을 의미한다. 하지만 REQUIRED와 달리 진행 중인 트랜잭션이 존재하지 않을 경우 익셉션이 발생한다.
  • REQUIRES_NEW
    항상 새로운 트랜잭션을 시작한다. 진행 중인 트랜잭션이 존재하면 기존 트랜잭션을 일시 중지하고 새로운 트랜잭션을 시작한다. 새로 시작된 트랜잭션이 종료된 뒤에 기존 트랜잭션이 계속된다.
  • SUPPORTS
    메서드가 트랜잭션을 필요로 하지는 않지만, 진행 중인 트랜잭션이 존재하면 트랜잭션을 사용한다는 것을 의미한다. 진행 중인 트랜잭션이 존재하지 않더라도 메서드는 정상적으로 동작한다.
  • NOT_SUPPORTED
    메서드가 트랜잭션을 필요로 하지 않음을 의미한다. SUPPORTS와 달리 진행 중인 트랜잭션이 존재할 경우 메서드가 실행되는 동안 트랜잭션은 일시 중지되고 메서드 실행이 종료된 후에 트랜잭션을 계속 진행한다.
  • NEVER
    메서드가 트랜잭션을 필요로 하지 않는다. 만약 진행 중인 트랜잭션이 존재하면 익셉션이 발생한다.
  • NESTED
    진행 중인 트랜잭션이 존재하면 기존 트랜잭션에 중첩된 트랜잭션에서 메서드를 실행한다. 진행 중인 트랜잭션이 존재하지 않으면 REQUIRED와 동일하게 동작한다. 이 기능은 JDBC 3.0 드라이버를 사용할 때에만 적용된다.(JTA Provider가 이 기능을 지원할 경우에도 사용 가능하다.)

0개의 댓글