1

将 My

 3 years ago
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.
neoserver,ios ssh client

本文假设你已经了解或知道以下技能,尤其而且是勾选的内容。

  • Gradle
  • SpringBoot
  • Mybatis Plus
  • MongoDB
  • SpringBoot MongoDB Data
  • SpringTest

GitHub 项目, Blog 教程

需要同时使用 Mybatis-PlusMongoDB,所以就去了解了一下如何集成它们。

集成 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.sqldata-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

  1. 测试 UserInfoLogRepository 中由 MongoDB Data 自动实现的 findUserInfoLogByIdEquals() 方法
  2. 测试 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"
    }
    ]
    }
  1. 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

  2. 使用 @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 无法处理。

  3. 最好使用不同的后缀名区分 Mybatis MapperMongo Repository,或者放到不同的包
    也是为了避免扫描混乱,出现 Mybatis 扫描到 Mongo Repository 或是 Mongo 扫描到 Mybatis Mapper 的情况,出现上面的那两个错误。


那么,关于在 SpringBoot 中同时使用 Mybatis Plus 和 MongoDB 的搭建就到这里啦


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK