工作老系统添加新的功能 需要调用其他数据库的资源,应用多数据源,老大只给一晚上解决,哎,总算是磨出来(看了很多好的博客),我也记录一篇,也给后人方便。
原理简单说明,详细请看代码分析。
1. 配置多个数据源 =》 每个数据源一个key。
2. 将多个数据源注入到DynmicDataSource(自己定义,必须实现AbstractRoutingDataSource)。
3. 设置默认数据源,其他由业务手动获取key后动态注入达到切换数据源的效果。
4. 授人以鱼不如授人以渔,我看了很多同学博客,懂了原理才能进步,希望不要以完成任务为目的,多多分析,其它同学还可以有aop实现动态注入数据源,在这里我时间紧迫,且工作需求不大,在这里就不再分析aop情况。
1.pom 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion > 4.0.0</modelVersion >
<groupId > com.mybatis.datasources</groupId >
<artifactId > spring-boot-datasources</artifactId >
<version > 0.0.1-SNAPSHOT</version >
<packaging > jar</packaging >
<name > spring-boot-datasources</name >
<description > Demo project for Spring Boot</description >
<parent >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-parent</artifactId >
<version > 1.5.2.RELEASE</version >
<relativePath />
</parent >
<properties >
<project.build.sourceEncoding > UTF-8</project.build.sourceEncoding >
<project.reporting.outputEncoding > UTF-8</project.reporting.outputEncoding >
<java.version > 1.8</java.version >
</properties >
<dependencies >
<dependency >
<groupId > org.mybatis.spring.boot</groupId >
<artifactId > mybatis-spring-boot-starter</artifactId >
<version > 1.3.0</version >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId > mysql</groupId >
<artifactId > mysql-connector-java</artifactId >
<scope > runtime</scope >
</dependency >
<dependency >
<groupId > org.projectlombok</groupId >
<artifactId > lombok</artifactId >
<optional > true</optional >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-test</artifactId >
<scope > test</scope >
</dependency >
<dependency >
<groupId > com.alibaba</groupId >
<artifactId > druid</artifactId >
<version > 1.0.29</version >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-maven-plugin</artifactId >
</plugin >
</plugins >
</build >
</project >
2.application.properties 配置 主从数据源基本数据
#主数据源
primary.datasource .driverClassName = com .mysql .jdbc .Driver
primary.datasource .url = jdbc:mysql://localhost:3306 /mybatis
primary.datasource .username = root
primary.datasource .password = root
#从数据源
customer.datasource .driverClassName = com .mysql .jdbc .Driver
customer.datasource .url = jdbc:mysql://localhost:3306 /lucene
customer.datasource .username = root
customer.datasource .password = root
3.DataSourceType (数据源key)
public enum DataSourceType {
mybatis , lucene
}
4.DynamicDataSource (动态数据源 )
package com.mybatis.datasources.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* Created by guoyao on 2017/4/18.
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<DataSourceType> contextHolder=new ThreadLocal<>();
/**
* 动态数据源(继承AbstractRoutingDataSource)
*/
@Override
protected Object determineCurrentLookupKey () {
return getDatabaseType();
}
public static void setDatabaseType (DataSourceType type) {
contextHolder.set(type);
}
public static DataSourceType getDatabaseType () {
return contextHolder.get();
}
}
5.动态数据源源码分析 AbstractRoutingDataSource
protected DataSource determineTargetDataSource () {
Assert.notNull(this .resolvedDataSources, "DataSource router not initialized" );
Object lookupKey = this .determineCurrentLookupKey();
DataSource dataSource = (DataSource)this .resolvedDataSources.get (lookupKey);
if (dataSource == null && (this .lenientFallback || lookupKey == null )) {
dataSource = this .resolvedDefaultDataSource;
}
if (dataSource == null ) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]" );
} else {
return dataSource;
}
}
protected abstract Object determineCurrentLookupKey ();
6.mybatis-config 配置类编写
package com.mybatis.datasources.config;
import com.alibaba.druid.pool.DruidDataSourceFactory;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Created by guoyao on 2017/4/18.
*/
@Configuration
@MapperScan ("com.mybatis.datasources.mapper" )
public class MyBatisConfig {
@Autowired
private PrimaryDataSourceProperty primaryDataSourceProperty;
@Autowired
private CustomerDataSourceProperty customerDataSourceProperty;
/**
* 创建数据源(方法名即实例化对象名,可以由对象名指定注入@Qualifier )
*/
@Bean
public DataSource dbDataSource1 () throws Exception {
Properties props=new Properties();
props.put("driverClassName" , primaryDataSourceProperty.getDriverClassName());
props.put("url" , primaryDataSourceProperty.getUrl());
props.put("username" , primaryDataSourceProperty.getUsername());
props.put("password" , primaryDataSourceProperty.getPassword());
return DruidDataSourceFactory.createDataSource(props);
}
@Bean
public DataSource dbDataSource2 () throws Exception {
Properties props=new Properties();
props.put("driverClassName" , customerDataSourceProperty.getDriverClassName());
props.put("url" , customerDataSourceProperty.getUrl());
props.put("username" , customerDataSourceProperty.getUsername());
props.put("password" , customerDataSourceProperty.getPassword());
return DruidDataSourceFactory.createDataSource(props);
}
/**
* @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
*/
@Bean
@Primary
public DynamicDataSource ds (@Qualifier ("dbDataSource1") DataSource dbDataSource1,
@Qualifier ("dbDataSource2") DataSource dbDataSource2) {
Map<Object, Object> targetDataSources=new HashMap<>();
targetDataSources.put(DataSourceType.mybatis, dbDataSource1);
targetDataSources.put(DataSourceType.lucene, dbDataSource2);
DynamicDataSource dataSource=new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(dbDataSource1);
return dataSource;
}
/**
* 根据数据源创建SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory (DynamicDataSource ds) throws Exception {
SqlSessionFactoryBean fb=new SqlSessionFactoryBean();
fb.setDataSource(ds);
return fb.getObject();
}
/**
* 配置事务管理器
*/
@Bean
public DataSourceTransactionManager transactionManager (DynamicDataSource ds) throws Exception {
return new DataSourceTransactionManager(ds);
}
}
7.Application ***一定要关闭datasource 自动配置
package com .mybatis .datasources
import com .mybatis .datasources .config .CustomerDataSourceProperty
import com .mybatis .datasources .config .PrimaryDataSourceProperty
import org.springframework .boot .SpringApplication
import org.springframework .boot .autoconfigure .SpringBootApplication
import org.springframework .boot .autoconfigure .jdbc .DataSourceAutoConfiguration
import org.springframework .boot .context .properties .EnableConfigurationProperties
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) //关闭datasource的自动注入
@EnableConfigurationProperties({CustomerDataSourceProperty.class , PrimaryDataSourceProperty.class }) //注入配置文件
public class SpringBootDatasourcesApplication {
public static void main(String[] args) {
SpringApplication.run (SpringBootDatasourcesApplication.class , args)
}
}
8.测试
package com.mybatis.datasources.mapper;
import com.mybatis.datasources.dataobject.Book;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* Created by guoyao on 2017/4/18.
*/
@Mapper
public interface BookMapper {
List<Book> findAll();
}
@Mapper
public interface UserMapper {
List<User> findAll();
}
@Service ("bookService" )
public class BookServiceImpl implements BookService {
@Autowired
private BookMapper bookDao ;
@Override
public List<Book> findAll () {
DynamicDataSource.setDatabaseType(DataSourceType.lucene);
return bookDao.findAll();
}
}
@Service ("userService" )
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userDao ;
@Override
public List<User> findAll () {
return userDao.findAll();
}
}
/**
* Created by guoyao on 2017/4/18.
*/
@RestController
public class TestController {
@Autowired
private UserService userService;
@Autowired
private BookService bookService;
@RequestMapping ("/getUser" )
public List<User> getUser () {
return userService.findAll();
}
@RequestMapping ("/getBook" )
public List<Book> getBook () {
return bookService.findAll();
}
}