mybatis+spring 实现多数据源(注解版)

需求

  1. 系统架构是spring framework +各种组件
  2. 项目重构 打算使用mybatis 作为orm框架
  3. 项目依赖多个数据源、多个数据源需要读写分离
  4. 抛弃xml配置,完全注解化

问题

  1. 注解扫mapper过程中,一个mapper只能被一个sqlsessiontemplate装载 ,若想实现读写分离需要对每个库表定义不同的读写mapper,然后通过@MapperScan(basePackages = "mybatisSpring.mapper" sqlSessionTemplateRef = "") 分别指定数据源 。类似图中样式,basePackages分别指定namespace 与testmapper 然后再指定对应的sqlSessionTemplate。


    mybatis+spring 实现多数据源(注解版)_第1张图片
    image.png

    上述方式实现和逻辑比较简单,但是太粗糙,写的代码会很多。

  2. 理想使用方式 mapper层按库表逻辑划分,与业务逻辑完全剥离。

实现方案

  1. 使用AbstractRoutingDataSource进行多数据源管理
  2. 使用切面完成数据源的动态切换
  3. 直接引用sqlSessionTemplate完成读写操作

show me the code

  1. 代码结构


    mybatis+spring 实现多数据源(注解版)_第2张图片
    代码结构
  • annotation 自定义注解
  • conf java配置文件
  • dao 封装的dao层服务
  • mapper mybatis的接口mapper类
  • model DO层
  1. 具体代码
  • AbstractRoutingDataSource 的实现类,管理项目的所有数据源。
public class MomentRoutingDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal dataSourceHolder = new ThreadLocal<>();

    public static void setDataSource(DataSourceType dataSourceType) {
        dataSourceHolder.set(dataSourceType);
    }

    public static DataSourceType getDataSource() {
        return dataSourceHolder.get();
    }

    public static void clear() {
        dataSourceHolder.remove();
    }

    /**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * 

Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */ @Override protected Object determineCurrentLookupKey() { return getDataSource(); } }

  • spring 配置文件
@Configuration
@MapperScan(basePackages = "mybatisSpring.mapper" )
@ComponentScan(basePackages = "mybatisSpring")
// 开启切面
@EnableAspectJAutoProxy
public class SqlSessionTemplateConf {

    /**
     * 初始化dataSource
     *
     * @return
     * @throws Exception
     */
    private HikariDataSource getDataSource(String dbname) throws Exception {

        HikariDataSource dataSource = new HikariDataSource();
        String jdbcUrl = String.format("jdbc:mysql://%s:%s/%s?zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
                , "localhost", 3306, "mpaper_cms2");
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUsername("*******");
        dataSource.setPassword("******");
        dataSource.setMinimumIdle(10);
        dataSource.setMaximumPoolSize(20);
        //等待获取连接时间-默认30s,超过配置阈值抛异常
        dataSource.setConnectionTimeout(500);
        dataSource.setPoolName(dbname);
        return dataSource;
    }


    @Bean
    public DataSource momentRoutingDataSource() throws Exception {
        MomentRoutingDataSource momentRoutingDataSource = new MomentRoutingDataSource();
        Map targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceType.CMS_RO, getDataSource("cms_ro"));
        targetDataSource.put(DataSourceType.CMS_RW, getDataSource("cms_rw"));
        momentRoutingDataSource.setTargetDataSources(targetDataSource);
       return momentRoutingDataSource;
    }

    @Bean("momentSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(DataSource momentRoutingDataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(momentRoutingDataSource);
        return new SqlSessionTemplate(factoryBean.getObject());
    }

}
  • 切面实现& 定义注解
@Aspect
@Component
@Order(0)
public class CustomDataSourceAspect {
    // @annotation(mybatisSpring.annotation.DataSource) &&
    @Pointcut(value = "execution( * mybatisSpring.dao..*.*(..))")
    public void pointCut() {

    }

    @Before(value = "pointCut()")
    public void changeDataSource(JoinPoint joinPoint) throws NoSuchMethodException {
        Class aClass = joinPoint.getTarget().getClass();
        //拦截mapper方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (method != null && method.isAnnotationPresent(DataSource.class)) {
            DataSource dataSource = method.getAnnotation(DataSource.class);
            MomentRoutingDataSource.setDataSource(dataSource.type());
            System.out.println("Service Class 数据源切换至--->" + dataSource.type().getValue());
            return;
        }
        //拦截mapper类
        if (aClass.isAnnotationPresent(DataSource.class)) {
            DataSource dataSource = aClass.getAnnotation(DataSource.class);
            MomentRoutingDataSource.setDataSource(dataSource.type());
            System.out.println("Service Class 数据源切换至--->" + dataSource.type().getValue());
            return;
        }

    }

    /**
     * 方法结束后
     */
    @After(value = "pointCut()")
    public void afterReturning() throws Throwable {
        try {
            MomentRoutingDataSource.clear();
            System.out.println("数据源已移除!");
        } catch ( Exception e ) {
            e.printStackTrace();
            System.out.println("数据源移除报错!");
        }

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

    DataSourceType type() default DataSourceType.CMS_RO;
}

-dao 层代码

@Service
@DataSource(type = DataSourceType.CMS_RO)
public class NewsTvInfoDao {
    @Autowired
    private SqlSessionTemplate momentSqlSessionTemplate;

    public NewsTvInfo getNewsTvInfo(int id) {
        NewsTvInfoMapper mapper = momentSqlSessionTemplate.getMapper(NewsTvInfoMapper.class);
        return mapper.findByVid(id);
    }

    @DataSource(type = DataSourceType.CMS_RW)
    public NewsTvInfo getNewsTvInfoRw(int id) {
        ActivityMapper mapper = momentSqlSessionTemplate.getMapper(ActivityMapper.class);
        return mapper.findByVid(id);
    }
}

  • main 入口
public class MybatieSpringMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SqlSessionTemplateConf.class);
        // 方法前未添加注解,取类的注解注入的数据源
        NewsTvInfoDao dao = context.getBean(NewsTvInfoDao.class);
        NewsTvInfo newsTvInfo = dao.getNewsTvInfo(9102910);
        System.out.println(newsTvInfo.getBigPic());
         // 方法添加注解,取方法的注解注入的数据源
        NewsTvInfo newsTvInfoRw = dao.getNewsTvInfoRw(9102910);
        System.out.println(newsTvInfoRw.getBigPic());

    }
}

说明

  1. 代码未列完整,依赖mybatis ,mybatis-spring 依赖包
  2. 切面逻辑中,会先扫描方法后扫描类。因此方法添加的注解的优先级大于类的优先级,因此可以在dao层类的上方添加默认数据源。
  3. @EnableAspectJAutoProxy需要 添加此注解

你可能感兴趣的:(mybatis+spring 实现多数据源(注解版))