从零开始构建数据访问层

目的

一般公司的数据访问层有多种方式,比如说mybaitis和springjdbc。此外,还可能会使用sharding-jdbc来实现分库分表,并使用druid提供的datasource。一个合格的数据访问层应该是把他们结合起来。还可以有一些其他的功能,比如说实现慢sql的监控,缓存(当然mybaitis自己有本地缓存,这里指的是外部缓存),事务等等。

定义你的注解

注解是用来给开发者使用的。你的数据库访问层唯一对开发者java代码的侵入就应该是注解。注解用来配置数据源名,分库分表方式等等。
@Target : 用来说明该注解可以被声明在那些元素之前。这里使用ElementType.TYPE:说明该注解只能被声明在一个类前。
@Retention :用来说明该注解类的生命周期。RetentionPolicy.RUNTIME : 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。 如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
@Documented 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataSource {
    /**
     * 数据源名,缺省为初始数据源
     */
    String value() default "DEFAULT";
}

当然,为了实现分库分表,你还可以定义自己的sharding注解

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TableSharding {
    /**
     * 表名
     */
    public String tableName();
    /**
     * 分表方式
     */
    public String shardingType();
    /**
     * 根据什么字段分表
     */
    public String shardingColumn();

    public String locationRange();

    public String mapperClassReference();

}

初始化

我们知道,Spring中实现BeanFactoryPostProcessor接口的类在容器实例化任何bean之前读取bean的定义(配置元数据),并可以修改它。而BeanDefinitionRegistryPostProcessor调用发生在容器加载完所有bean定义后,在BeanFactoryPostProcessor之前,区别是在这里你可以新增bean。在这里,我们完成扫描工程中所有mapper文件。事实上,这也是是mybaits和spring整合的原理:即通过实现BeanDefinitionRegistryPostProcessor来扫描工程中的mapper文件来注册数据访问bean。(当然,你也可以一个个写xml配置)。

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        try {
                //MapperScannerConfigurer类是mybaits提供的扫描mapper文件的类,此处的MyMapperScannerConfigurerz做必要的功能扩充。
                MyMapperScannerConfigurer mapperScannerConfigurer = new MyMapperScannerConfigurer();
                String basepackge = this.getBasePackage();
                mapperScannerConfigurer.setBasePackage(basepackge);
                String mapperLocation = this.getMapperLocations();
                if (StringUtils.isNotBlank(mapperLocation)) {
                    Resource [] mapperResource = new Resource[1];
                    ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
                    try {
                        Resource[] r = resourceLoader.getResources(mapperLocation);
                        mapperResource = (Resource [])ArrayUtils.addAll(mapperResource, r);
                        mapperScannerConfigurer.setMapperLocations(mapperResource);
                    } catch (Exception e) {
                        System.out.println("resolve mapper location error: " + e);
                    }

                }

//dataSource是根据xml配置文件注入的一个multidatasource对象,代表多种可能的对象。                mapperScannerConfigurer.setDataSource(dataSource);
                Configuration configuration = createConfigurationByXml();
                mapperScannerConfigurer.setConfiguration(configuration);
                mapperScannerConfigurer.setTypeAliasesPackage(typeAliasesPackage);
            //必须得加上这一行
                mapperScannerConfigurer.setApplicationContext(this.applicationContext);
                mapperScannerConfigurer.afterPropertiesSet();
                mapperScannerConfigurer.postProcessBeanDefinitionRegistry(beanDefinitionRegistry);
                registerDALClient();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

我们的第二步初始化发生在BeanPostProcessor.postProcessBeforeInitialization中。这个方法在任何实例的初始化之前进行。这个主要是对spring jdbc中的JdbcDaoSupport中的setDataSource进行注入(@datasource)。同时,也顺便检查下mybaits的@datasource标记的数据源的合法性。

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class clazz = bean.getClass();
        while (clazz != Object.class) {
            if (clazz == MapperFactoryBean.class) {
                MapperFactoryBean factoryBean = (MapperFactoryBean) bean;
                Class actualMapper = factoryBean.getMapperInterface();
                DataSource dataSource = (DataSource)actualMapper.getAnnotation(DataSource.class);
                TableShardingRuler tableShardingRuler = (TableShardingRuler)actualMapper.getAnnotation(TableShardingRuler.class);
                //前后两个mapper的sqlSessionFactory可能指向同一个地址,
//                Environment environment = factoryBean.getSqlSession().getConfiguration().getEnvironment();
                javax.sql.DataSource db = getDataSourceForThisDao(actualMapper, beanName, dataSource, tableShardingRuler);
//                Environment environment_new = new Environment(environment.getId(), environment.getTransactionFactory(), db);
//                factoryBean.getSqlSession().getConfiguration().setEnvironment(environment_new);
                break;
            } else if (clazz == JdbcDaoSupport.class) {
                try {
                    DataSource dataSource = bean.getClass().getAnnotation(DataSource.class);
                    TableShardingRuler tableShardingRuler = bean.getClass().getAnnotation(TableShardingRuler.class);
                    javax.sql.DataSource db = getDataSourceForThisDao(bean, beanName, dataSource, tableShardingRuler);
                    Method m = clazz.getDeclaredMethod("setDataSource", javax.sql.DataSource.class);
                    m.invoke(bean, db);
                    break;
                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    e.printStackTrace();
                    break;
                }

            } else {
                clazz = clazz.getSuperclass();
            }
        }
        return bean;
    }

拦截器(mybaitis)

我们进行基于mybaits的拦截,来拦截mybaitis的数据源请求。mybaits的@interceptor可以进行拦截。(注意,springjdbc并不需要这一步)

@Intercepts({@Signature(
    type = Executor.class,
    method = "update",
    args = {MappedStatement.class, Object.class}
), @Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class DataSourceInterceptor implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        DataSource dataSource = (DataSource)Class.forName(className).getAnnotation(DataSource.class);
        MultiDataSourcesSwitcher.setDataSourceType(MultiDataSources.getDefaultDBType());
    }
}

拦截的主要目的是根据注解的值选定具体的数据源。数据源被保存在一个ThreadLocal变量中。

实际干活

是利用spring jdbc的AbstractRoutingDataSource支持多个数据源。在getconnection时,会根据上面的ThreadLocal选择具体的数据源。

配置cat监控

利用druid提供的StatFilter可以提供你对sql运行事件的一些监控。你可以继承该类。
然后在spring配置文件中作如下配置

<bean id="myStatFilterAspect" class="com.umetrip.dal.druid.MyStatFilterAspect" />

猜你喜欢

转载自blog.csdn.net/define_us/article/details/80100763