SpringBoot数据源的配置

本文详细介绍了SpringBoot中单一及多数据源的配置方法,包括传统Java配置、注入方式和更优雅的松散绑定配置。同时,深入探讨了动态数据源的实现,通过AOP在不同数据源间切换,实现读写分离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
    
  2. 编写代码读取配置文件

    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;
    }
    
  2. 通过@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 org.springframework.core.io.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 org.springframework.core.io.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接口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值