環境版本:php
官方文檔html
5.1.2. Annotation-based Configuration
The Spring Data JPA repositories support can be activated not only through an XML namespace but also by using an annotation through JavaConfig, as shown in the following example:java
Example 51. Spring Data JPA repositories using JavaConfigmysql
@Configuration @EnableJpaRepositories @EnableTransactionManagement class ApplicationConfig { @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); return builder.setType(EmbeddedDatabaseType.HSQL).build(); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setGenerateDdl(true); LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan("com.acme.domain"); factory.setDataSource(dataSource()); return factory; } @Bean public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { JpaTransactionManager txManager = new JpaTransactionManager(); txManager.setEntityManagerFactory(entityManagerFactory); return txManager; } } 複製代碼
文檔中說明,基於註解的配置包含三個關鍵Bean:git
PlatformTransactionManager
——事務管理LocalContainerEntityManagerFactoryBean
——實體類管理DataSource
——數據源同時還有一個關鍵註解@EnableJpaRepositories
github
官方文檔web
多數據源使用@Primary
指明優先級spring
You can even go further by leveraging what
DataSourceProperties
does for you — that is, by providing a default embedded database with a sensible username and password if no URL is provided. You can easily initialize aDataSourceBuilder
from the state of anyDataSourceProperties
object, so you could also inject the DataSource that Spring Boot creates automatically. However, that would split your configuration into two namespaces:url
,username
,password
,type
, anddriver
onspring.datasource
and the rest on your custom namespace (app.datasource
). To avoid that, you can redefine a customDataSourceProperties
on your custom namespace, as shown in the following example:sql@Bean @Primary @ConfigurationProperties("app.datasource") public DataSourceProperties dataSourceProperties() { return new DataSourceProperties(); } @Bean @ConfigurationProperties("app.datasource.configuration") public HikariDataSource dataSource(DataSourceProperties properties) { return properties.initializeDataSourceBuilder().type(HikariDataSource.class) .build(); } 複製代碼
84.2 Configure Two DataSources
If you need to configure multiple data sources, you can apply the same tricks that are described in the previous section. You must, however, mark one of the
DataSource
instances as@Primary
, because various auto-configurations down the road expect to be able to get one by type.數據庫If you create your own
DataSource
, the auto-configuration backs off. In the following example, we provide the exact same feature set as the auto-configuration provides on the primary data source:@Bean @Primary @ConfigurationProperties("app.datasource.first") public DataSourceProperties firstDataSourceProperties() { return new DataSourceProperties(); } @Bean @Primary @ConfigurationProperties("app.datasource.first.configuration") public HikariDataSource firstDataSource() { return firstDataSourceProperties().initializeDataSourceBuilder() .type(HikariDataSource.class).build(); } @Bean @ConfigurationProperties("app.datasource.second") public BasicDataSource secondDataSource() { return DataSourceBuilder.create().type(BasicDataSource.class).build(); } 複製代碼
或者使用兩個EntityManagers
,並結合@EnableJpaRepositories
進行配置
84.10 Use Two EntityManagers
Even if the default
EntityManagerFactory
works fine, you need to define a new one. Otherwise, the presence of the second bean of that type switches off the default. To make it easy to do, you can use the convenientEntityManagerBuilder
provided by Spring Boot. Alternatively, you can just theLocalContainerEntityManagerFactoryBean
directly from Spring ORM, as shown in the following example:// add two data sources configured as above @Bean public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory( EntityManagerFactoryBuilder builder) { return builder .dataSource(customerDataSource()) .packages(Customer.class) .persistenceUnit("customers") .build(); } @Bean public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory( EntityManagerFactoryBuilder builder) { return builder .dataSource(orderDataSource()) .packages(Order.class) .persistenceUnit("orders") .build(); } 複製代碼
The configuration above almost works on its own. To complete the picture, you need to configure
TransactionManagers
for the twoEntityManagers
as well. If you mark one of them as@Primary
, it could be picked up by the defaultJpaTransactionManager
in Spring Boot. The other would have to be explicitly injected into a new instance. Alternatively, you might be able to use a JTA transaction manager that spans both.If you use Spring Data, you need to configure
@EnableJpaRepositories
accordingly, as shown in the following example:@Configuration @EnableJpaRepositories(basePackageClasses = Customer.class, entityManagerFactoryRef = "customerEntityManagerFactory") public class CustomerConfiguration { ... } @Configuration @EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "orderEntityManagerFactory") public class OrderConfiguration { ... } 複製代碼
自動裝配類中聲明瞭配置Bean和條件裝配順序
package org.springframework.boot.autoconfigure.data.jpa;
/** * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's JPA Repositories. * <p> * Activates when there is a bean of type {@link javax.sql.DataSource} configured in the * context, the Spring Data JPA * {@link org.springframework.data.jpa.repository.JpaRepository} type is on the classpath, * and there is no other, existing * {@link org.springframework.data.jpa.repository.JpaRepository} configured. * <p> * Once in effect, the auto-configuration is the equivalent of enabling JPA repositories * using the {@link org.springframework.data.jpa.repository.config.EnableJpaRepositories} * annotation. * <p> * This configuration class will activate <em>after</em> the Hibernate auto-configuration. * * @author Phillip Webb * @author Josh Long * @see EnableJpaRepositories */
@Configuration
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class,
JpaRepositoryConfigExtension.class })
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true", matchIfMissing = true)
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class,
TaskExecutionAutoConfiguration.class })
public class JpaRepositoriesAutoConfiguration {
@Bean
@Conditional(BootstrapExecutorCondition.class)
public EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer( Map<String, AsyncTaskExecutor> taskExecutors) {
return (builder) -> {
AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor(
taskExecutors);
if (bootstrapExecutor != null) {
builder.setBootstrapExecutor(bootstrapExecutor);
}
};
}
private AsyncTaskExecutor determineBootstrapExecutor( Map<String, AsyncTaskExecutor> taskExecutors) {
if (taskExecutors.size() == 1) {
return taskExecutors.values().iterator().next();
}
return taskExecutors
.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
}
private static final class BootstrapExecutorCondition extends AnyNestedCondition {
BootstrapExecutorCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "bootstrap-mode", havingValue = "deferred", matchIfMissing = false)
static class DeferredBootstrapMode {
}
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "bootstrap-mode", havingValue = "lazy", matchIfMissing = false)
static class LazyBootstrapMode {
}
}
}
複製代碼
引入的配置類中聲明瞭EnableJpaRepositories
註解
package org.springframework.boot.autoconfigure.data.jpa;
class JpaRepositoriesAutoConfigureRegistrar extends AbstractRepositoryConfigurationSourceSupport {
private BootstrapMode bootstrapMode = null;
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableJpaRepositories.class;
}
@Override
protected Class<?> getConfiguration() {
return EnableJpaRepositoriesConfiguration.class;
}
@Override
protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
return new JpaRepositoryConfigExtension();
}
@Override
protected BootstrapMode getBootstrapMode() {
return (this.bootstrapMode == null) ? super.getBootstrapMode()
: this.bootstrapMode;
}
@Override
public void setEnvironment(Environment environment) {
super.setEnvironment(environment);
configureBootstrapMode(environment);
}
private void configureBootstrapMode(Environment environment) {
String property = environment
.getProperty("spring.data.jpa.repositories.bootstrap-mode");
if (StringUtils.hasText(property)) {
this.bootstrapMode = BootstrapMode
.valueOf(property.toUpperCase(Locale.ENGLISH));
}
}
@EnableJpaRepositories
private static class EnableJpaRepositoriesConfiguration {}
}
複製代碼
EnableJpaRepositories
註解定義
package org.springframework.data.jpa.repository.config;
/** * Annotation to enable JPA repositories. Will scan the package of the annotated configuration class for Spring Data * repositories by default. * * @author Oliver Gierke * @author Thomas Darimont */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(JpaRepositoriesRegistrar.class)
public @interface EnableJpaRepositories {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
String repositoryImplementationPostfix() default "Impl";
String namedQueriesLocation() default "";
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
/** * 實體管理工廠 * @return */
String entityManagerFactoryRef() default "entityManagerFactory";
/** * 事務管理 * @return */
String transactionManagerRef() default "transactionManager";
boolean considerNestedRepositories() default false;
boolean enableDefaultTransactions() default true;
/** * Configures when the repositories are initialized in the bootstrap lifecycle. {@link BootstrapMode#DEFAULT} * (default) means eager initialization except all repository interfaces annotated with {@link Lazy}, * {@link BootstrapMode#LAZY} means lazy by default including injection of lazy-initialization proxies into client * beans so that those can be instantiated but will only trigger the initialization upon first repository usage (i.e a * method invocation on it). This means repositories can still be uninitialized when the application context has * completed its bootstrap. {@link BootstrapMode#DEFERRED} is fundamentally the same as {@link BootstrapMode#LAZY}, * but triggers repository initialization when the application context finishes its bootstrap. * * @return * @since 2.1 */
BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
}
複製代碼
查找關鍵Bean的註冊時機
package org.springframework.boot.autoconfigure.orm.jpa;
@Configuration
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class })
@Conditional(HibernateEntityManagerCondition.class)
@EnableConfigurationProperties(JpaProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {}
複製代碼
進入配置Bean
package org.springframework.boot.autoconfigure.orm.jpa;
@Configuration
@EnableConfigurationProperties(HibernateProperties.class)
@ConditionalOnSingleCandidate(DataSource.class)
class HibernateJpaConfiguration extends JpaBaseConfiguration {}
複製代碼
在其父類JpaBaseConfiguration
中聲明瞭裝配時機和條件
package org.springframework.boot.autoconfigure.orm.jpa;
/** * 該類在2.0版本進行了重構 * @since 2.0.0 */
@Configuration
@EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
@Bean
@ConditionalOnMissingBean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
if (this.transactionManagerCustomizers != null) {
// 默認使用方法名稱
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
@Bean
@ConditionalOnMissingBean
public JpaVendorAdapter jpaVendorAdapter() {
AbstractJpaVendorAdapter adapter = createJpaVendorAdapter();
adapter.setShowSql(this.properties.isShowSql());
adapter.setDatabase(this.properties.determineDatabase(this.dataSource));
adapter.setDatabasePlatform(this.properties.getDatabasePlatform());
adapter.setGenerateDdl(this.properties.isGenerateDdl());
return adapter;
}
@Bean
@ConditionalOnMissingBean
public EntityManagerFactoryBuilder entityManagerFactoryBuilder( JpaVendorAdapter jpaVendorAdapter, ObjectProvider<PersistenceUnitManager> persistenceUnitManager, ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers) {
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(
jpaVendorAdapter, this.properties.getProperties(),
persistenceUnitManager.getIfAvailable());
customizers.orderedStream()
.forEach((customizer) -> customizer.customize(builder));
return builder;
}
@Bean
@Primary
@ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class,
EntityManagerFactory.class })
public LocalContainerEntityManagerFactoryBean entityManagerFactory( EntityManagerFactoryBuilder factoryBuilder) {
Map<String, Object> vendorProperties = getVendorProperties();
customizeVendorProperties(vendorProperties);
return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan())
.properties(vendorProperties).mappingResources(getMappingResources())
.jta(isJta()).build();
}
}
複製代碼
爬完文檔和源碼,對須要配置Bean有了基本瞭解,新建工程進行多數據源測試
以當前最新版本SpringBoot進行測試
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.fjy8018</groupId>
<artifactId>jpadatasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jpadatasource</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
經過包路徑區分主庫和備份庫
主庫實體類
package top.fjy8018.jpadatasource.entity.primary;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
/** * @author F嘉陽 * @date 2019-03-30 9:11 */
@Data
@Entity
public class Product {
@Id
private Integer id;
private String name;
private Integer price;
}
複製代碼
備份庫實體類
package top.fjy8018.jpadatasource.entity.backup;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
/** * @author F嘉陽 * @date 2019-03-30 9:36 */
@Data
@Entity(name = "tb_order")
public class Order {
@Id
private Integer id;
private String orderName;
private Integer price;
}
複製代碼
若此處不指定表名爲tb_order
則會與MySQL關鍵字衝突致使建表失敗
報錯:
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL " create table Order ( id integer not null, orderName varchar(255), price integer, primary key (id) ) engine=InnoDB" via JDBC Statement
...
複製代碼
package top.fjy8018.jpadatasource.repository.backup;
import org.springframework.data.jpa.repository.JpaRepository;
import top.fjy8018.jpadatasource.entity.backup.Order;
/** * @author F嘉陽 * @date 2019-03-30 9:12 */
public interface OrderRepository extends JpaRepository<Order, Integer> {
}
複製代碼
package top.fjy8018.jpadatasource.repository.primary;
import org.springframework.data.jpa.repository.JpaRepository;
import top.fjy8018.jpadatasource.entity.primary.Product;
/** * @author F嘉陽 * @date 2019-03-30 9:12 */
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
複製代碼
官方配置樣例:
Both data sources are also bound for advanced customizations. For instance, you could configure them as follows:
app.datasource.first.url=jdbc:mysql://localhost/first app.datasource.first.username=dbuser app.datasource.first.password=dbpass app.datasource.first.configuration.maximum-pool-size=30 app.datasource.second.url=jdbc:mysql://localhost/second app.datasource.second.username=dbuser app.datasource.second.password=dbpass app.datasource.second.max-total=30 複製代碼
You can apply the same concept to the secondary
DataSource
as well, as shown in the following example:@Bean @Primary @ConfigurationProperties("app.datasource.first") public DataSourceProperties firstDataSourceProperties() { return new DataSourceProperties(); } @Bean @Primary @ConfigurationProperties("app.datasource.first.configuration") public HikariDataSource firstDataSource() { return firstDataSourceProperties().initializeDataSourceBuilder() .type(HikariDataSource.class).build(); } @Bean @ConfigurationProperties("app.datasource.second") public DataSourceProperties secondDataSourceProperties() { return new DataSourceProperties(); } @Bean @ConfigurationProperties("app.datasource.second.configuration") public BasicDataSource secondDataSource() { return secondDataSourceProperties().initializeDataSourceBuilder() .type(BasicDataSource.class).build(); } 複製代碼
The preceding example configures two data sources on custom namespaces with the same logic as Spring Boot would use in auto-configuration. Note that each
configuration
sub namespace provides advanced settings based on the chosen implementation.
參照編寫配置文件,此處注意SpringBoot 2.x的驅動變化
spring:
datasource:
first:
driver-class-name: com.mysql.cj.jdbc.Driver
username: financial_adminer
password: financial_adminer_pass
url: jdbc:mysql://localhost/jpa_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
configuration:
maximum-pool-size: 30
second:
driver-class-name: com.mysql.cj.jdbc.Driver
username: financial_adminer
password: financial_adminer_pass
url: jdbc:mysql://localhost/jpa_test_bak?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
configuration:
maximum-pool-size: 20
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQL55Dialect
properties:
hibernate:
format_sql: true
use_sql_comments: true
複製代碼
配置類
package top.fjy8018.jpadatasource.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/** * @author F嘉陽 * @date 2019-03-30 9:24 */
@Configuration
public class DataAccessConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first.configuration")
public HikariDataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.datasource.second.configuration")
public HikariDataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
}
複製代碼
配置完成後發現只在主庫自動建表,備份庫無變化,屬於預期狀況,由於目前僅配置了三個必須Bean的其中一個
繼續配置實體管理和事務管理Bean
官方文檔樣例
84.10 Use Two EntityManagers
Even if the default
EntityManagerFactory
works fine, you need to define a new one. Otherwise, the presence of the second bean of that type switches off the default. To make it easy to do, you can use the convenientEntityManagerBuilder
provided by Spring Boot. Alternatively, you can just theLocalContainerEntityManagerFactoryBean
directly from Spring ORM, as shown in the following example:// add two data sources configured as above @Bean public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory( EntityManagerFactoryBuilder builder) { return builder .dataSource(customerDataSource()) .packages(Customer.class) .persistenceUnit("customers") .build(); } @Bean public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory( EntityManagerFactoryBuilder builder) { return builder .dataSource(orderDataSource()) .packages(Order.class) .persistenceUnit("orders") .build(); } 複製代碼
The configuration above almost works on its own. To complete the picture, you need to configure
TransactionManagers
for the twoEntityManagers
as well. If you mark one of them as@Primary
, it could be picked up by the defaultJpaTransactionManager
in Spring Boot. The other would have to be explicitly injected into a new instance. Alternatively, you might be able to use a JTA transaction manager that spans both.If you use Spring Data, you need to configure
@EnableJpaRepositories
accordingly, as shown in the following example:@Configuration @EnableJpaRepositories(basePackageClasses = Customer.class, entityManagerFactoryRef = "customerEntityManagerFactory") public class CustomerConfiguration { ... } @Configuration @EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "orderEntityManagerFactory") public class OrderConfiguration { ... } 複製代碼
工程配置
package top.fjy8018.jpadatasource.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import top.fjy8018.jpadatasource.entity.backup.Order;
import top.fjy8018.jpadatasource.entity.primary.Product;
import top.fjy8018.jpadatasource.repository.backup.OrderRepository;
import top.fjy8018.jpadatasource.repository.primary.ProductRepository;
import javax.sql.DataSource;
/** * @author F嘉陽 * @date 2019-03-30 9:24 */
@Configuration
public class DataAccessConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first.configuration")
public HikariDataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.datasource.second.configuration")
public HikariDataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("firstDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(Product.class)
.persistenceUnit("first")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("secondDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(Order.class)
.persistenceUnit("second")
.build();
}
@Bean
public PlatformTransactionManager primaryTransactionManager( @Qualifier("firstEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
@Bean
public PlatformTransactionManager backupTransactionManager( @Qualifier("secondEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
@EnableJpaRepositories(basePackageClasses = ProductRepository.class,
entityManagerFactoryRef = "firstEntityManagerFactory")
@Primary
public class PrimaryConfiguration {
}
@EnableJpaRepositories(basePackageClasses = OrderRepository.class,
entityManagerFactoryRef = "secondEntityManagerFactory")
public class secondConfiguration {
}
}
複製代碼
此時配置直接啓動會報錯
Method requestMappingHandlerMapping in org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration required a single bean, but 2 were found:
- firstEntityManagerFactory: defined by method 'firstEntityManagerFactory' in class path resource [top/fjy8018/jpadatasource/config/DataAccessConfig.class]
- secondEntityManagerFactory: defined by method 'secondEntityManagerFactory' in class path resource [top/fjy8018/jpadatasource/config/DataAccessConfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
複製代碼
意思是自動裝配時發現兩個類型一致的Bean,主要指定優先級,而官方文檔對此處可能出現的問題並未提示
指定firstEntityManagerFactory
爲優先Bean,同時文檔樣例建立了事務管理器但未配置到@EnableJpaRepositories
註解中,此處也一併配置
最終配置類以下
package top.fjy8018.jpadatasource.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import top.fjy8018.jpadatasource.entity.backup.Order;
import top.fjy8018.jpadatasource.entity.primary.Product;
import top.fjy8018.jpadatasource.repository.backup.OrderRepository;
import top.fjy8018.jpadatasource.repository.primary.ProductRepository;
import javax.sql.DataSource;
/** * @author F嘉陽 * @date 2019-03-30 9:24 */
@Configuration
public class DataAccessConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first.configuration")
public HikariDataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.datasource.second.configuration")
public HikariDataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("firstDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(Product.class)
.persistenceUnit("first")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("secondDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages(Order.class)
.persistenceUnit("second")
.build();
}
@Bean
public PlatformTransactionManager primaryTransactionManager( @Qualifier("firstEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
@Bean
public PlatformTransactionManager backupTransactionManager( @Qualifier("secondEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
@EnableJpaRepositories(basePackageClasses = ProductRepository.class,
entityManagerFactoryRef = "firstEntityManagerFactory", transactionManagerRef = "primaryTransactionManager")
@Primary
public class PrimaryConfiguration {
}
@EnableJpaRepositories(basePackageClasses = OrderRepository.class,
entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "backupTransactionManager")
public class secondConfiguration {
}
}
複製代碼
配置完成後啓動
發現日誌中建表SQL沒有輸出,同時查看數據庫,確認表未建立
但官方文檔對多數據源的配置說明已經結束,使人疑惑
自動建表語句沒生成猜測可能因爲與ddl相關的屬性未配置致使
進入配置文件查看ddl配置,發現默認爲false
將其配置爲true後總配置文件以下
spring:
datasource:
first:
driver-class-name: com.mysql.cj.jdbc.Driver
username: financial_adminer
password: financial_adminer_pass
url: jdbc:mysql://120.79.226.26/jpa_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
configuration:
maximum-pool-size: 30
second:
driver-class-name: com.mysql.cj.jdbc.Driver
username: financial_adminer
password: financial_adminer_pass
url: jdbc:mysql://120.79.226.26/jpa_test_bak?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
configuration:
maximum-pool-size: 20
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQL55Dialect
properties:
hibernate:
format_sql: true
use_sql_comments: true
generate-ddl: true
複製代碼
啓動工程發現自動建表生效
查看數據庫狀況,與預期一致,主庫只包含主庫的表,備份庫只包含備份庫的表,多數據配置完成
若在單數據源配置下應當無需配置該項,同時發現本應該默認將駝峯屬性名映射爲_
拼接名稱策略也失效,說明自定義配置源時覆蓋了默認配置或者致使默認配置失效
查詢源碼分析緣由,發現hibernate
相關配置均在HibernateProperties
配置類中
package org.springframework.boot.autoconfigure.orm.jpa;
/** * Configuration properties for Hibernate. * * @author Stephane Nicoll * @since 2.1.0 * @see JpaProperties */
@ConfigurationProperties("spring.jpa.hibernate")
public class HibernateProperties {}
複製代碼
而該類在HibernateJpaConfiguration
中進行注入,但配置類中進行了條件裝配註解@ConditionalOnSingleCandidate
標註,致使在多數據源狀況下不知足該配置,類沒有被裝配到Spring Bean中
package org.springframework.boot.autoconfigure.orm.jpa;
/** * {@link JpaBaseConfiguration} implementation for Hibernate. * @since 2.0.0 */
@Configuration
@EnableConfigurationProperties(HibernateProperties.class)
@ConditionalOnSingleCandidate(DataSource.class)
class HibernateJpaConfiguration extends JpaBaseConfiguration {
private final HibernateProperties hibernateProperties;
HibernateJpaConfiguration(DataSource dataSource, JpaProperties jpaProperties,
ConfigurableListableBeanFactory beanFactory,
ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers,
HibernateProperties hibernateProperties,
ObjectProvider<Collection<DataSourcePoolMetadataProvider>> metadataProviders,
ObjectProvider<SchemaManagementProvider> providers,
ObjectProvider<PhysicalNamingStrategy> physicalNamingStrategy,
ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy,
ObjectProvider<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
super(dataSource, jpaProperties, jtaTransactionManager,
transactionManagerCustomizers);
this.hibernateProperties = hibernateProperties;
this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider(providers);
this.poolMetadataProvider = new CompositeDataSourcePoolMetadataProvider(
metadataProviders.getIfAvailable());
this.hibernatePropertiesCustomizers = determineHibernatePropertiesCustomizers(
physicalNamingStrategy.getIfAvailable(),
implicitNamingStrategy.getIfAvailable(), beanFactory,
hibernatePropertiesCustomizers.orderedStream()
.collect(Collectors.toList()));
}
...
}
複製代碼
解決方法就只能本身工程內實現該類的配置,注意,Spring 2.x版本自動裝配變化較大,當前配置基於最新版2.1.3有效,並不表明對後續版本必定兼容
工程配置
package top.fjy8018.jpadatasource.config;
import com.zaxxer.hikari.HikariDataSource;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.cfg.AvailableSettings;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.SchemaManagement;
import org.springframework.boot.jdbc.SchemaManagementProvider;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate5.SpringBeanContainer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.ClassUtils;
import top.fjy8018.jpadatasource.entity.backup.Order;
import top.fjy8018.jpadatasource.entity.primary.Product;
import top.fjy8018.jpadatasource.repository.backup.OrderRepository;
import top.fjy8018.jpadatasource.repository.primary.ProductRepository;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/** * @author F嘉陽 * @date 2019-03-30 9:24 */
@Configuration
public class DataAccessConfig {
private final ObjectProvider<SchemaManagementProvider> providers;
private final HibernateProperties hibernateProperties;
private final JpaProperties properties;
private final ObjectProvider<PhysicalNamingStrategy> physicalNamingStrategy;
private final ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy;
private final ConfigurableListableBeanFactory beanFactory;
private final ObjectProvider<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers;
@Autowired
public DataAccessConfig(ObjectProvider<SchemaManagementProvider> providers, HibernateProperties hibernateProperties, JpaProperties properties, ObjectProvider<PhysicalNamingStrategy> physicalNamingStrategy, ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy, ConfigurableListableBeanFactory beanFactory, ObjectProvider<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
this.providers = providers;
this.hibernateProperties = hibernateProperties;
this.properties = properties;
this.physicalNamingStrategy = physicalNamingStrategy;
this.implicitNamingStrategy = implicitNamingStrategy;
this.beanFactory = beanFactory;
this.hibernatePropertiesCustomizers = hibernatePropertiesCustomizers;
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.first.configuration")
public HikariDataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.datasource.second.configuration")
public HikariDataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("firstDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
// 加入規約配置
.properties(getVendorProperties(dataSource))
.packages(Product.class)
.persistenceUnit("first")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("secondDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.properties(getVendorProperties(dataSource))
.packages(Order.class)
.persistenceUnit("second")
.build();
}
@Bean
public PlatformTransactionManager primaryTransactionManager( @Qualifier("firstEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
@Bean
public PlatformTransactionManager backupTransactionManager( @Qualifier("secondEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
@EnableJpaRepositories(basePackageClasses = ProductRepository.class,
entityManagerFactoryRef = "firstEntityManagerFactory", transactionManagerRef = "primaryTransactionManager")
@Primary
public class PrimaryConfiguration {
}
@EnableJpaRepositories(basePackageClasses = OrderRepository.class,
entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "backupTransactionManager")
public class secondConfiguration {
}
/** * 獲取配置文件信息 * * @param dataSource * @return */
private Map<String, Object> getVendorProperties(DataSource dataSource) {
List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers = determineHibernatePropertiesCustomizers(
physicalNamingStrategy.getIfAvailable(),
implicitNamingStrategy.getIfAvailable(), beanFactory,
this.hibernatePropertiesCustomizers.orderedStream()
.collect(Collectors.toList()));
Supplier<String> defaultDdlMode = () -> new HibernateDefaultDdlAutoProvider(providers)
.getDefaultDdlAuto(dataSource);
return new LinkedHashMap<>(this.hibernateProperties.determineHibernateProperties(
properties.getProperties(),
new HibernateSettings().ddlAuto(defaultDdlMode)
.hibernatePropertiesCustomizers(
hibernatePropertiesCustomizers)));
}
/** * 命名策略自動判斷 * * @param physicalNamingStrategy * @param implicitNamingStrategy * @param beanFactory * @param hibernatePropertiesCustomizers * @return */
private List<HibernatePropertiesCustomizer> determineHibernatePropertiesCustomizers( PhysicalNamingStrategy physicalNamingStrategy, ImplicitNamingStrategy implicitNamingStrategy, ConfigurableListableBeanFactory beanFactory, List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
List<HibernatePropertiesCustomizer> customizers = new ArrayList<>();
if (ClassUtils.isPresent(
"org.hibernate.resource.beans.container.spi.BeanContainer",
getClass().getClassLoader())) {
customizers
.add((properties) -> properties.put(AvailableSettings.BEAN_CONTAINER,
new SpringBeanContainer(beanFactory)));
}
if (physicalNamingStrategy != null || implicitNamingStrategy != null) {
customizers.add(new NamingStrategiesHibernatePropertiesCustomizer(
physicalNamingStrategy, implicitNamingStrategy));
}
customizers.addAll(hibernatePropertiesCustomizers);
return customizers;
}
/** * 自動進行建表操做 */
class HibernateDefaultDdlAutoProvider implements SchemaManagementProvider {
private final Iterable<SchemaManagementProvider> providers;
HibernateDefaultDdlAutoProvider(Iterable<SchemaManagementProvider> providers) {
this.providers = providers;
}
public String getDefaultDdlAuto(DataSource dataSource) {
if (!EmbeddedDatabaseConnection.isEmbedded(dataSource)) {
return "none";
}
SchemaManagement schemaManagement = getSchemaManagement(dataSource);
if (SchemaManagement.MANAGED.equals(schemaManagement)) {
return "none";
}
return "create-drop";
}
@Override
public SchemaManagement getSchemaManagement(DataSource dataSource) {
return StreamSupport.stream(this.providers.spliterator(), false)
.map((provider) -> provider.getSchemaManagement(dataSource))
.filter(SchemaManagement.MANAGED::equals).findFirst()
.orElse(SchemaManagement.UNMANAGED);
}
}
private static class NamingStrategiesHibernatePropertiesCustomizer implements HibernatePropertiesCustomizer {
private final PhysicalNamingStrategy physicalNamingStrategy;
private final ImplicitNamingStrategy implicitNamingStrategy;
NamingStrategiesHibernatePropertiesCustomizer(
PhysicalNamingStrategy physicalNamingStrategy,
ImplicitNamingStrategy implicitNamingStrategy) {
this.physicalNamingStrategy = physicalNamingStrategy;
this.implicitNamingStrategy = implicitNamingStrategy;
}
/** * 數據庫命名映射策略 * * @param hibernateProperties the JPA vendor properties to customize */
@Override
public void customize(Map<String, Object> hibernateProperties) {
if (this.physicalNamingStrategy != null) {
hibernateProperties.put("hibernate.physical_naming_strategy",
this.physicalNamingStrategy);
}
if (this.implicitNamingStrategy != null) {
hibernateProperties.put("hibernate.implicit_naming_strategy",
this.implicitNamingStrategy);
}
}
}
}
複製代碼
配置Bean完成後則規約配置會生效,便無需在配置文件顯示配置DDL語句執行開關
啓動日誌也可看出結果符合預期
由此發現,在SpringBoot版本遷移過程當中不必定互相兼容,同時官方文檔儘管全面,但隨着版本變化文檔也有不許確狀況。而若是徹底依靠爬源碼複製源碼配置也是沒法實現最簡單化的配置,須要結合文檔、源碼、日誌逐一分析
工程源碼已開放,注意配置本身的數據庫鏈接地址,工程內的鏈接地址已經失效