上节继续学习,稍微复杂的业务系统,一般会将数据库按业务拆开,比如产品系统的数据库放在product db中,订单系统的数据库放在order db中...,然后,如果量大了,可能每个库还要考虑做读、写分离,以进一步提高系统性能,下面就来看看如何处理:

核心思路:配置多个数据源,然后利用RoutingDataSource结合AOP来动态切不同的库。

要解决的问题:

1、配置文件中,多数据源的配置节点如何设计?

 druid:
type: com.alibaba.druid.pool.DruidDataSource
study:
master: #study库的主库
url: jdbc:mysql://localhost:/study?useSSL=false&characterEncoding=UTF-&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: A1b2c3@def.com
initial-size:
min-idle:
max-active:
test-on-borrow: true
slave: #study库的从库
url: jdbc:mysql://localhost:/study_slave?useSSL=false&characterEncoding=UTF-&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: A1b2c3@def.com
initial-size:
min-idle:
max-active:
test-on-borrow: true
product:
master: #product库的主库
url: jdbc:mysql://localhost:/product?useSSL=false&characterEncoding=UTF-&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: A1b2c3@def.com
initial-size:
min-idle:
max-active:
test-on-borrow: true
slave: #product库的从库
url: jdbc:mysql://localhost:/product_slave?useSSL=false&characterEncoding=UTF-&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: A1b2c3@def.com
initial-size:
min-idle:
max-active:
test-on-borrow: true

上面的配置写法供参数,如果slave节点数要扩展,按这个格式,改造成slave1,slave2... 自行扩展。

2、配置类如何设计?

 package com.cnblogs.yjmyzz.db.config;

 /**
* Created by jimmy on 6/18/17.
*/ import com.cnblogs.yjmyzz.db.datasource.DbContextHolder;
import com.cnblogs.yjmyzz.db.datasource.MasterSlaveRoutingDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; @Configuration
@EnableTransactionManagement
public class DataSourceConfiguration { @Value("${druid.type}")
private Class<? extends DataSource> dataSourceType; @Bean(name = "studyMasterDataSource")
@ConfigurationProperties(prefix = "druid.study.master")
public DataSource studyMasterDataSource() {
return DataSourceBuilder.create().type(dataSourceType).build();
} @Bean(name = "studySlaveDataSource")
@ConfigurationProperties(prefix = "druid.study.slave")
public DataSource studySlaveDataSource1() {
return DataSourceBuilder.create().type(dataSourceType).build();
} @Bean(name = "productMasterDataSource")
@ConfigurationProperties(prefix = "druid.product.master")
public DataSource productMasterDataSource() {
return DataSourceBuilder.create().type(dataSourceType).build();
} @Bean(name = "productSlaveDataSource")
@ConfigurationProperties(prefix = "druid.product.slave")
public DataSource productSlaveDataSource1() {
return DataSourceBuilder.create().type(dataSourceType).build();
} @Bean(name = "dataSource")
@Primary
public AbstractRoutingDataSource dataSource() {
MasterSlaveRoutingDataSource proxy = new MasterSlaveRoutingDataSource();
Map<Object, Object> targetDataResources = new HashMap<>();
targetDataResources.put(DbContextHolder.DbType.PRODUCT_MASTER, productMasterDataSource());
targetDataResources.put(DbContextHolder.DbType.PRODUCT_SLAVE, productSlaveDataSource1());
targetDataResources.put(DbContextHolder.DbType.STUDY_MASTER, studyMasterDataSource());
targetDataResources.put(DbContextHolder.DbType.STUDY_SLAVE, studySlaveDataSource1());
proxy.setDefaultTargetDataSource(productMasterDataSource());
proxy.setTargetDataSources(targetDataResources);
proxy.afterPropertiesSet();
return proxy;
} }

参考这个,一看就明,不说多(注:@Primary一定要在动态数据源上,否则事务回滚无效!)

3、根据什么来切换db?

有很多选择,

a、用约定的方法前缀,比如:get/query/list开头的约定为读从库,其它为主库,但是这样还要考虑不同业务库的切换(即:何时切换到product库,何时切换到order库,可以再用不同的Scanner来处理,略复杂)

b、用自定义注解来处理,比如 @ProductMaster注解,表示切换到product的master库,这样同时把业务库,以及主还是从,一次性解决了,推荐这种。

这里,我定义了4个注解,代表product,study二个库的主及从。

4、aop在哪里拦截,如何拦截?

service层和mapper层都可以拦截,推荐在服务层拦截,否则如果一个业务方法里,即有读又有写,还得考虑如果遇到事务,要考虑的东西更多。

当然,如果拦截特定的注解,就不用过多考虑在哪个层,只认注解就行(当然,注解还是建议打在服务层上)。

dubbo-starter的一个小坑:spring boot中,只有managed bean才能用aop拦截,而dubbo-starter中的@service注解不是spring中的注解(是阿里package下的自定义注解),生成的service provider实例,aop拦截不到,解决办法,再加一个注解让spring认识它,参考:

Aop拦截类的参考代码如下:

 package com.cnblogs.yjmyzz.db.aspect;

 import com.cnblogs.yjmyzz.db.annotation.ProductMaster;
import com.cnblogs.yjmyzz.db.annotation.ProductSlave;
import com.cnblogs.yjmyzz.db.annotation.StudyMaster;
import com.cnblogs.yjmyzz.db.annotation.StudySlave;
import com.cnblogs.yjmyzz.db.datasource.DbContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component; @Aspect
@Component
public class MasterSlaveAspect implements Ordered { public static final Logger logger = LoggerFactory.getLogger(MasterSlaveAspect.class); /**
* 切换到product主库
*
* @param proceedingJoinPoint
* @param productMaster
* @return
* @throws Throwable
*/
@Around("@annotation(productMaster)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ProductMaster productMaster) throws Throwable {
try {
logger.info("set database connection to product-master only");
DbContextHolder.setDbType(DbContextHolder.DbType.PRODUCT_MASTER);
Object result = proceedingJoinPoint.proceed();
return result;
} finally {
DbContextHolder.clearDbType();
logger.info("restore database connection");
}
} /**
* 切换到product从库
*
* @param proceedingJoinPoint
* @param productSlave
* @return
* @throws Throwable
*/
@Around("@annotation(productSlave)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ProductSlave productSlave) throws Throwable {
try {
logger.info("set database connection to product-slave only");
DbContextHolder.setDbType(DbContextHolder.DbType.PRODUCT_SLAVE);
Object result = proceedingJoinPoint.proceed();
return result;
} finally {
DbContextHolder.clearDbType();
logger.info("restore database connection");
}
} /**
* 切换到study主库
*
* @param proceedingJoinPoint
* @param studyMaster
* @return
* @throws Throwable
*/
@Around("@annotation(studyMaster)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, StudyMaster studyMaster) throws Throwable {
try {
logger.info("set database connection to study-master only");
DbContextHolder.setDbType(DbContextHolder.DbType.STUDY_MASTER);
Object result = proceedingJoinPoint.proceed();
return result;
} finally {
DbContextHolder.clearDbType();
logger.info("restore database connection");
}
} /**
* 切换到study从库
*
* @param proceedingJoinPoint
* @param studySlave
* @return
* @throws Throwable
*/
@Around("@annotation(studySlave)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, StudySlave studySlave) throws Throwable {
try {
logger.info("set database connection to study-slave only");
DbContextHolder.setDbType(DbContextHolder.DbType.STUDY_SLAVE);
Object result = proceedingJoinPoint.proceed();
return result;
} finally {
DbContextHolder.clearDbType();
logger.info("restore database connection");
}
} @Override
public int getOrder() {
return 0;
}
}

5、其它事项

启用类上,一定要排除spring-boot自带的datasource配置,即:

 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableAspectJAutoProxy
@ComponentScan("com.cnblogs.yjmyzz")
@MapperScan(basePackages = "com.cnblogs.yjmyzz.dao.mapper")
public class ServiceProvider {
public static void main(String[] args) {
SpringApplication.run(ServiceProvider.class, args);
}
}

第1行:@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

6、日志中如何输出格式化且带参数值的sql?

一般的sql输出是这样的:

我们可以把它变成下面这样:

是不是更友好!

方法:加一个mybtais的拦截器即可

package com.cnblogs.yjmyzz.db.interceptor;

import com.cnblogs.yjmyzz.util.PrettySQLFormatter;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties; /**
* Created by 菩提树下的杨过(http://yjmyzz.cnblogs.com/) on 28/07/2017.
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
public class MybatisInterceptor implements Interceptor { private static Logger logger = LoggerFactory.getLogger(MybatisInterceptor.class); private Properties properties; private final static SimpleDateFormat sdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
String sqlId = mappedStatement.getId();
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
Object returnValue;
long start = System.currentTimeMillis();
returnValue = invocation.proceed();
long end = System.currentTimeMillis();
long time = (end - start);
if (time > 1) {
String sql = getSql(configuration, boundSql, sqlId, time);
logger.debug("mapper method ==> " + sql.split("\\^")[0] + "\n," + PrettySQLFormatter.getPrettySql(sql.split("\\^")[1]) + "\n\n," + "sql execute time ==> " + time + " ms\n\n");
}
return returnValue;
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
this.properties = properties;
} public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
String sql = showSql(configuration, boundSql);
StringBuilder str = new StringBuilder(100);
str.append(sqlId);
str.append("^");
str.append(sql);
str.append("^");
str.append(time);
str.append("ms");
return str.toString();
} private static String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
value = "'" + sdt.format(obj) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
} }
return value;
} public static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject)); } else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}
}

这里面还用了hibernate的一个小工具,用于格式化sql

package com.cnblogs.yjmyzz.util;

import org.hibernate.engine.jdbc.internal.FormatStyle;

public class PrettySQLFormatter {

    public static void print(String sql) {
System.out.println(FormatStyle.BASIC.getFormatter().format(sql));
} public static void print(String remark, String sql) {
System.out.println(remark
+ FormatStyle.BASIC.getFormatter().format(sql));
} public static String getPrettySql(String sql) {
return FormatStyle.BASIC.getFormatter().format(sql);
} public static String getPrettySql(String remark, String sql) {
return remark + FormatStyle.BASIC.getFormatter().format(sql);
} public static void main(String[] args) {
System.out.println(getPrettySql("select * from MyUser as A join MyFriend as B on A.id = B.pid where B.name like ? "));
}
}

接下来,把这个拦截器配置在mybatis-config.xml里

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings> <plugins>
<plugin interceptor="com.cnblogs.yjmyzz.db.interceptor.MybatisInterceptor">
</plugin>
</plugins> </configuration>

最后在application.yml里指定mybatis-config.xml所在的路径:

示例源码见:https://github.com/yjmyzz/spring-boot-dubbo-demo (dubbox2.8.5-multi-ds分支)

spring-boot 速成(9) druid+mybatis 多数据源及读写分离的处理的更多相关文章

  1. MyBatis多数据源配置(读写分离)

    原文:http://blog.csdn.net/isea533/article/details/46815385 MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用 ...

  2. spring boot(七):springboot+mybatis多数据源最简解决方案

    说起多数据源,一般都来解决那些问题呢,主从模式或者业务比较复杂需要连接不同的分库来支持业务.我们项目是后者的模式,网上找了很多,大都是根据jpa来做多数据源解决方案,要不就是老的spring多数据源解 ...

  3. 07.深入浅出 Spring Boot - 数据访问之Mybatis(附代码下载)

    MyBatis 在Spring Boot应用非常广,非常强大的一个半自动的ORM框架. 代码下载:https://github.com/Jackson0714/study-spring-boot.gi ...

  4. SpringBoot2 + Druid + Mybatis 多数据源动态配置

    在大数据高并发的应用场景下,为了更快的响应用户请求,读写分离是比较常见的应对方案.读写分离会使用多数据源的使用.下面记录如何搭建SpringBoot2 + Druid + Mybatis  多数据源配 ...

  5. spring boot 2.0.0 + mybatis 报:Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required

    spring boot 2.0.0 + mybatis 报:Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required 无法启动 ...

  6. Spring Boot 框架下使用MyBatis访问数据库之基于XML配置的方式

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 XML ...

  7. Spring Boot 整合 JPA 使用多个数据源

    介绍 JPA(Java Persistence API)Java 持久化 API,是 Java 持久化的标准规范,Hibernate 是持久化规范的技术实现,而 Spring Data JPA 是在 ...

  8. spring boot打印sql语句-mybatis

    spring boot打印sql语句-mybatis 概述 当自己编写的程序出现了BUG等等,找了很久 调试运行了几遍到mapper层也进去调试进了源码,非常麻烦 我就想打印出sql语句,好进行解决B ...

  9. 6、Spring Boot 2.x 集成 MyBatis

    1.6 Spring Boot 2.x 集成 MyBatis 简介 详细介绍如何在Spring Boot中整合MyBatis,并通过注解方式实现映射. 完整源码: 1.6.1 创建 spring-bo ...

随机推荐

  1. 在ASP.Net中两种利用CSS实现多界面的方法

    通过使页面动态加载不同CSS实现多界面(类型于csdn的blog): 方法一: <%@page language="C#"%><%@import namespac ...

  2. UVALive 7456 Least Crucial Node

    题目链接 题意: 给定一个无向图,一个汇集点,问哪一个点是最关键的,如果有多个关键点,输出序号最小的那个. 因为数据量比较小,所以暴力搜索就行,每去掉一个点,寻找和汇集点相连的还剩几个点,以此确定哪个 ...

  3. 最短路 spfa+STL

    与迪杰斯特拉相同的是spfa也是用来求单源点的最短路径问题,但是,当问题中的边是有向负边的时候,迪杰斯特拉就无能为力了, 而且给我的感觉是spfa如何结合STL来用的话代码比迪杰斯特拉的还要短一点,只 ...

  4. mysql集成部署

    经常听说mysql数据库是集成在系统中,也一直不太明白集成的概念.今天才明白集成的概念就是将mysql所有的文件放到一个文件夹下放到系统中,也就是将mysql采用目录迁移部署的方式进行安装.在上一篇研 ...

  5. mybatis查询缓存——(十三)

    1.     mybatis缓存介绍 如下图,是mybatis一级缓存和二级缓存的区别图解: mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存.

  6. OE中的bitbake使用

    OpenEmbedded是一些脚本(shell和python脚本)和数据构成的自动构建系统.     脚本实现构建过程,包括下载(fetch).解包(unpack).打补丁(patch).config ...

  7. linux通过sendmail发送邮件

    安装sendmail: [root@li676-235 ~]# yum install sendmail 安装好后执行. [root@li676-235 ~]# /etc/init.d/sendmai ...

  8. Android 6.0 API

    Android 6.0 (M) 为用户和应用开发者提供了新功能.本文旨在介绍其中最值得关注的 API. 着手开发 要着手开发 Android 6.0 应用,您必须先获得 Android SDK,然后使 ...

  9. Vue项目中如何引用外部js

    第一种方法:(感觉这个有问题) 1.把需要的js放到static文件夹下 2.在Index.html页面引入 3.在webpack.base.conf.js添加下面代码 externals: { 'W ...

  10. 测试开发之Django——No4.Django中前端框架的配置与添加

    我们在开发一个web项目的时候,虽然我们不是专业开发,但是我们也想要做出来一个美美的前端页面. 这种时候,百度上铺天盖地的前端框架就是我们的最好选择了. 当然,在网上直接下载的框架,我们是不能直接用的 ...