使用SpringBoot JPA来配置多数据源,实现对于多数据的读写操作,SpringBoot版本为2.1.3.RELEASE,由于不同的SpringBoot版本配置相差太大要特别注意,尽量查看官网文档。
导入Maven依赖
<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>com.example</groupId>
<artifactId>vehicle</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vehicle</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-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--定时任务和@Slf4j注解日志的依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<!-- Use MySQL Connector-J -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
代码包组织结构
DemoApplicaton作为程序main方法入口,代码组织结构基本按照经典model dao service controller来组织,这里为了简便测试,去掉了service层,事务管理加载repository层。
首先注意DemoApplicaiton类的位置,作为SpringBoot程序入口类,其默认扫描类的规则是:与其同一级或者下一级,若想要扫描其他包类则可以使用注解@ComponentScan(basePackages="com.putian")
@SpringBootApplication
@ComponentScan(basePackages="com.putian")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置文件
applicaiton.properties配置文件如下,SpringBoot理念是“约定优于配置”,前提是得熟悉这些默认的约定,配置文件以及注解不熟悉的话实在太难搞。其实配置文件中的配置项一般在项目启动时都加载到某个具体的配置类中作为基本属性来使用,因此找到具体的配置类就可以明确配置项的名称以及使用规则等。
#服务器开放端口
server.port=8888
#spring.data.elasticsearch.repositories.enabled=true
#es连接地址
#spring.data.elasticsearch.cluster-nodes = 10.3.10.144:9300
#es集群名称
#spring.data.elasticsearch.cluster-name = es
#This is the default for MySQL, no change to the database structure.
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database=MYSQL
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
#spring.datasource.url=jdbc:mysql://10.3.10.172:3306/ac-device
#spring.datasource.username=root
#spring.datasource.password=Smart@001
spring.datasource.primary.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.primary.jdbc-url=jdbc:mysql://127.0.0.1:3306/ac-device?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.primary.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.primary.username=root
spring.datasource.primary.password=1234
# JPA hikari 数据库连接池配置
spring.datasource.primary.hikari.maximum-pool-size=20
spring.datasource.primary.hikari.minimum-idle=5
spring.datasource.secondary.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.secondary.jdbc-url=jdbc:mysql://127.0.0.1:3306/warehouse?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.secondary.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.username=root
spring.datasource.secondary.password=1234
# JPA hikari 数据库连接池配置
spring.datasource.secondary.hikari.maximum-pool-size=20
spring.datasource.secondary.hikari.minimum-idle=5
多数据的配置
多数据源的配置核心在于数据源的分别配置以及不同数据源与实体类,repository以及事务的分别映射。
配置主数据源以及从数据源
@Configuration
public class DataSourceConfig {
@Bean(name="primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primatyDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name="secondaryDataSource")
@Qualifier("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
主数据源的详细配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages= { "com.putian.repository.acdevice" }) //设置Repository所在位置
public class PrimaryConfig {
@Primary
@Bean
PlatformTransactionManager transactionManagerPrimary() {
return new JpaTransactionManager(entityManagerPrimary().getObject());
}
@Primary
@Bean(name = "entityManagerPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerPrimary() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(primaryDataSource);
factoryBean.setJpaVendorAdapter(vendorAdapter);
factoryBean.setPackagesToScan("com.putian.model.acdevice");
return factoryBean;
}
@Autowired
@Qualifier("primaryDataSource")
private DataSource primaryDataSource;
}
从数据源的详细配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerSecondary",
transactionManagerRef="transactionManagerSecondary",
basePackages= { "com.putian.repository.warehouse" }) //设置Repository所在位置
public class SecondaryConfig {
@Autowired
private DataSource secondaryDataSource;
@Bean
PlatformTransactionManager transactionManagerSecondary() {
return new JpaTransactionManager(entityManagerSecondary().getObject());
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerSecondary() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(secondaryDataSource);
factoryBean.setJpaVendorAdapter(vendorAdapter);
factoryBean.setPackagesToScan("com.putian.model.warehouse");
return factoryBean;
}
}
数据源对应实体类是通过LocalContainerEntityManagerFactoryBean.setPackagesToScan方法来实现对应的。
数据源对应Repository类是通过注解@EnableJpaRepositories中basePackages来实现的。
从代码来看,数据源的配置项主要包括EntityManager,TransactionManager以及基本实体类包路径,不同的SpringBoot版本对于EntityManager配置差异较大,这里2.1.3形式配置方式如上所示,之前旧版本如下所示:
https://www.cnblogs.com/fengmao/p/7538854.html
@Primary
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource)
.properties(getVendorProperties(primaryDataSource))
.packages("com.didispace.domain.p") //设置实体类所在位置
.persistenceUnit("primaryPersistenceUnit")
.build();
}
@Autowired
private JpaProperties jpaProperties;
private Map<String, String> getVendorProperties(DataSource dataSource) {
return jpaProperties.getHibernateProperties(dataSource);
}
之后根据数据库表结构以及具体的需求编写model以及repository即可。
核心注解解析
1.@Configuration注解
* Indicates that a class declares one or more {@link Bean @Bean} methods and
* may be processed by the Spring container to generate bean definitions and
* service requests for those beans at runtime
该注解主要用于表示该类里面声明了Bean,需要由Spring容器来产生bean的定义。
2.@Bean注解
Indicates that a method produces a bean to be managed by the Spring container.
表明该方法将产生一个Spring容器管理的Bean,Bean名称默认与方法名相同,也可以通过name属性来指定。
3.@Qualifier注解
* This annotation may be used on a field or parameter as a qualifier for
* candidate beans when autowiring. It may also be used to annotate other
* custom annotations that can then in turn be used as qualifiers.
可以参考https://blog.csdn.net/qq_36567005/article/details/80611139
4.@ConfigurationProperties注解
可以简单认为下面Bean的配置项由配置文件中具有某些特殊规则前缀的项来对应
@Bean(name="primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primatyDataSource() {
return DataSourceBuilder.create().build();
}
* Annotation for externalized configuration. Add this to a class definition or a
* {@code @Bean} method in a {@code @Configuration} class if you want to bind and validate
* some external Properties (e.g. from a .properties file).
/**
* The name prefix of the properties that are valid to bind to this object. Synonym
* for {@link #prefix()}. A valid prefix is defined by one or more words separated
* with dots (e.g. {@code "acme.system.feature"}).
* @return the name prefix of the properties to bind
*/
即primaryDataSource Bean中配置项由系统配置属性中具有前缀spring.datasource.primary来对应,与配置文件相对应
spring.datasource.primary.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.primary.jdbc-url=jdbc:mysql://127.0.0.1:3306/ac-device?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.primary.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.primary.username=root
spring.datasource.primary.password=1234
# JPA hikari 数据库连接池配置
spring.datasource.primary.hikari.maximum-pool-size=20
spring.datasource.primary.hikari.minimum-idle=5
下面具体研究一下DataSource Bean是如何与配置文件中配置参数对应的
DataSourceBuilder.create().build();
build方法
@SuppressWarnings("unchecked")
public T build() {
Class<? extends DataSource> type = getType();
DataSource result = BeanUtils.instantiateClass(type);
maybeGetDriverClassName();
bind(result);
return (T) result;
}
首先获取type属性,由type属性来实例化某种DataSource,其中type用来标识某种数据库连接池类型
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" };
加载数据库连接的驱动类
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);
}
}
如果配置属性中没有driverClassName,则从url属性汇总解析出驱动类
最后执行bind方法
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));
}
可知DataSourceBuilder中属性是与配置文件中属性对应的。
5.@EnableJpaRepositories注解
6.@Primary注解
EntityManager构造过程
参考链接:
https://www.jianshu.com/p/c4cd03347daa
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.repositories