MyBatis-Plus联表查询的短板,终于有一款工具补齐了+fluent mybatis生成代码设置
mybatis-plus
作为mybatis
的增强工具,它的出现极大的简化了开发中的数据库操作,但是长久以来,它的联表查询能力一直被大家所诟病。一旦遇到left join
或right join
的左右连接,你还是得老老实实的打开xml文件,手写上一大段的sql语句。
直到前几天,偶然碰到了这么一款叫做mybatis-plus-join
的工具(后面就简称mpj
了),使用了一下,不得不说真香!彻底将我从xml地狱中解放了出来,终于可以以类似mybatis-plus
中QueryWrapper
的方式来进行联表查询了,话不多说,我们下面开始体验。
引入依赖
首先在项目中引入引入依赖坐标,因为mpj
中依赖较高版本mybatis-plus
中的一些api,所以项目建议直接使用高版本。
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
引入相关依赖后,在springboot
项目中,像往常一样正常配置数据源连接信息就可以了。
数据准备
因为要实现联表查询,所以我们先来建几张表进行测试。
订单表:
![](https://pic1.zhimg.com/v2-5cc7295c7c75f5767513e9d6d9052e78_b.jpg)
用户表,包含用户姓名:
![](https://picx.zhimg.com/v2-e3f1ca3a8ab93ff34dabdb7488a8b9bb_b.jpg)
商品表,包含商品名称和单价:
![](https://pica.zhimg.com/v2-41bb8ad5ed0fd33419f9d9f72000a7ec_b.jpg)
在订单表中,通过用户id和商品id与其他两张表进行关联。
修改Mapper
以往在使用myatis-plus
的时候,我们的Mapper
层接口都是直接继承的BaseMapper
,使用mpj
后需要对其进行修改,改为继承MPJBaseMapper
接口。
@Mapper
public interface OrderMapper extends MPJBaseMapper<Order> {
}
对其余两个表的Mapper
接口也进行相同的改造。此外,我们的service
也可以选择继承MPJBaseService
,serviceImpl
选择继承MPJBaseServiceImpl
,这两者为非必须继承。
查询
Mapper
接口改造完成后,我们把它注入到Service
中,虽然说我们要完成3张表的联表查询,但是以Order
作为主表的话,那么只注入这一个对应的OrderMapper
就可以,非常简单。
@Service
@AllArgsConstructor
public class OrderServiceImpl implements OrderService {
private final OrderMapper orderMapper;
}
MPJLambdaWrapper
接下来,我们体验一下再也不用写sql的联表查询:
public void getOrder() {
List<OrderDto> list = orderMapper.selectJoinList(OrderDto.class,
new MPJLambdaWrapper<Order>()
.selectAll(Order.class)
.select(Product::getUnitPrice)
.selectAs(User::getName,OrderDto::getUserName)
.selectAs(Product::getName,OrderDto::getProductName)
.leftJoin(User.class, User::getId, Order::getUserId)
.leftJoin(Product.class, Product::getId, Order::getProductId)
.eq(Order::getStatus,3));
list.forEach(System.out::println);
}
不看代码,我们先调用接口来看一下执行结果:
![](https://pic2.zhimg.com/v2-4ee2c763f046810e52de570ee0ca7f59_b.png)
可以看到,成功查询出了关联表中的信息,下面我们一点点介绍上面代码的语义。
首先,调用mapper
的selectJoinList()
方法,进行关联查询,返回多条结果。后面的第一个参数OrderDto.class
代表接收返回查询结果的类,作用和我们之前在xml
中写的resultType
类似。
这个类可以直接继承实体,再添加上需要在关联查询中返回的列即可:
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class OrderDto extends Order {
String userName;
String productName;
Double unitPrice;
}
接下来的MPJLambdaWrapper
就是构建查询条件的核心了,看一下我们在上面用到的几个方法:
selectAll()
:查询指定实体类的全部字段select()
:查询指定的字段,支持可变长参数同时查询多个字段,但是在同一个select
中只能查询相同表的字段,所以如果查询多张表的字段需要分开写selectAs()
:字段别名查询,用于数据库字段与接收结果的dto
中属性名称不一致时转换leftJoin()
:左连接,其中第一个参数是参与联表的表对应的实体类,第二个参数是这张表联表的ON
字段,第三个参数是参与联表的ON
的另一个实体类属性
除此之外,还可以正常调用mybatis-plus
中的各种原生方法,文档中还提到,默认主表别名是t
,其他的表别名以先后调用的顺序使用t1
、t2
、t3
以此类推。
我们用插件读取日志转化为可读的sql语句,可以看到两条左连接条件都被正确地添加到了sql中:
![](https://picx.zhimg.com/v2-02abe674ecee02a918ba584a22dabf05_b.jpg)
MPJQueryWrapper
和mybatis-plus
非常类似,除了LamdaWrapper
外还提供了普通QueryWrapper
的写法,改造上面的代码:
public void getOrderSimple() {
List<OrderDto> list = orderMapper.selectJoinList(OrderDto.class,
new MPJQueryWrapper<Order>()
.selectAll(Order.class)
.select("t2.unit_price","t2.name as product_name")
.select("t1.name as user_name")
.leftJoin("t_user t1 on t1.id = t.user_id")
.leftJoin("t_product t2 on t2.id = t.product_id")
.eq("t.status", "3")
);
list.forEach(System.out::println);
}
运行结果与之前完全相同,需要注意的是,这样写时在引用表名时不要使用数据库中的原表名,主表默认使用t
,其他表使用join
语句中我们为它起的别名,如果使用原表名在运行中会出现报错。
并且,在MPJQueryWrapper
中,可以更灵活的支持子查询操作,如果业务比较复杂,那么使用这种方式也是不错的选择。
分页查询
mpj
中也能很好的支持列表查询中的分页功能,首先我们要在项目中加入分页拦截器:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
接下来改造上面的代码,调用selectJoinPage()
方法:
public void page() {
IPage<OrderDto> orderPage = orderMapper.selectJoinPage(
new Page<OrderDto>(2,10),
OrderDto.class,
new MPJLambdaWrapper<Order>()
.selectAll(Order.class)
.select(Product::getUnitPrice)
.selectAs(User::getName, OrderDto::getUserName)
.selectAs(Product::getName, OrderDto::getProductName)
.leftJoin(User.class, User::getId, Order::getUserId)
.leftJoin(Product.class, Product::getId, Order::getProductId)
.orderByAsc(Order::getId));
orderPage.getRecords().forEach(System.out::println);
}
注意在这里需要添加一个分页参数的Page
对象,我们再执行上面的代码,并对日志进行解析,查看sql语句:
![](https://pica.zhimg.com/v2-0eb3a84f7b4d3c02178c673c4e73ffd6_b.jpg)
可以看到底层通过添加limit
进行了分页,同理,MPJQueryWrapper
也可以这样进行分页。
最后
经过简单的测试,个人感觉mpj
这款工具在联表查询方面还是比较实用的,能更应对项目中不是非常复杂的场景下的sql查询,大大提高我们的生产效率。当然,在项目的issues
中也能看到当前版本中也仍然存在一些问题,希望在后续版本迭代中能继续完善。
那么,这次的分享就到这里,我是Hydra,下篇文章再见。
作者简介,码农参上
,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术。欢迎添加好友,进一步交流。
使用fluent mybatis可以不用写具体的xml文件,通过java api可以构造出比较复杂的业务sql语句,做到代码逻辑和sql逻辑的合一。
不再需要在Dao中组装查询或更新操作,在xml或mapper中再组装参数。那对比原生Mybatis, Mybatis Plus或者其他框架,FluentMybatis提供了哪些便利呢?
需求场景设置
我们通过一个比较典型的业务需求来具体实现和对比下,假如有学生成绩表结构如下:
createtable`student_score` ( idbigintauto_incrementcomment'主键ID'primarykey, student_idbigintnotnullcomment'学号', gender_mantinyintdefault0notnullcomment'性别,0:女;1:男', school_termintnullcomment'学期', subjectvarchar(30)nullcomment'学科', scoreintnullcomment'成绩', gmt_createdatetimenotnullcomment'记录创建时间', gmt_modifieddatetimenotnullcomment'记录最后修改时间', is_deletedtinyintdefault0notnullcomment'逻辑删除标识' )engine=InnoDBdefaultcharset=utf8;
现在有需求:
统计2000年三门学科('英语', '数学', '语文')及格分数按学期,学科统计最低分,最高分和平均分, 且样本数需要大于1条,统计结果按学期和学科排序。
我们可以写SQL语句如下
selectschool_term, subject, count(score)ascount, min(score)asmin_score, max(score)asmax_score, avg(score)asmax_score fromstudent_score whereschool_term>=2000 andsubjectin('英语','数学','语文') andscore>=60 andis_deleted=0 groupbyschool_term,subject havingcount(score)>1 orderbyschool_term,subject;
那上面的需求,分别用fluent mybatis, 原生mybatis 和 Mybatis plus来实现一番。
三者实现对比
使用fluent mybatis 来实现上面的功能
![](https://pic2.zhimg.com/v2-a4be08f9b60a2f9bbd66ca82f42bcc71_b.jpg)
具体代码
我们可以看到fluent api的能力,以及IDE对代码的渲染效果。java技术进阶路线:https://www.yoodb.com/
换成mybatis原生实现效果
-
定义Mapper接口
publicinterfaceMyStudentScoreMapper{ List<Map<String,Object>>summaryScore(SummaryQueryparas); }
-
定义接口需要用到的参数实体 SummaryQuery
@Data @Accessors(chain=true) publicclassSummaryQuery{ privateIntegerschoolTerm; privateList<String>subjects; privateIntegerscore; privateIntegerminCount; }
-
定义实现业务逻辑的mapper xml文件
<selectid="summaryScore"resultType="map"parameterType="cn.org.fluent.mybatis.springboot.demo.mapper.SummaryQuery"> selectschool_term, subject, count(score)ascount, min(score)asmin_score, max(score)asmax_score, avg(score)asmax_score fromstudent_score whereschool_term>=#{schoolTerm} andsubjectin <foreachcollection="subjects"item="item"open="("close=")"separator=","> #{item} </foreach> andscore>=#{score} andis_deleted=0 groupbyschool_term,subject havingcount(score)>#{minCount} orderbyschool_term,subject </select>
-
实现业务接口(这里是测试类, 实际应用中应该对应Dao类)
@RunWith(SpringRunner.class) @SpringBootTest(classes=QuickStartApplication.class) publicclassMybatisDemo{ @Autowired privateMyStudentScoreMappermapper; @Test publicvoidmybatis_demo(){ //构造查询参数 SummaryQueryparas=newSummaryQuery() .setSchoolTerm(2000) .setSubjects(Arrays.asList("英语","数学","语文")) .setScore(60) .setMinCount(1); List<Map<String,Object>>summary=mapper.summaryScore(paras); System.out.println(summary); } }
总之,直接使用mybatis,实现步骤还是相当的繁琐,效率太低。那换成mybatis plus的效果怎样呢?
换成mybatis plus实现效果
mybatis plus的实现比mybatis会简单比较多,实现效果如下
![](https://picx.zhimg.com/v2-02c3aad710c70efd60bb12f8ff4f26ed_b.jpg)
如红框圈出的,写mybatis plus实现用到了比较多字符串的硬编码(可以用Entity的get lambda方法部分代替字符串编码)。字符串的硬编码,会给开发同学造成不小的使用门槛,个人觉的主要有2点:
-
字段名称的记忆和敲码困难
-
Entity属性跟随数据库字段发生变更后的运行时错误
其他框架,比如TkMybatis在封装和易用性上比mybatis plus要弱,就不再比较了。
生成代码编码比较
fluent mybatis生成代码设置
publicclassAppEntityGenerator{ staticfinalStringurl="jdbc:mysql://localhost:3306/fluent_mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8"; publicstaticvoidmain(String[]args){ FileGenerator.build(Abc.class); } @Tables( /**数据库连接信息**/ url=url,username="root",password="password", /**Entity类parentpackage路径**/ basePack="cn.org.fluent.mybatis.springboot.demo", /**Entity代码源目录**/ srcDir="spring-boot-demo/src/main/java", /**Dao代码源目录**/ daoDir="spring-boot-demo/src/main/java", /**如果表定义记录创建,记录修改,逻辑删除字段**/ gmtCreated="gmt_create",gmtModified="gmt_modified",logicDeleted="is_deleted", /**需要生成文件的表(表名称:对应的Entity名称)**/ tables=@Table(value={"student_score"}) ) staticclassAbc{ } }
mybatis plus代码生成设置
publicclassCodeGenerator{ staticStringdbUrl="jdbc:mysql://localhost:3306/fluent_mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8"; @Test publicvoidgenerateCode(){ GlobalConfigconfig=newGlobalConfig(); DataSourceConfigdataSourceConfig=newDataSourceConfig(); dataSourceConfig.setDbType(DbType.MYSQL) .setUrl(dbUrl) .setUsername("root") .setPassword("password") .setDriverName(Driver.class.getName()); StrategyConfigstrategyConfig=newStrategyConfig(); strategyConfig .setCapitalMode(true) .setEntityLombokModel(false) .setNaming(NamingStrategy.underline_to_camel) .setColumnNaming(NamingStrategy.underline_to_camel) .setEntityTableFieldAnnotationEnable(true) .setFieldPrefix(newString[]{"test_"}) .setInclude(newString[]{"student_score"}) .setLogicDeleteFieldName("is_deleted") .setTableFillList(Arrays.asList( newTableFill("gmt_create",FieldFill.INSERT), newTableFill("gmt_modified",FieldFill.INSERT_UPDATE))); config .setActiveRecord(false) .setIdType(IdType.AUTO) .setOutputDir(System.getProperty("user.dir")+"/src/main/java/") .setFileOverride(true); newAutoGenerator().setGlobalConfig(config) .setDataSource(dataSourceConfig) .setStrategy(strategyConfig) .setPackageInfo( newPackageConfig() .setParent("com.mp.demo") .setController("controller") .setEntity("entity") ).execute(); } }
FluentMybatis特性一览
![](https://pica.zhimg.com/v2-c0426d77ae8073336412a79f8f130784_b.jpg)
三者对比总结
看完3个框架对同一个功能点的实现, 各位看官肯定会有自己的判断,笔者这里也总结了一份比较。
![](https://pica.zhimg.com/v2-20793e3e1044677751995dc81c8a8148_b.jpg)
项目源码及其相关文档获取方式:关注“Java精选”公众号,回复“fluent mybatis”即可获取最新源项目。
作者:稻草江南
http://juejin.cn/post/6886019929519177735
公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群! Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!------ 特别推荐 ------特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。点击“阅读原文”,了解更多精彩内容!文章有帮助的话,点在看,转发吧!
![](https://img2024.cnblogs.com/blog/572188/202410/572188-20241025104601449-94941185.png)
![](https://img2024.cnblogs.com/blog/572188/202410/572188-20241025104612394-555783224.png)