주 해 를 기반 으로 한 springboot+my batis 의 다 중 데이터 소스 구성 요소 구현 코드

일반적인 업무 개발 에서 우 리 는 여러 개의 데이터 소스 를 사용 합 니 다.예 를 들 어 일부 데 이 터 는 my sql 인 스 턴 스 에 존재 합 니 다.일부 데 이 터 는 Oacle 데이터베이스 에 있 습 니 다.이때 프로젝트 는 springboot 와 my batis 를 바탕 으로 두 개의 데이터 소스 만 설정 하면 됩 니 다.
dataSource-sql Session Factory-SqlSession Template 설정 만 하면 됩 니 다.
다음 코드 는 먼저 메 인 데이터 원본 을 설정 합 니 다.@Primary 주석 표 지 를 통 해 기본 데이터 원본 으로 설정 하고 설정 파일 에 있 는 spring.datasource 를 데이터 원본 으로 설정 하여 SqlSession Factory Bean 을 생 성 합 니 다.마지막 으로 SqlSession Template 를 설정 합 니 다.

@Configuration
@MapperScan(basePackages = "com.xxx.mysql.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {

    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druid() {
        return new DruidDataSource();
    }

    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return bean.getObject();
    }

    @Bean("primarySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }
}
그 다음 에 같은 절차 에 따라 Oacle 기반 데이터 원본 을 설정 하고 주 해 를 통 해 basePackages 스 캔 에 대응 하 는 패 키 지 를 설정 하여 특정한 패 키 지 를 사용 하 는 mapper 인 터 페 이 스 를 실현 합 니 다.

@Configuration
@MapperScan(basePackages = "com.nbclass.oracle.mapper", sqlSessionFactoryRef = "oracleSqlSessionFactory")
public class OracleDataSourceConfig {

    @Bean(name = "oracleDataSource")
    @ConfigurationProperties(prefix = "spring.secondary")
    public DataSource oracleDruid(){
        return new DruidDataSource();
    }

    @Bean(name = "oracleSqlSessionFactory")
    public SqlSessionFactory oracleSqlSessionFactory(@Qualifier("oracleDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:oracle/mapper/*.xml"));
        return bean.getObject();
    }

    @Bean("oracleSqlSessionTemplate")
    public SqlSessionTemplate oracleSqlSessionTemplate(@Qualifier("oracleSqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }
}
이렇게 하면 한 프로젝트 에서 여러 개의 데이터 소스 를 사용 하 는 기능 을 실현 할 수 있 습 니 다.이런 실현 방식 에 대해 서도 충분히 간단 합 니 다.그러나 만약 에 우리 의 데이터 베이스 인 스 턴 스 가 많 고 모든 인 스 턴 스 가 설정 을 위주 로 한다 면 여기 서 유지 하면 가방 이름 이 너무 많 고 유연성 이 부족 할 수 있 습 니 다.
현재 업무 에 대한 침입 이 충분 하고 mapper 방법 입도 에서 지정 한 데이터 원본 을 지원 할 수 있 는 방안 을 고려 하고 있 습 니 다.자 연 스 럽 게 주 해 를 통 해 이 루어 질 수 있다 는 생각 이 들 었 습 니 다.먼저 주 해 를 사용자 정의@DBKey:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DBKey {

    String DEFAULT = "default"; //        

    String value() default DEFAULT;
}
사고방식 은 위 에서 springboot 원생 설정 을 바탕 으로 하 는 것 과 유사 합 니 다.먼저 기본 데이터베이스 노드 를 정의 합 니 다.mapper 인터페이스 방법/클래스 가 아무런 주석 이 지정 되 지 않 았 을 때 이 노드 를 기본 으로 갑 니 다.주석 지원 은 value 파라미터 가 선택 한 데이터 원본 노드 이름 을 표시 합 니 다.주해 의 실현 논리 에 대해 서 는 반사 로 mapper 인터페이스 방법/클래스 의 주해 값 을 얻 은 다음 에 특정한 데이터 원본 을 지정 할 수 있 습 니 다.
그럼 이 조작 을 언제 실행 해서 가 져 올 까요?spring AOP 를 사용 하여 mapper 층 에 짜 는 것 을 고려 할 수 있 습 니 다.접점 에서 구체 적 인 mapper 방법 을 실행 하기 전에 해당 하 는 데이터 원본 설정 을 threa Local 에 넣 으 면 이 논리 가 있 으 면 즉시 실행 합 니 다.
우선,db 설정 의 상하 문 대상 을 정의 합 니 다.모든 데이터 원본 key 인 스 턴 스 를 유지 하고 현재 스 레 드 에서 사용 하 는 데이터 원본 key:

public class DBContextHolder {

    private static final ThreadLocal<String> DB_KEY_CONTEXT = new ThreadLocal<>();

    // app           ,       
    private static Set<String> allDBKeys = new HashSet<>();

    public static String getDBKey() {
        return DB_KEY_CONTEXT.get();
    }

    public static void setDBKey(String dbKey) {
        //key      
        if (containKey(dbKey)) {
            DB_KEY_CONTEXT.set(dbKey);
        } else {
            throw new KeyNotFoundException("datasource[" + dbKey + "] not found!");
        }
    }

    public static void addDBKey(String dbKey) {
        allDBKeys.add(dbKey);
    }

    public static boolean containKey(String dbKey) {
        return allDBKeys.contains(dbKey);
    }

    public static void clear() {
        DB_KEY_CONTEXT.remove();
    }
}
그 다음 에 접점 을 정의 합 니 다.접점 before 방법 에서 현재 mapper 인터페이스의@DBKey 주석 에 따라 해당 하 는 데이터 원본 key 를 선택 합 니 다.

@Aspect
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class DSAdvice implements BeforeAdvice {

    @Pointcut("execution(* com.xxx..*.repository.*.*(..))")
    public void daoMethod() {
    }

    @Before("daoMethod()")
    public void beforeDao(JoinPoint point) {
        try {
            innerBefore(point, false);
        } catch (Exception e) {
            logger.error("DefaultDSAdviceException",
                    "Failed to set database key,please resolve it as soon as possible!", e);
        }
    }

    /**
     * @param isClass        
     */
    public void innerBefore(JoinPoint point, boolean isClass) {
        String methodName = point.getSignature().getName();

        Class<?> clazz = getClass(point, isClass);
        //       
        String dbKey = DBKey.DEFAULT;
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
        Method method = null;
        try {
            method = clazz.getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("can't find " + methodName + " in " + clazz.toString());
        }
        //       ,       datasource
        if (method.isAnnotationPresent(DBKey.class)) {
            DBKey key = method.getAnnotation(DBKey.class);
            dbKey = key.value();
        } else {
            //        ,         
            clazz = method.getDeclaringClass();
            if (clazz.isAnnotationPresent(DBKey.class)) {
                DBKey key = clazz.getAnnotation(DBKey.class);
                dbKey = key.value();
            }
        }
        DBContextHolder.setDBKey(dbKey);
    }


    private Class<?> getClass(JoinPoint point, boolean isClass) {
        Object target = point.getTarget();
        String methodName = point.getSignature().getName();

        Class<?> clazz = target.getClass();
        if (!isClass) {
            Class<?>[] clazzList = target.getClass().getInterfaces();

            if (clazzList == null || clazzList.length == 0) {
                throw new MutiDBException("   mapper class,methodName =" + methodName);
            }
            clazz = clazzList[0];
        }

        return clazz;
    }
}
mapper 를 실행 하기 전에 이 mapper 인터페이스 에서 최종 적 으로 사용 하 는 데이터 원본 이 thread Local 에 들 어 갔 으 므 로 새로운 경로 데이터 원본 인터페이스 논 리 를 다시 쓰 면 됩 니 다.

public class RoutingDatasource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String dbKey = DBContextHolder.getDBKey();
        return dbKey;
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        for (Object key : targetDataSources.keySet()) {
            DBContextHolder.addDBKey(String.valueOf(key));
        }
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
}
또한,저 희 는 서비스 가 시작 되 고 my batis 를 설정 할 때 모든 db 설정 을 불 러 옵 니 다:

@Bean
    @ConditionalOnMissingBean(DataSource.class)
    @Autowired
    public DataSource dataSource(MybatisProperties mybatisProperties) {
        Map<Object, Object> dsMap = new HashMap<>(mybatisProperties.getNodes().size());
        for (String nodeName : mybatisProperties.getNodes().keySet()) {
            dsMap.put(nodeName, buildDataSource(nodeName, mybatisProperties));
            DBContextHolder.addDBKey(nodeName);
        }
        RoutingDatasource dataSource = new RoutingDatasource();
        dataSource.setTargetDataSources(dsMap);
        if (null == dsMap.get(DBKey.DEFAULT)) {
            throw new RuntimeException(
                    String.format("Default DataSource [%s] not exists", DBKey.DEFAULT));
        }
        dataSource.setDefaultTargetDataSource(dsMap.get(DBKey.DEFAULT));
        return dataSource;
    }



@ConfigurationProperties(prefix = "mybatis")
@Data
public class MybatisProperties {

    private Map<String, String> params;

    private Map<String, Object> nodes;

    /**
     * mapper    :  location ,  
     */
    private String mapperLocations = "classpath*:com/iqiyi/xiu/**/mapper/*.xml";

    /**
     * Mapper    base package
     */
    private String basePackage = "com.iqiyi.xiu.**.repository";

    /**
     * mybatis      
     */
    private String configLocation = "classpath:mybatis-config.xml";
}
그럼 thread Local 의 key 는 언제 소각 합 니까?사실은 my batis 기반 차단 기 를 사용자 정의 할 수 있 습 니 다.차단기 에서 DBContextHolder.clear()방법 으로 이 key 를 소각 할 수 있 습 니 다.구체 적 인 코드 는 붙 이지 않 겠 습 니 다.이렇게 되면 우 리 는 주 해 를 바탕 으로 다 중 데이터 소스 전환 을 지원 하 는 미들웨어 를 완성 했다.
그럼 최적화 할 점 이 있 나 요?사실은 mapper 인터페이스/소재 클래스 의 주 해 를 가 져 올 때 반 사 를 사용 하여 얻 은 것 을 발견 할 수 있 습 니 다.그러면 우 리 는 일반 반사 호출 이 비교적 성능 을 소모 한 다 는 것 을 알 기 때문에 여기에 로 컬 캐 시 를 추가 하여 성능 을 최적화 하 는 것 을 고려 할 수 있 습 니 다.

private final static Map<String, String> METHOD_CACHE = new ConcurrentHashMap<>();
//....
public void innerBefore(JoinPoint point, boolean isClass) {
        String methodName = point.getSignature().getName();

        Class<?> clazz = getClass(point, isClass);
        //key   +   
        String keyString = clazz.toString() + methodName;
        //       
        String dbKey = DBKey.DEFAULT;
        //          mapper         key,     
        if (METHOD_CACHE.containsKey(keyString)) {
            dbKey = METHOD_CACHE.get(keyString);
        } else {
            Class<?>[] parameterTypes =
                    ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
            Method method = null;

            try {
                method = clazz.getMethod(methodName, parameterTypes);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("can't find " + methodName + " in " + clazz.toString());
            }
             //       ,       datasource
            if (method.isAnnotationPresent(DBKey.class)) {
                DBKey key = method.getAnnotation(DBKey.class);
                dbKey = key.value();
            } else {
                clazz = method.getDeclaringClass();
                //         
                if (clazz.isAnnotationPresent(DBKey.class)) {
                    DBKey key = clazz.getAnnotation(DBKey.class);
                    dbKey = key.value();
                }
            }
           //      
            METHOD_CACHE.put(keyString, dbKey);
        }
        DBContextHolder.setDBKey(dbKey);
    }
이렇게 되면 이 mapper 인 터 페 이 스 를 처음 호출 할 때 만 반사 호출 논리 로 대응 하 는 데이터 원본 을 가 져 올 수 있 고 그 다음 에는 로 컬 캐 시 를 사용 하여 성능 을 향상 시 킬 수 있 습 니 다.
주 해 를 기반 으로 한 spring boot+my batis 의 다 중 데이터 소스 구성 요소 구현 코드 에 관 한 글 은 여기까지 입 니 다.더 많은 spring boot my batis 다 중 데이터 소스 구성 요소 내용 은 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 도 많은 지원 바 랍 니 다!

좋은 웹페이지 즐겨찾기