강의

멘토링

로드맵

Inflearn brand logo image

인프런 커뮤니티 질문&답변

조영석님의 프로필 이미지
조영석

작성한 질문수

스프링 부트 웹 개발 입문 - 따라하며 배우기

Transactional 에서 Exception (Checked vs Unchecked)

@Transactional 애너테이션을 적용해도 매번 session을 맺어서 처리하는 이유

작성

·

3.7K

0

@Transactional(rollbackFor = Exception.class)
public int testTran(DatasetList dsList) {

    Dataset dsParam = DatasetUtil.getGdsParam(dsList);
    // Dataset을 SQL에 적용할 변수형인 Map으로 변환하면서 사용자 정보 입력
    Map mapParam = DatasetUtil.getMap(dsParam, DatasetUtil.UPPER_CASE);

    int iRes1 = codemap.updateTran1(mapParam);

    log.info("==================== ||||||22222222222222||||||| ====================");

    int iRes2 = codemap.updateTran2(mapParam);  //미존재테이블로 에러 발생
    log.info("==================== |||||||3333333333333|||||| ====================");
    return iRes2;
}

[2023-06-28 16:07:47:67472] [http-nio-8808-exec-3] DEBUG o.s.web.servlet.DispatcherServlet - POST "/web/common/CodeAction?mode=testTran", parameters={masked}

[2023-06-28 16:07:47:67473] [http-nio-8808-exec-3] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped to com.skcc.nxcus_spring.usrcode.action.codecontroller#testTran(HttpServletRequest, HttpServletResponse)

[2023-06-28 16:07:47:67473] [http-nio-8808-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession

[2023-06-28 16:07:47:67473] [http-nio-8808-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@628385f5] was not registered for synchronization because synchronization is not active

[2023-06-28 16:07:47:67473] [http-nio-8808-exec-3] DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource

[2023-06-28 16:07:47:67474] [http-nio-8808-exec-3] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [HikariProxyConnection@1752952453 wrapping net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy@4499b10a] will not be managed by Spring

[2023-06-28 16:07:47:67474] [http-nio-8808-exec-3] DEBUG c.s.n.m.nxmdb.CodeMapper.updateTran1 - ==> Preparing: UPDATE COM_COMMON_COD SET COMM_NM = 'Transactional111' WHERE COMM_GRP_CD = 'MEMSTS' AND COMM_CD = 'A'

[2023-06-28 16:07:47:67474] [http-nio-8808-exec-3] DEBUG c.s.n.m.nxmdb.CodeMapper.updateTran1 - ==> Parameters:

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG jdbc.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)

1. UPDATE

COM_COMMON_COD

SET

COMM_NM = 'Transactional111'

WHERE

COMM_GRP_CD = 'MEMSTS'

AND COMM_CD = 'A'

{executed in 3 msec}

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG c.s.n.m.nxmdb.CodeMapper.updateTran1 - <== Updates: 1

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@628385f5]

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] INFO c.s.n.usrcode.biz.codeservice - ==================== ||||||22222222222222||||||| ====================

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@532be999] was not registered for synchronization because synchronization is not active

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [HikariProxyConnection@471746278 wrapping net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy@4499b10a] will not be managed by Spring

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG c.s.n.m.nxmdb.CodeMapper.updateTran2 - ==> Preparing: UPDATE COM_COMMON_COD SET COMM_NM = 'Transactional2222' WHERE COMM_GRP_CD = 'MEMSTS' AND COMM_CD = 'D'

[2023-06-28 16:07:47:67477] [http-nio-8808-exec-3] DEBUG c.s.n.m.nxmdb.CodeMapper.updateTran2 - ==> Parameters:

[2023-06-28 16:07:47:67478] [http-nio-8808-exec-3] DEBUG jdbc.sqltiming - com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)

1. UPDATE

COM_COMMON_COD

SET

COMM_NM = 'Transactional2222'

WHERE

COMM_GRP_CD = 'MEMSTS'

AND COMM_CD = 'D'

{executed in 1 msec}

[2023-06-28 16:07:47:67478] [http-nio-8808-exec-3] DEBUG c.s.n.m.nxmdb.CodeMapper.updateTran2 - <== Updates: 1

[2023-06-28 16:07:47:67478] [http-nio-8808-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@532be999]

[2023-06-28 16:07:47:67478] [http-nio-8808-exec-3] INFO c.s.n.usrcode.biz.codeservice - ==================== |||||||3333333333333|||||| ====================

[2023-06-28 16:07:47:67478] [http-nio-8808-exec-3] DEBUG o.s.web.servlet.DispatcherServlet - Completed 200 OK

 

 

@Transactional 애너테이션을 적용해도 각각의 sql 마다 매번 session을 맺어서 처리하는데요. 이유를 잘 모르겠습니다. 설정문제일까요?

 

 

답변 2

2

조영석님의 프로필 이미지
조영석
질문자

원인을 찾았습니다. 데이터소스를 여러개 설정해 놓으면 @Transactional이 안 먹는 현상이었습니다. ( 리모트db 테스트와 jndi 테스트때문에 여러개 만듬..;;;)

@Bean(name = "FirstDatasource") @Profile("!jndi") @ConfigurationProperties("spring.datasource.nxmdb") public DataSource firstDatasource() { log.info("firstDatasource====="); return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); }

---> 추가해야함.

@Bean(name = "transactionManager") @Primary public PlatformTransactionManager transactionManager() { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(firstDatasource()); return transactionManager; }

트랜잭션 매니저를 생성해주니 잘 동작합니다.

------------------------------------------------------------

 

  • 리모트 db 트랜잭션

    <!-- Spring Boot Starter JTA Atomikos --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> <version>2.7.12</version> <!-- Replace with the appropriate Spring Boot version --> </dependency>


    oracle xa설정 sqlplus sys as sysdba grant select on sys.dba_pending_transactions to anytest; grant select on sys.pending_trans$ to anytest; grant select on sys.dba_2pc_pending to anytest; grant execute on sys.dbms_system to anytest; grant execute on dbms_xa to anytest; GRANT EXECUTE ON sys.dbms_system TO system; GRANT SELECT ON sys.dba_pending_transactions TO system; GRANT SELECT ON sys.dba_2pc_pending TO system; GRANT SELECT ON sys.pending_trans$ TO system; GRANT SELECT ON sys.dba_pending_transactions TO system;



    application-local.yml spring: config: activate: on-profile: local jta: enabled: true nxmdb : datasource: jdbc-url: jdbc:oracle:thin:@localhost:1521/xe username: system password: root driverClassName: oracle.jdbc.OracleDriver anytest : datasource: jdbc-url: jdbc:oracle:thin:@localhost:1521/xe username: anytest password: nxcus2023 driverClassName: oracle.jdbc.OracleDriver db1: datasource: xa-data-source-class-name: oracle.jdbc.xa.client.OracleXADataSource xa-properties: user: system password: root url: jdbc:oracle:thin:@localhost:1521/xe db2: datasource: xa-data-source-class-name: oracle.jdbc.xa.client.OracleXADataSource xa-properties: user: anytest password: nxcus2023 url: jdbc:oracle:thin:@localhost:1521/xe server: port: 8808


    package com.skcc.nxcus_spring.config; import com.atomikos.icatch.jta.UserTransactionImp; import com.atomikos.icatch.jta.UserTransactionManager; import com.zaxxer.hikari.HikariDataSource; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.*; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.jta.JtaTransactionManager; import javax.sql.DataSource; import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import java.util.Properties; @Slf4j @Configuration @EnableTransactionManagement @MapperScan(value="com.skcc.nxcus_spring.mapper.nxmdb", sqlSessionFactoryRef="nxmdbSessionFactory") public class FirstDatasourceConfig { /* @Bean(name = "firstDatasource") @Profile("!jndi") @ConfigurationProperties(prefix = "spring.nxmdb.datasource") public DataSource firstDatasource() { log.info("originalDataSource====="); return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); } @Bean(name = "firstDatasource") @Profile("jndi") public DataSource jndifirstDatasource() { log.info("jndifirstDatasource====="); JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup(); return jndiDataSourceLookup.getDataSource("OCBX01"); } / @Bean(name = "nxmdbSessionFactory") public SqlSessionFactory nxmdbSessionFactory(@Qualifier("firstDatasource")DataSource dataSource) throws Exception { log.info("nxmdbSessionFactory====="); SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setVfs(SpringBootVFS.class); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resource = resolver.getResources("mapper/.xml"); factoryBean.setMapperLocations(resource); factoryBean.setTypeAliasesPackage("com.skcc.nxcus_spring"); return factoryBean.getObject(); } @Bean(name = "nxmdbSqlSessionTemplate") public SqlSessionTemplate nxmdbSessionTemplate(SqlSessionFactory nxmdbSessionFactory) { log.info("nxmdbSessionTemplate====="); return new SqlSessionTemplate(nxmdbSessionFactory); } /* @Bean(name = "nxmdbTransactionManager") public PlatformTransactionManager nxmdbTransactionManager(@Qualifier("firstDatasource") DataSource dataSource) { log.info("nxmdbTransactionManager====="); DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } */ ////////////////////////////////////////////////////////////////////// @Value("${spring.db1.datasource.xa-data-source-class-name}") String ds1XaDataSourceClassName; @Value("${spring.db1.datasource.xa-properties.user}") String ds1User; @Value("${spring.db1.datasource.xa-properties.password}") String ds1Password; @Value("${spring.db1.datasource.xa-properties.url}") String ds1Url; @Bean(name = "firstDatasource") public DataSource dataSource() { log.info("db1DataSource AtomikosDataSourceBean"); AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); ds.setUniqueResourceName(DS1_DATASOURCE); ds.setXaDataSourceClassName(ds1XaDataSourceClassName); Properties p = new Properties(); p.setProperty("user", ds1User); p.setProperty("password", ds1Password); p.setProperty("URL", ds1Url); ds.setXaProperties (p); return ds; } @Bean(name = "userTransaction") public UserTransaction userTransaction() throws Throwable { UserTransactionImp userTransactionImp = new UserTransactionImp(); userTransactionImp.setTransactionTimeout(10000); return userTransactionImp; } @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close") public TransactionManager atomikosTransactionManager() throws Throwable { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); return userTransactionManager; } @Bean(name = "multiTxManager") @DependsOn({ "userTransaction", "atomikosTransactionManager" }) public PlatformTransactionManager transactionManager() throws Throwable { log.info("multiTxManager=PlatformTransactionManager==transactionManager=="); UserTransaction userTransaction = userTransaction(); JtaTransactionManager manager = new JtaTransactionManager(userTransaction, atomikosTransactionManager()); return manager; } }



    package com.skcc.nxcus_spring.config; import com.zaxxer.hikari.HikariDataSource; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Profile; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.Properties; @Slf4j @Configuration @MapperScan(value="com.skcc.nxcus_spring.mapper.anytest", sqlSessionFactoryRef="anytestSessionFactory") public class SecondDatasourceConfig { /* @Bean(name = "secondDatasource") @Profile("!jndi") @ConfigurationProperties("spring.anytest.datasource") public DataSource secondDatasource() { log.info("local or server"); return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); } @Bean(name = "secondDatasource") @Profile("jndi") public DataSource secoundjndiDatasource() { JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup(); return jndiDataSourceLookup.getDataSource("OCBX"); } / @Bean(name = "anytestSessionFactory") public SqlSessionFactory anytestSessionFactory(@Qualifier("secondDatasource")DataSource ds) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(ds); factoryBean.setVfs(SpringBootVFS.class); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resource = resolver.getResources("mapper/.xml"); factoryBean.setMapperLocations(resource); factoryBean.setTypeAliasesPackage("com.skcc.nxcus_spring"); return factoryBean.getObject(); } @Bean(name = "anytestSqlSessionTemplate") public SqlSessionTemplate anytestSqlSessionTemplate(SqlSessionFactory anytestSessionFactory) { return new SqlSessionTemplate(anytestSessionFactory); } /* @Bean(name = "fepdbTransactionManager") public PlatformTransactionManager remoteTransactionManager(@Qualifier("secondDatasource") DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } */ ////////////////////////////////////// public static final String DS2_DATASOURCE = "secondDatasource"; @Value("${spring.db2.datasource.xa-data-source-class-name}") String ds2XaDataSourceClassName; @Value("${spring.db2.datasource.xa-properties.user}") String ds2User; @Value("${spring.db2.datasource.xa-properties.password}") String ds2Password; @Value("${spring.db2.datasource.xa-properties.url}") String ds2Url; @Bean(name = "secondDatasource") public DataSource dataSource() { log.info("db2DataSource AtomikosDataSourceBean"); AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); ds.setUniqueResourceName(DS2_DATASOURCE); ds.setXaDataSourceClassName(ds2XaDataSourceClassName); Properties p = new Properties(); p.setProperty("user", ds2User); p.setProperty("password", ds2Password); p.setProperty("URL", ds2Url); ds.setXaProperties (p); return ds; } }







    @Transactional(rollbackFor = Exception.class, value = "multiTxManager") public int testTran(DatasetList dsList) { Dataset dsParam = DatasetUtil.getGdsParam(dsList); // Dataset을 SQL에 적용할 변수형인 Map으로 변환하면서 사용자 정보 입력 Map mapParam = DatasetUtil.getMap(dsParam, DatasetUtil.UPPER_CASE); log.info("aop class={}", codeMapper.getClass()); //트랜잭션이 활성화되었는지 확인 boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}",txActive); int iRes1 = 0; iRes1 = codeMapper.updateTran1(mapParam); iRes1 = fepMapper.updateFep(mapParam); //리모트디비 iRes1 = codeMapper.updateTran2(mapParam); return iRes1; }

    작성하니 세션도 db별로 1개씩 총 2개 생성되고 롤백도 잘 되네요.








IT늦공 김부장님의 프로필 이미지
IT늦공 김부장
지식공유자

DataSource 를 여러개 사용하는것 까지 하시다니 대단하시네요.
그리고, 덕분에 저도 이런 상황에 대한 지식을 얻었습니다.

제 강의를 보면서 Transaction 부분에 관심이 있으신분은 처음인것 같아요.

지극히 제 개인적인 기준으로 굳이 어떤 개발 레벨을 나눠야 한다면
그중 하나가 저는 DB 관리부분 특히 트랜잭션 부분에 대해 알고 있는가라고 생각해요.

실무에서 정말 크리티컬한 상황을 발생시키거든요.
덕분에 저도 공부하게되었습니다.
감사합니다.

0

IT늦공 김부장님의 프로필 이미지
IT늦공 김부장
지식공유자

안녕하세요.
좀더 정확한 내용파악을 위해서 codemap.updateTran1, codemap.updateTran2 메소드를 보면 좋을것 같습니다.

codemap 객체가 위에 올려준 메소드가 있는 클래스와 같은지 다른지도 궁금하네요.
아마도 다른 클래스의 객체이지 않을까 하는데요.

다른 클래스의 객체일때 Propagation 설정이 신규 세션을 맺도록 되어있지 않을까 생각도 해봅니다.
소스를 좀더 보여주시면 좀더 도움이 될 수 있을것 같습니다.

 

조영석님의 프로필 이미지
조영석

작성한 질문수

질문하기