关键字:springcloud、mybatis、多数据源负载均衡、拦截器动态分页
配置文件是通过springcloudconfig远程分布式配置。采用阿里Druid数据源。并支持一主多从的读写分离。分页组件通过拦截器拦截带有page后缀的方法名,动态的设置total总数。
1. 解析配置文件初始化数据源
@Configuration public class DataSourceConfiguration { /** * 数据源类型 */ @Value("${spring.datasource.type}") private Class<? extends DataSource> dataSourceType; /** * 主数据源配置 * * @return */ @Bean(name = "masterDataSource", destroyMethod = "close") @Primary @ConfigurationProperties(prefix = "spring.datasource") public DataSource masterDataSource() { DataSource source = DataSourceBuilder.create().type(dataSourceType).build(); return source; } /** * 从数据源配置 * * @return */ @Bean(name = "slaveDataSource0") @ConfigurationProperties(prefix = "spring.slave0") public DataSource slaveDataSource0() { DataSource source = DataSourceBuilder.create().type(dataSourceType).build(); return source; } /** * 从数据源集合 * * @return */ @Bean(name = "slaveDataSources") public List<DataSource> slaveDataSources() { List<DataSource> slaveDataSources = new ArrayList(); slaveDataSources.add(slaveDataSource0()); return slaveDataSources; } }
2. 定义数据源枚举类型
public enum DataSourceType { master("master", "master"), slave("slave", "slave"); private String type; private String name; DataSourceType(String type, String name) { this.type = type; this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
3. TheadLocal保存数据源类型
public class DataSourceContextHolder { private static final ThreadLocal<String> local = new ThreadLocal<String>(); public static ThreadLocal<String> getLocal() { return local; } public static void slave() { local.set(DataSourceType.slave.getType()); } public static void master() { local.set(DataSourceType.master.getType()); } public static String getJdbcType() { return local.get(); } public static void clearDataSource(){ local.remove(); } }
4. 自定义sqlSessionProxy,并将数据源填充到DataSourceRoute
@Configuration @ConditionalOnClass({EnableTransactionManagement.class}) @Import({DataSourceConfiguration.class}) public class DataSourceSqlSessionFactory { private Logger logger = Logger.getLogger(DataSourceSqlSessionFactory.class); @Value("${spring.datasource.type}") private Class<? extends DataSource> dataSourceType; @Value("${mybatis.mapper-locations}") private String mapperLocations; @Value("${mybatis.type-aliases-package}") private String aliasesPackage; @Value("${slave.datasource.number}") private int dataSourceNumber; @Resource(name = "masterDataSource") private DataSource masterDataSource; @Resource(name = "slaveDataSources") private List<DataSource> slaveDataSources; @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory() throws Exception { logger.info("======================= init sqlSessionFactory"); SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(roundRobinDataSourceProxy()); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations)); sqlSessionFactoryBean.setTypeAliasesPackage(aliasesPackage); sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); return sqlSessionFactoryBean.getObject(); } @Bean(name = "roundRobinDataSourceProxy") public AbstractRoutingDataSource roundRobinDataSourceProxy() { logger.info("======================= init robinDataSourceProxy"); DataSourceRoute proxy = new DataSourceRoute(dataSourceNumber); Map<Object, Object> targetDataSources = new HashMap(); targetDataSources.put(DataSourceType.master.getType(), masterDataSource); if(null != slaveDataSources) { for(int i=0; i<slaveDataSources.size(); i++){ targetDataSources.put(i, slaveDataSources.get(i)); } } proxy.setDefaultTargetDataSource(masterDataSource); proxy.setTargetDataSources(targetDataSources); return proxy; } }
5. 自定义路由
public class DataSourceRoute extends AbstractRoutingDataSource { private Logger logger = Logger.getLogger(DataSourceRoute.class); private final int dataSourceNumber; public DataSourceRoute(int dataSourceNumber) { this.dataSourceNumber = dataSourceNumber; } @Override protected Object determineCurrentLookupKey() { String typeKey = DataSourceContextHolder.getJdbcType(); logger.info("==================== swtich dataSource:" + typeKey); if (typeKey.equals(DataSourceType.master.getType())) { return DataSourceType.master.getType(); }else{ //从数据源随机分配 Random random = new Random(); int slaveDsIndex = random.nextInt(dataSourceNumber); return slaveDsIndex; } } }
6. 定义切面,dao层定义切面
@Aspect @Component public class DataSourceAop { private Logger logger = Logger.getLogger(DataSourceAop.class); @Before("execution(* com.dbq.iot.mapper..*.get*(..)) || execution(* com.dbq.iot.mapper..*.isExist*(..)) " + "|| execution(* com.dbq.iot.mapper..*.select*(..)) || execution(* com.dbq.iot.mapper..*.count*(..)) " + "|| execution(* com.dbq.iot.mapper..*.list*(..)) || execution(* com.dbq.iot.mapper..*.query*(..))" + "|| execution(* com.dbq.iot.mapper..*.find*(..))|| execution(* com.dbq.iot.mapper..*.search*(..))") public void setSlaveDataSourceType(JoinPoint joinPoint) { DataSourceContextHolder.slave(); logger.info("=========slave, method:" + joinPoint.getSignature().getName()); } @Before("execution(* com.dbq.iot.mapper..*.add*(..)) || execution(* com.dbq.iot.mapper..*.del*(..))" + "||execution(* com.dbq.iot.mapper..*.upDate*(..)) || execution(* com.dbq.iot.mapper..*.insert*(..))" + "||execution(* com.dbq.iot.mapper..*.create*(..)) || execution(* com.dbq.iot.mapper..*.update*(..))" + "||execution(* com.dbq.iot.mapper..*.delete*(..)) || execution(* com.dbq.iot.mapper..*.remove*(..))" + "||execution(* com.dbq.iot.mapper..*.save*(..)) || execution(* com.dbq.iot.mapper..*.relieve*(..))" + "|| execution(* com.dbq.iot.mapper..*.edit*(..))") public void setMasterDataSourceType(JoinPoint joinPoint) { DataSourceContextHolder.master(); logger.info("=========master, method:" + joinPoint.getSignature().getName()); } }
7. 最后在写库增加事务管理
@Configuration @Import({DataSourceConfiguration.class}) public class DataSouceTranscation extends DataSourceTransactionManagerAutoConfiguration { private Logger logger = Logger.getLogger(DataSouceTranscation.class); @Resource(name = "masterDataSource") private DataSource masterDataSource; /** * 配置事务管理器 * * @return */ @Bean(name = "transactionManager") public DataSourceTransactionManager transactionManagers() { logger.info("===================== init transactionManager"); return new DataSourceTransactionManager(masterDataSource); } }
8. 在配置文件中增加数据源配置
spring.datasource.name=writedb spring.datasource.url=jdbc:mysql://192.168.0.1/master?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false spring.datasource.username=root spring.datasource.password=1234 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.filters=stat spring.datasource.initialSize=20 spring.datasource.minIdle=20 spring.datasource.maxActive=200 spring.datasource.maxWait=60000 #从库的数量 slave.datasource.number=1 spring.slave0.name=readdb spring.slave0.url=jdbc:mysql://192.168.0.2/slave?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false spring.slave0.username=root spring.slave0.password=1234 spring.slave0.type=com.alibaba.druid.pool.DruidDataSource spring.slave0.driver-class-name=com.mysql.jdbc.Driver spring.slave0.filters=stat spring.slave0.initialSize=20 spring.slave0.minIdle=20 spring.slave0.maxActive=200 spring.slave0.maxWait=60000