本文最后更新于:2021年6月21日 中午
前置要求 本文假设你已经了解或知道以下技能,尤其而且是勾选的内容。
场景
GitHub 项目 , Blog 教程
需要同时使用 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 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; } }
用户信息 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 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: 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 { UserInfoLog findUserInfoLogByIdEquals (Long id) ; }
自定义更加复杂需求的 Dao 接口:com.rxliuli.example.springbootmybatisplusmongo.repository.CustomUserInfoLogRepository
1 2 3 4 5 6 7 8 9 public interface CustomUserInfoLogRepository { 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 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> { 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 的搭建就到这里啦