天天看點

[springboot] spring-data-jpa多資料源配置與使用

 項目依賴

<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資訊顯示的确實如此:

[springboot] spring-data-jpa多資料源配置與使用

回到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定義多個預設資料源

繼續閱讀