SpringBoot数据源的配置
一、单一数据源三种配置方式
1.传统Java配置
- 创建jdbc.properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/hahaha
jdbc.username=root
jdbc.password=a123456
- 编写代码读取配置文件
package com.ihaha.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
/**
* @author: Aaron
* @Date: 2018/12/1 16:56
*/
@Configuration
@PropertySource("classpath:application.properties")
public class ConfigureOne {
@Value("${jdbc.url}")
String url;
@Value("${jdbc.driverClassName}")
String driverClassName;
@Value("${jdbc.username}")
String username;
@Value("${jdbc.password}")
String password;
@Bean
public DataSource datasource(){
HikariDataSource dataSource = new HikariDataSource();
dataSource.setPassword(password);
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setDriverClassName(driverClassName);
return dataSource;
}
}
2.springboot注入方式
- 新建一个类,用于属性注入
package com.ihaha.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author: Aaron
* @Date: 2018/12/1 17:07
*/
@Data
@ConfigurationProperties(prefix = "jdbc")
public class JdbcProperties {
private String url;
private String driverClassName;
private String username;
private String password;
}
- 通过@EnableConfigurationProperties注解,使用上面的属性
package com.ihaha.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
*@author: Aaron
*@Date: 2018/12/1 17:15
*/
@Configuration
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfig {
@Bean
public DataSource getDatasource(JdbcProperties jdbc){
DruidDataSource dataSource = new DruidDataSource ();
dataSource.setPassword(jdbc.getPassword());
dataSource.setUrl(jdbc.getUrl());
dataSource.setUsername(jdbc.getUsername());
dataSource.setDriverClassName(jdbc.getDriverClassName());
return dataSource;
}
}
3.更加优雅的注入
直接通过@ConfigurationProperties松散绑定:
package com.ihaha.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @author: Aaron
* @Date: 2018/12/1 16:44
*/
@Configuration
public class DataBaseConfig {
@Bean
@ConfigurationProperties(prefix = "jdbc")
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
}
二、多数据源的配置
业务需求:项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库。
首先禁掉spring boot自带的DataSourceAutoConfiguration
,因为它会读取application.properties
文件的spring.datasource.*
属性并自动配置单数据源。在@SpringBootApplication
注解中添加exclude
属性即可:
package com.ihaha;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* @author: Aaron
* @Date: 2018/12/1 15:57
*/
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
public class IhahaApplication {
public static void main(String[] args) {
SpringApplication.run(IhahaApplication.class);
}
}
1.配置文件
# A库
spring.datasource.demoa.url=jdbc:mysql://localhost:3306/demoa
spring.datasource.demoa.username=root
spring.datasource.demoa.password=a123456
spring.datasource.demoa.driver-class-name=com.mysql.jdbc.Driver
# 省略
# 其它库
spring.datasource.demob.url=jdbc:mysql://localhost:3306/demob
spring.datasource.demob.username=root
spring.datasource.demob.password=a123456
spring.datasource.demob.driver-class-name=com.mysql.jdbc.Driver
2.手动创建数据源
package com.ihaha.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @author: Aaron
* @Date: 2018/12/1 21:37
*/
@Configuration
public class ManyDatasource {
//创建第一个数据库连接池
@Bean(name="datasourceA")
@ConfigurationProperties(prefix = "spring.datasource.demoa")
public DataSource getDatasourceA(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
//创建第二个数据库连接池
@Bean(name="datasourceB")
@ConfigurationProperties(prefix = "spring.datasource.demob")
public DataSource getDatasourceB(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
3.配置SqlSessionFactory
package com.ihaha.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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 .support.PathMatchingResourcePatternResolver;
import tk.mybatis.spring.annotation.MapperScan;
import javax.sql.DataSource;
/**
* @author: Aaron
* @Date: 2018/12/1 21:30
*/
@Configuration
@MapperScan(basePackages = {"com.ihaha.mapper.datasourceA"}, sqlSessionFactoryRef = "sqlSessionFactory1")
public class MybatisDbAConfig {
@Autowired
@Qualifier("datasourceA")
private DataSource datasourceA;
@Bean(name = "sqlSessionFactory1")
@Primary
public SqlSessionFactory sqlSessionFactory1() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(datasourceA);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/demoA/*.xml"));
return factoryBean.getObject();
}
@Bean
@Primary
public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
// 使用上面配置的Factory
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1());
return template;
}
}
同理,配置datasourceA的SQLSessionFactory,注意,这里有一个坑,就是如果不配置:
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/demoA/*.xml"));
报异常:Invalid bound statement (not found)!
找了一上午的Bug,终于搞定!
三、动态数据源
使用动态数据源的初衷,是能在应用层做到读写分离,即在程序代码中控制不同的查询方法去连接不同的库。除了这种方法以外,数据库中间件也是个不错的选择,它的优点是数据库集群对应用来说只暴露为单库,不需要切换数据源的代码逻辑。
1.定义一个ContextHolder
package com.ihaha.config.dynamic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author: Aaron
* @Date: 2018/12/2 16:52
*/
public class DataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "datasourceA";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
log.debug("切换到{}数据源", dbType);
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
2.自定义一个DataSource
package com.ihaha.config.dynamic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
/**
* @author: Aaron
* @Date: 2018/12/2 16:59
*/
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
log.debug("数据源为{}", DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
}
3.创建动态数据源
package com.ihaha.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.ihaha.config.dynamic.DynamicDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author: Aaron
* @Date: 2018/12/1 21:37
*/
@Configuration
public class ManyDatasource {
//创建第一个数据库连接池
@Bean(name="datasourceA")
@ConfigurationProperties(prefix = "spring.datasource.demoa")
public DataSource getDatasourceA(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
//创建第二个数据库连接池
@Bean(name="datasourceB")
@ConfigurationProperties(prefix = "spring.datasource.demob")
public DataSource getDatasourceB(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Bean(name = "dynamicDataSource")
public DataSource getDynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(getDatasourceA());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap(5);
dsMap.put("datasourceA", getDatasourceA());
dsMap.put("datasourceB", getDatasourceB());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 配置@Transactional注解事务
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(getDynamicDataSource());
}
}
4.自定义注释 @DS
package com.ihaha.config.dynamic;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: Aaron
* @Date: 2018/12/1 17:10
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD
})
public @interface DS {
String value() default "datasourceA";
}
5.编写AOP切面
package com.ihaha.config.dynamic;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(DS)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
@After("@annotation(DS)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
6.SqlSessionFactory
package com.ihaha.config.dynamic;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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 .support.PathMatchingResourcePatternResolver;
import tk.mybatis.spring.annotation.MapperScan;
import javax.sql.DataSource;
/**
* @author: Aaron
* @Date: 2018/12/2 17:12
*/
@Configuration
@MapperScan(basePackages = {"com.ihaha.mapper.datasourceB"}, sqlSessionFactoryRef = "dynamicSqlSessionFactory")
public class DynamicDataSourceConfig {
@Autowired
private DynamicDataSource dynamicDataSource;
@Bean(name = "dynamicSqlSessionFactory")
@Primary
public SqlSessionFactory dynamicSqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dynamicDataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/dynamic/*.xml"));
return factoryBean.getObject();
}
@Bean
@Primary
public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
// 使用上面配置的Factory
SqlSessionTemplate template = new SqlSessionTemplate(dynamicSqlSessionFactory());
return template;
}
}
完事儿!
四、补充点
1.@Primary
2.@Qualifier
3.Springboot默认的静态资源路径为
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public
4.指定配置文件
spring:
profiles:
active: dev
5.添加拦截器
- 自定义拦截器,实现HandlerInterceptor接口
- 编写配置类,注册拦截器,实现WebMvcConfigurer接口