1. 程式人生 > >Spring Boot使用spring-data-jpa配置Mysql多資料來源

Spring Boot使用spring-data-jpa配置Mysql多資料來源

轉載請註明出處 :Spring Boot使用spring-data-jpa配置Mysql多資料來源

我們在之前的文章中已經學習了Spring Boot中使用mysql資料庫

在單資料來源的情況下,Spring Boot的配置非常簡單,只需要在application.properties檔案中配置連線引數即可。

但是往往隨著業務量發展,我們通常會進行資料庫拆分或是引入其他資料庫,從而我們需要配置多個數據源,下面基於之前的Spring-data-jpa例子分別介紹多資料來源的配置方式。

目前有需求是會使用兩個mysql的資料來源。

注意,本文使用於 Spring Boot 2.0之前的版本,2.0之後的版本有部分區別,可檢視文後說明。

記錄配置步驟如下:

檢查需要的包

如果沒有則在pom.xml中補全。

<!-- Use MySQL Connector-J -->

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
<!-- JPA Data (We are going to use Repositories, Entities, Hibernate, etc...) -->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

目錄結構

目錄結構很重要,尤其是多資料來源的情況下。

本次結構如圖

定義DataSourceConfig

package com.biologic.util;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
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;

@Configuration
public class MysqlDataSourceConfig {

	@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();
	}

}

引數配置

對應的application.properties配置如下:


# 通用部分設定
spring.jpa.database=MYSQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

#primary資料庫
spring.datasource.primary.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

#secondary資料庫
spring.datasource.secondary.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

第一個資料來源配置

新增對第一資料來源的JPA配置,注意兩處註釋的地方,用於指定資料來源對應的Entity實體和Repository定義位置,用@Primary區分主資料來源。

package com.example.demo.mysql.config;

import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
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.orm.jpa.vendor.Database;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= { "com.example.demo.mysql.reposity.primary" }) //設定Repository所在位置


public class MysqlPrimaryConfig {

    @Autowired 
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

    @Primary
    @Bean(name = "entityManagerPrimary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(primaryDataSource)
                .properties(getVendorProperties(primaryDataSource))
                .packages("com.example.demo.mysql.entity.primary") //設定實體類所在位置
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }

    @Autowired
    private JpaProperties jpaProperties;

    private Map<String, String> getVendorProperties(DataSource dataSource) {
        jpaProperties.setDatabase(Database.MYSQL);
        Map<String,String> map = new HashMap<>();
        map.put("hibernate.dialect","org.hibernate.dialect.MySQL5Dialect");
        map.put("hibernate.hbm2ddl.auto","update");
        map.put("hibernate.physical_naming_strategy","org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl");
        jpaProperties.setProperties(map);
        return  jpaProperties.getHibernateProperties(dataSource);
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")
    public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }

}

第二個資料來源配置

新增對第二資料來源的JPA配置,內容與第一資料來源類似,只是修改repository和entity儲存的路徑,具體如下:

package com.example.demo.mysql.config;

import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactorySecondary",
        transactionManagerRef="transactionManagerSecondary",
        basePackages= { "com.example.demo.mysql.reposity.secondary" }) //設定Repository所在位置
public class MysqlSecondaryConfig {

    @Autowired 
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Bean(name = "entityManagerSecondary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(secondaryDataSource)
                .properties(getVendorProperties(secondaryDataSource))
                .packages("com.example.demo.mysql.entity.secondary") //設定實體類所在位置
                .persistenceUnit("secondaryPersistenceUnit")
                .build();
    }

    @Autowired
    private JpaProperties jpaProperties;

    private Map<String, String> getVendorProperties(DataSource dataSource) {
        jpaProperties.setDatabase(Database.MYSQL);
        Map<String,String> map = new HashMap<>();
        map.put("hibernate.dialect","org.hibernate.dialect.MySQL5Dialect");
        map.put("hibernate.hbm2ddl.auto","update");
        map.put("hibernate.physical_naming_strategy","org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl");
        jpaProperties.setProperties(map);
        return  jpaProperties.getHibernateProperties(dataSource);
    }

    @Bean(name = "transactionManagerSecondary")
    public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }

}

建立實體和Repository介面

完成了以上配置之後,
主資料來源的實體位於: com.biologic.entity.mysqlprimary
主資料來源的資料訪問物件位於:com.biologic.api.repository.mysqlprimary
第二資料來源的實體位於: com.biologic.entity.mysqlsecondary
第二資料來源的資料訪問介面位於:com.biologic.api.repository.mysqlsecondary

分別在這些package下建立各自的實體和資料訪問介面

主資料來源下,建立User實體和對應的Repository介面

User.java

package com.example.demo.mysql.entity.primary;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class User {

	   @Id
	    @GeneratedValue
	    private Long id;

	    @Column(nullable = false)
	    private String name;

	    @Column(nullable = false)
	    private Integer age;

	    public User(){}

	    public User(String name, Integer age) {
	        this.name = name;
	        this.age = age;
	    }

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public Integer getAge() {
			return age;
		}

		public void setAge(Integer age) {
			this.age = age;
		}
	    
	    

}

UserRepository.java

package com.example.demo.mysql.reposity.primary;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.mysql.entity.primary.User;


@Repository
public interface UserMysqlRepository extends JpaRepository<User, Long> {

}


第二資料來源下,建立Message實體和對應的Repository介面
Message.java

package com.example.demo.mysql.entity.secondary;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Message {

	@Id
	@GeneratedValue
	private Long id;

	@Column(nullable = false)
	private String name;

	@Column(nullable = false)
	private String content;

	public Message() {
	}

	public Message(String name, String content) {
		this.name = name;
		this.content = content;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}
	
	
}


MessageRepository.java

package com.example.demo.mysql.reposity.secondary;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.mysql.entity.secondary.Message;

@Repository
public interface MessageRepository extends JpaRepository<Message, Long> {

}


測試使用

Controller方式

package com.example.demo.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.mysql.entity.primary.User;
import com.example.demo.mysql.entity.secondary.Message;
import com.example.demo.mysql.reposity.primary.UserMysqlRepository;
import com.example.demo.mysql.reposity.secondary.MessageRepository;

@RestController
public class HelloWorldController {

	@Autowired
	private UserMysqlRepository userMysqlRepository;

	@Autowired
	private MessageRepository messageRepository;

	@RequestMapping("/hello")
	public int index() {

		userMysqlRepository.save(new User("aaa", 10));
		userMysqlRepository.save(new User("bbb", 20));
		userMysqlRepository.save(new User("ccc", 30));
		userMysqlRepository.save(new User("ddd", 40));
		userMysqlRepository.save(new User("eee", 50));

		System.out.println(userMysqlRepository.findAll().size());

		messageRepository.save(new Message("o1", "aaaaaaaaaa"));
		messageRepository.save(new Message("o2", "bbbbbbbbbb"));
		messageRepository.save(new Message("o3", "cccccccccc"));

		return userMysqlRepository.findAll().size() + messageRepository.findAll().size();
	}
}

ApplicationTests方式
測試用例來驗證使用這兩個針對不同資料來源的配置進行資料操作。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTests {

	@Autowired
	private UserRepository userRepository;
	@Autowired
	private MessageRepository messageRepository;

	@Test
	public void test() throws Exception {

		userRepository.save(new User("aaa", 10));
		userRepository.save(new User("bbb", 20));
		userRepository.save(new User("ccc", 30));
		userRepository.save(new User("ddd", 40));
		userRepository.save(new User("eee", 50));

		Assert.assertEquals(5, userRepository.findAll().size());

		messageRepository.save(new Message("o1", "aaaaaaaaaa"));
		messageRepository.save(new Message("o2", "bbbbbbbbbb"));
		messageRepository.save(new Message("o3", "cccccccccc"));

		Assert.assertEquals(3, messageRepository.findAll().size());

	}

}

注意事項:版本問題

主要記錄spring boot升級2.0後報的錯,即springboot1.*正常,測試版本為1.5.4

不同點一:getVendorProperties呼叫不同

2.0之前的呼叫型別為DataSource

 private Map getVendorProperties(DataSource dataSource) {
        return jpaProperties.getHibernateProperties(dataSource);
    }

2.0之後的呼叫型別為HiberateSettings

public Map<String, Object> getVerdorProperties(){
        return jpaProperties.getHibernateProperties(new HibernateSettings());
    }

不同點二:資料庫注入方式不同
2.0之前為

    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;
 
    @Bean
    @Primary
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource DataSource1() {
        return DataSourceBuilder.create().build();
    }

2.0之後為

    @Bean
    @Primary
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSourceProperties primaryDataSourceProperties(){
        return new DataSourceProperties();
    }
 
    @Bean
    @Primary
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource(){
        return primaryDataSourceProperties().initializeDataSourceBuilder().build();
    }
 
    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

資料庫注入方式如果不修改的話報錯為

java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName

HibernateSettings類其實就是配置列名生成策略的,如果已經在yml裡配置過了,這裡直接new 一個空類過去就行了

spring:  
  datasource:  
    primary:  
      url: jdbc:mysql://localhost:3306/company?autoReconnect=true&useUnicode=true  
      username: root  
      password: root  
    secondary:  
      url: jdbc:mysql://localhost:3306/com1?autoReconnect=true&useUnicode=true  
      username: root  
      password: root  
  jpa:  
    database: mysql  
    generate-ddl: true  
    show-sql: true  
    hibernate:  
      ddl-auto: update  
      naming:  
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy  

Spring Boot 2.1.0參見上文程式碼,引進了HibernateProperties。
同時,在Spring Boot 2.1.0中預設的mysql-connector-java版本為8.0.13,連線低版本mysql配置上比較繁瑣,建議在配置檔案中手動指定相應版本,如本文中指定5.1.46這個版本。
runtimeOnly(‘mysql:mysql-connector-java:5.1.46’)

注意事項 : 是否需要exclude自動HibernateJpaAutoConfiguration等

在配置正確的情況下,不需要exclude任何配置即可配置成功。
但是網上很多帖子說需要
配置

spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

或者


這樣的配置可能導致報錯

Description:

Field jpaProperties in com.biologic.util.MysqlPrimaryConfig required a bean of type 'org.springframework.boot.autoconfigure.orm.jpa.JpaProperties' that could not be found.


Action:

Consider defining a bean of type 'org.springframework.boot.autoconfigure.orm.jpa.JpaProperties' in your configuration.

可能遇到的問題–No bean named ‘entityManagerFactory’ available

2018-12-17 15:01:57.618  WARN 26428 --- [           main] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageRepository': Cannot create inner bean '(inner bean)#7348e75e' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#7348e75e': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available

正常情況下 只要在MysqlPrimaryConfig配置了@Primary引數,就會自動成為entityManagerFactory。
如果報這個錯,需要檢查是否加了@Primary,@Primary是否在其他配置中重複。
以及MysqlPrimaryConfig是否加了@Configuration的標記,以及整個專案的包掃描情況,確保MysqlPrimaryConfig被掃描到。

沒有正確載入配置和掃描包導致的錯誤還有如下幾種報錯:

Description:
Cannot determine embedded database driver class for database type NONE
Action:
If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (the profiles "dev" are currently active).
not managed type
At least one JPA metamodel must be present!

關鍵時候可以嘗試強制載入包, 在啟動檔案加入

@ComponentScan("com.biologic.entity")

注意事項,mongodb多資料來源與mysql多資料來源同時配置

專案中同時配置了mongodb和mysql甚至redis,會導致配置載入十分混亂。

導致各種奇怪的異常

Multiple Spring Data modules found, entering strict repository configuration mode

此時 專案會進入嚴格的引數配置模式,要求每種模式都有具體的指向。
比如

@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")

以及
配置檔案中

spring.data.redis.repositories.enabled = false

尤其是 目錄結構需要規劃好。

可用原始碼下載

https://download.csdn.net/download/q383965374/10856658

參考連結:
https://www.cnblogs.com/sxdcgaq8080/p/7978205.html

轉載請註明出處 :Spring Boot使用spring-data-jpa配置Mysql多資料來源