日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1 概述

在實際業務開發中通常會在單個應用中通過 分庫分表 或者 讀寫分離的方式來提供應用的讀寫性能。

在具體的開發中有很多方式:

  1. 通過不同的 mApper,映射到不同的 mybatis 源的方式
  2. 通過繼承 Spring 的 AbstractRoutingDataSource 抽象類并重寫 determineCurrentLookupKey 方法來管理多個數據源的方式

本文將詳細介紹在 SpringBoot 應用中如何通過 自定義注解 和 aop 的方式實現多數據源的訪問,采用了第二種的方式。

2 關鍵點

  • 通過 @Aspect 注解來解析自定義注解
  • 通過繼承 Spring 的 AbstractRoutingDataSource 抽象類,重寫 determineCurrentLookupKey 方法來管理多個數據源
  • 通過 自定義注解 中的參數用來訪問不同的數據源
  • 由于 mybatis 的事務 和 sqlSession 的打開和關閉 也是通過 aop 來實現的,因此這里必須通過 @Order 注解來提高自定義注解的優先級

3 使用場景

  1. 分庫分表,根據業務來劃分不同的庫,比如與用戶相關的表在 db_user 庫,與訂單相關的表在 db_order 庫。
  2. 讀寫分離,master 和 slave 模式,master 庫只用來寫入數據,slave 庫只用來讀取數據。

這里根據場景 1 來實現具體的例子。

4 具體的例子

4.1 開發環境

  • SpringBoot: 2.2.2.RELEASE
  • mybatis-spring-boot-starter: 2.1.1
  • HikariCP: 3.4.1

4.2 數據庫和表

  • db_user 庫 的 t_user 表如下
CREATE TABLE `t_user` (
  `id` int(18) NOT NULL AUTO_INCREMENT COMMENT '流水號',
  `name` varchar(25) COLLATE utf8_bin DEFAULT NULL COMMENT '名稱',
  `age` int(10) DEFAULT NULL COMMENT '年齡',
  `sex` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '性別',
  `remarks` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '備注',
  `create_date` datetime DEFAULT NULL COMMENT '創建時間',
  `create_user` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '創建人',
  `update_date` datetime DEFAULT NULL COMMENT '更新時間',
  `update_user` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '更新人',
  `del_flag` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '刪除標記(0:正常;1:刪除)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用戶信息表'
  • db_order 庫 的 t_order 表如下
CREATE TABLE `t_order` (
  `id` int(18) NOT NULL AUTO_INCREMENT COMMENT '流水號',
  `user_id` int(18) DEFAULT NULL COMMENT '用戶id',
  `order_date` datetime DEFAULT NULL COMMENT '訂單時間',
  `order_amount` decimal(10,0) DEFAULT NULL COMMENT '訂單金額',
  `remarks` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '備注',
  `create_date` datetime DEFAULT NULL COMMENT '創建時間',
  `create_user` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '創建人',
  `update_date` datetime DEFAULT NULL COMMENT '更新時間',
  `update_user` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人',
  `del_flag` char(1) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '刪除標記(0:正常;1:刪除)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='訂單表'

4.3 代碼結構如下

在 SpringBoot 中實現多數據源訪問的最佳實踐

 

4.4 自定義注解和 AOP 實現

  1. MultiDataSource
import com.ckJAVA.entity.DbEnum;
import java.lang.annotation.*;

/**
 * 數據庫切換的注解,只作用在方法上
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiDataSource {

    // 用于指定數據庫名稱的
    DbEnum value() default DbEnum.user;
}
  1. 通過 aop 來讀取注解的配置,并在方法前后進行數據庫的切換
import com.ckjava.aop.annotation.MultiDataSource;
import com.ckjava.config.MultiDataSourceHolder;
import com.ckjava.entity.DbEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 通過 aop 來讀取注解的配置,并在方法前后進行數據庫的切換
 */
@Aspect
@Component
@Order(1)
public class MultiDataSourceAspect {

    @Pointcut("@annotation(com.ckjava.aop.annotation.MultiDataSource)")
    public void dataSourcePointCut() {
    }

    /**
     * 在方法執行前設置數據庫 key
     *
     * @param point
     */
    @Before("dataSourcePointCut()")
    public void before(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        // 在含有 MultiDataSource 注解的方法執行前,設置線程的數據庫源變量
        MultiDataSource dataSource = method.getAnnotation(MultiDataSource.class);
        if (dataSource == null) {
            MultiDataSourceHolder.setDataSource(DbEnum.user);
        } else {
            MultiDataSourceHolder.setDataSource(dataSource.value());
        }
    }

    /**
     * 在方法執行后移除 數據庫 key
     */
    @After("dataSourcePointCut()")
    public void after() {
        // 移除線程本地數據庫源變量
        MultiDataSourceHolder.clearDataSource();
    }
}

4.5 繼承 AbstractRoutingDataSource 抽象類,重寫 determineCurrentLookupKey 方法

具體如下

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * AbstractRoutingDataSource 在獲取 Connection 前會通過 determineTargetDataSource 來從多個數據源中根據 key 來
 *
 * 獲取一個 DataSource 對象,這里 determineCurrentLookupKey 由子類來實現
 */
public class MultiRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return MultiDataSourceHolder.getDataSource();
    }

}
  • MultiRoutingDataSource 將作為 SqlSessionFactory 和 DataSourceTransactionManager 的數據源

4.6 多數據源配置以及 mybatis 配置

  • MultiDataSourceConfig
import com.ckjava.entity.DbEnum;
import com.ckjava.properties.OrderDataSourceProperties;
import com.ckjava.properties.UserDataSourceProperties;
import com.zaxxer.hikari.HikariDataSource;
import org.Apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

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

@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {"com.ckjava.dao"}, sqlSessionFactoryRef = "sqlSessionFactory") // 掃描 Mapper 接口并容器管理
public class MultiDataSourceConfig {

    // 精確到 master 目錄,以便跟其他數據源隔離
    public static final String MAPPER_LOCATION = "classpath:mapper/data/*Mapper.xml";
    public static final String CONFIG_LOCATION = "classpath:mapper/data/config.xml";

    @Autowired
    private UserDataSourceProperties userSourceProperties;
    @Autowired
    private OrderDataSourceProperties orderDataSourceProperties;

    @Bean("userDataSource")
    public DataSource userDataSource() {
        HikariDataSource dataSource = DataSourceBuilder
                .create()
                .driverClassName(userSourceProperties.getDriverClassName())
                .url(userSourceProperties.getUrl())
                .username(userSourceProperties.getUsername())
                .password(userSourceProperties.getPassword())
                .type(HikariDataSource.class)
                .build();
        dataSource.setMaximumPoolSize(userSourceProperties.getMaxPoolSize());
        dataSource.setAutoCommit(true);
        return dataSource;
    }

    @Bean("orderDataSource")
    public DataSource orderDataSource() {
        HikariDataSource dataSource = DataSourceBuilder
                .create()
                .driverClassName(orderDataSourceProperties.getDriverClassName())
                .url(orderDataSourceProperties.getUrl())
                .username(orderDataSourceProperties.getUsername())
                .password(orderDataSourceProperties.getPassword())
                .type(HikariDataSource.class)
                .build();
        dataSource.setMaximumPoolSize(orderDataSourceProperties.getMaxPoolSize());
        dataSource.setAutoCommit(true);
        return dataSource;
    }

    @Bean
    @Primary
    public DataSource multiRoutingDataSource(@Qualifier("userDataSource") DataSource userDataSource,
                                             @Qualifier("orderDataSource") DataSource orderDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DbEnum.user, userDataSource);
        targetDataSources.put(DbEnum.order, orderDataSource);

        MultiRoutingDataSource myRoutingDataSource = new MultiRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(userDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }

    @Bean(name = "transactionManager")
    @Primary
    public PlatformTransactionManager transactionManager(@Qualifier("userDataSource") DataSource userDataSource,
                                                         @Qualifier("orderDataSource") DataSource orderDataSource) throws Exception {
        return new DataSourceTransactionManager(multiRoutingDataSource(userDataSource, orderDataSource));
    }

    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("userDataSource") DataSource userDataSource,
                                               @Qualifier("orderDataSource") DataSource orderDataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(multiRoutingDataSource(userDataSource, orderDataSource));
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MultiDataSourceConfig.MAPPER_LOCATION));
        sessionFactory.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(MultiDataSourceConfig.CONFIG_LOCATION));
        return sessionFactory.getObject();
    }
}

4.7 通過 ThreadLocal 來存儲當前線程的 數據庫 key 變量

具體如下

import com.ckjava.entity.DbEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 通過 ThreadLocal 來存儲當前線程的 數據庫 key 變量
 */
public class MultiDataSourceHolder {

    private static final Logger logger = LoggerFactory.getLogger(MultiDataSourceHolder.class);

    /**
     * 通過 ThreadLocal 來存儲當前線程的 dataSource key
     */
    private static final ThreadLocal<DbEnum> contextHolder = new ThreadLocal<>();

    public static void setDataSource(DbEnum dataSource) {
        logger.info("Thread {} set datasource {}", Thread.currentThread().getName(), dataSource);
        contextHolder.set(dataSource);
    }

    public static DbEnum getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        logger.info("Thread {} unset datasource {}", Thread.currentThread().getName(), contextHolder.get());
        contextHolder.remove();
    }
}

4.8 在 TUserService 和 TOrderService 上使用

  • TUserService
import com.ckjava.aop.annotation.MultiDataSource;
import com.ckjava.dao.TUserDao;
import com.ckjava.entity.DbEnum;
import com.ckjava.entity.TUserEntity;
import com.ckjava.entity.base.PageParamer;
import com.ckjava.service.base.BaseMybatisCrudService;
import com.ckjava.xutils.http.Page;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class TUserService extends BaseMybatisCrudService<TUserEntity, TUserDao> {
    @Override
    public Class<TUserEntity> getClassType() {
        return TUserEntity.class;
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<TUserEntity> get(long id) {
        return super.get(id);
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<List<TUserEntity>> getAll() {
        return super.getAll();
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<List<TUserEntity>> getByParam(TUserEntity entity) {
        return super.getByParam(entity);
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<Page<TUserEntity>> getPage(PageParamer pageParamer) {
        return super.getPage(pageParamer);
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<Long> save(TUserEntity entity) {
        return super.save(entity);
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<Long> update(TUserEntity entity) {
        return super.update(entity);
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<Long> saveOrUpdate(TUserEntity entity) {
        return super.saveOrUpdate(entity);
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<Integer> delete(Long id) {
        return super.delete(id);
    }

    @Override
    @MultiDataSource(DbEnum.user)
    public Optional<Integer> deletePhysical(Long id) {
        return super.deletePhysical(id);
    }
}
  • TOrderService
import com.ckjava.aop.annotation.MultiDataSource;
import com.ckjava.dao.TOrderDao;
import com.ckjava.entity.DbEnum;
import com.ckjava.entity.TOrderEntity;
import com.ckjava.entity.base.PageParamer;
import com.ckjava.service.base.BaseMybatisCrudService;
import com.ckjava.xutils.http.Page;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class TOrderService extends BaseMybatisCrudService<TOrderEntity, TOrderDao> {

    @Override
    public Class<TOrderEntity> getClassType() {
        return TOrderEntity.class;
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<TOrderEntity> get(long id) {
        return super.get(id);
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<List<TOrderEntity>> getAll() {
        return super.getAll();
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<List<TOrderEntity>> getByParam(TOrderEntity entity) {
        return super.getByParam(entity);
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<Page<TOrderEntity>> getPage(PageParamer pageParamer) {
        return super.getPage(pageParamer);
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<Long> save(TOrderEntity entity) {
        return super.save(entity);
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<Long> update(TOrderEntity entity) {
        return super.update(entity);
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<Long> saveOrUpdate(TOrderEntity entity) {
        return super.saveOrUpdate(entity);
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<Integer> delete(Long id) {
        return super.delete(id);
    }

    @Override
    @MultiDataSource(DbEnum.order)
    public Optional<Integer> deletePhysical(Long id) {
        return super.deletePhysical(id);
    }
}

5 測試

  • TestOrderService 測試如下
import com.ckjava.entity.TOrderEntity;
import com.ckjava.service.TOrderService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestOrderService {

	@Autowired
	private TOrderService tOrderService;

	@Test
	public void test_get() {
		tOrderService.get(1L).ifPresent(entity -> {
			System.out.println(entity.getId());
		});
	}

}
  • 測試 test_get 方法,輸出如下
12:43:06.033 [main] INFO  c.c.config.MultiDataSourceHolder - Thread main set datasource order
12:43:06.042 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
12:43:06.359 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
12:43:06.516 [main] INFO  c.c.config.MultiDataSourceHolder - Thread main unset datasource order
1

從上面可見,在數據源加載前,先修改了 datasource key 為 order 數據庫。

  • TestUserService 測試如下
import com.ckjava.service.TUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestUserService {

	@Autowired
	private TUserService tUserService;

	@Test
	public void test_get() {
		tUserService.get(1L).ifPresent(entity -> {
			System.out.println(entity.getId());
		});
	}

}

  • 執行 test_get 方法,輸出如下
12:45:30.389 [main] INFO  c.c.config.MultiDataSourceHolder - Thread main set datasource user
12:45:30.403 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
12:45:30.906 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
12:45:31.159 [main] INFO  c.c.config.MultiDataSourceHolder - Thread main unset datasource user
1

從輸出結果看,在數據源加載前,先修改了 datasource key 為 user 數據庫。

6 代碼

例子的完整代碼:gitee spring-boot-multidb

分享到:
標簽:訪問 SpringBoot
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定