springboot + mybatis配置多数据源示例
在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。
代码结构:
简要原理:
1)databasetype列出所有的数据源的key---key
2)databasecontextholder是一个线程安全的databasetype容器,并提供了向其中设置和获取databasetype的方法
3)dynamicdatasource继承abstractroutingdatasource并重写其中的方法determinecurrentlookupkey(),在该方法中使用databasecontextholder获取当前线程的databasetype
4)mybatisconfig中生成2个数据源datasource的bean---value
5)mybatisconfig中将1)和4)组成的key-value对写入到dynamicdatasource动态数据源的targetdatasources属性(当然,同时也会设置2个数据源其中的一个为dynamicdatasource的defaulttargetdatasource属性中)
6)将dynamicdatasource作为primary数据源注入到sqlsessionfactory的datasource属性中去,并且该datasource作为transactionmanager的入参来构造datasourcetransactionmanager
7)使用的时候,在dao层或service层先使用databasecontextholder设置将要使用的数据源key,然后再调用mapper层进行相应的操作,建议放在dao层去做(当然也可以使用spring aop+自定注解去做)
注意:在mapper层进行操作的时候,会先调用determinecurrentlookupkey()方法获取一个数据源(获取数据源:先根据设置去targetdatasources中去找,若没有,则选择defaulttargetdatasource),之后在进行数据库操作。
1、假设有两个数据库,配置如下
application.properties
#the first datasource jdbc.driverclassname = com.mysql.jdbc.driver jdbc.url = jdbc:mysql://xxx:3306/mytestdb?zerodatetimebehavior=converttonull&useunicode=true&characterencoding=utf-8 jdbc.username = root jdbc.password = 123 #the second datasource jdbc2.driverclassname = com.mysql.jdbc.driver jdbc2.url = jdbc:mysql://xxx:3306/mytestdb2?zerodatetimebehavior=converttonull&useunicode=true&characterencoding=utf-8 jdbc2.username = root jdbc2.password = 123
说明:在之前的配置的基础上,只增加了上述的第二个数据源。
2、databasetype
package com.xxx.firstboot.common.datasource; /** * 列出所有的数据源key(常用数据库名称来命名) * 注意: * 1)这里数据源与数据库是一对一的 * 2)databasetype中的变量名称就是数据库的名称 */ public enum databasetype { mytestdb,mytestdb2 }
作用:列举数据源的key。
3、databasecontextholder
package com.xxx.firstboot.common.datasource; /** * 作用: * 1、保存一个线程安全的databasetype容器 */ public class databasecontextholder { private static final threadlocal<databasetype> contextholder = new threadlocal<>(); public static void setdatabasetype(databasetype type){ contextholder.set(type); } public static databasetype getdatabasetype(){ return contextholder.get(); } }
作用:构建一个databasetype容器,并提供了向其中设置和获取databasetype的方法
4、dynamicdatasource
package com.xxx.firstboot.common.datasource; import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource; /** * 动态数据源(需要继承abstractroutingdatasource) */ public class dynamicdatasource extends abstractroutingdatasource { protected object determinecurrentlookupkey() { return databasecontextholder.getdatabasetype(); } }
作用:使用databasecontextholder获取当前线程的databasetype
5、mybatisconfig
package com.xxx.firstboot.common; import java.util.hashmap; import java.util.map; import java.util.properties; import javax.sql.datasource; import org.apache.ibatis.session.sqlsessionfactory; import org.mybatis.spring.sqlsessionfactorybean; import org.mybatis.spring.annotation.mapperscan; import org.springframework.beans.factory.annotation.autowired; import org.springframework.beans.factory.annotation.qualifier; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.context.annotation.primary; import org.springframework.core.env.environment; import org.springframework.core.io.support.pathmatchingresourcepatternresolver; import org.springframework.jdbc.datasource.datasourcetransactionmanager; import com.alibaba.druid.pool.druiddatasourcefactory; import com.xxx.firstboot.common.datasource.databasetype; import com.xxx.firstboot.common.datasource.dynamicdatasource; /** * springboot集成mybatis的基本入口 1)创建数据源(如果采用的是默认的tomcat-jdbc数据源,则不需要) * 2)创建sqlsessionfactory 3)配置事务管理器,除非需要使用事务,否则不用配置 */ @configuration // 该注解类似于spring配置文件 @mapperscan(basepackages = "com.xxx.firstboot.mapper") public class mybatisconfig { @autowired private environment env; /** * 创建数据源(数据源的名称:方法名可以取为xxxdatasource(),xxx为数据库名称,该名称也就是数据源的名称) */ @bean public datasource mytestdbdatasource() throws exception { properties props = new properties(); props.put("driverclassname", env.getproperty("jdbc.driverclassname")); props.put("url", env.getproperty("jdbc.url")); props.put("username", env.getproperty("jdbc.username")); props.put("password", env.getproperty("jdbc.password")); return druiddatasourcefactory.createdatasource(props); } @bean public datasource mytestdb2datasource() throws exception { properties props = new properties(); props.put("driverclassname", env.getproperty("jdbc2.driverclassname")); props.put("url", env.getproperty("jdbc2.url")); props.put("username", env.getproperty("jdbc2.username")); props.put("password", env.getproperty("jdbc2.password")); return druiddatasourcefactory.createdatasource(props); } /** * @primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错 * @qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个datasource类型的实例) */ @bean @primary public dynamicdatasource datasource(@qualifier("mytestdbdatasource") datasource mytestdbdatasource, @qualifier("mytestdb2datasource") datasource mytestdb2datasource) { map<object, object> targetdatasources = new hashmap<>(); targetdatasources.put(databasetype.mytestdb, mytestdbdatasource); targetdatasources.put(databasetype.mytestdb2, mytestdb2datasource); dynamicdatasource datasource = new dynamicdatasource(); datasource.settargetdatasources(targetdatasources);// 该方法是abstractroutingdatasource的方法 datasource.setdefaulttargetdatasource(mytestdbdatasource);// 默认的datasource设置为mytestdbdatasource 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")));// return fb.getobject(); } /** * 配置事务管理器 */ @bean public datasourcetransactionmanager transactionmanager(dynamicdatasource datasource) throws exception { return new datasourcetransactionmanager(datasource); } }
作用:
- 通过读取application.properties文件生成两个数据源(mytestdbdatasource、mytestdb2datasource)
- 使用以上生成的两个数据源构造动态数据源datasource
- @primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错(一般用于多数据源的情况下)
- @qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有两个datasource类型的实例,需要指定名称注入)
- @bean:生成的bean实例的名称是方法名(例如上边的@qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@bean注解进行注入的)
- 通过动态数据源构造sqlsessionfactory和事务管理器(如果不需要事务,后者可以去掉)
6、使用
shopmapper:
package com.xxx.firstboot.mapper; import org.apache.ibatis.annotations.param; import org.apache.ibatis.annotations.result; import org.apache.ibatis.annotations.results; import org.apache.ibatis.annotations.select; import com.xxx.firstboot.domain.shop; public interface shopmapper { @select("select * from t_shop where id = #{id}") @results(value = { @result(id = true, column = "id", property = "id"), @result(column = "shop_name", property = "shopname") }) public shop getshop(@param("id") int id); }
shopdao:
package com.xxx.firstboot.dao; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.repository; import com.xxx.firstboot.common.datasource.databasecontextholder; import com.xxx.firstboot.common.datasource.databasetype; import com.xxx.firstboot.domain.shop; import com.xxx.firstboot.mapper.shopmapper; @repository public class shopdao { @autowired private shopmapper mapper; /** * 获取shop */ public shop getshop(int id) { databasecontextholder.setdatabasetype(databasetype.mytestdb2); return mapper.getshop(id); } }
注意:首先设置了数据源的key,然后调用mapper(在mapper中会首先根据该key从动态数据源中查询出相应的数据源,之后取出连接进行数据库操作)
shopservice:
package com.xxx.firstboot.service; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.service; import com.xxx.firstboot.dao.shopdao; import com.xxx.firstboot.domain.shop; @service public class shopservice { @autowired private shopdao dao; public shop getshop(int id) { return dao.getshop(id); } }
shopcontroller:
package com.xxx.firstboot.web; import org.springframework.beans.factory.annotation.autowired; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.bind.annotation.requestparam; import org.springframework.web.bind.annotation.restcontroller; import com.xxx.firstboot.domain.shop; import com.xxx.firstboot.service.shopservice; import io.swagger.annotations.api; import io.swagger.annotations.apioperation; @restcontroller @requestmapping("/shop") @api("shopcontroller相关api") public class shopcontroller { @autowired private shopservice service; @apioperation("获取shop信息,测试多数据源") @requestmapping(value = "/getshop", method = requestmethod.get) public shop getshop(@requestparam("id") int id) { return service.getshop(id); } }
补:其实databasecontextholder和dynamicdatasource完全可以合为一个类
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读