본문 바로가기

JAVA/Spring

데이터베이스 연결

반응형

스프링을 이용해 데이터베이스와 상호 작용

  • 스프링은 JDBC 위에 추상 계층을 추가해 데이터베이스와 상호작용을 편리하게 만든다.
  • 스프링은 하이버네이트와 마이바티스 등 ORM 프레임워크로 데이터베이스 상호 작용도 단순화 시킨다.
  1. MyBank
  • 고객이 자신의 잔액 세부 정보를 살펴보고 금융거래 명세서(bankstatement)를 생성하며, 정기예금을 개설하고 체크북을 요청할 수 있게 해주는 인터넷 뱅킹 애플리케이션이다.
  • BANK_ACCOUNT_DETAILS와 FIXED_DEPOSIT_DETAILS 테이블
BANK_ACCOUNT_DETAILS   1<-------------------1..* FIXED_DEPOSIT_DETAILS
- ACCOUNT_ID (PK)                                - FIXED_DEPOSIT_ID (PK)
- BALANCE_AMOUNT                                 - ACCOUNT_ID (FK)
- LAST_TRANSCTION_TS                             - FD_CREATION_DATE
                                                 - AMOUNT
                                                 - TENURE
                                                 - ACTIVE
  • 은행계좌정보는 BALANCE_AMOUNT 테이블, 정기예금정보는 FIXED_DEPOSIT_DETAILS 테이블
  • 은행고객이 새 정기 예금을 개설하면 BANK_ACCOUNT_DETAILS의 BALANCE_AMOUNT에서 개설할 정기 예금 금액에 해당하는 잔액을 차감하고 FIXED_DEPOSIT_DETAILS 테이블에 새 정기 예금 정보를 저장한다.

BANK_ACCOUNT_DETAILS

  • ACCOUNT_ID : 고객 은행 계좌를 유일하게 식별하는 계좌 식별자
  • BALANCE_AMOUNT : 은행 계좡의 현재 잔액을 저장한다. 고객이 정기 예금을 개설하면 정기 예금액에 해당하는 금액을 컬럼에서 차감한다.
  • LAST_TRANSCTION_TS : 계좌에서 마지막으로 거래가 일어난 날짜와 시간을 저장한다.

FIXED_DEPOSIT_DETAILS

  • FIXED_DEPOSIT_ID : 정기예금을 유일하게 식별하는 정기 예금 식별자. MySQL 데이터베이스에서 자동으로 생성
  • ACCOUNT_ID : 정기예금과 연관된 은행 계좌에 대한 외래키
  • FD_CREATION_DATE : 정기예금이 만들어진 날짜
  • AMOUNT : 정기예금금액
  • TENURE : 정기 예금 만기를 저장한다 (단위-개월) 정기예금만기는 12개월이상 60개월 이하
  • ACTIVE : 정기 예금이 현재 유효한지 여부를 표시한다.
  1. 스프링 JDBC 모듈로 MyBank 애플리케이션 개발
  • 스프링 JDBC 모듈을 사용하면 연결을 열고 닫기, 트랜잭션 관리, 예외 처리 등의 저수준 세부사항을 스프링이 처리해주기 때문에 데이터 소스와의 상호작용이 단순해진다.
  • 데이터 소스를 식별하는 javax.sql.DataSource 객체를 설정한다.
  • 데이터베이스와 상호작용하기 위해 스프링 JDBC 모듈 클래스를 사용하는 DAO를 구현한다.

데이터 소스 설정

    <context:property-placeholder location="classpath*:META-INF/spring/database.properties" />

    <bean class="org.apache.commons.dbcp2.BasicDataSource"
        destroy-method="close" id="dataSource">
        <property name="driverClassName" value="${database.driverClassName}" />
        <property name="url" value="${database.url}" />
        <property name="username" value="${database.username}" />
        <property name="password" value="${database.password}" />
    </bean>
  • dataSource 빈은 javax.sql.DataSource 객체이며, 데이터 소스로의 연결을 생성하는 팩토리 역할을 한다.
  • BasicDataSource 클래스는 javax.sql.DataSource 인터페이스를 구현한 클래스로 연결 풀링 기능을 지원한다.
  • BasicDataSource 클래스의 close 메서드는 풀에 있는 모든 유휴 연결을 닫는다.
  • BasicDataSource 클래스의 빈 정의에서 destroy-method 속성이 close로 지정되어 있기 때문에 스프링 컨테이너가 dataSource 빈 인스턴스를 제거할 때 풀에 있는 유휴 연결도 모두 닫힌다.

자바 EE 환경에서 데이터 소스 설정

  • 애플리케이션 서버에 배포하는 엔터프라이즈 애플리케이션을 개발한다면, 전형적인 경우 javax.sql.DataSource 객체를 애플리케이션 서버의 JNDI에 등록할 것이다.
  • 스프링 jee 스키마의 엘리먼트를 사용해 JNDI에 바인드된 데이터 소스를 스프링 빈으로 사용할 수 있다.
  • <jee:jndi-lookup jndi-name="java:comp/env/jdbc/bankAppDb" id="dataSource" />
  • jndi-name 속성은 javax.sql.dataSource 객체를 JNDI에 바인딩 할때 사용한 이름이고, id속성은 ApplicationContext에 빈을 등록할 때 사용하는 이름이다.
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    JndiLocatorDelegate delegate = JndiLocatorDelegate.creatDefaultResourceRefLocator();
    return delegate.lookup("jdbc/bankAppDb", DataSource.class);
}
  • JndiLocatorDelegate의 creatDefaultResourceRefLocator 메서드는 모든 JNDI 이름 검색에 자동으로 java:comp/env 접두사를 붙이라고 지정한다.
  • JndiLocatorDelegate의 lookup 메서드는 실제 JNDI 검색을 수행해 주어진 이름을 찾는다.
  • lookup 메서드의 두번재 인수는 JNDI 검색에서 얻어올 객체의 타입을 지정한다.
  • destroyMethod 속성값을 ""로 설정한다. JNDI에 바인딩된 DataSource 객체의 생애주기는 애플리케이션 서버가 관리하므로, 스프링 컨테이너는 자신이 종료될 때 DataSource 객체의 정리 메서드를 호출하면 안된다.

스프링 JDBC 모듈 클래스를 사용하는 DAO

  • 스프링 JDBC 모듈은 데이터베이스와 상호작용을 쉽게 만들어주는 여러 클래스를 제공한다.

JdbcTemplate

  • JdbcTemplate 클래스는 Connection, Statement, ResultSet 객체를 관리하고, JDBC 예외를 잡아서 (IncorrectResultSetColumnCountException, CannotGetJdbcCoonnectionException)로 변환하며, 배치 연산을 수행하는 등의 일을 한다.
  • JdbcTemplate은 javax.sql.DataSource를 둘러싼 래퍼 역할을 한다.
  • JdbcTemplate 인스턴스는 보통 데이터베이스 연결을 얻을 때 사용할 javax.sql.DataSource 객체로 초기화 한다.
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
  • JdbcTemplate 클래스가 javax.sql.DataSource 객체를 참조하는 dataSource 프로퍼티를 정의한 것이다.
  • JdbcTemplate 인스턴스는 스레드 안전하다(thread-safe) 애플리케이션의 여러 DAO가 같은 JdbcTemplate 클래스 인스턴스를 통해 데이터베이스와 상호작용해도 된다는 뜻이다.
@Repository(value = "fixedDepositDao")
public class FixedDepositDaoImpl implements FixedDepositDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public int createFixedDeposit(final FixedDepositDetails fdd) {
        final String sql = "insert into fixed_deposit_details(account_id, fd_creation_date, amount, tenure, active) "
                + "values(?, ?, ?, ?, ?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(new PreparedStatementCreator() {

            @Override
            public PreparedStatement createPreparedStatement(Connection con)
                    throws SQLException {
                PreparedStatement ps = con.prepareStatement(sql,
                        new String[] { "fixed_deposit_id" });
                ps.setInt(1, fdd.getBankAccountId());
                ps.setDate(2, new java.sql.Date(fdd.getFdCreationDate()
                        .getTime()));
                ps.setInt(3, fdd.getFdAmount());
                ps.setInt(4, fdd.getTenure());
                ps.setString(5, fdd.getActive());
                return ps;
            }
        }, keyHolder);
        return keyHolder.getKey().intValue();
    }

    public FixedDepositDetails getFixedDeposit(final int fixedDepositId) {
        final String sql = "select * from fixed_deposit_details where fixed_deposit_id = :fixedDepositId";
        SqlParameterSource namedParameters = new MapSqlParameterSource(
                "fixedDepositId", fixedDepositId);
        return namedParameterJdbcTemplate.queryForObject(sql, namedParameters,
                new RowMapper<FixedDepositDetails>() {
                    public FixedDepositDetails mapRow(ResultSet rs, int rowNum)
                            throws SQLException {
                        FixedDepositDetails fdd = new FixedDepositDetails();
                        fdd.setActive(rs.getString("active"));
                        fdd.setBankAccountId(rs.getInt("account_id"));
                        fdd.setFdAmount(rs.getInt("amount"));
                        fdd.setFdCreationDate(rs.getDate("fd_creation_date"));
                        fdd.setFixedDepositId(rs.getInt("fixed_deposit_id"));
                        fdd.setTenure(rs.getInt("tenure"));
                        return fdd;
                    }
                });
    }
}

NamedParameterJdbcTemplate

  • JdbcTemplate에서는 ? 위치 지정자를 사용해 SQL 문안에 파라미터를 지정한다.
  • NamedParameterJdbcTemplate은 JdbcTemplate 인스턴스를 감싸는 래퍼로, SQL 문안에서 ? 대신 파라미터 이름을 사용할 수 있다.
    <bean id="namedJdbcTemplate"
        class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="dataSource" />
    </bean>

SimpleJdbcInsert

  • SimpleJdbcInsert 클래스는 데이터베이스 메타데이터를 활용해 테이블에 로우를 삽입하는 기본 SQL 삽입문을 쉽게 쓸 수 있다.
@Repository(value = "bankAccountDao")
public class BankAccountDaoImpl implements BankAccountDao {
    private SimpleJdbcInsert insertBankAccountDetail;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private void setDataSource(DataSource dataSource) {
        this.insertBankAccountDetail = new SimpleJdbcInsert(dataSource).withTableName("bank_account_details")
                .usingGeneratedKeyColumns("account_id");
    }

    @Override
    public int createBankAccount(final BankAccountDetails bankAccountDetails) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("balance_amount", bankAccountDetails.getBalanceAmount());
        parameters.put("last_transaction_ts",
                new java.sql.Date(bankAccountDetails.getLastTransactionTimestamp().getTime()));
        Number key = insertBankAccountDetail.executeAndReturnKey(parameters);
        return key.intValue();
    }

    public void subtractFromAccount(int bankAccountId, int amount) {
        jdbcTemplate.update("update bank_account_details set balance_amount = ? where account_id = ?", amount,
                bankAccountId);
    }
}
  • usingGeneratedKeyColumns 메서드는 자동생성 키를 폼하한 테이블 컬럼의 이름을 설정한다.
  • SimpleJdbcInsert 클래스가 내부적으로 JdbcTemplate을 사용해 실제 SQL 삽입 연산을 수행한다.
public class BankApp {
    private static Logger logger = LogManager.getLogger(BankApp.class);

    public static void main(String args[]) throws Exception {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath:META-INF/spring/applicationContext.xml");

        BankAccountService bankAccountService = context.getBean(BankAccountService.class);
        BankAccountDetails bankAccountDetails = new BankAccountDetails();
        bankAccountDetails.setBalanceAmount(1000);
        bankAccountDetails.setLastTransactionTimestamp(new Date());

        int bankAccountId = bankAccountService.createBankAccount(bankAccountDetails);

        logger.info("Created bank account with id - " + bankAccountId);

        FixedDepositService fixedDepositService = context.getBean(FixedDepositService.class);

        FixedDepositDetails fdd = new FixedDepositDetails();
        fdd.setActive("Y");
        fdd.setBankAccountId(bankAccountId);
        fdd.setFdCreationDate(new Date());
        fdd.setFdAmount(500);
        fdd.setTenure(12);
        int fixedDepositId = fixedDepositService.createFixedDeposit(fdd);
        logger.info("Created fixed deposit with id - " + fixedDepositId);

        logger.info(fixedDepositService.getFixedDeposit(fixedDepositId));
        context.close();
    }
}
  • 저장프로시저나 함수실행, SimpleJdbcCall 클래스를 사용해 저장 프로시저나 함수를 실행할 수 있다.
  • 일괄갱신으로 JdbcTemplate의 batchUpdate 메서드를 사용하면 같은 PreparedStatement를 통해 여러 데이터베이스 갱신 호출을 일괄 실행할 수 있다.
  • 객체지향적인 방식으로 관계형 데이터베이스 접근으로 MappingSqlQuery를 사용하면 ResultSet가 반환한 각 로우를 객체에 매핑할 수 있다.
  • 내장 데이터베이스 인스턴스 설정으로 스프링 jdbc 스키마를 사용해 HSQL, H2 등 데이터베이스 인스턴스를 만들고, 데이터베이스 인스턴스를 javax.sql.DataSource 타입의 빈으로 스프링 컨테이너에 등록할 수 있다.

하이버네이트로 MyBank 애플리케이션 개발

  • 스프링 ORM 모듈은 하이버네이트, 자바영속성 API(JPA), 자바 데이터 오브젝트와 통합할 수 있게 한다.

SessionFactory 인스턴스 설정

  • SessionFactory는 하이버네이트 Session 객체를 생성하는 팩토리다.
  • 영속적인 엔티티에 대한 생성, 읽기, 삭제, 갱신 연산을 수행하기 위해 DAO가 사용하는 객체가 Session 객체다.
  • 스프링 org.springframework.orm.hibernate5.LocalSessionFactoryBean(FactoryBean 구현)은 DAO 클래스가 Session 인스턴스를 얻을 때 사용하는 SessionFactory 인스턴스를 만든다.
    <context:property-placeholder
        location="classpath*:META-INF/spring/database.properties" />

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="sample.spring" />
    </bean>

    <bean class="org.apache.commons.dbcp2.BasicDataSource"
        destroy-method="close" id="dataSource">
        <property name="driverClassName" value="${database.driverClassName}" />
        <property name="url" value="${database.url}" />
        <property name="username" value="${database.username}" />
        <property name="password" value="${database.password}" />
    </bean>
  • dataSource 프로퍼티는 javax.sql.DataSource 타입 빈에 대한 참조를 지정한다.
  • packagesToScan 프로퍼티는 스프링이 영속적 클래스를 찾는 대상 패키지을 지정한다.
  • sample.spring 패키지에서 찾아 org.springframework.orm.hibernate5.LocalSessionFactoryBean을 사용해 자동으로 @Entity 애너테이션을 감지하도록 설정한다.
  • packagesToScan 대신 annotatedClasses를 사용해서 모든 영속적 클래스를 직접 지정할 수도 있다.
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="annotatedClasses">    
            <list>
                <value>sample.spring.bankapp.domain.FixedDepositDetails</value>
            </list>
        </property>
    </bean>

하이버네이트 API를 사용하는 DAO

@Repository(value = "fixedDepositDao")
public class FixedDepositDaoImpl implements FixedDepositDao {

    @Autowired
    @Qualifier("sessionFactory")
    private SessionFactory sessionFactory;

    public int createFixedDeposit(final FixedDepositDetails fixedDepositDetails) {
        sessionFactory.getCurrentSession().save(fixedDepositDetails);
        return fixedDepositDetails.getFixedDepositId();
    }

    public FixedDepositDetails getFixedDeposit(final int fixedDepositId) {
        String hql = "from FixedDepositDetails as fixedDepositDetails where fixedDepositDetails.fixedDepositId ="
                + fixedDepositId;
        return (FixedDepositDetails) sessionFactory.getCurrentSession()
                .createQuery(hql).uniqueResult();
    }
}
  • SessionFactory 인스턴스가 FixedDepositDaoImpl 인스턴스에 자동 연결된다.
  • FactoryBean 구현인 LocalSessionFactoryBean이 SessionFactory의 getCurrentSession 메서드를 호출해서 Session 인스턴스를 얻는다.
  • getCurrentSession 메서드 호출이 현재 트랜잭션이나 현재 스레드와 연관된 Session 객체를 반환한다.
  • 스프링에게 트랜잭션 관리를 맡길 때는 getCurrentSession 메서드를 호출하는 것이 유용하다.

스프링을 통한 트랜잭션 관리

  • 스프링의 트랜잭션 관리 추상화를 사용해 명시적으로 트랜잭션 시작, 종료, 커밋을 한다.
  • 선언적인 트랜잭션 관리를 사용할 때는 스프링 @Transactional을 사용해 트랜잭션 안에서 실행하는 메서드를 지정한다.

프로그램을 사용한 트랜잭션 관리

  • TransactionTemplate 클래스를 사용하거나 PlatformTransactionManager 인터페이스 구현을 사용하면 프로그램으로 트랜잭션을 관리할 수 있다.
  • TransactionTemplate 클래스는 트랜잭션 시작과 커밋을 처리함으로써 트랜잭션 관리를 단순하게 해준다. (TransactionCallback 인터페이스만 구현하면 된다.)
  • <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"/> <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED" /> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED" /> </bean>
  • TransactionTemplate의 transactionManager 프로퍼티는 트랜잭션 관리를 책임질 스프링 PlatformTransactionManager 구현을 참조한다.
  • isolationLevelName 프로퍼티는 트랜잭션 매니저가 관리할 트랜잭션 격리 수준을 결정한다.
  • propagationBehaviorName 프로퍼티는 트랜잭션 전파 방식을 설정한다.
  • 스프링은 애플리케이션에서 사용할 데이터 접근 기술에 맞춰 선택할 수 있도록 몇가지 내장 PlatformTransactionManager 구현을 제공한다.
  • DataSourceTransactionManager는 데이터베이스와 상호작용하기 위해 단순한 JDBC를 사용하는 애플리케이션에 적합하다.
  • HibernateTransactionManager는 데이터베이스와 상호작용하기 위해 하이버네이트 Session을 사용하는 경우
  • JpaTransactionManager 는 데이터 접근에 JPA의 EntityManager를 사용하는 경우
  • HibernateTransactionManager, JpaTransactionManager는 데이터베이스 상호작용에 단순한 JDBC를 사용하는 경우도 지원한다.
  • transactionManager 프로퍼티는 DataSourceTransactionManager 인스턴스를 참조한다. (단순 JDBC를 데이터 접근에 사용하기 때문)
@Service(value = "fixedDepositService")
public class FixedDepositServiceImpl implements FixedDepositService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    @Qualifier(value = "fixedDepositDao")
    private FixedDepositDao myFixedDepositDao;

    @Autowired
    private BankAccountDao bankAccountDao;

    @Override
    public int createFixedDeposit(final FixedDepositDetails fixedDepositDetails)
            throws Exception {
        // -- create fixed deposit
        transactionTemplate
                .execute(new TransactionCallback<FixedDepositDetails>() {

                    @Override
                    public FixedDepositDetails doInTransaction(
                            TransactionStatus status) {
                        try {
                            myFixedDepositDao.createFixedDeposit(fixedDepositDetails);
                            bankAccountDao.subtractFromAccount(
                                    fixedDepositDetails.getBankAccountId(), fixedDepositDetails.getFdAmount());
                        } catch (Exception e) {
                            status.setRollbackOnly();
                        }
                        return fixedDepositDetails;
                    }
                });
        return fixedDepositDetails.getFixedDepositId();
    }

    @Override
    public FixedDepositDetails getFixedDeposit(int fixedDepositId) {
        return myFixedDepositDao.getFixedDeposit(fixedDepositId);
    }
}
public class BankApp {
    private static Logger logger = LogManager.getLogger(BankApp.class);

    public static void main(String args[]) throws Exception {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath:META-INF/spring/applicationContext.xml");

        BankAccountService bankAccountService = context
                .getBean(BankAccountService.class);
        FixedDepositService fixedDepositService = context
                .getBean(FixedDepositService.class);

        BankAccountDetails bankAccountDetails = new BankAccountDetails();
        bankAccountDetails.setBalanceAmount(1000);
        bankAccountDetails.setLastTransactionTimestamp(new Date());

        int bankAccountId = bankAccountService
                .createBankAccount(bankAccountDetails);

        logger.info("Created bank account (with balance amount 1000) with id - "
                + bankAccountId);

        FixedDepositDetails fdd = new FixedDepositDetails();

        fdd.setActive("Y");
        fdd.setFdAmount(1500);
        fdd.setBankAccountId(bankAccountId);
        fdd.setFdCreationDate(new Date());
        fdd.setTenure(12);

        int fixedDepositId = fixedDepositService.createFixedDeposit(fdd);
        logger.info("Created fixed deposit (for 1500 amount) with id - "
                + fixedDepositId
                + ". Check FIXED_DEPOSIT_DETAILS table to verify that the transaction was rolled back.");
        context.close();
    }
}

선언적 트랜잭션 관리

  • 프로그램을 사용해 트랜잭션을 관리하면 애플리케이션 코드와 스프링에만 있는 클래스가 밀접하게 결합된다.
  • 선언적 트랜잭션 관리를 사용하려면 메서드나 클래스에 스프링의 @Transactional을 설정한다.
    <tx:annotation-driven transaction-manager="txManager" />

    <bean id="txManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
  • 엘리먼트의 transaction-manager 속성을 트랜잭션관리에 사용하기 위해 PlatformTransactionManager 구현에 대한 참조를 지정한다.
    <tx:annotation-driven transaction-manager="txManager" />

    <bean id="txManager"
        class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
  • 하이버네이트 ORM을 사용하는 경우 HibernateTransactionManager 트랜잭션 관리를 사용한다.
import org.springframework.transaction.annotation.Transactional;


@Service(value = "fixedDepositService")
public class FixedDepositServiceImpl implements FixedDepositService {

    @Autowired
    private FixedDepositDao myFixedDepositDao;

    @Autowired
    private BankAccountDao bankAccountDao;

    @Override
    @Transactional
    public int createFixedDeposit(FixedDepositDetails fdd) throws Exception {
        // -- create fixed deposit
        bankAccountDao.subtractFromAccount(fdd.getBankAccountId()
                .getAccountId(), fdd.getFdAmount());
        return myFixedDepositDao.createFixedDeposit(fdd);
    }

    @Override
    @Transactional
    public FixedDepositDetails getFixedDeposit(int fixedDepositId) {
        return myFixedDepositDao.getFixedDeposit(fixedDepositId);
    }
}
  • @Transactional을 createFixedDeposit 메서드에 설정한다.
  • createFixedDeposit을 트랜잭션 안에서 실행한다는 뜻이다. transaction-manager 속성을 통해 지정한 트랜잭션 매니저를 사용해 트랜잭션을 관리한다.
  • RuntimeException 예외가 발생하면 트랜잭션이 자동으로 롤백된다.

애플리케이션에 여러 트랜잭션 매니저가 정의된 경우, @Transactional 애너테이션의 transactionManager 속성을 통해 PlatformTransactionManager 구현의 빈 이름을 지정할 수 있다.

@Service
public class SomeServiceImpl implements SomeService {

    @Transactional(transactionManager="tx1")
    public int methodA() {}

    @Transactional(transactionManager="tx2")
    public int methodB() {}

}
    <tx:annotation-driven/>

    <bean id="tx1"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="tx2"
        class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
  • 엘리먼트에서는 transaction-manager 속성을 지정하지 않는다.
  • @Transactional 애너테이션의 transactionManager 속성이 트랜잭션을 관리할 트랜잭션 매니저를 지정한다.

스프링의 JTA 지원

  • 트랜잭션 내부에 트랜잭션이 필요한 자원이 여러 존재할 경우 JTA를 활용해 트랜잭션을 관리한다.
  • JTA 트랜잭션을 관리할 때 사용할 수 있도록 JtaTransactionManager 클래스를 제공한다.
  • 대부분 애플리케이션 서버 환경에서는 JtaTransactionManager가 적합할 것이다.
  • 하지만 스프링은 서버의 개별적인 특성을 활용해 JTA 트랜잭션을 관리할 수 있도록 벤더별 JtaTransactionManager 구현도 제공한다.
  • 자원별 트랜잭션 매니저(DataSourceTransactionManager, HibernateTransactionManager, JmsTransactionManager)는 ResourceTransactionManager 인터페이스를 구현하며, 한 가지 대상 자원과 연결된 트랜잭션만 관리한다. DataSource, SessionFactory, EntityManagerFactory와 관련된 트랜잭션을 관리한다.
  • 스프링 tx 스키마는 애플리켕션이 배포된 애플리케이션 서버를 자동 감지해서 적당한 JTA 트랜잭션 매니저를 설정해주는 엘리먼트를 제공한다.

자바 기반을 사용하는 MyBank 개발

@Configuration
@PropertySource("classpath:/META-INF/database.properties")
@EnableTransactionManagement
public class DatabaseConfig {
    @Autowired
    private Environment env;

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(env.getProperty("database.driverClassName"));
        dataSource.setUrl(env.getProperty("database.url"));
        dataSource.setUsername(env.getProperty("database.username"));
        dataSource.setPassword(env.getProperty("database.password"));
        return dataSource;
    }

    @Bean
    public SessionFactory sessionFactory(DataSource dataSource) {
        LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(dataSource);
        builder.scanPackages("sample.spring");
        builder.setProperty("hibernate.show_sql", "true");
        builder.setProperty("hibernate.id.new_generator_mappings", "false");
        return builder.buildSessionFactory();
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager(SessionFactory sessionFactory) {
        return new HibernateTransactionManager(sessionFactory);
    }
}
  • @EnableTransactionManagement 애너테이션은 스프링 tx 스키마의 엘리먼트와 같은 역할을 한다.
  • @EnableTransactionManagement는 @Transactional 애너테이션을 지원을 활성화 한다.
  • @Bean 설정한 platformTransactionManager 메서드는 스프링이 트랜잭션 관리에 사용할 HibernateTransactionManager 인스턴스를 반환한다.

배워서 바로 쓰는 스프링프레임워크
애시시 사린, 제이 샤르마 지음
오현석 옮김

반응형

'JAVA > Spring' 카테고리의 다른 글

AOP  (0) 2022.01.28
캐싱  (0) 2022.01.28
빈과 빈 정의 커스텀  (0) 2021.12.26
의존관계주입  (0) 2021.12.26
빈 설정  (0) 2021.12.22