SpringBoot(十九) JPA 多数据源配置

多数据源设计一般应用到中大型的项目中,项目关联的业务性比较复杂,使用的数据库比较分散。我们使用多数据源的目的也有很多,比如:分布式数据库读写分离、集成不同数据库等。不管初衷是什么也都是为了提高项目的可维护性、稳定性、响应速度。

目标

基于SpringBoot项目整合JPA + Druid 完成多数据源根据包名自动切换。

数据初始化

创建两个数据库(test、books
test 数据库内包含了一张数据表t_user

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`t_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`t_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
`t_age` int(10) NULL DEFAULT NULL COMMENT '年龄',
`t_address` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '家庭住址',
`t_pwd` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL COMMENT '用户登录密码',
PRIMARY KEY (`t_id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'Ray', 6, 'zh', '123');
INSERT INTO `t_user` VALUES (2, 'q343509740', 18, 'zh', '456');
INSERT INTO `t_user` VALUES (3, 'Rain', 24, 'zh', '789');

SET FOREIGN_KEY_CHECKS = 1;

books 数据库内包含了一张数据库book

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`b_id` int(11) NOT NULL AUTO_INCREMENT,
`b_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`b_id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book` VALUES (1, 'SpringBoot入门');
INSERT INTO `book` VALUES (2, 'SpringData实战');
INSERT INTO `book` VALUES (3, 'SpringData放弃');

SET FOREIGN_KEY_CHECKS = 1;

构建项目

pom.xml

添加JPA、WEB、Druid 等依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<dependencies>
<!--JPA-->
<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>
<!--Druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

appliction.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
spring:
# 数据库配置
datasource:
## 提取公共属性
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver

# user 数据库的配置
user:
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
# book 数据库的配置
book:
url: jdbc:mysql://localhost:3306/books?characterEncoding=utf8

# Druid 常用配置
druid:
# 配置控制统计拦截的filters,去掉后监控界面sql将无法统计,wall用于防火墙
filter: stat,wall
# 最大活跃数
max-active: 20
# 初始化数量
initial-size: 1
# 最小连接池数量
min-idle: 1
# 最大连接等待超时时间
max-wait: 60000
# 打开PSCache,并且指定每个连接PSCache的大小,mysql可以设置为false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
# 配置间隔多久才进行一次检测,检测需要关闭的空间连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 用来检测连接是否有效
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
async-init: true




# JPA
jpa:
properties:
hibernate:
# 显示sql语句
show_sql: true
# 格式化sql语句
format_sql: true

可以看到上面两个数据源命名时都有前缀,分别是userbook 。这个是我们配置时必要的属性,下面我们就来编写数据库源的映射配置类。

数据源配置类

我们自定义一个数据源配置类DataSourceConfigurer(与applition.java 同级),在类内对应声明两个数据源的Bean对象,以及使用application.yml配置文件内的前缀属性配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author Ray
* @date 2018/8/7 0007
* 数据源配置类
* 注意:DataSourceBuilder
*/
@Configuration
public class DataSourceConfigurer {

/**
* 用户数据源
*/
@Bean(name = "userDataSource") //装配该方法返回值为userDataSource管理bean
@Qualifier(value = "userDataSource") //spring装配bean唯一标识,为了防止注入时冲突问题
@ConfigurationProperties(prefix = "spring.datasource.user") //application.yml配置文件中该数据源的配置前缀
public DataSource userDataSource(){
// return DataSourceBuilder.create().build(); // HikariCP
return DruidDataSourceBuilder.create().build(); // Druid
}

/**
* 书籍数据源
*/
@Bean(name = "bookDataSource")
@Primary //配置该数据源为主数据源
@Qualifier(value = "bookDataSource")
@ConfigurationProperties(prefix = "spring.datasource.book")
public DataSource bookDataSource(){
return DruidDataSourceBuilder.create().build();
}
}

  1. 两个数据库分别定义了两个数据源,userDataSource、bookDataSource。
  2. 使用@Qualifier 注解并且设置内容,是为了防止注入时冲突问题。
  3. xxxDataSource则是配置使用了spring.datasource.xxx前缀的配置。
  4. @Primary 配置了数据源为主数据源。

DataSourceConfigurer

我们自定义配置文件类(与applition.java 同级)
配置步骤如下:

  1. 注入xxx数据源
  2. 注入JPA配置实体
  3. 配置数据库的方言
  4. 配置工厂实体 EntityManagerFactoryxxx
  5. 配置事务 transactionManagerxxx
  6. 配置实体 EntityManager

BookDataSourceConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* @author Ray
* @date 2018/8/7 0007
*/
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryBook", // 实体管理器引用
transactionManagerRef = "transactionManagerBook", // 事务管理器引用
basePackages = {"com.ray.springboot219.book"} // 设置书籍数据源所应用到的包
)
public class BookDataSourceConfigurer {

/**
* 注入书籍数据源
*/
@Autowired
@Qualifier("bookDataSource")
private DataSource bookDataSource;

/**
* 注入JPA配置实体
*/
@Autowired
private JpaProperties jpaProperties;

/**
* 这里其实不需要配置数据库的方言
* 像hibernate.hbm2ddl.auto 可以在这里配置,不过一般在application.yml 配置文件中配置
* @return
*/
private Map<String, Object> getVendorProperties(){
HibernateSettings hibernateSettings = new HibernateSettings();
return jpaProperties.getHibernateProperties(hibernateSettings);
}

/**
* 配置工厂实体 EntityManagerFactoryBook
* @param builder
* @return 实体管理工厂
* packages 扫描@Entity注释所在的包名
* persistenceUnit 持久性单元名称,如果只有一个数据源,可以省略,但如果配置多个数据源,应给它们不同的名字
* properties 标准JPA或供应商特定配置的通用属性,这些属性覆盖构造函数中提供的任何值
*/
@Primary
@Bean(name = "entityManagerFactoryBook")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBook(EntityManagerFactoryBuilder builder){
return builder
.dataSource(bookDataSource)
.properties(getVendorProperties())
.packages(new String[]{"com.ray.springboot219.book"})
.persistenceUnit("bookPersistenceUnit")
.build();
}

/**
* 配置事务 transactionManagerBook
* @param builder
* @return 事务管理器
*/
@Primary
@Bean(name = "transactionManagerBook")
public PlatformTransactionManager transactionManagerBook(EntityManagerFactoryBuilder builder){
return new JpaTransactionManager(entityManagerFactoryBook(builder).getObject());
}

/**
* 配置实体 EntityManager
* @param builder
* @return 实体管理器
*/
@Primary
@Bean(name = "entityManagerBook")
public EntityManager entityManager(EntityManagerFactoryBuilder builder){
return entityManagerFactoryBook(builder).getObject().createEntityManager();
}
}

注意: 使用@Primary注解来配置主实体管理器、主事务管理器。

UserDataSourceConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* @author Ray
* @date 2018/8/7 0007
*/
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryUser", // 实体管理器引用
transactionManagerRef="transactionManagerUser", // 事务管理器引用
basePackages = { "com.ray.springboot219.user"}) // 设置用户数据源所应用到的包
public class UserDataSourceConfigurer {

/**
* 注入用户数据源
*/
@Autowired
@Qualifier("userDataSource")
private DataSource userDataSource;

/**
* 注入JPA配置实体
*/
@Autowired
private JpaProperties jpaProperties;

/**
* 这里其实不需要配置数据库的方言
* 像hibernate.hbm2ddl.auto 可以在这里配置,不过一般在application.yml 配置文件中配置
* @return
*/
private Map<String, Object> getVendorProperties(){
HibernateSettings hibernateSettings = new HibernateSettings();
return jpaProperties.getHibernateProperties(hibernateSettings);
}

/**
* 配置工厂实体 EntityManagerFactoryUser
* @param builder
* @return 实体管理工厂
* packages 扫描@Entity注释所在的包名
* persistenceUnit 持久性单元名称,如果只有一个数据源,可以省略,但如果配置多个数据源,应给它们不同的名字
* properties 标准JPA或供应商特定配置的通用属性,这些属性覆盖构造函数中提供的任何值
*/
@Bean(name = "entityManagerFactoryUser")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryUser(EntityManagerFactoryBuilder builder){
return builder
.dataSource(userDataSource)
.properties(getVendorProperties())
.packages(new String[]{"com.ray.springboot219.user"})
.persistenceUnit("userPersistenceUnit")
.build();
}

/**
* 配置事务 transactionManagerUser
* @param builder
* @return 事务管理器
*/
@Bean(name = "transactionManagerUser")
public PlatformTransactionManager transactionManagerUser(EntityManagerFactoryBuilder builder){
return new JpaTransactionManager(entityManagerFactoryUser(builder).getObject());
}

/**
* 配置实体 EntityManager
* @param builder
* @return 实体管理器
*/
@Bean(name = "entityManagerUser")
public EntityManager entityManager(EntityManagerFactoryBuilder builder){
return entityManagerFactoryUser(builder).getObject().createEntityManager();
}
}

注意: 跟BookDataSourceConfigurer 的几乎一样,只是少了@Primart 注解以及修改对应的参数等

Entity

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @author Ray
* @date 2018/8/7 0007
* 用户实体类
*/
@Entity
@Table(name = "t_user")
@Data
public class User {

@Id
@Column(name = "t_id")
@GeneratedValue
private Long id;

@Column(name = "t_name")
private String name;

@Column(name = "t_age")
private int age;

@Column(name = "t_address")
private String address;

@Column(name = "t_pwd")
private String pwd;
}

Book

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author Ray
* @date 2018/8/7 0007
* 书籍实体类
*/
@Entity
@Table(name = "book")
@Data
public class Book {

@Id
@Column(name = "b_id")
@GeneratedValue
private Long id;

@Column(name = "b_name")
private String name;

}

JPA

创建BaseJPA 继承 JpaRepository,作用解耦

BaseJPA

1
2
3
4
5
6
7
8
9
10
/**
* @author Ray
* @date 2018/8/7 0007
* 该泛型接口继承自JpaRepository<T, Long>
*/
public interface BaseDAO<T> extends
JpaRepository<T, Long>,
JpaSpecificationExecutor<T>,
Serializable {
}

UserJPA

1
2
3
4
5
6
/**
* @author Ray
* @date 2018/8/7 0007
*/
public interface UserJPA extends BaseDAO<User> {
}

BookJPA

1
2
3
4
5
6
/**
* @author Ray
* @date 2018/8/7 0007
*/
public interface BookJPA extends BaseDAO<Book> {
}

Controller

注入了xxxJPA,调用了jpa内部的findAll()方法来读取全部数据列表,并通过@RestController注解作用返回Json字符串。

UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author Ray
* @date 2018/8/7 0007
*/
@RestController
@RequestMapping(value = "/user")
public class UserController {

@Autowired
private UserJPA userJPA;

/**
* 查询用户列表
*/
@RequestMapping(value = "/list")
public List<User> list(){
return userJPA.findAll();
}
}

BookController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author Ray
* @date 2018/8/7 0007
*/
@RestController
@RequestMapping(value = "/book")
public class BookController {

@Autowired
private BookJPA bookJPA;

/**
* 查询书籍列表
*/
@RequestMapping(value = "/list")
public List<Book> list(){
return bookJPA.findAll();
}
}

运行测试

上面步骤我们编码已经完成,下面我们开启项目来测试数据是否对应不同的数据库获取,运行项目控制台输出内容如下所示:

可以看到项目启动时加载了我们配置的两个数据源,这个name的配置对应数据源实现类内。

访问用户列表:http://localhost:8080/user/list

访问书籍列表:http://localhost:8080/book/list

完整项目结构:

-------------- 本文结束  感谢您的阅读 --------------