[MyBatis->JPA] Transaction 설정편

maxxyoung·2023년 7월 21일
2

MyBatis->JPA

목록 보기
2/4

MyBatis와 JPA 동시 적용과 트랜잭션

설정 환경

  • springboot 2.2.5
  • gradle
  • mybatis 사용

추가된 설정

@Configuration
@EnableJpaRepositories(
        basePackages = "com.maxxyoung.**.repository",
        entityManagerFactoryRef = "entityManagerFactory"
)
@MapperScan(
        basePackages = "com.maxxyoung.**.dao",
        sqlSessionFactoryRef = "sqlSessionFactory"
)
public class DatabaseConfig {
    /**
     * hikari config
     */
    @Bean(name= "hikariConfig")
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    /**
     * datasource
     */
    @Bean(name= "dataSource")
    public HikariDataSource dataSource(@Qualifier("hikariConfig") HikariConfig hikariConfig) {
        return new HikariDataSource(hikariConfig);
    }

    @Bean(name= "jpaDataSource")
    public HikariDataSource jpaDataSource(@Qualifier("hikariConfig") HikariConfig hikariConfig) {
        return new HikariDataSource(hikariConfig);
    }

    /**
     * sessionfactory
     */
    @Bean(name= "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mybatis/sql/**/*.xml")); //mapper path
        sessionFactoryBean.setTypeAliasesPackage("com.maxxyoung.domain, com.maxxyoung.dto");
        sessionFactoryBean.setTypeHandlersPackage("com.maxxyoung.typehandler");
        Objects.requireNonNull(sessionFactoryBean.getObject()).getConfiguration().setMapUnderscoreToCamelCase(true); //camelCase
        return sessionFactoryBean.getObject();
    }

    /**
     * sqlsession
     */
    @Bean(name= "sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    /**
     * jpa entityManagerFactory
     */
    @Bean(name = "entityManagerFactory")
    public EntityManagerFactory entityManagerFactory(@Qualifier("jpaDataSource") DataSource dataSource) {

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setPackagesToScan("kr.influencercard.**.jpa.entity");
        factory.setDataSource(dataSource);
        factory.setPersistenceUnitName("MySQL");

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setShowSql(true);
        factory.setJpaVendorAdapter(vendorAdapter);

        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "none");
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        properties.put("hibernate.format_sql", true);
        properties.put("hibernate.show_sql", false);  // sql은 log4j로 출력 org.hibernate.SQL=DEBUG
        properties.put("hibernate.globally_quoted_identifiers", true);  // 예약어 컬럼명 사용 허용
        factory.setJpaPropertyMap(properties);
        factory.afterPropertiesSet();

        return factory.getObject();
    }

    /**
     * transaction manager
     */
    @Bean(name= "txManager")
    public PlatformTransactionManager txManager(@Qualifier("dataSource") DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
        dataSourceTransactionManager.setNestedTransactionAllowed(true); // nested

        return dataSourceTransactionManager;
    }

    @Bean(name= "jpaTxManager")
    public PlatformTransactionManager jpaTxManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);

        return jpaTransactionManager;
    }

처음에는 다음과 같이 설정을 진행했다. 설정을 통해 프로퍼티에서 hikari 정보를 가져오고, 가져온 정보로 jpa, jdbc 트랜잭션을 각각 만들어준다.

설정을 하는 도중에 properties spring.datasource.hikari 데이터를 가져와 사용하는데 이미 dataSource 데이터를 가지고 있는 spring.datasource와의 차이점이 무엇인지 궁금했다.

spring.datasource.hikari, spring.datasource 설정 차이

  • 자동 설정
    • spring.datasource.url이 모든 Datasource의 url이 된다.
  • 수동 설정 (Java Config)
    • spring.datasource.jdbc-url로 해야 HikariCP가 인식한다.
  • spring.datasource.hikari 설정을 이용할 경우 자동, 수동 설정에 관계 없이 사용가능
    참고 향로님 블로그

(실제로 java config로 설정을 하는데 있어서 spring.datasource.url만가지고는 jdbc-url을 찾는다는 오류를 뱉었다.)

문제점 발생

다음과 같이 트랜잭션 2개를 설정할 경우 문제가 있다

  • 선언적 트랜잭션 즉 어노테이션을 이용한 트랜잭션을 사용할 경우 트랜잭션이 2개여서 @Transactional(value="사용할 트랜잭션 이름") 써주어한다.(트랜잭션 2개 중 어떤 트랜잭션 사용할지)
    • 두 개의 트랜잭션을 사용하기 위해 기존 트랜잭션 어노테이션을 모두 수정해야함 -> 가능은 하지만 인력낭비
  • 혹시나 jdbc, jpa 트랜잭션이 하나로 묶인다면? -> 처리불가

해결 방법

@Configuration
@EnableJpaRepositories(basePackages = "com.maxxyoung.**.repository")
@MapperScan("com.maxxyoung.dao, com.maxxyoung.inflma.dao")
public class DatabaseConfig {
    JpaTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }
}
  • 프로퍼티에 설정값만 적어주면 spring boot가 알아서 bean들을 만들어준다. 따라서 JPA transaction manager기본 트랜잭션 매니저로 등록해주면 된다!
    • jpa transaction managerdata source에 직접 접근이 가능하기 때문에 mybatis와 동시에 사용이 가능

    • 한가지 주의해야할 점은 JPA 트랜잭션 매니저와 JDBC Connection을 위한 DataSource는 동일한 DataSource 객체를 바라보고 있어야 하나의 트랜잭션으로 제대로 묶임

      참고 : 일반 JDBC 코드에 대해 DataSource의 연결을 등록하려면 Jpa 트랜잭션 매니저 인스턴스가 DataSource (setDataSource (javax.sql.DataSource))를 인식해야합니다. 주어진 DataSource는 JPA 트랜잭션 매니저의 EntityManagerFactory에서 사용하는 것과 분명히 일치해야합니다. JPA 트랜잭션 매니저는 EntityManagerFactory의 연결 팩토리로 사용되는 DataSource를 자동 감지하므로 일반적으로 "dataSource"속성을 명시적으로 지정할 필요가 없습니다.

하지만

bealdung 참고

However, if we're using a Spring Boot project and have a spring-data-* or spring-tx dependencies on the classpath, then transaction management will be enabled by default.

스프링부트의 경우 spring-data-*에 따라 트랜잭션 매니저가 자동으로 등록된다. JPA를 사용한다면 JpaTransactionManager자동으로 등록된다.
아마 이 부분이지 않을까 싶다.

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {

	private final DataSource dataSource;

	private final JpaProperties properties;

	private final JtaTransactionManager jtaTransactionManager;

	private ConfigurableListableBeanFactory beanFactory;

	@Bean
	@ConditionalOnMissingBean
	public PlatformTransactionManager transactionManager(
			ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
		return transactionManager;
	}

결론! 스프링부트에서 아무설정 안해도 기본으로 jpa 트랜잭션으로 등록된다!

profile
오직 나만을 위한 글. 틀린 부분 말씀해 주시면 감사드립니다.

0개의 댓글