728x90

들어가면서...

우리는 개발 소트코드는 git ,svn 등 형상관리툴을 이용하여 pull, commit 등을 매일 수백번씩하면서 관리해간다. 

하지만 db의 버전은 어떻게 관리해왔는가? 물론 지금 말하는 db 버전은 mysql 5.7 , mysql 8.0 oracle 10g 이런 버전이 아니고 개발도중에 자주 바뀌는 스키마, 기초 테스트 데이터 를 얘기한다. 팀 동료가 스키마가 바뀐상태로 개발소스들을 commit 하였다면 pull 내려 받은 나는 코드들이 제대로 작동하지 않을수 있고 반대로 내가 변경한 데이터들이 바뀌면 개발팀원 전체에 영향을 줄수 있다.

이런 문제들을 flyway라는 마이그레이션 툴을 사용하여 database를 형상관리해보자!

공식사이트에 기재되어 있는 flyway 작동방식을 설명해주는 많이 보았을 이미지 이다.  미안하지만 그대로 가지고와 봤다. 뭐 설명을 하려고 하니 딱히 설명할것도 없을것 같다. 우리가 이미 code 형상 관리에 대하여 어느정도 이해가 있으니 그림만 봐도 대충 이해가 될것 같다. 즉 형상관리를 시작하는 시점의 데이터가 있고 추후 변경되는 부분의 데이터들을 감지하여 변경된 부분만 엡데이트 해주는 방식이다.

 

flyway를 잘 사용하면 이렇게 Axel 과 Christian 라는 개발자 각자 필요한 DDL 을 만들고 배포할수 잇다. 공식사이트 소개를 예로 들자면 gradle, maven, CLI, java api 를 통하여 flyway 를 실행할수 있다고 하는데 이글에서 springboot 으로 실행하는 방법을 알아보자.

 

pom.xml dependency 추가

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

src/main/resources 폴더에 db 폴더 ,그리고 그안에 migration 폴더를 만든다.

migration 폴더에 V1__Base_version.sql 파일을 생성

DROP TABLE IF EXISTS user ;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'pk',
  `name` varchar(20) NOT NULL COMMENT '이름',
  `age` int(5) DEFAULT NULL COMMENT '나이',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

주의 : .sql 경로를 혹시 자기가 원하는 폴더에 위치하고 싶으면 spring.flyway.locations 설정을 하면 된다.

User 객체 생성

@Data
@NoArgsConstructor
public class User {

    private Long id;
    private String name;
    private Integer age;

}

Interface 작성

public interface UserService {

    int create(String name, Integer age);
    
    List<User> getByName(String name);

    int deleteByName(String name);

    int getAllUsers();

    int deleteAllUsers();

}

@Service
public class UserServiceImpl implements UserService {

    private JdbcTemplate jdbcTemplate;

    UserServiceImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public int create(String name, Integer age) {
        return jdbcTemplate.update("insert into USER(NAME, AGE) values(?, ?)", name, age);
    }

    @Override
    public List<User> getByName(String name) {
        List<User> users = jdbcTemplate.query("select * from USER where NAME = ?", (resultSet, i) -> {
            User user = new User();
            user.setId(resultSet.getLong("ID"));
            user.setName(resultSet.getString("NAME"));
            user.setAge(resultSet.getInt("AGE"));
            return user;
        }, name);
        return users;
    }

    @Override
    public int deleteByName(String name) {
        return jdbcTemplate.update("delete from USER where NAME = ?", name);
    }

    @Override
    public int getAllUsers() {
        return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
    }

    @Override
    public int deleteAllUsers() {
        return jdbcTemplate.update("delete from USER");
    }

}

테스트 코드 작성

@Slf4j
@SpringBootTest
public class FlywayDemoApplicationTests {

    @Autowired
    private UserService userSerivce;

    @Test
    public void test() throws Exception {
        userSerivce.deleteAllUsers();

        userSerivce.create("Tom", 10);
        userSerivce.create("Mike", 11);
        userSerivce.create("Didispace", 30);
        userSerivce.create("Oscar", 21);
        userSerivce.create("Linda", 17);

        // Oscar 라는 이름을 가진 사용자 조회 , 나이 가 정확한지 확인.
        List<User> userList = userSerivce.getByName("Oscar");
        Assertions.assertEquals(21, userList.get(0).getAge().intValue());

        // 5명 있음.
        Assertions.assertEquals(5, userSerivce.getAllUsers());

        // 2명 삭제.
        userSerivce.deleteByName("Tom");
        userSerivce.deleteByName("Mike");

        // 아직 5명 있을걸?
        Assertions.assertEquals(3, userSerivce.getAllUsers());
    }

}

테스트 코드 실행 결과

디비테이블 확인 2개 추가 된걸 확인 할수 있다.

  • user 현재 테이블
  • flyway_schema_history : flyway가 관리하고 있는 테이블. 해당 테이블에 수행된 .sql 스크립트 내역들을 기재하고 있다.

위 내용을 이서 진행해보자. 만일 특정 개발자가 address 라는 컬럼을 테이블에 추가했다면 어떻게 될까?

ALTER TABLE `user` ADD COLUMN `address` VARCHAR(20) DEFAULT NULL;

팁! 스크립트 파일 명명 규칙은 버전번호_쿼리에 대한 설명 .sql 이다.

다시 테스트 코드를 돌리면 아래와 같은 수행완료 로그를 볼수 있다.

2022-12-26 16:58:12.025  INFO 37330 --- [           main] o.f.c.i.database.base.DatabaseType       : Database: jdbc:mysql://localhost:3306/test (MySQL 8.0)
2022-12-26 16:58:12.063  INFO 37330 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 2 migrations (execution time 00:00.020s)
2022-12-26 16:58:12.075  INFO 37330 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema `test`: 1
2022-12-26 16:58:12.082  INFO 37330 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema `test` to version "1.1 - alter table user"
2022-12-26 16:58:12.113  INFO 37330 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 1 migration to schema `test` (execution time 00:00.045s)

테이블을 확인해보자

마찬가리로 history 테이블에도 아래와 같이 내역이 추가되었다.

어떤가 어제는 더이상 누가 스키마를 바꿨는지 팀원들한테 일일이 확인 안해봐도 되지 않은가?

끝!

'Springboot2.x 강좌 > DB연결' 카테고리의 다른 글

트랜잭션 기본설정 알아보기  (0) 2023.01.05
MyBatis 의 다중 DataSource  (0) 2023.01.05
Spring Data JPA 다중 DataSource  (0) 2023.01.05
JdbcTemplate 다중 DataSource  (0) 2023.01.05
XML 로 Mybatis 설정하기  (0) 2023.01.05
728x90

특징

  • 독립성 : 하나의 트랜젝션은 다른 트랜젝션에 포함될수 없고 독립적이다.
  • 일관성 : 작업 처리의 결과가 항상 일관된다.
  • 원자성 : Database 에 모두반영 (Success) 혹은 모두 반영(Fail)되지 않음을 말한다.
  • 지속성 : 성공적으로 완료된 결과는 영구적용되어야 한다.

트랜젝션 commit & rollback

  • 성공상태이고 success로 반영된 상태
  • 프로세스가 성공아닌 실패로 끝나거나 비정상적으로 종료 되었을때 앞서 성공한 프레세들은 하나로 묶여서 수행전 상태로 되돌아가야 한다.

트랜젝션의 논리적 5가지 상태

  • Active : 현재 실행 중인 상태.
  • Failed : 트랜젝션이 실행중 오류로 인한 중단.
  • Aborted : 트랜잭션이 비정상 종료로 인하여 롤백 된 상태.
  • Partially Committed : 트랜젝션의 연산이 마지막까지 실행 되고 Commit 되기 직전상태.
  • Committed : 트랜젝션이 모두 성공적으로 진행되고 Commit 연산까지 실행된 상태.

그래서 Springboot 에서 트랜젝션은 어떻게 설정하고 사용하는데?

일반적으로 spring-boot-starter-jdbc 와 spring-boot-starter-data-jpa 를 추가하면 springboot 가 알아서 DataSourceTransactionManager 와 JpaTransactionManager 을 주입하기 때문에 사용자는 별도로 @Transactional 어노테이션을 붙일 필요가 없다.

앞서 작성한 spring data jap 다중 datasource 을 기준으로 테스트 코드를 작성해 본다.

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

	@Autowired
	private UserRepository userRepository;

	@Test
	public void test() throws Exception {

		// 10 data 입력 
		userRepository.save(new User("AAA", 10));
		userRepository.save(new User("BBB", 20));
		userRepository.save(new User("CCC", 30));
		userRepository.save(new User("DDD", 40));
		userRepository.save(new User("EEE", 50));
		userRepository.save(new User("FFF", 60));
		userRepository.save(new User("GGG", 70));
		userRepository.save(new User("HHH", 80));
		userRepository.save(new User("III", 90));
		userRepository.save(new User("JJJ", 100));

		// validation 생략
	}

}

User 객체 @Max(50) 이므로 age가 50 보다 큰수입력시 Exception이 발생한다.

@Entity
@Data
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @Max(50)
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

}

log

2022-12-26 11:55:29.581 ERROR 24424 --- [           main] o.h.i.ExceptionMapperStandardImpl        : HHH000346: Error during managed flush [Validation failed for classes [com.blake.demo.User] during persist time for groups [javax.validation.groups.Default, ] List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='최대수치50을 초과할수 없습니다.', propertyPath=age, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{javax.validation.constraints.Max.message}'} ]]

database 확인

이미지에서 확인할수 있는것처럼 테스트진행중 예외가 발생하여 앞5개 데이터는 정상 입력되었지만 나머지 5개 데이터는 입력실패, 이때 모두 성공 or 실패로 처리하려면 트랜젝션기능을 이용할수 있다.

테스트코드를 살펴보자

@Test
@Transactional
public void test() throws Exception {

    // test 내영은 생략

}

코드에서 처럼 @Transactional 어노테이션 하나만 달아주면 끝!

public interface UserService {
    
    @Transactional
    User update(String name, String password);
    
}

트랜잭션 상세

앞서 작성 글중에서 spring data jap 다중 datasource 사용하는 환경일경우 트랜젝션 선언시 트랜잭션 매니저를 지정해 줘야 한다. 예를 들면

@Transactional(value="transactionManagerPrimary")

트랜젝션 매니저를 지정하는것 외에도 우리는 트랜젝션을 격리,전파 등 컨트롤할수 있다.

격리(Isolation)

개념 : 트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준을 말한다.

위치 : org.springframework.transaction.annotation.Isolation 에서 확인할수 있다.

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
}
  • DEFAULT : 기본 격리 수준(, DB의 Isolation Level을 따름)
  • READ_UNCOMMITTED : 커밋되지 않는(트랜잭션 처리중인) 데이터에 대한 읽기를 허용
  • READ_COMMITTED : 트랜잭션이 커밋 된 확정 데이터만 읽기 허용
  • REPEATABLE_READ : 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정하는 것을 막아준다. 선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신/삭제를 보류한다.
  • SERIALIZABLE : 가장 강력한 격리수준이다. 순차적으로 진행해서 여러 트랜잭션이 동시에 같은 테이블 정보를 엑세스하지 못하게 한다.

그럼 어떻게 지정하는데? 지정방법은 아래와 같다!

@Transactional(isolation = Isolation.DEFAULT)

트랜젝션의 전파(propagation)

개념 : propagation 속성을 이용해 호출한 쪽의 트랜잭션을 그대로 사용할 수도 있고, 새롭게 트랜잭션을 생성할 수도 있다. 트랜잭션의 경계를 정의하며 시작 방법을 결정하는 속성이다.

위치 : org.springframework.transaction.annotation.Propagation 에서 확인 할수 있다.

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
}
  • REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성한다.
  • SUPPORTS : 이미 진행중인 부모 트랜잭션이 있으면 참여하고, 없을 경우 Non-transaction으로 실행된다.
  • MANDATORY : 이미 진행중인 트랜잭션이 반드시 있어야만 실행되며, 혼자서는 독립적으로 트랜잭션을 진행하면 안 되는 경우에 사용한다.
  • REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성, 이미 진행중인 트랜잭션이 있다면 잠깐 보류되고 해당 트랜잭션 경계가 종료 된 후 다시 시작된다.
  • NOT_SUPPORTED : Non-transaction으로 시작하며, 이미 진행중인 트랜잭션이 있으면 끝날 때까지 보류된 후 실행된다.
  • NEVER : Non-transaction으로 시작하며, 실행되며 부모 트랜잭션이 존재한다면 예외가 발생합니다.
  • NESTED : 이미 진행중인 부모 트랜잭션이 있을 경우 중첩 트랜잭션을 생성하여 실행되며, 생성된 중첩 트랜잭션은 부모 트랜잭션이 rollback되면 함께 되지만, 해당 트랜잭션안에서의 Commit/Rollback은 부모 트랜잭션에 영향을 주지 않는다.

propagation 설정을 아래와 같이 할수 있다.

@Transactional(propagation = Propagation.REQUIRED)

끝!

'Springboot2.x 강좌 > DB연결' 카테고리의 다른 글

Flyway 로 DataBase 를 형상관리해보자  (0) 2023.01.05
MyBatis 의 다중 DataSource  (0) 2023.01.05
Spring Data JPA 다중 DataSource  (0) 2023.01.05
JdbcTemplate 다중 DataSource  (0) 2023.01.05
XML 로 Mybatis 설정하기  (0) 2023.01.05
728x90

application.properties 설정

spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

application.yml 설정

spring:
    datasource:
        primary:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/test1
            password: root
            username: root
        secondary:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/test2
            password: root
            username: root

단일 datasource 설정과 다르게 다중 datasource 는 spring.datasource 뒤에 접두사 primary 와 secondary 로 구분하고 datasource 를 초기화 할때 사용하게 된다. Springboot 2.x 은 spring.datasource.secondary.jdbc-url 를 사용하고 Springboot 1.x 은 spring.datasource.secondary.url 를 사용하한다. 실행시 java.lang.IllegalArgumentException:jdbcUrl is required with driverClassName 발생하면 해당 버전별 설정이 제대로 되었는지 확인해보면 된다.

 

Config 클래스 작성해보겠다. 기존 JdbcTemplate & Jpa 설정과 동일하다.

@Configuration
public class DataSourceConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

}

Primary 설정

@Configuration
@MapperScan(
        basePackages = "com.blake.demo.p",
        sqlSessionFactoryRef = "sqlSessionFactoryPrimary",
        sqlSessionTemplateRef = "sqlSessionTemplatePrimary")
public class PrimaryConfig {

    private DataSource primaryDataSource;

    public PrimaryConfig(@Qualifier("primaryDataSource") DataSource primaryDataSource) {
        this.primaryDataSource = primaryDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryPrimary() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(primaryDataSource);
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplatePrimary() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactoryPrimary());
    }

}

Secondary 설정

@Configuration
@MapperScan(
        basePackages = "com.blake.demo.s",
        sqlSessionFactoryRef = "sqlSessionFactorySecondary",
        sqlSessionTemplateRef = "sqlSessionTemplateSecondary")
public class SecondaryConfig {

    private DataSource secondaryDataSource;

    public SecondaryConfig(@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        this.secondaryDataSource = secondaryDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactorySecondary() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(secondaryDataSource);
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplateSecondary() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactorySecondary());
    }

}

User & Repository 는 왜 없냐고 물어보는 분들을 위하여 아래와 같은 소스도 보여준다.

위치는 com.blake.demo.p 패키지 아래에 넣는다.

@Data
@NoArgsConstructor
public class UserPrimary {

    private Long id;

    private String name;
    private Integer age;

    public UserPrimary(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

public interface UserMapperPrimary {

    @Select("SELECT * FROM USER WHERE NAME = #{name}")
    UserPrimary findByName(@Param("name") String name);

    @Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})")
    int insert(@Param("name") String name, @Param("age") Integer age);

    @Delete("DELETE FROM USER")
    int deleteAll();

}

위치는 com.blake.demo.s 패키지 아래에 넣는다.

@Data
@NoArgsConstructor
public class UserSecondary {

    private Long id;

    private String name;
    private Integer age;

    public UserSecondary(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

public interface UserMapperSecondary {

    @Select("SELECT * FROM USER WHERE NAME = #{name}")
    UserSecondary findByName(@Param("name") String name);

    @Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})")
    int insert(@Param("name") String name, @Param("age") Integer age);

    @Delete("DELETE FROM USER")
    int deleteAll();
}

테스트 코드 작성

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class DemoApplicationTests {

    @Autowired
    private UserMapperPrimary userMapperPrimary;
    @Autowired
    private UserMapperSecondary userMapperSecondary;

    @Before
    public void setUp() {
        userMapperPrimary.deleteAll();
        userMapperSecondary.deleteAll();
    }

    @Test
    public void test() throws Exception {

        userMapperPrimary.insert("AAA", 20);

        UserPrimary userPrimary = userMapperPrimary.findByName("AAA");
        Assert.assertEquals(20, userPrimary.getAge().intValue());

        UserSecondary userSecondary = userMapperSecondary.findByName("AAA");
        Assert.assertNull(userSecondary);

        userMapperSecondary.insert("BBB", 20);

        userPrimary = userMapperPrimary.findByName("BBB");
        Assert.assertNull(userPrimary);

        userSecondary = userMapperSecondary.findByName("BBB");
        Assert.assertEquals(20, userSecondary.getAge().intValue());
    }

}

끝!

728x90

application.properties 설정

spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
# SQL Log 출력 여부
spring.jpa.show-sql=true
# Hibernate 의 DDL 수행 전략
spring.jpa.hibernate.ddl-auto=create-drop

application.yml 설정

spring:
    datasource:
        primary:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/test1
            password: root
            username: root
        secondary:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://localhost:3306/test2
            password: root
            username: root
    jpa:
        hibernate:
            ddl-auto: create-drop
        show-sql: true
단일 datasource 설정과 다르게 다중 datasource 는 spring.datasource 뒤에 접두사 primary 와 secondary 로 구분하고 datasource 를 초기화 할때 사용하게 된다.
Springboot 2.x 은 spring.datasource.secondary.jdbc-url 를 사용하고 Springboot 1.x 은 spring.datasource.secondary.url 를 사용하한다. 실행시 java.lang.IllegalArgumentException:jdbcUrl is required with driverClassName 발생하면 해당 버전별 설정이 제대로 되었는지 확인해보면 된다.
 
JPA 단일 DataSource 시 설정을 먼저 보자
@Configuration
public class DataSourceConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

}

일반 JdbcTemplate 설정과 동일하다. 하지만 다중 DataSource 는 두가지로 나누어서 설정한다.

Primary 설정

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= { "com.blake.demo.p" })
public class PrimaryConfig {

    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

    @Autowired
    private JpaProperties jpaProperties;
    @Autowired
    private HibernateProperties hibernateProperties;

    private Map<String, Object> getVendorProperties() {
        return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
    }

    @Primary
    @Bean(name = "entityManagerPrimary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(primaryDataSource)
                .packages("com.blake.demo.p")
                .persistenceUnit("primaryPersistenceUnit")
                .properties(getVendorProperties())
                .build();
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")
    public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }

}

Secondary 설정

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactorySecondary",
        transactionManagerRef="transactionManagerSecondary",
        basePackages= { "com.blake.demo.s" })
public class SecondaryConfig {

    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Autowired
    private JpaProperties jpaProperties;
    @Autowired
    private HibernateProperties hibernateProperties;

    private Map<String, Object> getVendorProperties() {
        return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
    }

    @Bean(name = "entityManagerSecondary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactorySecondary(builder).getObject().createEntityManager();
    }

    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(secondaryDataSource)
                .packages("com.blake.demo.s")
                .persistenceUnit("secondaryPersistenceUnit")
                .properties(getVendorProperties())
                .build();
    }

    @Bean(name = "transactionManagerSecondary")
    PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
    }

}

테스트 코드 작성

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaDataSourceDemoApplicationTests {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MessageRepository messageRepository;

    @Test
    public void test() throws Exception {
        userRepository.save(new User("aaa", 10));
        userRepository.save(new User("bbb", 20));
        userRepository.save(new User("ccc", 30));
        userRepository.save(new User("ddd", 40));
        userRepository.save(new User("eee", 50));

        Assert.assertEquals(5, userRepository.findAll().size());

        messageRepository.save(new Message("o1", "aaaaaaaaaa"));
        messageRepository.save(new Message("o2", "bbbbbbbbbb"));
        messageRepository.save(new Message("o3", "cccccccccc"));

        Assert.assertEquals(3, messageRepository.findAll().size());
    }

}

끝!

'Springboot2.x 강좌 > DB연결' 카테고리의 다른 글

트랜잭션 기본설정 알아보기  (0) 2023.01.05
MyBatis 의 다중 DataSource  (0) 2023.01.05
JdbcTemplate 다중 DataSource  (0) 2023.01.05
XML 로 Mybatis 설정하기  (0) 2023.01.05
Mybatis 로 Mysql 연결하기  (0) 2023.01.05

+ Recent posts