[springboot] spring-data-jpa多数据源配置与使用

 项目依赖

    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.2.RELEASE
         
    

    
            org.springframework.boot
            spring-boot-starter-data-jpa
    

    
            mysql
            mysql-connector-java
    

多数据源配置

 首先在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 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");
	}

如果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 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信息显示的确实如此:

[springboot] spring-data-jpa多数据源配置与使用_第1张图片

回到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 entityManagerFactoryPrimary) throws Exception {
        return entityManagerFactoryPrimary.getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary () throws Exception {
        Map 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 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 entityManagerFactorySecondary) throws Exception {
        return entityManagerFactorySecondary.getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary () throws Exception {
        Map 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 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定义多个默认数据源

你可能感兴趣的:(SpringBoot)