Druid是一个JDBC组件库,包括数据库连接池、SQL Parser等组件。DruidDataSource是最好的数据库连接池。
Druid是一个JDBC组件,它包括三部分:
DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系。
DruidDataSource 高效可管理的数据库连接池。
SQLParser
Druid可以做什么?
1) 可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
2) 替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
3) 数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
4) SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter-Chain机制,很方便编写JDBC层的扩展插件。
maven central repository http://repo1.maven.org/maven2/com/alibaba/druid/
com.alibaba
druid
1.1.5
https://github.com/alibaba/druid/wiki/常见问题
slaveDateSource
classpath:mapper/*Mapper.xml
同时,我们还需要再web中注入提供Druid视图显示的service
DruidStatView
com.alibaba.druid.support.http.StatViewServlet
loginUsername
admin
loginPassword
admin
DruidStatView
/druid/*
要查看内置监控,访问路径是/druid/index.html
StatViewSerlvet展示出来的监控信息比较敏感,是系统运行的内部情况,如果你需要做访问控制,可以配置allow和deny这两个参数。比如:
DruidStatView
com.alibaba.druid.support.http.StatViewServlet
allow
128.242.127.1/24,128.242.128.1
deny
128.242.127.4
在StatViewSerlvet输出的html页面中,有一个功能是Reset All,执行这个操作之后,会导致所有计数器清零,重新计数。你可以通过配置参数关闭它。
DruidStatView
com.alibaba.druid.support.http.StatViewServlet
resetEnable
false
至此基本配置都完成了 如果还需要使用Web应用、URI监控、Session监控、Spring监控等则还需要继续增加配置。
按需要配置web和spring的关联监控
相关拓展
为了提高数据库的性能,降低主库承担的请求数量,我们还要实现读写分离,这就涉及到了根据注解来动态切换数据库,这里我们可以通过自定义注解来实现。这里用到
DataSource 是一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataSource {
String value();
}
DataSourceAspect 通过spring的aop面向切面编程, 拦截切面中注解(@DataSource)的value值,并将该值提供至
DynamicDataSource,再由DynamicDataSource切换数据源的连接。
/**
* Created by zhanghe
* 2018/3/3.
*
* 数据库动态切换切面类
*/
public class DataSourceAspect {
/**
* DataSourceConf 使用的是阿里的Disconf开源框架,我们用它来维护我们得到配置文件,可以进行热部署
*/
@Autowired
private DataSourceConf conf;
private static Logger logger = LoggerFactory.getLogger(com.abc.framework.db.DataSourceAspect.class);
public DataSourceAspect() {
}
public void before(JoinPoint joinPoint) {
logger.debug("log begin method: " + joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName());
try {
Object e = joinPoint.getTarget();
String method = joinPoint.getSignature().getName();
Class[] parameterTypes = ((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes();
//获取注解 @DateSource 的value值
String dataSourceValue = this.buildDataSource(e, method, parameterTypes);
//如果注解为 @Transactional ,根据事物是否只读 readOnly 来判断,只读为从库
dataSourceValue = this.buildTransactional(dataSourceValue, e, method, parameterTypes);
if(dataSourceValue == null) {
//如果没注解,那么从Disconf 中获取默认数据库名称
String defaultDataSource = this.conf.getDefaultDataSourceName();
defaultDataSource = defaultDataSource == null? com.abc.framework.db.DynamicDataSource.getDefaultDataSourceName():defaultDataSource;
logger.debug("切换默认数据源:[ " + defaultDataSource + " ]");
//DynamicDataSource负责真正意义上的切换数据库
com.abc.framework.db.DynamicDataSource.setDataSourceKey(defaultDataSource);
} else {
logger.debug("切换数据源到:[ " + dataSourceValue + " ]");
com.abc.framework.db.DynamicDataSource.setDataSourceKey(dataSourceValue);
}
} catch (Exception var7) {
logger.error("切换数据源异常", var7);
}
}
//根据java反射机制 获取到 注解@DateSource 的value值
private String buildDataSource(Object target, String method, Class>[] parameterTypes) throws NoSuchMethodException {
String dataSourceValue = null;
Class[] classes = target.getClass().getInterfaces();
Method method1 = classes[0].getMethod(method, parameterTypes);
if(method1 != null && method1.isAnnotationPresent(DataSource.class)) {
dataSourceValue = ((DataSource)method1.getAnnotation(DataSource.class)).value();
}
return dataSourceValue;
}
//如果注解为 @Transactional ,根据事物是否只读 readOnly 来判断,只读为从库
private String buildTransactional(String dataSourceValue, Object target, String method, Class>[] parameterTypes) throws NoSuchMethodException {
Class clazz = target.getClass();
Method m = clazz.getMethod(method, parameterTypes);
if(m.isAnnotationPresent(Transactional.class)) {
Transactional transactional = (Transactional)m.getAnnotation(Transactional.class);
if(transactional.readOnly()) {
dataSourceValue = com.abc.framework.db.DynamicDataSource.getSlaveDataSource();
} else {
dataSourceValue = com.abc.framework.db.DynamicDataSource.getMasterDataSourceName();
}
}
return dataSourceValue;
}
//性能统计
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time = System.currentTimeMillis();
Object retVal = pjp.proceed();
time = System.currentTimeMillis() - time;
logger.debug(pjp.getTarget().getClass().getName() + "." + pjp.getSignature().getName() + "process time: " + time + " ms");
return retVal;
}
public void doAfter(JoinPoint jp) {
logger.debug("log Ending method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName());
}
}
DynamicDataSource 根据DataSourceAspect提供的数据库名称value,切换至对应的数据库
/**
* Created by zhanghe
* 2018/3/3.
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
//里面存放的是从注解获取的 当前数据库 名称 @DataSource 的 value值
private static final ThreadLocal dataSourceKey = new InheritableThreadLocal();
//从库名称列表
private static List slaveDataSourceNames;
//主库名称
private static String masterDataSourceName;
//默认数据库名称
private static String defaultDataSourceName;
private static Random random = new Random();
public DynamicDataSource() {
}
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
//重写父类的方法,根据dataSourceKey决定当前数据库
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
public static String getSlaveDataSource() {
//随机获取从库
return (String)slaveDataSourceNames.get(random.nextInt(slaveDataSourceNames.size()));
}
public static void setSlaveDataSourceNames(List slaveDataSourceNames) {
slaveDataSourceNames = slaveDataSourceNames;
}
public static String getDefaultDataSourceName() {
return defaultDataSourceName;
}
public static void setDefaultDataSourceName(String defaultDataSourceName) {
defaultDataSourceName = defaultDataSourceName;
}
public static String getMasterDataSourceName() {
return masterDataSourceName;
}
public static void setMasterDataSourceName(String masterDataSourceName) {
masterDataSourceName = masterDataSourceName;
}
}
至于为什么 AbstractRoutingDataSource 这个抽象类可以切换数据库
看看其注释:
翻译:文摘{ @link javax.sql。根据查找键,将{@link #getConnection()}调用路由到多个目标数据源中的一个。后者通常(但不一定)是通过一些线程绑定的事务上下文确定的。
再看看 AbstractRoutingDataSource 的 determineTargetDataSource()方法;
可以看出determinCurrentLookupKey()这个方法是关键,那么我们的DynamicDataSource 就重写了这个方法
定义了需要转换的数据库名称。
有不正确的地方欢迎指出,一起探讨 共勉共进。
个人原创,转载需经博主同意且标明出处