項目依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
多資料源配置
首先在
application.properties中添加資料源相關配置
,注意這裡的url變成了jdbc-url。
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
建立一個Spring配置類,定義兩個DataSource用來讀取
application.properties
中的不同配置。如下例子中,主資料源配置為
spring.datasource.primary
開頭的配置,第二資料源配置為
spring.datasource.secondary
開頭的配置。
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@Primary
@ConfigurationProperties(prefix="spring.datasource.secondary")
public DataSource secondaryDataSource() {
return 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;
}
build()方法内容如下:
@SuppressWarnings("unchecked")
public T build() {
Class<? extends DataSource> type = getType();
DataSource result = BeanUtils.instantiateClass(type);
maybeGetDriverClassName();
bind(result);
return (T) result;
}
第一行是擷取DataSource實際類型,getType()源碼:
private Class<? extends DataSource> getType() {
Class<? extends DataSource> type = (this.type != null) ? this.type
: findType(this.classLoader);
if (type != null) {
return type;
}
throw new IllegalStateException("No supported DataSource type found");
}
如果type未指定,則調用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<? extends DataSource> findType(ClassLoader classLoader) {
for (String name : DATA_SOURCE_TYPE_NAMES) {
try {
return (Class<? extends DataSource>) 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,jdbcUrl屬性同理:
public class HikariDataSource extends HikariConfig implements DataSource, Closeable
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariDataSource.class);
private final AtomicBoolean isShutdown = new AtomicBoolean();
}
//類繼承了HikariConfig
public 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;
....
}
新增primaryDataSource資料源的JPA配置,注意兩處注釋的地方,用于指定資料源對應的
Entity
實體和
Repository所在包路徑
,用
@Primary
區分主資料源。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages= { "com.bo.entity.p" }) //設定Repository所在位置
public class PrimaryConfig {
@Resource(name= "primaryDataSource")
private DataSource primaryDataSource;
@Primary
@Bean(name = "entityManagerPrimary")
public EntityManager entityManager(FactoryBean<EntityManagerFactory> entityManagerFactoryPrimary) throws Exception {
return entityManagerFactoryPrimary.getObject().createEntityManager();
}
@Primary
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary () throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
properties.put("hibernate.show_sql", true);
properties.put("hibernate.format_sql", true);
properties.put("hibernate.hbm2ddl.auto", "update");// eg. validate | update | create | create-drop
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
// 設定JPA的EntityManagerFactory擴充卡
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
// 設定資料源
entityManagerFactoryBean.setDataSource(primaryDataSource);
// 設定實體類所在位置
entityManagerFactoryBean.setPackagesToScan("com.bo.entity.p");
// 設定Hibernate
entityManagerFactoryBean.getJpaPropertyMap().putAll(properties);
entityManagerFactoryBean.setPersistenceUnitName("primaryPersistenceUnit");
return entityManagerFactoryBean;
}
@Primary
@Bean(name = "transactionManagerPrimary")
public PlatformTransactionManager transactionManagerPrimary(FactoryBean<EntityManagerFactory> entityManagerFactoryPrimary) throws Exception {
return new JpaTransactionManager(entityManagerFactoryPrimary.getObject());
}
}
新增secondaryDataSource據源的JPA配置,内容與第一資料源類似,具體如下:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactorySecondary",
transactionManagerRef="transactionManagerSecondary",
basePackages= { "com.bo.entity.s" }) //設定Repository所在位置
public class PrimaryConfig {
@Resource(name= "secondaryDataSource")
private DataSource secondaryDataSource;
@Primary
@Bean(name = "entityManagerSecondary")
public EntityManager entityManager(FactoryBean<EntityManagerFactory> entityManagerFactorySecondary) throws Exception {
return entityManagerFactorySecondary.getObject().createEntityManager();
}
@Primary
@Bean(name = "entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary () throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
properties.put("hibernate.show_sql", true);
properties.put("hibernate.format_sql", true);
properties.put("hibernate.hbm2ddl.auto", "update");// eg. validate | update | create | create-drop
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
// 設定JPA的EntityManagerFactory擴充卡
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
// 設定資料源
entityManagerFactoryBean.setDataSource(secondaryDataSource);
// 設定實體類所在位置
entityManagerFactoryBean.setPackagesToScan("com.bo.entity.s");
// 設定Hibernate
entityManagerFactoryBean.getJpaPropertyMap().putAll(properties);
entityManagerFactoryBean.setPersistenceUnitName("secondaryPersistenceUnit");
return entityManagerFactoryBean;
}
@Primary
@Bean(name = "transactionManagerSecondary")
public PlatformTransactionManager transactionManagerPrimary(FactoryBean<EntityManagerFactory> entityManagerFactorySecondary) throws Exception {
return new JpaTransactionManager(entityManagerFactorySecondary.getObject());
}
}
補充一下:為了使用自定義資料源,需要将預設資料源自動配置排除在外,具體方法:
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
public class ShirodemoApplication {
public static void main(String[] args) {
SpringApplication.run(ShirodemoApplication.class, args);
}
}
參考資料
Spring Boot多資料源配置與使用
springboot2.0為JPA定義多個預設資料源