在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。
代码结构:
简要原理:
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); } }
作用:
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完全可以合为一个类
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
springboot 配置多源数据库问题。 测试了一下springboot配置多源数据库,mysql和postgresql,yml配置如下: 运行程序,出现如下错误: …… 9:30:59:048] [INFO] - org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.prepareWebA
本文向大家介绍springboot v2.0.3版本多数据源配置方法,包括了springboot v2.0.3版本多数据源配置方法的使用技巧和注意事项,需要的朋友参考一下 本篇分享的是springboot多数据源配置,在从springboot v1.5版本升级到v2.0.3时,发现之前写的多数据源的方式不可用了,捕获错误信息如: 异常:jdbcUrl is required with driver
本文向大家介绍详解MyBatis多数据源配置(读写分离),包括了详解MyBatis多数据源配置(读写分离)的使用技巧和注意事项,需要的朋友参考一下 MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用起来可能会很麻烦。 实际应用中可能存在多种结合的情况,你可以理解本文的含义,不要死板的使用。 多数据源的可能情况 1.主从 通常是MySQL一主多从的情况,本文的例子
本文向大家介绍spring+Jpa多数据源配置的方法示例,包括了spring+Jpa多数据源配置的方法示例的使用技巧和注意事项,需要的朋友参考一下 今天临下班时遇到了一个需求,我的管理平台需要从不同的数据库中获取数据信息,这就需要进行Spring的多数据源配置,对于这种配置,第一次永远都是痛苦的,不过经历了这次的折磨,今后肯定会对这种配置印象深刻。我们这里简单回顾一下流程。 我们配置了两个数据库,
本文向大家介绍SpringBoot快速配置数据源的方法,包括了SpringBoot快速配置数据源的方法的使用技巧和注意事项,需要的朋友参考一下 SpringBoot如何快速配置数据源;有如下两种方式: 通过spring-boot-starter-jdbc快速配置数据源 自定义数据源DataSource 首先我们需要明确数据源DataSource有什么作用: 通过DataSource可以获取数据库连
我刚开始使用springboot,我一直在尝试用它和spring security来配置一个项目,但不幸的是,我无法运行它。我得到了下一个错误: 嵌套的异常是org。springframework。靴子自动配置。jdbc。DataSourceProperties$DataSourceBeanCreationException:未能确定合适的驱动程序类 应用程序无法启动 描述: 配置DataSour