Druid作为“数据库连接池”的使用以及数据库的动态切换

Druid简介

    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/

   Maven坐标

      
       com.alibaba
       druid
       1.1.5
  

  常见问题汇总

      https://github.com/alibaba/druid/wiki/常见问题


以上是Druid作者温绍对druid的介绍说明 ,接下来,我们在项目中实践一下


    
那么首先,既然说到Druid是“数据库连接池”,那么我们就需要写配置文件来连接到数据库,直接贴码我的配置文件,参数含义就不赘述了,代码中我都写了注释



    
    
    
    

    
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
            
                
                
            
        
        
        

    
    
    
        
        
        
        
    
    
    
        
        
        
        
        
        
        
        
        
        
    
    
    
        
        
        
    
    
    
        
        
        
    

    
    
    
        
            
            
            
            
        
    
    
    
        
        
        
            
                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
  	
  
  • deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝。
  • 如果allow没有配置或者为空,则允许所有访问

在StatViewSerlvet输出的html页面中,有一个功能是Reset All,执行这个操作之后,会导致所有计数器清零,重新计数。你可以通过配置参数关闭它。

  
      DruidStatView
      com.alibaba.druid.support.http.StatViewServlet
  	
  		resetEnable
  		false
  	
  

至此基本配置都完成了  如果还需要使用Web应用、URI监控、Session监控、Spring监控等则还需要继续增加配置。

    按需要配置web和spring的关联监控

  • Web关联监控配置 
    https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
  • Spring关联监控配置 
    https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_Druid%E5%92%8CSpring%E5%85%B3%E8%81%94%E7%9B%91%E6%8E%A7%E9%85%8D%E7%BD%AE

相关拓展


        为了提高数据库的性能,降低主库承担的请求数量,我们还要实现读写分离,这就涉及到了根据注解来动态切换数据库,这里我们可以通过自定义注解来实现。这里用到

        

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 就重写了这个方法

定义了需要转换的数据库名称。


有不正确的地方欢迎指出,一起探讨 共勉共进。

个人原创,转载需经博主同意且标明出处

你可能感兴趣的:(JAVA)