JPA와 Mybatis를 동시에 사용하고 있다.
두 기술의 트랜잭션을 서로 공유하면서 사용하고 있는데 세팅한 방법을 기술한다.
실무에선 2개 이상의 데이터베이스를 한프로젝트에서 사용한다.
application.yml에서는 아래와 같이 세팅한다
spring:
${보통 DB이름}: //자유롭게 입력
datasource:
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: ${DB URL}
username: ${DB 계정이름}
password: ${DB 계정비밀번호}
hikari:
poolName: ${poolName} //자유롭게 입력
connectionTimeout: ??
maximumPoolSize: ?
minimumIdle: ?
${보통 DB이름}: //자유롭게 입력
datasource:
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: ${DB URL}
username: ${DB 계정이름}
password: ${DB 계정비밀번호}
hikari:
poolName: ${poolName} //자유롭게 입력
connectionTimeout: ??
maximumPoolSize: ?
minimumIdle: ?
- 멀티 database를 세팅할때 핵심은 url이 아닌 jdbcUrl 혹은 jdbc-url을 사용해야 한다.
- spring boot2.0부터 기본 커넥션 풀이 tomcat-jdbc => HikariCP로 변경되었는데 HikariCP의 Database URL 설정은 jdbcUrl로 사용하기 때문이다.
- 단일 DB설정의 경우에는 자동으로 url을 jdbcUrl로 인식하여 주입해주므로 url을 사용해도 된다.
- hikari의 상세설정은 서비스 환경마다 다르기 때문에 알맞게 설정하면된다.
이제 application.yml에서 설정한 DB에 접근하기 위한 datasource 설정파일을 만들자
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
/**
* @EnableTransactionManagement : XML의 <tx:annotation-driven/>와 동일, Spring의 선언적 트랜잭션 처리 기능 활성화
* 해당 어노테이션을 사용하면 DataSourceTransactionManager로 구성되기 때문에 @Scheduled가 동작하지 않는 이슈가
* 발생한다. 그래서 트랜잭션 매니저를 JpaTransactionManage로 구현한다
* @Primary 를 사용해 우선적으로 등록할 트랜잭션 매니져 Bean을 지정
*/
@EnableTransactionManagement
@MapperScan(value="{마이바티스에서 사용할 DAO 경로}", sqlSessionFactoryRef="testSqlSessionFactory")
/**
* JpaRepository를 상속받아서 사용할 경우 해당 인터페이스가 존재하는 경로를
* 명시해줘야 사용가능하다.
*/
@EnableJpaRepositories(
entityManagerFactoryRef = "testJpaEntityManagerFactory",
transactionManagerRef = "testTransactionManager",
basePackages = "{repository 경로}"
)
public class JpaConfig {
/**
* Datasource : Connection Pool을 지원하는 인터페이스
*
*/
@Primary
@Bean(name="testDataSource")
@ConfigurationProperties(prefix="${yml에서 세팅한 DB 경로(택 1)}")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
/**
* SqlSessionFactory : SqlSession을 찍어내는 역할
* Datasourc를 참조하여 MyBatis와 Mysql 서버를 연동한다. SqlSession을 사용하기 위해 사용한다.
* @param testDataSource
* @param applicationContext
*/
@Primary
@Bean(name = "testSqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource testDataSource, ApplicationContext applicationContext) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(testDataSource);
sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:${.xml이 세팅된 경로(마이바티스 쿼리가 저장된 곳}"));
return sqlSessionFactoryBean.getObject();
}
/**
* SqlSessionTemplate : SqlSession을 구현하고 코드에서 SqlSession을 대체하는 역할을 한다. 마이바티스 예외처리나 세션의 생명주기 관리
* @param testSqlSessionFactory
*/
@Primary
@Bean(name="testSqlSessionTemplate")
public SqlSessionTemplate apiSqlSessionTemplate(SqlSessionFactory testSqlSessionFactory) throws Exception {
return new SqlSessionTemplate(testSqlSessionFactory);
}
/**
* LocalContainerEntityManagerFactoryBean
* EntityManager를 생성하는 팩토리
* SessionFactoryBean과 동일한 역할, Datasource와 mapper를 스캔할 .xml 경로를 지정하듯이
* datasource와 엔티티가 저장된 폴더 경로를 매핑해주면 된다.
* @param builder
* @param dataSource
* @return
*/
@Primary
@Bean( name = "testJpaEntityManagerFactory" )
public LocalContainerEntityManagerFactoryBean jpaEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("testDataSource") DataSource dataSource ) {
return builder.dataSource(dataSource).packages("${엔티티가 저장된 경로}").build();
}
/**
* JpaTransactionManager : EntityManagerFactory를 전달받아 JPA에서 트랜잭션을 관리
*/
@Primary
@Bean(name = "testTransactionManager")
public JpaTransactionManager transactionManager(
@Qualifier("testJpaEntityManagerFactory") LocalContainerEntityManagerFactoryBean mfBean
) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory( mfBean.getObject() );
return transactionManager;
}
}
- Spring은 PlatformTransacitonManager 인터페이스로 트랜잭션을 처리한다.
- 이 인터페이스의 구현체 중 하나인 JpaTransactionManager가 있다. 이는 JPA를 위해 주로 사용하지만 트랜잭션이 사용하고 있는 Datasource에 직접 접근가능하여 일반적인 JDBC를 바로 사용할 수 있다.
- MyBatis는 트랜잭션 관리를 SqlSession이 아닌 Spring Transaction에 위임한다.
- 따라서, JpaTransactionManager를 사용하면 JPA와 MyBatis를 같은 트랜잭션으로 묶을 수 있다.
- 만약 2개 이상의 DB를 설정할 경우 자주 사용하는 DB의 config에 @Primary를 붙여줘야 해당 빈부터 로드하여 충돌이 나지 않는다.
@Transactional(value = "testTransactionManager")
- JpaTransactionManager의 Bean이름을 반드시 value의 값으로 설정해서 사용해야한다.
- 이것도 망각한채 서비스단에서 에러발생시 혹은 테스트코드에서 rollBack이 되지않아 조금 시간을 날렸다.
※ QuerDsl 사용시 유의할점
@Repository
public class Repository extends QuerydslRepositorySupport {
public Repository() {
super(Repository.class);
}
@Override
@PersistenceContext(unitName = "testJpaEntityManagerFactory")
public void setEntityManager(EntityManager entityManager) {
super.setEntityManager(entityManager);
this.queryFactory = new JPAQueryFactory(entityManager);
}
- 멀티 DB 설정을 했을 경우 어떤 entityManagerFactory를 사용할지 몰라 에러를 낸다.
- 따라서 어떤걸 사용할지 세팅을 해줘야 한다.
혹은 아래와 같이 사용할 수 있다.
@Configuration
public class QueryDslConfig {
@PersistenceContext(unitName = "testJpaEntityManagerFactory")
private EntityManager testEntityManager;
@Bean
public JPAQueryFactory testJpaQueryFactory() {
return new JPAQueryFactory(testEntityManager);
}
}
위의 config에서 설정한 EntityManagerFactory 빈을 영속성 컨텍스트의 unitName으로 주입된 EntityManage를 만들어준다. 그리고 해당 entityManager를 주입받은 JPAQueryFactory 객체를 빈으로 등로한다.
@Repository
public class TestQueryRepository {
private final JPAQueryFactory queryFactory;
public TestQueryRepository(@Qualifier("testJpaQueryFactory") JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}
//...
}
QueryDsl를 사용하는 레파지토리에서 해당 jpaQueryFactory 빈을 주입받아 사용하면된다.
그러면 QuerydslRepositorySupport 를 상속받지 않고도 사용할 수 있다.
참고한 글들을 보면 EntityManagerFactory에 persistence 관련 설정을 했다.
@Bean
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceUnitName("hello");
entityManagerFactoryBean.setPersistenceXmlLocation("classpath:/META-INF/persistence.xml");
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.afterPropertiesSet();
return entityManagerFactoryBean.getObject();
}
8.11. Use a Traditional persistence.xml File
Spring Boot will not search for or use a META-INF/persistence.xml by default. If you prefer to use a traditional persistence.xml, you need to define your own @Bean of type LocalEntityManagerFactoryBean (with an ID of ‘entityManagerFactory’) and set the persistence unit name there.
See JpaBaseConfiguration for the default settings.
- 기존은 META-INF - persistence.xml에 설정했는데 알고보니까 위와 같이 XmlLocation을 세팅 해줘야 persistence.xml을 읽는다는걸 이제야 알았다..
- https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-access.use-traditional-persistence-xml
참고
(★)1. https://thecodinglog.github.io/jpa/mybatis/spring/2019/09/11/jpa-with-mybatis-in-transaction.html
2.https://www.inflearn.com/questions/12499
(★)3. https://rangerang.tistory.com/70
4. https://jojoldu.tistory.com/296
5. https://bongdev.tistory.com/149
'Tech > Spring' 카테고리의 다른 글
요청으로 들어온 language 값에 따른 GlobalExceptioner에서 다국어 처리 (i18n, yml) + spring validaiton 다국어처리 (0) | 2022.05.23 |
---|---|
Validation 클래스 단위 제약과 조건부 검사 설정 (0) | 2021.12.22 |
Spring Filter에서 request Body 가공하기 (0) | 2021.07.19 |
단위 테스트를위한 ReflectionTestUtils (1) | 2021.03.23 |
Springboot + GCP Cloud Storage 연동(파일 업로드, 다운로드) (0) | 2021.03.22 |