'Entity'에 해당하는 글 2건



한 개의 프로젝트에서 하나의 Database에만 접속하는 경우가 대부분이지만, 2개 이상의 데이터 베이스에 접속하는 경우도 발생하게 된다.

이럴 경우 어떻게 셋팅을 해야 하는지 정리한다.

 

application.yml 설정파일

spring:
    profiles:
        active: local
    application:
        name: usercms

application-local.yml

server:
    port: 9090
spring:
    profiles: local
    domain: localhost
    datasource-a-write:
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://a-db-url:3306/a?autoReconnect=true&useSSL=false
        username: id
        password: password
    datasource-a-read:
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://a-db-url:3306/a?autoReconnect=true&useSSL=false
        username: id
        password: password
    datasource-b-write:
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://b-db-url:3306/b?autoReconnect=true&useSSL=false
        username: id
        password: password
    datasource-b-read:
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://b-db-url:3306/b?autoReconnect=true&useSSL=false
        username: id
        password: password

 

Database의 read, write 구분을 위한 설정

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Slf4j
public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceType = TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "read" : "write";
        return dataSourceType;
    }
}

 

a 서버에 접속하기 위한 DataSource

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Configuration
public class ADataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource-a-read")
    public DataSource aReadDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource-a-write")
    public DataSource aWriteDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public DataSource aRoutingDataSource(@Qualifier("aWriteDataSource") DataSource writeDataSource, @Qualifier("aReadDataSource") DataSource readDataSource) {
        ReplicationRoutingDataSource aRoutingDataSource = new ReplicationRoutingDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<Object, Object>();
        dataSourceMap.put("write", writeDataSource);
        dataSourceMap.put("read", readDataSource);
        aRoutingDataSource.setTargetDataSources(dataSourceMap);
        aRoutingDataSource.setDefaultTargetDataSource(aReadDataSource());

        return aRoutingDataSource;
    }

    @Primary
    @Bean
    public DataSource aDataSource(@Qualifier("aRoutingDataSource") DataSource routingDataSource) {
        log.debug("#### DATA SOURCE");
        return new LazyConnectionDataSourceProxy(routingDataSource);
    }
}

 

a entity에 a DataSource를 맵핑하기 위한 소스. a를 메인으로 사용하기 위해 @Primary 어노테이션 사용

import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration
//@ComponentScan
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {
                "a DataSource를 이용하는 도메인의 entity가 위치한 패키지"
        },
        entityManagerFactoryRef = "aEntityManagerFactory",
        transactionManagerRef = "aTransactionManager"
)
public class ADataManagerConfig {
    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean aEntityManagerFactory(@Qualifier("aDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
        emfb.setDataSource(dataSource);
        emfb.setPersistenceProvider(new HibernatePersistenceProvider());
        emfb.setPersistenceUnitName("aEntityManager");
        emfb.setPackagesToScan(
                "a DataSource를 이용하는 도메인의 entity가 위치한 패키지"
        );
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setShowSql(true);
        jpaVendorAdapter.setGenerateDdl(false);
//        //properties.setProperty(“hibernate.hbm2ddl.auto”, “none”);
//        Properties properties = new Properties();
//        properties.setProperty("show_sql", "true");
//        emfb.setJpaProperties(properties);
        emfb.setJpaVendorAdapter(jpaVendorAdapter);

        return emfb;
    }

    @Primary
    @Bean
    public PlatformTransactionManager aTransactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor aExceptionTranslationPostProcessor() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}

 

b 서버에 접속하기 위한 DataSource

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Configuration
public class BDataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource-b-read")
    public DataSource bReadDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource-b-write")
    public DataSource bWriteDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public DataSource bRoutingDataSource(@Qualifier("bWriteDataSource") DataSource writeDataSource, @Qualifier("bReadDataSource") DataSource readDataSource) {
        ReplicationRoutingDataSource bRoutingDataSource = new ReplicationRoutingDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<Object, Object>();
        dataSourceMap.put("write", writeDataSource);
        dataSourceMap.put("read", readDataSource);
        bRoutingDataSource.setTargetDataSources(dataSourceMap);
        bRoutingDataSource.setDefaultTargetDataSource(bReadDataSource());

        return bRoutingDataSource;
    }

    @Primary
    @Bean
    public DataSource bDataSource(@Qualifier("bRoutingDataSource") DataSource routingDataSource) {
        log.debug("#### DATA SOURCE");
        return new LazyConnectionDataSourceProxy(routingDataSource);
    }
}

 

b entity에 b DataSource를 맵핑하기 위한 소스

import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration
//@ComponentScan
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {
                "b DataSource를 이용하는 도메인의 entity가 위치한 패키지"
        },
        entityManagerFactoryRef = "bEntityManagerFactory",
        transactionManagerRef = "bTransactionManager"
)
public class BDataManagerConfig {
    @Bean
    public LocalContainerEntityManagerFactoryBean bEntityManagerFactory(@Qualifier("bDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
        emfb.setDataSource(dataSource);
        emfb.setPersistenceProvider(new HibernatePersistenceProvider());
        emfb.setPersistenceUnitName("bEntityManager");
        emfb.setPackagesToScan(
                "b DataSource를 이용하는 도메인의 entity가 위치한 패키지"
        );
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setShowSql(true);
        jpaVendorAdapter.setGenerateDdl(false);
//        //properties.setProperty(“hibernate.hbm2ddl.auto”, “none”);
//        Properties properties = new Properties();
//        properties.setProperty("show_sql", "true");
//        emfb.setJpaProperties(properties);
        emfb.setJpaVendorAdapter(jpaVendorAdapter);

        return emfb;
    }

    @Bean
    public PlatformTransactionManager bTransactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor bExceptionTranslationPostProcessor() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}

 

이와 같이 하게 되면 2개의 별도 DB에 접속할 수 있다.

다만 QueryDSL을 사용하게 될 경우 기존에는 QuerydslRepositorySupport을 상속받아 사용했는데, 이렇게 되면 2개 중 어떤 것을 사용해야 할지 몰라 에러를 내게 된다.

따라서 다음과 같이 QuerydslRepositorySupport을 상속받아 특정 메소드를 OverRide 한 클래스를 만들어야 한다.

그리고 QueryDSL을 사용하는 곳에서는 새롭게 상속받아 작성한 클래스를 상속받아 사용하면 된다.

 

a QuerydslRepositorySupport

import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Repository
public abstract class AQueryDslRepositorySupport extends QuerydslRepositorySupport {
    public AQueryDslRepositorySupport(Class<?> domainClass) {
        super(domainClass);
    }

    @Override
    @PersistenceContext(unitName = "aEntityManager")
    public void setEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
}

 

b QuerydslRepositorySupport

import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Repository
public abstract class BQueryDslRepositorySupport extends QuerydslRepositorySupport {
    public BQueryDslRepositorySupport(Class<?> domainClass) {
        super(domainClass);
    }

    @Override
    @PersistenceContext(unitName = "bEntityManager")
    public void setEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
}

 

각 entityManger에 맞게 QueryDSL을 사용하게 되는 경우...

import kr.co.within.cms.user.api.infrastructure.config.AQueryDslRepositorySupport;

public class AQueryRepositoryImpl extends AQueryDslRepositorySupport implements AQueryRepositoryCustom {
    public AQueryRepositoryImpl() {
        super(A.class);
    }
}
import kr.co.within.cms.user.api.infrastructure.config.BQueryDslRepositorySupport;

public class BQueryRepositoryImpl extends BQueryDslRepositorySupport implements BQueryRepositoryCustom {
    public BQueryRepositoryImpl() {
        super(B.class);
    }
}

 

기억이 짧아 자꾸 까먹는 나를 위하여 기록한다.

 

참고. 멀티 DataSource에서 트랜잭션을 처리하기 위해서는 아래 포스팅 참조.

https://supawer0728.github.io/2018/03/22/spring-multi-transaction/

 

(Spring)다중 DataSource 처리

서론Spring Application을 만들면서 여러 DataSource와 transaction이 존재하고 하나의 transaction 내에 commit과 rollback이 잘 동작하도록 하려면 어떻게 설정해야 할까? 실제로 구현을 해본 적은 없지만 세 가지 방법이 머릿속에 떠올랐다. @Transactional의 propagation을 이용 spring-

supawer0728.github.io

 

 




WRITTEN BY
체리필터
프로그램 그리고 인생...

트랙백  0 , 댓글  0개가 달렸습니다.
secret


Java로 Entity를 만들고 멤버 변수로 1 ~ 31일을 만들어 둔 다음 넘오는 날짜에 따라 특정 날짜 변수에 값을 담는 작업을 하다 알게 된 내용이다.

역시 새롭게 알게 된 내용이라 정리 차원으로 올린다.

 

MontTimeTable Entity는 아래와 같다.

@Entity
@EntityListeners(value = {AuditingEntityListener.class})
@Data
@Table(name = "month_timetable")
public class MonthTimetable {
    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private Integer policy_instance_id;

    @Column
    private Integer year;

    @Column
    private Integer month;

    @Column
    private Integer d1;
    @Column
    private Integer d2;
    @Column
    private Integer d3;
    @Column
    private Integer d4;
    @Column
    private Integer d5;
    @Column
    private Integer d6;
    @Column
    private Integer d7;
    @Column
    private Integer d8;
    @Column
    private Integer d9;
    @Column
    private Integer d10;
    @Column
    private Integer d11;
    @Column
    private Integer d12;
    @Column
    private Integer d13;
    @Column
    private Integer d14;
    @Column
    private Integer d15;
    @Column
    private Integer d16;
    @Column
    private Integer d17;
    @Column
    private Integer d18;
    @Column
    private Integer d19;
    @Column
    private Integer d20;
    @Column
    private Integer d21;
    @Column
    private Integer d22;
    @Column
    private Integer d23;
    @Column
    private Integer d24;
    @Column
    private Integer d25;
    @Column
    private Integer d26;
    @Column
    private Integer d27;
    @Column
    private Integer d28;
    @Column
    private Integer d29;
    @Column
    private Integer d30;
    @Column
    private Integer d31;

    @CreatedDate
    @Column(updatable = false, name = "created_at")
    private Date createdAt;

    @LastModifiedDate
    @Column(name = "updated_at")
    @Temporal(TemporalType.TIMESTAMP)
    private Date updatedAt;
}

 

그리고 넘어온 날짜 기반으로 각 필요한 날짜 변수에 값을 셋팅을 해야 하는데 아래와 같은 방법으로 Set 하게 된다.

@Service
@Slf4j
public class MonthTimetableService {
    public void createTimeTable(Long id, List<Date> timetableList) {
        Map<String, MonthTimetable> monthTimetableMap = new HashMap<>();

        for (Date date:timetableList) {
            LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            int year  = localDate.getYear();
            int month = localDate.getMonthValue();
            int day   = localDate.getDayOfMonth();

            MonthTimetable monthTimetable = monthTimetableMap.get(year+""+month);
            if(monthTimetable == null) {
                monthTimetable = new MonthTimetable();
                monthTimetable.setYear(year);
                monthTimetable.setMonth(month);

                monthTimetableMap.put(year+""+month, monthTimetable);
            }

            this.setDay(monthTimetable, day);
        }
    }

    private void setDay(MonthTimetable monthTimetable, int day) {
        try {
            String setterMethodName = "d" + day;
            Field field = MonthTimetable.class.getDeclaredField(setterMethodName);
            field.setAccessible(true);
            field.set(monthTimetable, 1);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            throw new PolicyException(ErrorMessage.FAIL_POLICY_ADD);
        }
    }
}

 

 

이렇게 하게 될 경우 동적으로 필요한 변수를 찾아 값을 Set 할 수 있게 된다.

 

참고 : https://stackoverflow.com/questions/11652598/how-to-instantiate-and-call-methods-dynamically-of-a-class-member-in-java




WRITTEN BY
체리필터
프로그램 그리고 인생...

트랙백  0 , 댓글  0개가 달렸습니다.
secret