1.前言
年初节奏还没有快起来,适合做做技术前瞻,无论是对个人还是团队都是好事。真要说分享,其实感觉也没啥好分享的,就像接手新项目一样,代码都是自己看,别人讲的再多,不看,不用,不踩坑都不会刻骨铭心。
今天要跟大家分享的mybatisPlus的join查询也是临时起意,早上有同事觉得在mybatisPlus的xml文件里配置子查询不好,嫌弃慢(其实是偷懒了没有正确使用,resultMap没有根据业务自己用自己的,避免多余的子查询),建议在代码层面实现,这里就分享这个join查询如你所愿。
2.分享内容
MybatisPlus的join查询工具包,这个几乎可以满足零xml的sql编写。实现这一工具包的有2个人,一个是河南的,一个是北京的。个人偏向使用北京小哥实现的,有boot-starter可以支持配置文件,功能更强点。
3.使用方法
3.1北京小哥的join工具包
3.1.1源码与帮助介绍
Git地址:https://github.com/yulichang/mybatis-plus-join.git
使用帮助:https://ylctmh.com/
3.1.2使用集成
1、依赖引入
<!-- mybatis-plus-join依赖 -->
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId>
<version>1.4.2.2</version>
</dependency>
2、配置
因为提供了boot-starter,所以可以直接使用配置文件,具体可以查看在线使用帮助。我这里是在多数据源场景下使用的,就不用配置文件了。
配置类
A、 MbatisPlus的配置类增加绑定配置
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.github.yulichang.injector.MPJSqlInjector;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
/**
* 分页插件配置
*
* @author zhengwen
*/
@Slf4j
@Configuration
public class MyBatisPlusConfig {
/**
* 分页插件 3.5.X
*/
@Bean
public PaginationInnerInterceptor paginationInnerInterceptor() {
log.debug("注册分页插件");
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
/* 只负责注册分页插件,不具体设置DbType、方言等
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
paginationInnerInterceptor.setDbType(DbType.TDENGINE);
// 开启 count 的 join 优化,只针对部分 left join
paginationInnerInterceptor.setOptimizeJoin(true);
*/
return paginationInnerInterceptor;
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.setInterceptors(Collections.singletonList(paginationInnerInterceptor()));
return mybatisPlusInterceptor;
}
/**
* GlobalConfig 配置 MPJSqlInjector
* 绑定MPJSqlInjector到全局配置
*/
@Bean
public GlobalConfig globalConfig(MPJSqlInjector mpjSqlInjector) {
GlobalConfig config = new GlobalConfig();
//绑定 mpjSqlInjector到全局配置
config.setSqlInjector(mpjSqlInjector);
//其他配置 略
return config;
}
}
B、 Mysql数据源配置注入全局配置
如果不是多数据源其实可以直接在MybatisPlus里的SqlSessionFactory注入全局配置。我这里是在Mysql数据源配置类注入配置。
MysqlServerConfig配置类的SqlSessionFactory增加配置
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.Collections;
/**
* mysql配置类
*
* @author zhengwen
*/
@Configuration
@MapperScan(basePackages = {"com.xiaotian.datatrans.mapper.mysql"}, sqlSessionTemplateRef = "mysqlSqlSessionTemplate")
public class MysqlServerConfig {
@Autowired
private PaginationInnerInterceptor paginationInnerInterceptor;
private static final String MAPPER_LOCATION = "classpath:mapper/mysql/*.xml";
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.mysql-service")
@Primary
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource, GlobalConfig globalConfig) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
//关联SqlSessionFactory与GlobalConfig
bean.setGlobalConfig(globalConfig);
//不在这里注入分页插件,会失效,(MyBatisPlusConfig只负责注册分页插件)
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
paginationInnerInterceptor.setMaxLimit(-1L);
paginationInnerInterceptor.setDbType(DbType.MYSQL);
// 开启 count 的 join 优化,只针对部分 left join
paginationInnerInterceptor.setOptimizeJoin(true);
mybatisPlusInterceptor.setInterceptors(Collections.singletonList(paginationInnerInterceptor));
bean.setPlugins(mybatisPlusInterceptor);
return bean.getObject();
}
@Bean(name = "mysqlTransactionManager")
@Primary
public DataSourceTransactionManager mysqlTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "mysqlSqlSessionTemplate")
@Primary
public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
3、Mapper变更
业务mapper原先是继承MybatisPlus的BaseMapper,这里改为继承MPJBaseMapper,点击进去实际上这个接口最终也有继承了BaseMapper的。
/**
* <p>
* 设备推送信息表 Mapper 接口
* </p>
*
* @author zhengwen
* @since 2023-02-03
*/
@DS("mysql-service")
public interface DeviceRecordMapper extends MPJBaseMapper<DeviceRecord> {
/**
* 测试
*
* @param page
* @param deviceRecord
* @return
*/
IPage<DeviceRecordDto> selectListBy(Page page, @Param("deviceRecord") DeviceRecord deviceRecord);
}
4、service实现使用
/**
* <p>
* 设备推送信息表 服务实现类
* </p>
*
* @author zhengwen
* @since 2023-02-03
*/
@Service
public class DeviceRecordServiceImpl extends ServiceImpl<DeviceRecordMapper, DeviceRecord> implements DeviceRecordService {
@Autowired
private DeviceRecordMapper deviceRecordMapper;
@Override
public IPage<DeviceRecordDto> selectPage(Page page, DeviceRecord customQueryParams) {
QueryWrapper<DeviceRecord> qw = new QueryWrapper<>();
IPage<DeviceRecordDto> records = deviceRecordMapper.selectPage(page, qw);
//IPage<Student> records = deviceRecordMapper.selectStudentPage(page, customQueryParams);
return records;
}
@Override
public IPage<DeviceRecordDto> getPageUseJoin(Page page, DeviceRecord customQueryParams) {
//分页查询
IPage<DeviceRecordDto> list = deviceRecordMapper.selectJoinPage(page, DeviceRecordDto.class,
new MPJLambdaWrapper<DeviceRecord>()
.selectAll(DeviceRecord.class)
//子查询
.selectCollection(DeviceRecordInfo.class, DeviceRecordDto::getRecordInfoList)
.leftJoin(DeviceRecordInfo.class, DeviceRecordInfo::getRecordId, DeviceRecord::getId));
return list;
}
}
3.1.3效果查看
1、2张表的sql
设备推送信息表:device_record
设备上报信息详情:device_record_info
CREATE TABLE `device_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`device_no` varchar(100) DEFAULT NULL COMMENT '设备编码',
`record_time` datetime DEFAULT NULL COMMENT '上报时间',
`device_type_no` varchar(10) DEFAULT NULL COMMENT '设备类型编码',
`device_name` varchar(200) DEFAULT NULL COMMENT '设备名称',
`record_type` varchar(10) DEFAULT NULL COMMENT '上报数据类型',
`service_id` varchar(20) DEFAULT NULL COMMENT '服务id',
`record_content` varchar(4000) DEFAULT NULL COMMENT '上报原始内容',
`create_by` int(11) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='设备推送信息表';
CREATE TABLE `device_record_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`tag_name` varchar(100) DEFAULT NULL COMMENT '字段名称',
`tag_key` varchar(50) DEFAULT NULL COMMENT '字段key',
`tag_val` varchar(50) DEFAULT NULL COMMENT '字段值',
`record_id` bigint(20) DEFAULT NULL COMMENT '记录id',
`tag_unit` varchar(30) DEFAULT NULL COMMENT '单位',
`tag_val_name` varchar(100) DEFAULT NULL COMMENT '字段值释意',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='设备上报信息详情';
2、2张表关系说明
Device_record的主键关联device_record_info的record_id字段:
3、接口请求效果
A、执行的sql
B、返回的数据
3.1.4使用总结
1、这里做了个简单示例,它的使用确实应该是可以做到零xml的sql编写的,就看你愿意不。
2、MPJLambdaWrapper增加过滤条件就跟MybatisPlus的QueryWrapper一致。
3、还有很多使用方法,可以自行去在线帮助文档查看,也可以自己在mapper后面“.“的看。
4、个人认为还是挺香的
3.2河南小哥的join工具包
3.2.1源码与帮助介绍
Git地址:https://gitee.com/mhb0409/mybatis-plus-join
使用帮助:就是项目的README.md里介绍
3.2.2使用集成
1、依赖引入
<dependency>
<groupId>icu.mhb</groupId>
<artifactId>mybatis-plus-join</artifactId>
<version>1.3.4</version>
</dependency>
2、配置
A、配置类
MyBatisPlusConfig需要继承JoinDefaultSqlInjector,并且需要重写getMethodList方法
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import icu.mhb.mybatisplus.plugln.injector.JoinDefaultSqlInjector;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
import java.util.List;
/**
* 分页插件配置
*
* @author zhengwen
*/
@Slf4j
@Configuration
public class MyBatisPlusConfig extends JoinDefaultSqlInjector {
/**
* 分页插件 3.5.X
*/
@Bean
public PaginationInnerInterceptor paginationInnerInterceptor() {
log.debug("注册分页插件");
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
/* 只负责注册分页插件,不具体设置DbType、方言等
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
paginationInnerInterceptor.setDbType(DbType.TDENGINE);
// 开启 count 的 join 优化,只针对部分 left join
paginationInnerInterceptor.setOptimizeJoin(true);
*/
return paginationInnerInterceptor;
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.setInterceptors(Collections.singletonList(paginationInnerInterceptor()));
return mybatisPlusInterceptor;
}
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
return methodList;
}
}
B、Mysql数据源配置类
全局配置绑定join的Injector
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import icu.mhb.mybatisplus.plugln.injector.JoinDefaultSqlInjector;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.Collections;
/**
* mysql配置类
*
* @author zhengwen
*/
@Configuration
@MapperScan(basePackages = {"com.xiaotian.datatrans.mapper.mysql"}, sqlSessionTemplateRef = "mysqlSqlSessionTemplate")
public class MysqlServerConfig {
@Autowired
private PaginationInnerInterceptor paginationInnerInterceptor;
private static final String MAPPER_LOCATION = "classpath:mapper/mysql/*.xml";
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.mysql-service")
@Primary
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public GlobalConfig globalConfig(JoinDefaultSqlInjector joinDefaultSqlInjector) {
GlobalConfig config = new GlobalConfig();
//绑定 mpjSqlInjector到全局配置
config.setSqlInjector(joinDefaultSqlInjector);
//其他配置 略
return config;
}
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource, GlobalConfig globalConfig) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
//关联SqlSessionFactory与GlobalConfig
bean.setGlobalConfig(globalConfig);
//不在这里注入分页插件,会失效,(MyBatisPlusConfig只负责注册分页插件)
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
paginationInnerInterceptor.setMaxLimit(-1L);
paginationInnerInterceptor.setDbType(DbType.MYSQL);
// 开启 count 的 join 优化,只针对部分 left join
paginationInnerInterceptor.setOptimizeJoin(true);
mybatisPlusInterceptor.setInterceptors(Collections.singletonList(paginationInnerInterceptor));
bean.setPlugins(mybatisPlusInterceptor);
return bean.getObject();
}
@Bean(name = "mysqlTransactionManager")
@Primary
public DataSourceTransactionManager mysqlTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "mysqlSqlSessionTemplate")
@Primary
public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
3、springBoot3的启动类还要增加import
否则,查询结果不能成功映射到MybatisPlus的IPage对象的records
启动类上增加注解@Import({JoinInterceptor.class, JoinInterceptorConfig.class})
import icu.mhb.mybatisplus.plugln.interceptor.JoinInterceptor;
import icu.mhb.mybatisplus.plugln.interceptor.JoinInterceptorConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Import;
/**
* @author zhengwen
*/
@Import({JoinInterceptor.class, JoinInterceptorConfig.class})
@EntityScan("com.xiaotian")
@SpringBootApplication
public class DataGeniusApplication {
public static void main(String[] args) {
SpringApplication.run(DataGeniusApplication.class, args);
System.out.println("--数据精灵启动成功--");
}
}
4、Mapper变更
业务mapper原先是继承MybatisPlus的BaseMapper,这里改为继承JoinBaseMapper,点击进去实际上这个接口最终也继承了BaseMapper的。
/**
* <p>
* 设备推送信息表 Mapper 接口
* </p>
*
* @author zhengwen
* @since 2023-02-03
*/
@DS("mysql-service")
public interface DeviceRecordMapper extends JoinBaseMapper<DeviceRecord> {
/**
* 测试
*
* @param page
* @param deviceRecord
* @return
*/
IPage<DeviceRecordDto> selectListBy(Page page, @Param("deviceRecord") DeviceRecord deviceRecord);
}
5、service实现类使用
/**
* <p>
* 设备推送信息表 服务实现类
* </p>
*
* @author zhengwen
* @since 2023-02-03
*/
@Service
public class DeviceRecordServiceImpl extends ServiceImpl<DeviceRecordMapper, DeviceRecord> implements DeviceRecordService {
@Autowired
private DeviceRecordMapper deviceRecordMapper;
@Override
public IPage<DeviceRecordDto> selectPage(Page page, DeviceRecord customQueryParams) {
QueryWrapper<DeviceRecord> qw = new QueryWrapper<>();
IPage<DeviceRecordDto> records = deviceRecordMapper.selectPage(page, qw);
//IPage<Student> records = deviceRecordMapper.selectStudentPage(page, customQueryParams);
return records;
}
@Override
public IPage<DeviceRecordDto> getPageUseJoin(Page page, DeviceRecord customQueryParams) {
//分页查询
JoinLambdaWrapper<DeviceRecord> joinLambdaWrapper = new JoinLambdaWrapper<>(DeviceRecord.class);
joinLambdaWrapper.leftJoin(DeviceRecordInfo.class, DeviceRecordInfo::getRecordId, DeviceRecord::getId)
.manyToManySelect(DeviceRecordDto::getRecordInfoList,DeviceRecordInfo.class)
//下面的这个end一定不能少哦,否在sql不会出现 left join xxx
.end();
IPage<DeviceRecordDto> list = deviceRecordMapper.joinSelectPage(page, joinLambdaWrapper, DeviceRecordDto.class);
return list;
}
}
3.2.3使用效果查看
效果与3.1.3一致
3.2.4使用总结
1、总体感觉基本与3.1.4一致
2、没有boot-starter感觉没有北京小哥的高大上
3、在线文档没有专门的网址
4、配置略比北京小哥的复杂一丢丢
4.总体感想
- 首先应该确实可以做到零XML的sql编写了
- 零sql编写应该是优缺点各一半吧
- 有没有觉得有了这个,JPA真的是没有必要用了呢?
- 这2各包在Maven中心库,看到发布第一版时间是同年同月,不过走的方向不一样,一个走插件方式,一个走重写。
- 2个作者都还在不断更新兼容新版本的MybatisPlus,点赞
- 2个作者,我也联系到人了,都很友好。就喜欢技术人这种面向问题的交流方式。
如果有需要,可以去我CSDN的gitCode下载demo,地址:https://gitcode.net/zwrlj527/data-trans.git。这个还是集成上次的多数据源做的集成。更多使用姿势,大家可以自行去他们的帮助文档查看并解锁使用。
希望可以帮到大家,uping!!!