SpringBoot数据源的配置

一、单一数据源三种配置方式

1.传统Java配置
  1. 创建jdbc.properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/hahaha
jdbc.username=root
jdbc.password=a123456
  1. 编写代码读取配置文件
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注入方式
  1. 新建一个类,用于属性注入
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;
}
  1. 通过@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.添加拦截器
  1. 自定义拦截器,实现HandlerInterceptor接口
  2. 编写配置类,注册拦截器,实现WebMvcConfigurer接口