将 My
source link: https://blog.rxliuli.com/p/6427d24f405346eea9fb15e9f317babb/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本文假设你已经了解或知道以下技能,尤其而且是勾选的内容。
- Gradle
- SpringBoot
- Mybatis Plus
- MongoDB
- SpringBoot MongoDB Data
- SpringTest
需要同时使用 Mybatis-Plus
和 MongoDB
,所以就去了解了一下如何集成它们。
集成 Mybatis Plus
创建 SpringBoot 项目
使用 SpringIO 创建 SpringBoot 项目,初始依赖选择 web
, h2
两个模块,gradle 配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.rxliuli.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
注:数据库吾辈这里为了简单起见直接使用了
H2DB
,真实项目中可能需要配置MySQL
之类。为了简化项目依赖配置文件,所以使用了 Gradle 而非 Maven。
引入 Mybatis-Plus 和 MongoDB 依赖
在 build.gradle
中引入 mybatis-plus-boot-starter
依赖
1
2
3
dependencies {
implementation group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.0.7.1'
}
添加测试数据库
在 src/resources 下创建两个 sql 文件 schema-h2.sql
和 data-h2.sql
,简单的使用 H2DB
创建数据库/表并添加数据以供测试使用。
数据库结构:schema-h2.sql
1
2
3
4
5
6
7
8
9
create schema spring_boot_mybatis_plus_mongo;
use spring_boot_mybatis_plus_mongo;
create table user_info (
id bigint primary key not null,
name varchar(20) not null,
age tinyint not null,
sex bool not null
);
数据库测试数据:data-h2.sql
1
2
3
4
use spring_boot_mybatis_plus_mongo;
insert into user_info (id, name, age, sex) values (1, 'rx', 17, false);
insert into user_info (id, name, age, sex) values (2, '琉璃', 18, false);
配置 Mybatis Plus
在 application.yml
中添加数据源配置
1
2
3
4
5
6
7
# DataSource Config
spring:
datasource:
driver-class-name: org.h2.Driver
schema: classpath*:db/schema-h2.sql
data: classpath*:db/data-h2.sql
url: jdbc:h2:mem:test
添加一些实体/Dao/Service
用户信息实体类:com.rxliuli.example.springbootmybatisplusmongo.entity.UserInfo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@TableName("user_info")
public class UserInfo implements Serializable {
@TableId
private Long id;
@TableField
private String name;
@TableField
private Integer age;
@TableField
private Boolean sex;
public UserInfo() {
}
public UserInfo(Long id, String name, Integer age, Boolean sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
// getter()/setter()
}
用户信息 Dao:com.rxliuli.example.springbootmybatisplusmongo.dao.UserInfoDao
1
2
3
@Repository
public interface UserInfoDao extends BaseMapper<UserInfo> {
}
用户信息业务接口:com.rxliuli.example.springbootmybatisplusmongo.service.UserInfoService
1
2
public interface UserInfoService extends IService<UserInfo> {
}
用户信息业务接口实现类:com.rxliuli.example.springbootmybatisplusmongo.service.impl.UserInfoServiceImpl
1
2
3
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoDao, UserInfo> implements UserInfoService {
}
配置 Mybatis Plus 扫描的路径
在启动类配置 Mybatis Plus
,这点非常重要,以致于吾辈要单独列出,可能会出现的问题参见 踩坑 部分
1
2
3
4
5
6
7
@SpringBootApplication
@MapperScan("com.rxliuli.example.springbootmybatisplusmongo.**.dao.**")
public class SpringBootMybatisPlusMongoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMybatisPlusMongoApplication.class, args);
}
}
测试使用 Mybatis Plus 的 UserInfoService
测试 Mybatis Plus 中 IService
接口的 list()
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserInfoServiceTest {
@Autowired
private UserInfoService userInfoService;
@Test
public void list() {
final List<UserInfo> list = userInfoService.list();
assertThat(list)
.isNotEmpty();
}
}
集成 MongoDB
引入 MongoDB Boot Starter
在 build.gradle
中引入 spring-boot-starter-data-mongodb
依赖
1
2
3
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}
配置 MongoDB
在 application.yml
中添加 MongoDB 的配置,现在 application.yaml
应该变成了下面这样
1
2
3
4
5
6
7
8
9
10
11
# DataSource Config
spring:
datasource:
driver-class-name: org.h2.Driver
schema: classpath*:db/schema-h2.sql
data: classpath*:db/data-h2.sql
url: jdbc:h2:mem:test
data:
# Integration mongodb
mongodb:
uri: mongodb://XXX:XXX@XXX:XXX/XXX
添加 Repository
定义一些简单操作的 Dao 接口:com.rxliuli.example.springbootmybatisplusmongo.repository.UserInfoLogRepository
1
2
3
4
5
6
7
8
9
10
@Repository
public interface UserInfoLogRepository extends MongoRepository<UserInfoLog, Long>, CustomUserInfoLogRepository {
/**
* 根据 id 查询用户日志信息
*
* @param id 查询的 id
* @return 用户日志
*/
UserInfoLog findUserInfoLogByIdEquals(Long id);
}
自定义更加复杂需求的 Dao 接口:com.rxliuli.example.springbootmybatisplusmongo.repository.CustomUserInfoLogRepository
1
2
3
4
5
6
7
8
9
public interface CustomUserInfoLogRepository {
/**
* 根据一些参数查询用户信息列表
*
* @param userInfoLog 参数对象
* @return 用户信息列表
*/
List<UserInfoLog> listByParam(UserInfoLog userInfoLog);
}
具体的实现类:com.rxliuli.example.springbootmybatisplusmongo.repository.UserInfoLogRepositoryImpl
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
/**
* 数据仓库 {@link UserInfoLogRepository} 的实现类,但请务必注意,实现类继承的是 {@link CustomUserInfoLogRepository} 接口,而非本应该继承的接口
*/
public class UserInfoLogRepositoryImpl implements CustomUserInfoLogRepository {
@Autowired
private MongoOperations mongoOperations;
@Override
public List<UserInfoLog> listByParam(UserInfoLog userInfoLog) {
final Criteria criteria = new Criteria();
if (userInfoLog.getUserId() != null) {
criteria.and("userId")
.is(userInfoLog.getUserId());
}
if (userInfoLog.getLogTime() != null) {
criteria.and("logTime")
.gte(userInfoLog.getLogTime());
}
if (userInfoLog.getOperate() != null) {
criteria.and("operate")
.regex(userInfoLog.getOperate());
}
return mongoOperations.find(new Query(criteria), UserInfoLog.class);
}
}
配置 MongoDB 扫描的路径
修改启动类,添加 @EnableMongoRepositories
注解用以配置 MongoDB 扫描的 Repository
路径
1
2
3
4
5
6
7
8
@SpringBootApplication
@MapperScan("com.rxliuli.example.springbootmybatisplusmongo.**.dao.**")
@EnableMongoRepositories("com.rxliuli.example.springbootmybatisplusmongo.**.repository.**")
public class SpringBootMybatisPlusMongoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMybatisPlusMongoApplication.class, args);
}
}
测试使用 MongoDB 的 UserInfoLogRepository
- 测试
UserInfoLogRepository
中由 MongoDB Data 自动实现的findUserInfoLogByIdEquals()
方法 - 测试
CustomUserInfoLogRepository
中自定义复杂的listByParam()
方法
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
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserInfoLogRepositoryTest {
@Autowired
private UserInfoLogRepository userInfoLogRepository;
/**
* 初始数据,最开始要运行一次
*/
@Test
public void insert() {
userInfoLogRepository.insert(Lists.newArrayList(
new UserInfoLog(1L, 1L, "登录", LocalDateTime.now()),
new UserInfoLog(2L, 1L, "退出", LocalDateTime.now()),
new UserInfoLog(3L, 2L, "登录", LocalDateTime.now()),
new UserInfoLog(4L, 3L, "退出", LocalDateTime.now())
));
}
@Test
public void findUserInfoLogByIdEquals() {
final UserInfoLog result = userInfoLogRepository.findUserInfoLogByIdEquals(1L);
assertThat(result)
.isNotNull();
}
@Test
public void listByParam() {
final UserInfoLog userInfoLog = new UserInfoLog(null, 1L, "登",
LocalDateTime.parse("2019-02-22T08:22:16.000Z", DateTimeFormatter.ISO_DATE_TIME));
final List<UserInfoLog> result = userInfoLogRepository.listByParam(userInfoLog);
assertThat(result)
.isNotEmpty()
.allMatch(log ->
Objects.equals(userInfoLog.getUserId(), log.getUserId())
&& log.getOperate().contains(userInfoLog.getOperate())
&& log.getLogTime().isAfter(userInfoLog.getLogTime())
);
}
}
同时使用 Mybatis Dao 和 MongoDB Repository
在 Service 中添加方法
用户信息业务接口:com.rxliuli.example.springbootmybatisplusmongo.service.UserInfoService
1
2
3
4
5
6
7
8
public interface UserInfoService extends IService<UserInfo> {
/**
* 获取用户信息与用户日志的映射表
*
* @return 以 {@link UserInfo} -> {@link List<UserInfoLog>} 形式的 {@link Map}
*/
Map<UserInfo, List<UserInfoLog>> listUserInfoAndLogMap();
}
用户信息业务接口实现类:com.rxliuli.example.springbootmybatisplusmongo.service.impl.UserInfoServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoDao, UserInfo> implements UserInfoService {
@Autowired
private UserInfoLogRepository userInfoLogRepository;
@Override
public Map<UserInfo, List<UserInfoLog>> listUserInfoAndLogMap() {
final List<UserInfo> userInfoList = list();
final List<UserInfoLog> userInfoLogList = userInfoLogRepository.findAll();
final Map<Long, List<UserInfoLog>> map = userInfoLogList.stream().collect(Collectors.groupingBy(UserInfoLog::getUserId));
return userInfoList.stream()
.collect(Collectors.toMap(user -> user, user -> map.getOrDefault(user.getId(), Collections.emptyList())));
}
}
添加简单的 RestAPI 进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/api/user-info")
public class UserInfoApi {
@Autowired
private UserInfoService userInfoService;
@GetMapping("/list")
public List<UserInfo> list() {
return userInfoService.list();
}
@GetMapping("/list-user-info-and-log-map")
public Map<String, List<UserInfoLog>> listUserInfoAndLogMap() {
return userInfoService.listUserInfoAndLogMap().entrySet().stream()
.collect(Collectors.toMap(kv -> JsonUtil.toJson(kv.getKey()), Map.Entry::getValue));
}
}
测试 RestAPI
现在,我们启动项目并打开浏览器,应当可以在以下地址看到对应的 JSON 数据
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14[
{
"id": 1,
"name": "rx",
"age": 17,
"sex": false
},
{
"id": 2,
"name": " 琉璃 ",
"age": 18,
"sex": false
}
] -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24{
"{\"id\":\"1\",\"name\":\"rx\",\"age\":17,\"sex\":false}": [
{
"id": 1,
"userId": 1,
"operate": " 登录 ",
"logTime": "2019-02-22T16:22:16.099"
},
{
"id": 2,
"userId": 1,
"operate": " 退出 ",
"logTime": "2019-02-22T16:22:16.099"
}
],
"{\"id\":\"2\",\"name\":\"琉璃 \",\"age\":18,\"sex\":false}": [
{
"id": 3,
"userId": 2,
"operate": " 登录 ",
"logTime": "2019-02-22T16:22:16.099"
}
]
}
Mybatis Plus 扫包范围
使用@MapperScan
限制 Mybatis Plus 扫描Dao
的范围,注意不要扫到 MongoDB 的Repository
,否则会抛出异常1
Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'userInfoLogRepository' defined in null: Cannot register bean definition [Root bean: class [org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] for bean 'userInfoLogRepository': There is already [Generic bean: class [org.mybatis.spring.mapper.MapperFactoryBean]; scope=singleton; abstract=false; lazyInit=false; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\Text\spring-boot\spring-boot-mybatis-plus-mongo\out\production\classes\com\rxliuli\example\springbootmybatisplusmongo\repository\UserInfoLogRepository.class]] bound.
原因是在 SpringMongoData 处理之前 Mybatis Plus 先扫描到并进行了代理,然后就会告诉你无法注册 SpringMongoData 相关的
Repository
使用
@EnableMongoRepositories
限制 SpringMongoData 扫描的范围既然说到限制,自然也不得不说一下 SpringMongoData 本身,如果你已经使用了
@MapperScan
扫描 Mybatis 需要处理的 Dao,那么添加与否并不重要。但是,吾辈要说但是了,但是,如果你先使用的 MongoDB,那么如果没有使用@MapperScan
处理 Mybatis 的 Dao 的话,就会抛出以下异常,所以为了安全起见还是都定义了吧1
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userInfoServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.rxliuli.example.springbootmybatisplusmongo.dao.UserInfoDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
说的是自动注入
BaseMapper
失败,实际上是因为 Mybatis 的 Dao SpringMongoData 无法处理。最好使用不同的后缀名区分
Mybatis Mapper
和Mongo Repository
,或者放到不同的包
也是为了避免扫描混乱,出现Mybatis
扫描到Mongo Repository
或是Mongo
扫描到Mybatis Mapper
的情况,出现上面的那两个错误。
那么,关于在 SpringBoot 中同时使用 Mybatis Plus 和 MongoDB 的搭建就到这里啦
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK