Spring Boot 动态数据源示例(多数据源自动切换)
本文实现案例场景:
某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。
为了在开发中以最简单的方法使用,本文基于注解和aop的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。
一配置二使用
1. 启动类注册动态数据源
2. 配置文件中配置多个数据源
3. 在需要的方法上使用注解指定数据源
1、在启动类添加 @import({dynamicdatasourceregister.class, mproxytransactionmanagementconfiguration.class})
@springbootapplication @import({dynamicdatasourceregister.class}) // 注册动态多数据源 public class springbootsampleapplication { // 省略其他代码 }
2、配置文件配置内容为: (不包括项目中的其他配置,这里只是数据源相关的)
# 主数据源,默认的 spring.datasource.driver-class-name=com.mysql.jdbc.driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 # 更多数据源 custom.datasource.names=ds1,ds2 custom.datasource.ds1.driver-class-name=com.mysql.jdbc.driver custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1 custom.datasource.ds1.username=root custom.datasource.ds1.password=123456 custom.datasource.ds2.driver-class-name=com.mysql.jdbc.driver custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2 custom.datasource.ds2.username=root custom.datasource.ds2.password=123456
3、使用方法
package org.springboot.sample.service; import java.sql.resultset; import java.sql.sqlexception; import java.util.list; import org.springboot.sample.datasource.targetdatasource; import org.springboot.sample.entity.student; import org.springboot.sample.mapper.studentmapper; import org.springframework.beans.factory.annotation.autowired; import org.springframework.jdbc.core.jdbctemplate; import org.springframework.jdbc.core.rowmapper; import org.springframework.stereotype.service; /** * student service * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月12日 */ @service public class studentservice { @autowired private jdbctemplate jdbctemplate; // mybatis的mapper方法定义接口 @autowired private studentmapper studentmapper; @targetdatasource(name="ds2") public list<student> likename(string name){ return studentmapper.likename(name); } public list<student> likenamebydefaultdatasource(string name){ return studentmapper.likename(name); } /** * 不指定数据源使用默认数据源 * * @return * @author shanhy * @create 2016年1月24日 */ public list<student> getlist(){ string sql = "select id,name,score_sum,score_avg, age from student"; return (list<student>) jdbctemplate.query(sql, new rowmapper<student>(){ @override public student maprow(resultset rs, int rownum) throws sqlexception { student stu = new student(); stu.setid(rs.getint("id")); stu.setage(rs.getint("age")); stu.setname(rs.getstring("name")); stu.setsumscore(rs.getstring("score_sum")); stu.setavgscore(rs.getstring("score_avg")); return stu; } }); } /** * 指定数据源 * * @return * @author shanhy * @create 2016年1月24日 */ @targetdatasource(name="ds1") public list<student> getlistbyds1(){ string sql = "select id,name,score_sum,score_avg, age from student"; return (list<student>) jdbctemplate.query(sql, new rowmapper<student>(){ @override public student maprow(resultset rs, int rownum) throws sqlexception { student stu = new student(); stu.setid(rs.getint("id")); stu.setage(rs.getint("age")); stu.setname(rs.getstring("name")); stu.setsumscore(rs.getstring("score_sum")); stu.setavgscore(rs.getstring("score_avg")); return stu; } }); } /** * 指定数据源 * * @return * @author shanhy * @create 2016年1月24日 */ @targetdatasource(name="ds2") public list<student> getlistbyds2(){ string sql = "select id,name,score_sum,score_avg, age from student"; return (list<student>) jdbctemplate.query(sql, new rowmapper<student>(){ @override public student maprow(resultset rs, int rownum) throws sqlexception { student stu = new student(); stu.setid(rs.getint("id")); stu.setage(rs.getint("age")); stu.setname(rs.getstring("name")); stu.setsumscore(rs.getstring("score_sum")); stu.setavgscore(rs.getstring("score_avg")); return stu; } }); } }
要注意的是,在使用mybatis时,注解@targetdatasource 不能直接在接口类mapper上使用。
按上面的代码中studentmapper为接口,代码如下:
package org.springboot.sample.mapper; import java.util.list; import org.springboot.sample.entity.student; /** * studentmapper,映射sql语句的接口,无逻辑实现 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月20日 */ public interface studentmapper { // 注解 @targetdatasource 不可以在这里使用 list<student> likename(string name); student getbyid(int id); string getnamebyid(int id); }
请将下面几个类放到spring boot项目中。
dynamicdatasource.java
dynamicdatasourceaspect.java
dynamicdatasourcecontextholder.java
dynamicdatasourceregister.java
targetdatasource.java
package org.springboot.sample.datasource; import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource; /** * 动态数据源 * * @author 单红宇(365384722) * @create 2016年1月22日 */ public class dynamicdatasource extends abstractroutingdatasource { @override protected object determinecurrentlookupkey() { return dynamicdatasourcecontextholder.getdatasourcetype(); } }
package org.springboot.sample.datasource; import org.aspectj.lang.joinpoint; import org.aspectj.lang.annotation.after; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.before; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.stereotype.component; /** * 切换数据源advice * * @author 单红宇(365384722) * @create 2016年1月23日 */ @aspect @order(-1)// 保证该aop在@transactional之前执行 @component public class dynamicdatasourceaspect { private static final logger logger = loggerfactory.getlogger(dynamicdatasourceaspect.class); @before("@annotation(ds)") public void changedatasource(joinpoint point, targetdatasource ds) throws throwable { string dsid = ds.name(); if (!dynamicdatasourcecontextholder.containsdatasource(dsid)) { logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getsignature()); } else { logger.debug("use datasource : {} > {}", ds.name(), point.getsignature()); dynamicdatasourcecontextholder.setdatasourcetype(ds.name()); } } @after("@annotation(ds)") public void restoredatasource(joinpoint point, targetdatasource ds) { logger.debug("revert datasource : {} > {}", ds.name(), point.getsignature()); dynamicdatasourcecontextholder.cleardatasourcetype(); } }
package org.springboot.sample.datasource; import java.util.arraylist; import java.util.list; public class dynamicdatasourcecontextholder { private static final threadlocal<string> contextholder = new threadlocal<string>(); public static list<string> datasourceids = new arraylist<>(); public static void setdatasourcetype(string datasourcetype) { contextholder.set(datasourcetype); } public static string getdatasourcetype() { return contextholder.get(); } public static void cleardatasourcetype() { contextholder.remove(); } /** * 判断指定datasrouce当前是否存在 * * @param datasourceid * @return * @author shanhy * @create 2016年1月24日 */ public static boolean containsdatasource(string datasourceid){ return datasourceids.contains(datasourceid); } }
package org.springboot.sample.datasource; import java.util.hashmap; import java.util.map; import javax.sql.datasource; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.mutablepropertyvalues; import org.springframework.beans.propertyvalues; import org.springframework.beans.factory.support.beandefinitionregistry; import org.springframework.beans.factory.support.genericbeandefinition; import org.springframework.boot.autoconfigure.jdbc.datasourcebuilder; import org.springframework.boot.bind.relaxeddatabinder; import org.springframework.boot.bind.relaxedpropertyresolver; import org.springframework.context.environmentaware; import org.springframework.context.annotation.importbeandefinitionregistrar; import org.springframework.core.convert.conversionservice; import org.springframework.core.convert.support.defaultconversionservice; import org.springframework.core.env.environment; import org.springframework.core.type.annotationmetadata; /** * 动态数据源注册<br/> * 启动动态数据源请在启动类中(如springbootsampleapplication) * 添加 @import(dynamicdatasourceregister.class) * * @author 单红宇(365384722) * @create 2016年1月24日 */ public class dynamicdatasourceregister implements importbeandefinitionregistrar, environmentaware { private static final logger logger = loggerfactory.getlogger(dynamicdatasourceregister.class); private conversionservice conversionservice = new defaultconversionservice(); private propertyvalues datasourcepropertyvalues; // 如配置文件中未指定数据源类型,使用该默认值 private static final object datasource_type_default = "org.apache.tomcat.jdbc.pool.datasource"; // private static final object datasource_type_default = // "com.zaxxer.hikari.hikaridatasource"; // 数据源 private datasource defaultdatasource; private map<string, datasource> customdatasources = new hashmap<>(); @override public void registerbeandefinitions(annotationmetadata importingclassmetadata, beandefinitionregistry registry) { map<object, object> targetdatasources = new hashmap<object, object>(); // 将主数据源添加到更多数据源中 targetdatasources.put("datasource", defaultdatasource); dynamicdatasourcecontextholder.datasourceids.add("datasource"); // 添加更多数据源 targetdatasources.putall(customdatasources); for (string key : customdatasources.keyset()) { dynamicdatasourcecontextholder.datasourceids.add(key); } // 创建dynamicdatasource genericbeandefinition beandefinition = new genericbeandefinition(); beandefinition.setbeanclass(dynamicdatasource.class); beandefinition.setsynthetic(true); mutablepropertyvalues mpv = beandefinition.getpropertyvalues(); mpv.addpropertyvalue("defaulttargetdatasource", defaultdatasource); mpv.addpropertyvalue("targetdatasources", targetdatasources); registry.registerbeandefinition("datasource", beandefinition); logger.info("dynamic datasource registry"); } /** * 创建datasource * * @param type * @param driverclassname * @param url * @param username * @param password * @return * @author shanhy * @create 2016年1月24日 */ @suppresswarnings("unchecked") public datasource builddatasource(map<string, object> dsmap) { try { object type = dsmap.get("type"); if (type == null) type = datasource_type_default;// 默认datasource class<? extends datasource> datasourcetype; datasourcetype = (class<? extends datasource>) class.forname((string) type); string driverclassname = dsmap.get("driver-class-name").tostring(); string url = dsmap.get("url").tostring(); string username = dsmap.get("username").tostring(); string password = dsmap.get("password").tostring(); datasourcebuilder factory = datasourcebuilder.create().driverclassname(driverclassname).url(url) .username(username).password(password).type(datasourcetype); return factory.build(); } catch (classnotfoundexception e) { e.printstacktrace(); } return null; } /** * 加载多数据源配置 */ @override public void setenvironment(environment env) { initdefaultdatasource(env); initcustomdatasources(env); } /** * 初始化主数据源 * * @author shanhy * @create 2016年1月24日 */ private void initdefaultdatasource(environment env) { // 读取主数据源 relaxedpropertyresolver propertyresolver = new relaxedpropertyresolver(env, "spring.datasource."); map<string, object> dsmap = new hashmap<>(); dsmap.put("type", propertyresolver.getproperty("type")); dsmap.put("driver-class-name", propertyresolver.getproperty("driver-class-name")); dsmap.put("url", propertyresolver.getproperty("url")); dsmap.put("username", propertyresolver.getproperty("username")); dsmap.put("password", propertyresolver.getproperty("password")); defaultdatasource = builddatasource(dsmap); databinder(defaultdatasource, env); } /** * 为datasource绑定更多数据 * * @param datasource * @param env * @author shanhy * @create 2016年1月25日 */ private void databinder(datasource datasource, environment env){ relaxeddatabinder databinder = new relaxeddatabinder(datasource); //databinder.setvalidator(new localvalidatorfactory().run(this.applicationcontext)); databinder.setconversionservice(conversionservice); databinder.setignorenestedproperties(false);//false databinder.setignoreinvalidfields(false);//false databinder.setignoreunknownfields(true);//true if(datasourcepropertyvalues == null){ map<string, object> rpr = new relaxedpropertyresolver(env, "spring.datasource").getsubproperties("."); map<string, object> values = new hashmap<>(rpr); // 排除已经设置的属性 values.remove("type"); values.remove("driver-class-name"); values.remove("url"); values.remove("username"); values.remove("password"); datasourcepropertyvalues = new mutablepropertyvalues(values); } databinder.bind(datasourcepropertyvalues); } /** * 初始化更多数据源 * * @author shanhy * @create 2016年1月24日 */ private void initcustomdatasources(environment env) { // 读取配置文件获取更多数据源,也可以通过defaultdatasource读取数据库获取更多数据源 relaxedpropertyresolver propertyresolver = new relaxedpropertyresolver(env, "custom.datasource."); string dsprefixs = propertyresolver.getproperty("names"); for (string dsprefix : dsprefixs.split(",")) {// 多个数据源 map<string, object> dsmap = propertyresolver.getsubproperties(dsprefix + "."); datasource ds = builddatasource(dsmap); customdatasources.put(dsprefix, ds); databinder(ds, env); } } }
package org.springboot.sample.datasource; import java.lang.annotation.documented; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; /** * 在方法上使用,用于指定使用哪个数据源 * * @author 单红宇(365384722) * @create 2016年1月23日 */ @target({ elementtype.method, elementtype.type }) @retention(retentionpolicy.runtime) @documented public @interface targetdatasource { string name(); }
本文代码博主是经过测试后没有问题才发出来共享给大家的。对于连接池参数配置会应用到所有数据源上。
比如配置一个:
spring.datasource.maximum-pool-size=80
那么我们所有的数据源都会自动应用上。
补充:
如果你使用的是springmvc,并集成了shiro,一般按网上的配置你可能是:
<bean class="org.springframework.aop.framework.autoproxy.defaultadvisorautoproxycreator" depends-on="lifecyclebeanpostprocessor"> <property name="proxytargetclass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor"> <property name="securitymanager" ref="securitymanager"/> </bean>
那么你请不要这样做,请按下面方法配置:
<!-- aop式方法级权限检查 --> <!-- 不要使用 defaultadvisorautoproxycreator 会出现二次代理的问题,这里不详述。 mark by shanhy 2016-05-15 --> <aop:config proxy-target-class="true"/> <!-- 或者你使用了 <aop:aspectj-autoproxy proxy-target-class="true" /> 也可以。 --> <bean class="org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor"> <property name="securitymanager" ref="securitymanager"/> </bean>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: swing中Tree与滚动条用法实例分析
推荐阅读
-
使用Spring boot 的profile功能实现多环境配置自动切换
-
Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例
-
Spring配置多个数据源并实现动态切换示例
-
Spring配置多个数据源并实现动态切换示例
-
使用Spring boot 的profile功能实现多环境配置自动切换
-
spring boot+mybatis 多数据源切换(实例讲解)
-
Spring + Mybatis 项目实现动态切换数据源实例详解
-
关于Spring3 + Mybatis3整合时多数据源动态切换的问题
-
Spring Boot + Mybatis 实现动态数据源案例分析
-
spring boot + mybatis实现动态切换数据源实例代码