服务提供动态的指标计算,可查不同库配置不同sql或者代码,实时计算指标,本来数据源是需要重启配置的,想了下可以做成不重启就新增。

设计:在mongodb中保存数据源信息,项目启动时加载到内存初始化数据源,运行时可动态的新增或者删除数据源不需要重启服务,没测过,暂时需求不明,先做个记录,后面可能会用到。

动态数据源工厂类

主要作用就是初始化数据源

import com.alibaba.druid.pool.DruidDataSource;
import com.google.common.collect.Maps;
import com.google.gson.reflect.TypeToken;
import com.linzi.risk.indicator.enums.YesOrNoEnum;
import com.linzi.risk.indicator.utils.GsonUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.linzi.risk.indicator.feign.IndicatorConfigFeignImpl.INDICATOR_CONFIG;

/**
 * @author chentiefeng
 * @date 2019/11/12 09:49
 */
@Slf4j
@Component("dynamicDataSourceFactory")
public class DynamicDataSourceFactory {
    public final static String DYNAMIC_DATA_SOURCE = "dynamic_data_source";
    /**
     * 运行时数据源
     */
    private static Map<String, DataSource> runtimeDataSourceMap = Maps.newConcurrentMap();
    private static MongoTemplate mongoTemplate;
    private static DataSourceProperties dataSourceProperties;
    private static DataSource defaultDataSource;
    private static String defaultDataSourceKey;


    /**
     * 所有数据源
     *
     * @return
     */
    public static List<DataSourceConfig> list() {
        Set<String> keys = runtimeDataSourceMap.keySet();
        Query query = Query.query(Criteria.where("key").is("dataSource"));
        Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG);
        List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() {
        }.getType());
        for (DataSourceConfig dataSourceConfig : list) {
            if (keys.contains(dataSourceConfig.getKey())) {
                dataSourceConfig.setRuntime(YesOrNoEnum.YES.getInitValue());
            }
        }
        return list;
    }

    public static Map<String, DataSource> getRuntimeDataSourceMap() {
        return runtimeDataSourceMap;
    }

    public static DataSource get(String key) {
        return runtimeDataSourceMap.get(key);
    }

    @PostConstruct
    public void init() {
        Query query = Query.query(Criteria.where("key").is("dataSource"));
        Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG);
        List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() {
        }.getType());
        for (DataSourceConfig dataSourceConfig : list) {
            initDataSource(dataSourceConfig);
        }
        if (defaultDataSource == null) {
            throw new RuntimeException("默认数据源不存在");
        }
    }

    public static DataSource getDefaultDataSource() {
        return defaultDataSource;
    }

    public static String getDefaultDataSourceKey() {
        return defaultDataSourceKey;
    }

    /**
     * 初始化数据源
     *
     * @param dataSourceConfig
     */
    private void initDataSource(DataSourceConfig dataSourceConfig) {
        DruidDataSource druidDataSource = getDruidDataSource(dataSourceProperties);
        druidDataSource.setDriverClassName(dataSourceConfig.getDriverClassName());
        druidDataSource.setUrl(dataSourceConfig.getUrl());
        druidDataSource.setUsername(dataSourceConfig.getUsername());
        druidDataSource.setPassword(dataSourceConfig.getPassword());
        try {
            druidDataSource.init();
            runtimeDataSourceMap.put(dataSourceConfig.getKey(), druidDataSource);
            if (YesOrNoEnum.YES.getInitValue().equals(dataSourceConfig.defaultDataSource)) {
                defaultDataSourceKey = dataSourceConfig.getKey();
                defaultDataSource = druidDataSource;
            }
        } catch (SQLException e) {
            log.error(e.getMessage(), e);
        }
    }

    @Data
    public class DataSourceConfig {
        private String key;
        private String desc;
        private String driverClassName;
        private String url;
        private String username;
        private String password;
        private Integer defaultDataSource;
        private Integer runtime;
    }

    @Resource
    public void setMongoTemplate(MongoTemplate mongoTemplate) {
        DynamicDataSourceFactory.mongoTemplate = mongoTemplate;
    }

    @Resource
    public void setDataSourceProperties(DataSourceProperties dataSourceProperties) {
        DynamicDataSourceFactory.dataSourceProperties = dataSourceProperties;
    }

    public static DruidDataSource getDruidDataSource(DataSourceProperties properties) {
        DruidDataSource druidDataSource = new DruidDataSource();

        druidDataSource.setInitialSize(properties.getInitialSize());
        druidDataSource.setMaxActive(properties.getMaxActive());
        druidDataSource.setMinIdle(properties.getMinIdle());
        druidDataSource.setMaxWait(properties.getMaxWait());
        druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
        druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
        druidDataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis());
        druidDataSource.setValidationQuery(properties.getValidationQuery());
        druidDataSource.setValidationQueryTimeout(properties.getValidationQueryTimeout());
        druidDataSource.setTestOnBorrow(properties.isTestOnBorrow());
        druidDataSource.setTestOnReturn(properties.isTestOnReturn());
        druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements());
        druidDataSource.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements());
        druidDataSource.setSharePreparedStatements(properties.isSharePreparedStatements());
        try {
            druidDataSource.setFilters(properties.getFilters());
        } catch (SQLException e) {
            log.error(e.getMessage(), e);
        }
        return druidDataSource;
    }
}

动态数据源路由

主要是druid提供的可以切换数据源

```java

package com.linzi.risk.indicator.datasources;



import org.springframework.context.annotation.DependsOn;

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

import org.springframework.stereotype.Component;



import javax.annotation.PostConstruct;

import javax.sql.DataSource;

import java.util.Map;

import java.util.Objects;

import java.util.stream.Collectors;



/**

 * 动态数据源

 *

 * @author chentiefeng

 * @date 2019/8/19 1:03

 */



@Component

@DependsOn("dynamicDataSourceFactory")

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();



    public DynamicRoutingDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {

        super.setDefaultTargetDataSource(defaultTargetDataSource);

        super.setTargetDataSources(targetDataSources);

        super.afterPropertiesSet();

    }



    @PostConstruct

    public void init() {

        super.setDefaultTargetDataSource(DynamicDataSourceFactory.getDefaultDataSource());

        Map<Object, Object> runtimeDataSourceMap = DynamicDataSourceFactory.getRuntimeDataSourceMap().entrySet().stream()

                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        super.setTargetDataSources(runtimeDataSourceMap);

        super.afterPropertiesSet();

    }



    @Override

    protected Object determineCurrentLookupKey() {

        return getDataSource();

    }



    public static void setDataSource(String dataSource) {

        contextHolder.set(dataSource);

    }



    public static String getDataSource() {

        return contextHolder.get();

    }



    public static void clearDataSource() {

        contextHolder.remove();

    }



    @Override

    protected DataSource determineTargetDataSource() {

        Object lookupKey = determineCurrentLookupKey();

        DataSource dataSource = DynamicDataSourceFactory.getRuntimeDataSourceMap().get(Objects.toString(lookupKey));

        if (dataSource == null) {

            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");

        }

        return dataSource;

    }

}

数据源注解

在service或者dao方法上注解可以方便切换数据源

package com.linzi.risk.indicator.datasources.annotation;

import java.lang.annotation.*;

/**
 * 多数据源注解
 * @author chentiefeng
 * @date 2019/9/16 22:16
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name();
}

切面

package com.linzi.risk.indicator.datasources.aspect;

import com.linzi.risk.indicator.datasources.DynamicRoutingDataSource;
import com.linzi.risk.indicator.datasources.DynamicDataSourceFactory;
import com.linzi.risk.indicator.datasources.annotation.DataSource;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 多数据源,切面处理类
 *
 * @author chentiefeng
 * @date 2019/9/16 22:20
 */
@Aspect
@Component
@Order(1)
public class DataSourceAspect {

    @Around("execution(* com.linzi.risk.indicator.dao.Dynamic*Dao.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource ds = method.getAnnotation(DataSource.class);
        if (ds == null) {
            DynamicRoutingDataSource.setDataSource(DynamicDataSourceFactory.getDefaultDataSourceKey());
        } else if (DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE.equals(ds.name())) {
            //动态参数数据源
            String dsKey = DynamicDataSourceFactory.getDefaultDataSourceKey();
            Object[] args = point.getArgs();
            if (ArrayUtils.isNotEmpty(args)) {
                dsKey = Objects.toString(args[args.length - 1], DynamicDataSourceFactory.getDefaultDataSourceKey());
            }
            DynamicRoutingDataSource.setDataSource(dsKey);
        } else {
            DynamicRoutingDataSource.setDataSource(ds.name());
        }
        try {
            return point.proceed();
        } finally {
            DynamicRoutingDataSource.clearDataSource();
        }
    }

}

调用

/**
     * 根据动态SQL查询Map结果
     *
     * @param statement     动态sql查询
     * @param dataSourceKey
     * @return
     */
    @DataSource(name = DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE)
    void execute(@Param("statement") String statement, String dataSourceKey);

    /**
     * 指定数据源查询
     *
     * @param statement     动态sql查询
     * @return
     */
    @DataSource(name = "risk_biz")
    void execute(@Param("statement") String statement);

数据源属性类

package com.linzi.risk.indicator.datasources;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author chentiefeng
 * @date 2019/11/13 11:00
 */
@Data
@Configuration
@ConfigurationProperties("spring.datasource.druid")
public class DataSourceProperties {
    private String driverClassName;
    private String url;
    private String username;
    private String password;

    /**
     * Druid默认参数
     */
    private int initialSize = 2;
    private int maxActive = 10;
    private int minIdle = -1;
    private long maxWait = 60 * 1000L;
    private long timeBetweenEvictionRunsMillis = 60 * 1000L;
    private long minEvictableIdleTimeMillis = 1000L * 60L * 30L;
    private long maxEvictableIdleTimeMillis = 1000L * 60L * 60L * 7;
    private String validationQuery = "select 1";
    private int validationQueryTimeout = -1;
    private boolean testOnBorrow = false;
    private boolean testOnReturn = false;
    private boolean testWhileIdle = true;
    private boolean poolPreparedStatements = false;
    private int maxOpenPreparedStatements = -1;
    private boolean sharePreparedStatements = false;
    private String filters = "stat,wall";
}

  

服务提供动态的指标计算,可查不同库配置不同sql或者代码,实时计算指标,本来数据源是需要重启配置的,想了下可以做成不重启就新增。设计:在mongodb中保存数据源信息,项目启动时加载到内存初始化数据源,运行时可动态的新增或者删除数据源不需要重启服务,没测过,暂时需求不明,先做个记录,后面可能会用到。
**动态数据源工厂类**主要作用就是初始化数据源```java
import com.alibaba.druid.pool.DruidDataSource;import com.google.common.collect.Maps;import com.google.gson.reflect.TypeToken;import com.linzi.risk.indicator.enums.YesOrNoEnum;import com.linzi.risk.indicator.utils.GsonUtil;import lombok.Data;import lombok.extern.slf4j.Slf4j;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;import javax.annotation.Resource;import javax.sql.DataSource;import java.sql.SQLException;import java.util.List;import java.util.Map;import java.util.Set;
import static com.linzi.risk.indicator.feign.IndicatorConfigFeignImpl.INDICATOR_CONFIG;
/** * @author chentiefeng * @date 2019/11/12 09:49 */@Slf4j@Component("dynamicDataSourceFactory")public class DynamicDataSourceFactory {    public final static String DYNAMIC_DATA_SOURCE = "dynamic_data_source";    /**     * 运行时数据源     */    private static Map<String, DataSource> runtimeDataSourceMap = Maps.newConcurrentMap();    private static MongoTemplate mongoTemplate;    private static DataSourceProperties dataSourceProperties;    private static DataSource defaultDataSource;    private static String defaultDataSourceKey;

    /**     * 所有数据源     *     * @return     */    public static List<DataSourceConfig> list() {        Set<String> keys = runtimeDataSourceMap.keySet();        Query query = Query.query(Criteria.where("key").is("dataSource"));        Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG);        List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() {        }.getType());        for (DataSourceConfig dataSourceConfig : list) {            if (keys.contains(dataSourceConfig.getKey())) {                dataSourceConfig.setRuntime(YesOrNoEnum.YES.getInitValue());            }        }        return list;    }
    public static Map<String, DataSource> getRuntimeDataSourceMap() {        return runtimeDataSourceMap;    }
    public static DataSource get(String key) {        return runtimeDataSourceMap.get(key);    }
    @PostConstruct    public void init() {        Query query = Query.query(Criteria.where("key").is("dataSource"));        Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG);        List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() {        }.getType());        for (DataSourceConfig dataSourceConfig : list) {            initDataSource(dataSourceConfig);        }        if (defaultDataSource == null) {            throw new RuntimeException("默认数据源不存在");        }    }
    public static DataSource getDefaultDataSource() {        return defaultDataSource;    }
    public static String getDefaultDataSourceKey() {        return defaultDataSourceKey;    }
    /**     * 初始化数据源     *     * @param dataSourceConfig     */    private void initDataSource(DataSourceConfig dataSourceConfig) {        DruidDataSource druidDataSource = getDruidDataSource(dataSourceProperties);        druidDataSource.setDriverClassName(dataSourceConfig.getDriverClassName());        druidDataSource.setUrl(dataSourceConfig.getUrl());        druidDataSource.setUsername(dataSourceConfig.getUsername());        druidDataSource.setPassword(dataSourceConfig.getPassword());        try {            druidDataSource.init();            runtimeDataSourceMap.put(dataSourceConfig.getKey(), druidDataSource);            if (YesOrNoEnum.YES.getInitValue().equals(dataSourceConfig.defaultDataSource)) {                defaultDataSourceKey = dataSourceConfig.getKey();                defaultDataSource = druidDataSource;            }        } catch (SQLException e) {            log.error(e.getMessage(), e);        }    }
    @Data    public class DataSourceConfig {        private String key;        private String desc;        private String driverClassName;        private String url;        private String username;        private String password;        private Integer defaultDataSource;        private Integer runtime;    }
    @Resource    public void setMongoTemplate(MongoTemplate mongoTemplate) {        DynamicDataSourceFactory.mongoTemplate = mongoTemplate;    }
    @Resource    public void setDataSourceProperties(DataSourceProperties dataSourceProperties) {        DynamicDataSourceFactory.dataSourceProperties = dataSourceProperties;    }
    public static DruidDataSource getDruidDataSource(DataSourceProperties properties) {        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setInitialSize(properties.getInitialSize());        druidDataSource.setMaxActive(properties.getMaxActive());        druidDataSource.setMinIdle(properties.getMinIdle());        druidDataSource.setMaxWait(properties.getMaxWait());        druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());        druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());        druidDataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis());        druidDataSource.setValidationQuery(properties.getValidationQuery());        druidDataSource.setValidationQueryTimeout(properties.getValidationQueryTimeout());        druidDataSource.setTestOnBorrow(properties.isTestOnBorrow());        druidDataSource.setTestOnReturn(properties.isTestOnReturn());        druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements());        druidDataSource.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements());        druidDataSource.setSharePreparedStatements(properties.isSharePreparedStatements());        try {            druidDataSource.setFilters(properties.getFilters());        } catch (SQLException e) {            log.error(e.getMessage(), e);        }        return druidDataSource;    }}
```
**动态数据源路由**主要是druid提供的可以切换数据源
```javapackage com.linzi.risk.indicator.datasources;
import org.springframework.context.annotation.DependsOn;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;import javax.sql.DataSource;import java.util.Map;import java.util.Objects;import java.util.stream.Collectors;
/** * 动态数据源 * * @author chentiefeng * @date 2019/8/19 1:03 */
@Component@DependsOn("dynamicDataSourceFactory")public class DynamicRoutingDataSource extends AbstractRoutingDataSource {    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    public DynamicRoutingDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {        super.setDefaultTargetDataSource(defaultTargetDataSource);        super.setTargetDataSources(targetDataSources);        super.afterPropertiesSet();    }
    @PostConstruct    public void init() {        super.setDefaultTargetDataSource(DynamicDataSourceFactory.getDefaultDataSource());        Map<Object, Object> runtimeDataSourceMap = DynamicDataSourceFactory.getRuntimeDataSourceMap().entrySet().stream()                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));        super.setTargetDataSources(runtimeDataSourceMap);        super.afterPropertiesSet();    }
    @Override    protected Object determineCurrentLookupKey() {        return getDataSource();    }
    public static void setDataSource(String dataSource) {        contextHolder.set(dataSource);    }
    public static String getDataSource() {        return contextHolder.get();    }
    public static void clearDataSource() {        contextHolder.remove();    }
    @Override    protected DataSource determineTargetDataSource() {        Object lookupKey = determineCurrentLookupKey();        DataSource dataSource = DynamicDataSourceFactory.getRuntimeDataSourceMap().get(Objects.toString(lookupKey));        if (dataSource == null) {            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");        }        return dataSource;    }}
```**动态数据源注解**在service或者dao方法上注解可以方便切换数据源
```javapackage com.linzi.risk.indicator.datasources.annotation;
import java.lang.annotation.*;
/** * 多数据源注解 * @author chentiefeng * @date 2019/9/16 22:16 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataSource {    String name();}```**切面**切点可以随便修改```javapackage com.linzi.risk.indicator.datasources.aspect;
import com.linzi.risk.indicator.datasources.DynamicRoutingDataSource;import com.linzi.risk.indicator.datasources.DynamicDataSourceFactory;import com.linzi.risk.indicator.datasources.annotation.DataSource;import org.apache.commons.lang.ArrayUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;
import java.lang.reflect.Method;import java.util.Objects;
/** * 多数据源,切面处理类 * * @author chentiefeng * @date 2019/9/16 22:20 */@Aspect@Component@Order(1)public class DataSourceAspect {
    @Around("execution(* com.linzi.risk.indicator.dao.Dynamic*Dao.*(..))")    public Object around(ProceedingJoinPoint point) throws Throwable {        MethodSignature signature = (MethodSignature) point.getSignature();        Method method = signature.getMethod();        DataSource ds = method.getAnnotation(DataSource.class);        if (ds == null) {            DynamicRoutingDataSource.setDataSource(DynamicDataSourceFactory.getDefaultDataSourceKey());        } else if (DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE.equals(ds.name())) {            //动态参数数据源            String dsKey = DynamicDataSourceFactory.getDefaultDataSourceKey();            Object[] args = point.getArgs();            if (ArrayUtils.isNotEmpty(args)) {                dsKey = Objects.toString(args[args.length - 1], DynamicDataSourceFactory.getDefaultDataSourceKey());            }            DynamicRoutingDataSource.setDataSource(dsKey);        } else {            DynamicRoutingDataSource.setDataSource(ds.name());        }        try {            return point.proceed();        } finally {            DynamicRoutingDataSource.clearDataSource();        }    }
}
```**调用**
```java/**     * 根据动态SQL查询Map结果     *     * @param statement     动态sql查询     * @param dataSourceKey     * @return     */    @DataSource(name = DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE)    void execute(@Param("statement") String statement, String dataSourceKey);        /**     * 指定数据源查询     *     * @param statement     动态sql查询     * @return     */    @DataSource(name = "risk_biz")    void execute(@Param("statement") String statement);```**数据源属性类**
```javapackage com.linzi.risk.indicator.datasources;
import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;
/** * @author chentiefeng * @date 2019/11/13 11:00 */@Data@Configuration@ConfigurationProperties("spring.datasource.druid")public class DataSourceProperties {    private String driverClassName;    private String url;    private String username;    private String password;
    /**     * Druid默认参数     */    private int initialSize = 2;    private int maxActive = 10;    private int minIdle = -1;    private long maxWait = 60 * 1000L;    private long timeBetweenEvictionRunsMillis = 60 * 1000L;    private long minEvictableIdleTimeMillis = 1000L * 60L * 30L;    private long maxEvictableIdleTimeMillis = 1000L * 60L * 60L * 7;    private String validationQuery = "select 1";    private int validationQueryTimeout = -1;    private boolean testOnBorrow = false;    private boolean testOnReturn = false;    private boolean testWhileIdle = true;    private boolean poolPreparedStatements = false;    private int maxOpenPreparedStatements = -1;    private boolean sharePreparedStatements = false;    private String filters = "stat,wall";}
```

12-29 09:33