optimize 纯分页兼容mp和健康检查优化 (#305)
* 修改健康检查选择数据库的逻辑 修改健康检查的 引用类型转基本类型可能的报错。 * 修复 mybatis cache 开启后, 引起的 association 实效 * pom 默认构建doc,source * 主从插件支持多主多从 * 给健康检查加入适配器, 可以处理: 开启健康检查没引入包的特殊情况
This commit is contained in:
parent
ec7b5d7198
commit
96472635ff
@ -61,10 +61,16 @@ public class GroupDataSource {
|
||||
}
|
||||
|
||||
public DataSource determineDataSource() {
|
||||
return dynamicDataSourceStrategy.determineDataSource(new ArrayList<>(dataSourceMap.values()));
|
||||
String dsKey = determineDsKey();
|
||||
return dataSourceMap.get(dsKey);
|
||||
}
|
||||
|
||||
public String determineDsKey() {
|
||||
return dynamicDataSourceStrategy.determineDSKey(new ArrayList<>(dataSourceMap.keySet()));
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return dataSourceMap.size();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2018 organization baomidou
|
||||
* <pre>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <pre/>
|
||||
*/
|
||||
package com.baomidou.dynamic.datasource.exception;
|
||||
|
||||
/**
|
||||
* exception when druid dataSource cannot select
|
||||
*
|
||||
* @author TaoYu
|
||||
* @since 2.5.6
|
||||
*/
|
||||
public class CannotSelectDataSourceException extends RuntimeException {
|
||||
|
||||
public CannotSelectDataSourceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CannotSelectDataSourceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -16,20 +16,30 @@
|
||||
*/
|
||||
package com.baomidou.dynamic.datasource.plugin;
|
||||
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.ds.GroupDataSource;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
||||
import com.baomidou.dynamic.datasource.support.DbHealthIndicator;
|
||||
import com.baomidou.dynamic.datasource.support.DdConstants;
|
||||
import com.baomidou.dynamic.datasource.support.HealthCheckAdapter;
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Plugin;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
@ -40,6 +50,7 @@ import java.util.Properties;
|
||||
*/
|
||||
@Intercepts({
|
||||
@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}),
|
||||
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
|
||||
@Slf4j
|
||||
public class MasterSlaveAutoRoutingPlugin implements Interceptor {
|
||||
@ -47,20 +58,25 @@ public class MasterSlaveAutoRoutingPlugin implements Interceptor {
|
||||
@Autowired
|
||||
private DynamicDataSourceProperties properties;
|
||||
|
||||
@Lazy
|
||||
@Autowired(required = false)
|
||||
private HealthCheckAdapter healthCheckAdapter;
|
||||
|
||||
@Autowired
|
||||
protected DataSource dynamicDataSource;
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
Object[] args = invocation.getArgs();
|
||||
MappedStatement ms = (MappedStatement) args[0];
|
||||
boolean empty = true;
|
||||
String pushedDataSource = null;
|
||||
try {
|
||||
empty = StringUtils.isEmpty(DynamicDataSourceContextHolder.peek());
|
||||
if (empty) {
|
||||
DynamicDataSourceContextHolder.push(getDataSource(ms));
|
||||
}
|
||||
String dataSource = getDataSource(ms);
|
||||
pushedDataSource = DynamicDataSourceContextHolder.push(dataSource);
|
||||
return invocation.proceed();
|
||||
} finally {
|
||||
if (empty) {
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
if (pushedDataSource != null) {
|
||||
DynamicDataSourceContextHolder.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,25 +88,46 @@ public class MasterSlaveAutoRoutingPlugin implements Interceptor {
|
||||
* @return 获取真实的数据源名称
|
||||
*/
|
||||
public String getDataSource(MappedStatement mappedStatement) {
|
||||
String slave = DdConstants.SLAVE;
|
||||
String currentDataSource = SqlCommandType.SELECT == mappedStatement.getSqlCommandType() ? DdConstants.SLAVE : DdConstants.MASTER;
|
||||
String dataSource = null;
|
||||
if (properties.isHealth()) {
|
||||
/*
|
||||
* 根据从库健康状况,判断是否切到主库
|
||||
*/
|
||||
boolean health = DbHealthIndicator.getDbHealth(DdConstants.SLAVE);
|
||||
if (!health) {
|
||||
health = DbHealthIndicator.getDbHealth(DdConstants.MASTER);
|
||||
DynamicRoutingDataSource dynamicRoutingDataSource = (DynamicRoutingDataSource) dynamicDataSource;
|
||||
// 当前数据源是从库
|
||||
if (DdConstants.SLAVE.equalsIgnoreCase(currentDataSource)) {
|
||||
Map<String, GroupDataSource> currentGroupDataSources = dynamicRoutingDataSource.getCurrentGroupDataSources();
|
||||
GroupDataSource groupDataSource = currentGroupDataSources.get(DdConstants.SLAVE);
|
||||
String dsKey = groupDataSource.determineDsKey();
|
||||
boolean health = healthCheckAdapter.getHealth(dsKey);
|
||||
if (health) {
|
||||
slave = DdConstants.MASTER;
|
||||
dataSource = dsKey;
|
||||
} else {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("从库无法连接, 请检查数据库配置, key: {}", dsKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 从库无法连接, 或者当前数据源需要操作主库
|
||||
if (dataSource == null) {
|
||||
// 当前数据源是主库
|
||||
Map<String, GroupDataSource> currentGroupDataSources = dynamicRoutingDataSource.getCurrentGroupDataSources();
|
||||
GroupDataSource groupDataSource = currentGroupDataSources.get(DdConstants.MASTER);
|
||||
dataSource = groupDataSource.determineDsKey();
|
||||
boolean health = healthCheckAdapter.getHealth(dataSource);
|
||||
if (!health) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("主库无法连接, 请检查数据库配置, key: {}", dataSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dataSource = currentDataSource;
|
||||
}
|
||||
return SqlCommandType.SELECT == mappedStatement.getSqlCommandType() ? slave : DdConstants.MASTER;
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return target instanceof Executor ? Plugin.wrap(target, this) : target;
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -61,7 +61,7 @@ import java.util.Map;
|
||||
@AllArgsConstructor
|
||||
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
|
||||
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
|
||||
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
|
||||
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class, DynamicDataSourceHealthCheckConfiguration.class})
|
||||
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class DynamicDataSourceAutoConfiguration {
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright © 2018 organization baomidou
|
||||
* <pre>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <pre/>
|
||||
*/
|
||||
package com.baomidou.dynamic.datasource.spring.boot.autoconfigure;
|
||||
|
||||
import com.baomidou.dynamic.datasource.support.DbHealthIndicator;
|
||||
import com.baomidou.dynamic.datasource.support.HealthCheckAdapter;
|
||||
import org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* @author liushang@zsyjr.com
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class DynamicDataSourceHealthCheckConfiguration {
|
||||
|
||||
private static final String DYNAMIC_HEALTH_CHECK = DynamicDataSourceProperties.PREFIX + ".health";
|
||||
|
||||
@Bean
|
||||
public HealthCheckAdapter healthCheckAdapter() {
|
||||
return new HealthCheckAdapter();
|
||||
}
|
||||
|
||||
@ConditionalOnClass(AbstractHealthIndicator.class)
|
||||
@ConditionalOnEnabledHealthIndicator("dynamicDS")
|
||||
public class HealthIndicatorConfiguration {
|
||||
|
||||
@Bean("dynamicDataSourceHealthCheck")
|
||||
@ConditionalOnProperty(DYNAMIC_HEALTH_CHECK)
|
||||
public DbHealthIndicator healthIndicator(DataSource dataSource,
|
||||
DynamicDataSourceProperties dynamicDataSourceProperties,
|
||||
HealthCheckAdapter healthCheckAdapter) {
|
||||
return new DbHealthIndicator(dataSource, dynamicDataSourceProperties.getHealthValidQuery(), healthCheckAdapter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -48,7 +48,7 @@ public class DynamicDataSourceProperties {
|
||||
|
||||
public static final String PREFIX = "spring.datasource.dynamic";
|
||||
public static final String HEALTH = PREFIX + ".health";
|
||||
|
||||
public static final String DEFAULT_VALID_QUERY = "SELECT 1";
|
||||
/**
|
||||
* 必须设置默认的库,默认master
|
||||
*/
|
||||
@ -73,6 +73,10 @@ public class DynamicDataSourceProperties {
|
||||
* 是否使用 spring actuator 监控检查,默认不检查
|
||||
*/
|
||||
private boolean health = false;
|
||||
/**
|
||||
* 监控检查SQL
|
||||
*/
|
||||
private String healthValidQuery = DEFAULT_VALID_QUERY;
|
||||
/**
|
||||
* 每一个数据源
|
||||
*/
|
||||
|
@ -17,6 +17,7 @@
|
||||
package com.baomidou.dynamic.datasource.strategy;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -35,5 +36,14 @@ public interface DynamicDataSourceStrategy {
|
||||
* @param dataSources given dataSources
|
||||
* @return final dataSource
|
||||
*/
|
||||
@Deprecated
|
||||
DataSource determineDataSource(List<DataSource> dataSources);
|
||||
|
||||
/**
|
||||
* determine a database from the given dataSources
|
||||
*
|
||||
* @param dsNames given dataSources
|
||||
* @return final dataSource
|
||||
*/
|
||||
String determineDSKey(List<String> dsNames);
|
||||
}
|
||||
|
@ -37,4 +37,9 @@ public class LoadBalanceDynamicDataSourceStrategy implements DynamicDataSourceSt
|
||||
public DataSource determineDataSource(List<DataSource> dataSources) {
|
||||
return dataSources.get(Math.abs(index.getAndAdd(1) % dataSources.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String determineDSKey(List<String> dsNames) {
|
||||
return dsNames.get(Math.abs(index.getAndAdd(1) % dsNames.size()));
|
||||
}
|
||||
}
|
||||
|
@ -32,4 +32,9 @@ public class RandomDynamicDataSourceStrategy implements DynamicDataSourceStrateg
|
||||
public DataSource determineDataSource(List<DataSource> dataSources) {
|
||||
return dataSources.get(ThreadLocalRandom.current().nextInt(dataSources.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String determineDSKey(List<String> dsNames) {
|
||||
return dsNames.get(ThreadLocalRandom.current().nextInt(dsNames.size()));
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package com.baomidou.dynamic.datasource.support;
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.dao.support.DataAccessUtils;
|
||||
import org.springframework.jdbc.IncorrectResultSetColumnCountException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
@ -31,7 +32,6 @@ import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 数据库健康状况指标
|
||||
@ -40,61 +40,63 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
public class DbHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
/**
|
||||
* 维护数据源健康状况
|
||||
*/
|
||||
private static final Map<String, Boolean> DB_HEALTH = new ConcurrentHashMap<>();
|
||||
private final String validQuery;
|
||||
|
||||
private final HealthCheckAdapter healthCheckAdapter;
|
||||
/**
|
||||
* 当前执行数据源
|
||||
*/
|
||||
private final DataSource dataSource;
|
||||
|
||||
public DbHealthIndicator(DataSource dataSource) {
|
||||
|
||||
public DbHealthIndicator(DataSource dataSource, String validQuery,HealthCheckAdapter healthCheckAdapter) {
|
||||
this.dataSource = dataSource;
|
||||
this.validQuery = validQuery;
|
||||
this.healthCheckAdapter = healthCheckAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源连接健康状况
|
||||
*
|
||||
* @param dataSource 数据源名称
|
||||
* @return 健康状况
|
||||
*/
|
||||
public static boolean getDbHealth(String dataSource) {
|
||||
return DB_HEALTH.get(dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接池健康状况
|
||||
*
|
||||
* @param dataSource 数据源名称
|
||||
* @param health 健康状况 false 不健康 true 健康
|
||||
* @return 设置状态
|
||||
*/
|
||||
public static Boolean setDbHealth(String dataSource, boolean health) {
|
||||
return DB_HEALTH.put(dataSource, health);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
if (dataSource instanceof DynamicRoutingDataSource) {
|
||||
Map<String, DataSource> dataSourceMap = ((DynamicRoutingDataSource) dataSource).getCurrentDataSources();
|
||||
// 循环检查当前数据源是否可用
|
||||
Boolean available = null;
|
||||
Boolean disable = null;
|
||||
for (Map.Entry<String, DataSource> dataSource : dataSourceMap.entrySet()) {
|
||||
Integer result = 0;
|
||||
Boolean resultAvailable = false;
|
||||
try {
|
||||
result = query(dataSource.getValue());
|
||||
resultAvailable = queryAvailable(dataSource.getValue());
|
||||
} catch (Throwable ignore){
|
||||
} finally {
|
||||
DB_HEALTH.put(dataSource.getKey(), 1 == result);
|
||||
builder.withDetail(dataSource.getKey(), result);
|
||||
healthCheckAdapter.putHealth(dataSource.getKey(), resultAvailable);
|
||||
builder.withDetail(dataSource.getKey(), resultAvailable);
|
||||
|
||||
if (resultAvailable) {
|
||||
available = true;
|
||||
} else {
|
||||
disable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (available != null) {
|
||||
if (disable != null) {
|
||||
builder.status(Status.OUT_OF_SERVICE);
|
||||
} else {
|
||||
builder.status(Status.UP);
|
||||
}
|
||||
}else{
|
||||
builder.status(Status.DOWN);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Integer query(DataSource dataSource) {
|
||||
//todo 这里应该可以配置或者可重写?
|
||||
List<Integer> results = new JdbcTemplate(dataSource).query("SELECT 1", new RowMapper<Integer>() {
|
||||
private Boolean queryAvailable(DataSource dataSource) {
|
||||
List<Integer> results = new JdbcTemplate(dataSource).query(this.validQuery, new RowMapper<Integer>() {
|
||||
|
||||
@Override
|
||||
public Integer mapRow(ResultSet resultSet, int i) throws SQLException {
|
||||
@ -106,6 +108,6 @@ public class DbHealthIndicator extends AbstractHealthIndicator {
|
||||
return (Integer) JdbcUtils.getResultSetValue(resultSet, 1, Integer.class);
|
||||
}
|
||||
});
|
||||
return DataAccessUtils.requiredSingleResult(results);
|
||||
return DataAccessUtils.requiredSingleResult(results) == 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright © 2018 organization baomidou
|
||||
* <pre>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <pre/>
|
||||
*/
|
||||
package com.baomidou.dynamic.datasource.support;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author ls9527
|
||||
*/
|
||||
public class HealthCheckAdapter {
|
||||
|
||||
/**
|
||||
* 维护数据源健康状况
|
||||
*/
|
||||
private static final Map<String, Boolean> DB_HEALTH = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public void putHealth(String key, Boolean healthState) {
|
||||
DB_HEALTH.put(key,healthState);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源连接健康状况
|
||||
*
|
||||
* @param dataSource 数据源名称
|
||||
* @return 健康状况
|
||||
*/
|
||||
public boolean getHealth(String dataSource) {
|
||||
Boolean isHealth = DB_HEALTH.get(dataSource);
|
||||
return isHealth != null && isHealth;
|
||||
}
|
||||
}
|
@ -65,8 +65,10 @@ public final class DynamicDataSourceContextHolder {
|
||||
*
|
||||
* @param ds 数据源名称
|
||||
*/
|
||||
public static void push(String ds) {
|
||||
LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds);
|
||||
public static String push(String ds) {
|
||||
String dataSourceStr = StringUtils.isEmpty(ds) ? "" : ds;
|
||||
LOOKUP_KEY_HOLDER.get().push(dataSourceStr);
|
||||
return dataSourceStr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user