springboot多数据源动态切换和自定义mybatis件分页插

1、配置多数据源

  增加druid依赖

    

  完整pom文件

  数据源配置文件

   

route.datasource.driver-class-name= com.mysql.jdbc.Driver
route.datasource.url= jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
route.datasource.username= root
route.datasource.password= 123456

operate.datasource.driver-class-name= com.mysql.jdbc.Driver
operate.datasource.url= jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8
operate.datasource.username= root
operate.datasource.password= 123456

multiple-datasource.properties

  初始化数据源

/**
 * 多数据源配置
 * @author zhouliang
 * @date 2017年9月20日
 */
@Configuration
@PropertySource("classpath:multiple-datasource.properties")
public class MyBatisConfig {
    @Autowired
    private Environment env;
    @Autowired MybatisSpringPageInterceptor inteceptor;

    /**
     * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
     */
    @Bean
    public DataSource operateDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("operate.datasource.driver-class-name"));
        props.put("url", env.getProperty("operate.datasource.url"));
        props.put("username", env.getProperty("operate.datasource.username"));
        props.put("password", env.getProperty("operate.datasource.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }
    @Bean
    public DataSource routeDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("route.datasource.driver-class-name"));
        props.put("url", env.getProperty("route.datasource.url"));
        props.put("username", env.getProperty("route.datasource.username"));
        props.put("password", env.getProperty("route.datasource.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(
            @Qualifier("routeDataSource") DataSource routeDataSource,
            @Qualifier("operateDataSource") DataSource operateDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        targetDataSources.put(DatabaseType.routeDS, routeDataSource);
        targetDataSources.put(DatabaseType.operateDS, operateDataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        return dataSource;
    }

    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds)
            throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(ds);// 指定数据源(这个必须有,否则报错)
        // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
        fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
        fb.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(env.getProperty("mybatis.mapperLocations")));//
        fb.setPlugins(new Interceptor[]{inteceptor});
        return fb.getObject();
    }

}

MyBatisConfig.java

  @Bean是分别注入两个数据源,

  当自动注入多个同样的bean时需要指定一个默认额,所以这里指定了一个默认的抽象数据源@primary

  数据源注入之后需要创建SqlSessionFactory

  创建抽象数据源类集成AbstractRoutingDataSource

  

 1 public class DynamicDataSource extends AbstractRoutingDataSource {
 2
 3
 4     @Override
 5     protected Object determineCurrentLookupKey() {
 6         // TODO Auto-generated method stub
 7         return DatabaseContextHolder.getDatabaseType();
 8     }
 9
10 }

DynamicDataSource.java

1 public enum DatabaseType {
2     routeDS,operateDS
3 }

DatabaseType.java

 1 public class DatabaseContextHolder {
 2     private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>();
 3
 4       public static void setDatabaseType(DatabaseType type){
 5         contextHolder.set(type);
 6       }
 7
 8       public static DatabaseType getDatabaseType(){
 9         return contextHolder.get();
10       }
11       public static void clearDatabaseType(){
12           contextHolder.remove();
13       }

DatabaseContextHolder.java

上述配置完成之后需要在配置文件中指定mybatis映射的xml文件的位置

  

  指定mybatis映射文件之后需要在项目的启动类上排除springboot默认的数据库的配置以及指定mybatis映射文件对应的接口

  

  不排除springboot默认的数据库配置类,项目启动的时候会报错启动失败

  @MapperScan指定了mybatis映射文件对应的接口所在的目录,这样避免了再每个接口上都加上@Mapper的注解

  到此两个数据源已经配置完成

2、数据源的动态切换

    这里设置了所有的实体bean都继承了一个父类,父类信息如下

    

 1 public class BaseBean implements Serializable{
 2     private int pageSize=10;
 3     private int pageNo=0;
 4     private long totalNum;
 5     private String totalMappedStatementId;
 6
 7
 8     private long shardValue = 0l;
 9
10     public BaseBean() {
11         super();
12         // TODO Auto-generated constructor stub
13     }
14
15     public BaseBean(int pageSize, int pageNo, int totalNum) {
16         super();
17         this.pageSize = pageSize;
18         this.pageNo = pageNo;
19         this.totalNum = totalNum;
20     }
21
22     public int getPageSize() {
23         return pageSize;
24     }
25
26     public long getShardValue() {
27         return shardValue;
28     }
29
30     public void setShardValue(long shardValue) {
31         this.shardValue = shardValue;
32     }
33
34     public String getTotalMappedStatementId() {
35         return totalMappedStatementId;
36     }
37
38     public void setTotalMappedStatementId(String totalMappedStatementId) {
39         this.totalMappedStatementId = totalMappedStatementId;
40     }
41
42     public void setPageSize(int pageSize) {
43         this.pageSize = pageSize;
44     }
45
46     public int getPageNo() {
47         return pageNo;
48     }
49
50     public void setPageNo(int pageNo) {
51         this.pageNo = pageNo;
52     }
53
54     public long getTotalNum() {
55         return totalNum;
56     }
57
58     public void setTotalNum(long totalNum) {
59         this.totalNum = totalNum;
60     }
61
62 }

BaseBean.java

  BaseBean中的shardValue属性是用来指定数据源的,默认值为0,其余属性是分页相关的。

  数据源的动态切换是通过spring的切面编程来实现的,通过对mybatis的映射文件对应的接口进行监控,代码如下

  

 1 @Aspect
 2 @Component
 3 public class DataSourceAspect {
 4     Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
 5
 6     @Before("execution(* zl.mybatis.mapper.*.*(..))")
 7     public void setDataSourcePgKey(JoinPoint point) {
 8         Object args[] = point.getArgs();
 9         for(Object obj:args){
10             if(obj instanceof BaseBean){
11                 BaseBean bean = (BaseBean) obj;
12                 if(Common.DB_0==bean.getShardValue()){
13                     logger.info("===========================使用数据源DB_route=======================");
14                     DatabaseContextHolder.setDatabaseType(DatabaseType.routeDS);
15                 }else{
16                     logger.info("===========================使用数据源DB_operate=======================");
17                     DatabaseContextHolder.setDatabaseType(DatabaseType.operateDS);
18                 }
19                 break;
20             }
21         }
22     }
23
24 }

DataSourceAspect.java

  @Before("execution(* zl.mybatis.mapper.*.*(..))")是对mybatis的映射文件对应的接口进行监控,根据获取到的参数实体类判断里面的shardValue的值来决定使用哪个数据源

  

  完成这一步之后springboot的多数据源动态切换完成了,接下来继续

3、实现自定义mybatis的分页插件

    mybatis本身提供了对数据库操作的拦截器,所以实现自定义分页的时候只需要实现这个接口自定义里面的拦截方法。这里我是只拦截了查询的方法

    

    具体代码如下

    

  1 @Component
  2 @Intercepts({ @Signature(type = Executor.class, method = "query",
  3     args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
  4 public class MybatisSpringPageInterceptor implements Interceptor {
  5
  6     private static final Logger logger = LoggerFactory.getLogger(MybatisSpringPageInterceptor.class.getName());
  7
  8
  9     @SuppressWarnings("unused")
 10     public Object intercept(Invocation arg0) throws Throwable {
 11         MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0];
 12         Object parameter = arg0.getArgs()[1];
 13         BoundSql boundSql = mappedStatement.getBoundSql(parameter);
 14         if (null == boundSql || StringUtils.isBlank(boundSql.getSql())) {
 15             return null;
 16         }
 17         RowBounds rowBounds = (RowBounds) arg0.getArgs()[2];
 18         Object parameterObject = boundSql.getParameterObject();
 19         BaseBean model = null;
 20         if (parameterObject instanceof BaseBean) {
 21             model = (BaseBean) parameterObject;
 22         } else {
 23             BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, boundSql.getSql());
 24             arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
 25             return arg0.proceed();
 26         }
 27         if (null == model) {
 28             throw new Exception("无法获取分页参数.");
 29         }
 30         if (model.getPageNo() == -1) {
 31             BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, boundSql.getSql());
 32             arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
 33             return arg0.proceed();
 34         }
 35         String shardSql = boundSql.getSql();
 36         queryTotal(mappedStatement, shardSql, parameterObject, boundSql,model);
 37
 38         if (null == rowBounds || rowBounds == RowBounds.DEFAULT) {
 39             rowBounds = new RowBounds(model.getPageSize() * (model.getPageNo() - 1), model.getPageSize());
 40         }
 41         String pagesql = getLimitSql(shardSql, rowBounds.getOffset(), rowBounds.getLimit());
 42         arg0.getArgs()[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
 43         BoundSql newBoundSql =  copyFromBoundSql(mappedStatement, boundSql, pagesql);
 44         arg0.getArgs()[0] = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
 45         return arg0.proceed();
 46     }
 47
 48     public static class BoundSqlSqlSource implements SqlSource {
 49         BoundSql boundSql;
 50
 51         public BoundSqlSqlSource(BoundSql boundSql) {
 52             this.boundSql = boundSql;
 53         }
 54
 55         public BoundSql getBoundSql(Object parameterObject) {
 56             return boundSql;
 57         }
 58     }
 59     private String getLimitSql(String sql, int start, int end) throws Exception{
 60         if(sql ==null){
 61             throw new Exception("execute sql is empty.");
 62         }
 63         StringBuffer sqlBuffer = new StringBuffer(sql.length()+300);
 64         sqlBuffer.append(sql);
 65         sqlBuffer.append(" LIMIT ").append(start).append(",").append(end);
 66         return sqlBuffer.toString();
 67     }
 68     private void queryTotal(MappedStatement mappedStatement, String replaceSql, Object parameterObject, BoundSql boundSql,BaseBean model) throws Exception{
 69         StringBuffer countSql = new StringBuffer();
 70
 71         if(model.getTotalMappedStatementId()!=null && model.getTotalMappedStatementId().length()>0){
 72             MappedStatement totalMappedStatement=mappedStatement.getConfiguration().getMappedStatement(model.getTotalMappedStatementId());
 73             BoundSql totalBoundSql = totalMappedStatement.getBoundSql(parameterObject);
 74
 75             countSql.append(totalBoundSql.getSql());
 76         }else{
 77             // 未指定,自动拼装
 78             countSql.append("SELECT COUNT(1) FROM (").append(replaceSql).append(") as total");
 79         }
 80
 81         Connection conn = null;
 82         PreparedStatement ps = null;
 83         ResultSet rs = null;
 84         try {
 85             conn = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
 86             if (logger.isDebugEnabled()) {
 87                 logger.debug(countSql.toString());
 88             }
 89             ps = conn.prepareStatement(countSql.toString());
 90             BoundSql countBS = copyFromBoundSql(mappedStatement, boundSql, countSql.toString());
 91             setParameters(ps, mappedStatement, countBS, parameterObject);
 92             rs = ps.executeQuery();
 93             if (rs.next()) {
 94                 model.setTotalNum(rs.getLong(1));
 95             }
 96         } catch (Exception e) {
 97             logger.error(e.getMessage(), e);
 98             throw new Exception(e.getMessage(), e);
 99         } finally {
100             try {
101                 if (null != rs) {
102                     rs.close();
103                 }
104             } catch (Exception e) {
105                 logger.error("rs.close() error!", e);
106             }
107             try {
108                 if (null != ps) {
109                     ps.close();
110                 }
111             } catch (Exception e) {
112                 logger.error("ps.close() error!", e);
113             }
114             try {
115                 if (null != conn) {
116                     conn.close();
117                 }
118             } catch (Exception e) {
119                 logger.error("conn.close() error!", e);
120             }
121         }
122     }
123     protected MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
124         Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
125         builder.resource(ms.getResource());
126         builder.fetchSize(ms.getFetchSize());
127         builder.statementType(ms.getStatementType());
128         builder.keyGenerator(ms.getKeyGenerator());
129         // builder.keyProperty(ms.getKeyProperties());
130         builder.timeout(ms.getTimeout());
131         builder.parameterMap(ms.getParameterMap());
132         builder.resultMaps(ms.getResultMaps());
133         builder.cache(ms.getCache());
134         MappedStatement newMs = builder.build();
135         return newMs;
136     }
137
138     /**
139      *
140      * @param ps
141      * @param mappedStatement
142      * @param boundSql
143      * @param parameterObject
144      * @throws SQLException
145      */
146     private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
147         ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
148         List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
149         if (parameterMappings != null) {
150             Configuration configuration = mappedStatement.getConfiguration();
151             TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
152             MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
153             for (int i = 0; i < parameterMappings.size(); i++) {
154                 ParameterMapping parameterMapping = parameterMappings.get(i);
155                 if (parameterMapping.getMode() != ParameterMode.OUT) {
156                     Object value;
157                     String propertyName = parameterMapping.getProperty();
158                     if (parameterObject == null) {
159                         value = null;
160                     } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
161                         value = parameterObject;
162                     } else if (boundSql.hasAdditionalParameter(propertyName)) {
163                         value = boundSql.getAdditionalParameter(propertyName);
164                     }  else {
165                         value = metaObject == null ? null : metaObject.getValue(propertyName);
166                     }
167                     TypeHandler typeHandler = parameterMapping.getTypeHandler();
168                     if (typeHandler == null) {
169                         throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
170                     }
171                     logger.debug(i + 1 + ":" + value);
172                     typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
173                 }
174             }
175         }
176     }
177     @Override
178     public Object plugin(Object arg0) {
179         return Plugin.wrap(arg0, this);
180     }
181
182     public void setProperties(Properties arg0) {
183
184     }
185
186
187     private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) {
188         BoundSql newBoundSql = new BoundSql(ms.getConfiguration(),sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
189         for (ParameterMapping mapping : boundSql.getParameterMappings()) {
190             String prop = mapping.getProperty();
191             if (boundSql.hasAdditionalParameter(prop)) {
192                 newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
193             }
194         }
195         return newBoundSql;
196     }
197
198 }

MybatisSpringPageInterceptor.java

  插件重写完成之后需要在注入SqlSessionFactory的时候指定这个插件,下面的代码是在MyBatisConfig.java中

    

现在已经完成了多数据源的动态切换以及自定义mybatis的分页查询了,剩下的测试步骤就不再啰嗦了,排版比较乱大家就将就着看吧!

项目的完整代码

时间: 2024-10-08 09:48:04

springboot多数据源动态切换和自定义mybatis件分页插的相关文章

Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法(转)

一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基本上没有什么问题,但唯一可能出现问题的就是在hibernate做添加操作生成主键策略的时候.因为我们都知道hibernate的数据库本地方言会针对不同的数据库采用不同的主键生成策略. 所以针对这一问题不得不采用自定义的主键生成策略,自己写一个主键生成器的表来维护主键生成方式或以及使用其他的方式来生成

基于spring的aop实现多数据源动态切换

https://lanjingling.github.io/2016/02/15/spring-aop-dynamicdatasource/ 基于spring的aop实现多数据源动态切换 发表于 2016-02-15   |   分类于 spring  | 一.多数据源动态切换原理 项目中我们经常会遇到多数据源的问题,尤其是数据同步或定时任务等项目更是如此:又例如:读写分离数据库配置的系统. 1.多数据源设置: 1)静态数据源切换:一般情况下,我们可以配置多个数据源,然后为每个数据源写一套对应的

基于Struts2 Spring ibatis Oracle10g架构 多数据源动态切换实例

一.概述 基于Spring动态配置多数据源,在大型的应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效的提高系统的水平伸缩性,而这样的解决方案就会补同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求以及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据. Spring配置多个数据源的方式和具体使用过程,Spring对于多数据源,以数据库表为参照,大体上可以分为两大类情况: 1.表级上的跨数据库,即对于不同的数据库却有不相同的表(表名和表结构完全

spring 数据源动态切换 与dubbo服务

1:问题描述 项目用了spring数据源动态切换,服务用的是dubbo.在运行一段时间后程序异常,更新操作没有切换到主库上.这个问题在先调用读操作后再调用写操作会出现.经分析原因有3: 第一:当程序运行一段时间后调用duboo服务时,读操作与写操作有可能会在一个线程里,当这种情况出现时 2:数据源配置 <idclass>    <name>       <key-type>          <keyvalue-ref/>          <keyv

koala 多数据源动态切换

koala 特点:多数据源动态切换,数据源分组和负载均衡(轮询) 项目源码以及demo:https://github.com/zeq9069/koala 请大家关注一下哦!!哈哈! 最近,在开发项目的时候后,要用到多个数据源的动态切换,于是就开发了koala 这个小小得框架,koala支持多数据源的切换,数据源的分组和负载均衡(轮询)! 只需要将jar引入到你自己的项目中之后,然后进行简单配置就可以使用了!!使用起来 非常方便,该项目提供了三个核心的注解@ChangeTo,@DataSource

spring+mybatis多数据源动态切换

spring mvc+mybatis+多数据源切换 选取oracle,mysql作为例子切换数据源.oracle为默认数据源,在测试的action中,进行mysql和oracle的动态切换. web.xml <context-param> <param-name>webAppRootKey</param-name> <param-value>trac</param-value> </context-param> <!-- Spr

springAOP实现基于注解的数据源动态切换

需求 代码实现读写数据库分离 武器 spring3.0以上版本 实现思路 1.继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,自定义数据源路由. 2.实现数据源类型管理工具,诸如DBContextHolder,包含设置和读取当前数据源配置. 3.实现数据源切换的AOP. 4.自定义只读注解,诸如@ReadOnlyKey. 5.配置transactionManager,实现aop. 代码示例 1.自定义的

springboot添加多数据源 以及 动态添加数据源动态切换数据源

<!-- Druid 数据连接池依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> //指定使用Druid做数据源spring.datasource.type=com.alibaba.druid.pool.Dru

基于Spring+Mybatis的多数据源动态切换

Spring单数据源直接在<bean id="dataSource">下配置数据源的各种连接参数.但动态数据源需要配置个各个数据源例如ds1.ds2等.然后在dataSource中动态根据传递过来的参数动态调用不同的数据源. 1.当进行访问时,首先通过DbContextHolder.setDbType("ds1");设置需要使用的数据源.DbContextHolder就是一个用来存储数据源信息的类,其中通过ThreadLocal来记录数据源信息. 2.D