注意标题:这里是定义多个默认类型的数据源,不是引用了druid等其他的DataSource
环境:
这里直接贴pom文件的内容:
引入的springboot为:
org.springframework.boot spring-boot-starter-parent 2.1.2.RELEASE
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-devtools runtime org.springframework.boot spring-boot-starter-test test mysql mysql-connector-java org.projectlombok lombok 1.18.4
这里使用的是springboot的版本是2.1.2.RELEASE,这里特意标注出版本,是想说版本不同代码肯定会有差异,配置也会随之不同。(至少我们在开发过程中的代码、文档也会有版本管理吧)
在springboot中,默认的dataSource根本不需要开发人员再单独写代码配置,只需要在application.properties文件中添加以下内容即可:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/shirodemo?useUnicode=true&characterEncoding=utf8spring.datasource.username=rootspring.datasource.password=123456
配置完成,启动springboot就可以使用这个数据源了。
在我们做项目过程中,单一数据源开始不能满足我们的需要了,需要配置多个数据源,那么该怎么操作呢?
首先,还是需要在application.properties文件中书写相关配置,与默认的配置略有差异,比如我这里配置的两个数据源:
spring.datasource.secondary.driverClassName=com.mysql.cj.jdbc.Driverspring.datasource.secondary.jdbcUrl=jdbc:mysql://localhost:3306/shirodemo?useUnicode=true&characterEncoding=utf8spring.datasource.secondary.username=rootspring.datasource.secondary.password=123456spring.datasource.primary.jdbcUrl=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8spring.datasource.primary.username=rootspring.datasource.primary.password=123456spring.datasource.primary.driverClassName=com.mysql.cj.jdbc.Driver
说明:
数据源的配置还是写在spring.datasource前缀之后,不同的数据源使用用于区别的名称,例如:primary、secondary等等,在名称后面再追加驱动类名、用户名、密码、url等内容。需要注意的是这里的驱动类名与默认的情况不同,默认采用的是driver-class-name,而这里使用的是driverClassName。另外url参数名也有变化,由原来的url变成了jdbcUrl(为啥不同稍后介绍)
配置完成了,那么接下来就开始编写自定义配置类(springboot是代码配置咯),先贴代码,然后解释:
import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;@Configurationpublic class DataSourceConfig { @Bean(name = "primaryDataSource") @Qualifier("primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource(){ return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @Qualifier("secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource(){ return DataSourceBuilder.create().build(); }}
接触过springboot的基本都可以看懂这个代码,需要注意的是@ConfigurationProperties这个注解,标明了前缀。另外一个需要看的就是定义的两个数据源,使用的代码却是一样的:
DataSourceBuilder.create().build();
结果会是一样吗?答案是不同的两个数据源。
分析一下DataSourceBuilder源码,DataSourceBuilder.create()的源码如下
public static DataSourceBuilder create() { return new DataSourceBuilder<>(null); } public static DataSourceBuilder create(ClassLoader classLoader) { return new DataSourceBuilder<>(classLoader); } private DataSourceBuilder(ClassLoader classLoader) { this.classLoader = classLoader; }
这里没什么特别的地方,是告诉DataSourceBuilder使用的classLoader而已。
build方法内容如下:
@SuppressWarnings("unchecked") public T build() { Class type = getType(); DataSource result = BeanUtils.instantiateClass(type); maybeGetDriverClassName(); bind(result); return (T) result; }
第一行是获取DataSource实际类型,getType源码
private Class getType() { Class type = (this.type != null) ? this.type : findType(this.classLoader); if (type != null) { return type; } throw new IllegalStateException("No supported DataSource type found"); }
如果未制定,则调用findType方法:
private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] { "com.zaxxer.hikari.HikariDataSource", "org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource" };。。。。。 @SuppressWarnings("unchecked") public static Class findType(ClassLoader classLoader) { for (String name : DATA_SOURCE_TYPE_NAMES) { try { return (Class ) ClassUtils.forName(name, classLoader); } catch (Exception ex) { // Swallow and continue } } return null; }
从上面的代码中,我们可以看出默认寻找的是三个指定的Datasource类型,所以默认情况下如果存在第一个,那么返回的就是com.zaxxer.hikari.HikariDataSource类型,事实上Debug信息显示的确实如此:
回到build方法中,第二行就是创建DataSource实例,不去深入。第三行maybeGetDriverClassName(),根据字面意思就是获取驱动类名称:
private void maybeGetDriverClassName() { if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) { String url = this.properties.get("url"); String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName(); this.properties.put("driverClassName", driverClass); } } private void bind(DataSource result) { ConfigurationPropertySource source = new MapConfigurationPropertySource( this.properties); ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases(); aliases.addAliases("url", "jdbc-url"); aliases.addAliases("username", "user"); Binder binder = new Binder(source.withAliases(aliases)); binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result)); }
这个方法的第一行代码就告诉我们,需要指定的属性是driverClassName。如果没有这个属性,则会跳转到bind方法进行属性绑定。如果这里还没绑定成功,spring容器会去绑定。
这里解释一下为啥使用driverClassName。这是因为使用的数据源是HikariDataSource,而这个数据源的属性就是driverClassName,所以。。。不解释了。简单贴一下HikariDataSource的源码:
public class HikariDataSource extends HikariConfig implements DataSource, Closeable{ private static final Logger LOGGER = LoggerFactory.getLogger(HikariDataSource.class); private final AtomicBoolean isShutdown = new AtomicBoolean();}//类继承了HikariConfigpublic class HikariConfig implements HikariConfigMXBean{ private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class); private static final char[] ID_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30); private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5); private static final long IDLE_TIMEOUT = MINUTES.toMillis(10); private static final long MAX_LIFETIME = MINUTES.toMillis(30); private static final int DEFAULT_POOL_SIZE = 10; private static boolean unitTest = false; // Properties changeable at runtime through the HikariConfigMXBean // private volatile String catalog; private volatile long connectionTimeout; private volatile long validationTimeout; private volatile long idleTimeout; private volatile long leakDetectionThreshold; private volatile long maxLifetime; private volatile int maxPoolSize; private volatile int minIdle; private volatile String username; private volatile String password; // Properties NOT changeable at runtime // private long initializationFailTimeout; private String connectionInitSql; private String connectionTestQuery; private String dataSourceClassName; private String dataSourceJndiName; private String driverClassName; private String jdbcUrl; private String poolName; private String schema; private String transactionIsolationName; private boolean isAutoCommit; private boolean isReadOnly; private boolean isIsolateInternalQueries; private boolean isRegisterMbeans; private boolean isAllowPoolSuspension; private DataSource dataSource; private Properties dataSourceProperties; private ThreadFactory threadFactory; private ScheduledExecutorService scheduledExecutor; private MetricsTrackerFactory metricsTrackerFactory; private Object metricRegistry; private Object healthCheckRegistry; private Properties healthCheckProperties;....}
看到了吧?这里有driverClassName
补充一下:为了使用自定义数据源,需要将默认数据源自动配置排除在外,具体方法:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class})@EnableTransactionManagementpublic class ShirodemoApplication { public static void main(String[] args) { SpringApplication.run(ShirodemoApplication.class, args); }}
在SpringBootApplication注解中添加exclude,指定DataSourceAutoConfiguration.class即可。