作者:字节飞扬 原文链接:https:www。cnblogs。combytesflypresultmapinmybatisplus。html MyBatisPlus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。 MyBatisPlus对MyBatis基本零侵入,完全可以与MyBatis混合使用,这点很赞。 在涉及到关系型数据库增删查改的业务时,我比较喜欢用MyBatisPlus,开发效率极高。具体的使用可以参考官网,或者自己上手摸索感受一下。 下面简单总结一下在MyBatisPlus中如何使用ResultMap。问题说明 先看个例子: 有如下两张表:createtabletbbook(idbigintprimarykey,namevarchar(32),authorvarchar(20));createtabletbhero(idbigintprimarykey,namevarchar(32),ageint,skillvarchar(32),bidbigint); 其中,tbhero中的bid关联tbbook表的id。 下面先看Hero实体类的代码,如下:importcom。baomidou。mybatisplus。annotation。TableFimportcom。baomidou。mybatisplus。annotation。TableId;importcom。baomidou。mybatisplus。annotation。TableNimportcom。fasterxml。jackson。annotation。JsonIimportlombok。Gimportlombok。NoArgsCimportlombok。SGetterSetterNoArgsConstructorTableName(tbhero)JsonInclude(JsonInclude。Include。NONNULL)publicclassHero{TableId(id)privateLTableField(valuename,keepGlobalFormattrue)privateSTableField(valueage,keepGlobalFormattrue)privateITableField(valueskill,keepGlobalFormattrue)privateSTableField(valuebid,keepGlobalFormattrue)privateLongbookId;数据库表中不存在以下字段(表join时会用到)TableField(valuebookname,existfalse)privateStringbookNTableField(valueauthor,existfalse)privateS} 注意了,我特地把tbhero表中的bid字段映射成实体类Hero中的bookId属性。测试BaseMapper中内置的insert()方法或者IService中的save()方法 MyBatisPlus打印出的SQL为:Preparing:INSERTINTOtbhero(id,name,age,skill,bid)VALUES(?,?,?,?,?)Parameters:1589788935356416(Long),阿飞(String),18(Integer),天下第一快剑(String),1(Long) 没毛病,MyBatisPlus会根据TableField指定的映射关系,生成对应的SQL。测试BaseMapper中内置的selectById()方法或者IService中的getById()方法 MyBatisPlus打印出的SQL为:Preparing:SELECTid,name,age,skill,bidASbookIdFROMtbheroWHEREid?Parameters:1(Long) 也没毛病,可以看到生成的SELECT中把bid做了别名bookId。测试自己写的SQL 比如现在我想连接tbhero与tbbook这两张表,如下:MapperRepositorypublicinterfaceHeroMapperextendsBaseMapperHero{Select({SELECTtbhero。,tbbook。nameasbookname,tbbook。authorFROMtbheroLEFTJOINtbbookONtbhero。bidtbbook。id{ew。customSqlSegment}})IPageHeropageQueryHero(Param(Constants。WRAPPER)WrapperHeroqueryWrapper,PageHeropage);} 查询MyBatisPlus打印出的SQL为:Preparing:SELECTtbhero。,tbbook。nameASbookname,tbbook。authorFROMtbheroLEFTJOINtbbookONtbhero。bidtbbook。idWHERE(bid?)ORDERBYidASCLIMIT?OFFSET?Parameters:2(Long),1(Long),1(Long) SQL没啥问题,过滤与分页也都正常,但是此时你会发现bookId属性为null,如下: 为什么呢? 调用BaseMapper中内置的selectById()方法并没有出现这种情况啊??? 回过头来再对比一下在HeroMapper中自己定义的查询与MyBatisPlus自带的selectById()有啥不同,还记得上面的刚刚的测试吗,生成的SQL有啥不同? 原来,MyBatisPlus为BaseMapper中内置的方法生成SQL时,会把SELECT子句中bid做别名bookId,而自己写的查询MyBatisPlus并不会帮你修改SELECT子句,也就导致bookId属性为null。解决方法方案一:表中的字段与实体类的属性严格保持一致(字段有下划线则属性用驼峰表示) 在这里就是tbhero表中的bid字段映射成实体类Hero中的bid属性。这样当然可以解决问题,但不是本篇讲的重点。方案二:把自己写的SQL中bid做别名bookId方案三:使用ResultMap,这是此篇的重点 在TableName设置autoResultMaptrueTableName(valuetbhero,autoResultMaptrue)publicclassHero{} 然后在自定义查询中添加ResultMap注解,如下:importorg。apache。ibatis。annotations。Mimportorg。apache。ibatis。annotations。Pimportorg。apache。ibatis。annotations。ResultMimportorg。apache。ibatis。annotations。Simportorg。springframework。stereotype。RMapperRepositorypublicinterfaceHeroMapperextendsBaseMapperHero{ResultMap(mybatisplusHero)Select({SELECTtbhero。,tbbook。nameasbookname,tbbook。authorFROMtbheroLEFTJOINtbbookONtbhero。bidtbbook。id{ew。customSqlSegment}})IPageHeropageQueryHero(Param(Constants。WRAPPER)WrapperHeroqueryWrapper,PageHeropage);} 这样,也能解决问题。 下面简单看下源码,ResultMap(mybatisplus实体类名)怎么来的。 详情见:com。baomidou。mybatisplus。core。metadata。TableInfoinitResultMapIfNeed()自动构建resultMap并注入(如果条件符合的话)voidinitResultMapIfNeed(){if(autoInitResultMapnullresultMap){StringidcurrentNamespaceDOTMYBATISPLUSUNDERSCOREentityType。getSimpleName();ListResultMappingresultMappingsnewArrayList();if(havePK()){ResultMappingidMappingnewResultMapping。Builder(configuration,keyProperty,StringUtils。getTargetColumn(keyColumn),keyType)。flags(Collections。singletonList(ResultFlag。ID))。build();resultMappings。add(idMapping);}if(CollectionUtils。isNotEmpty(fieldList)){fieldList。forEach(iresultMappings。add(i。getResultMapping(configuration)));}ResultMapresultMapnewResultMap。Builder(configuration,id,entityType,resultMappings)。build();configuration。addResultMap(resultMap);this。resultM}} 注意看上面的字符串id的构成,你应该可以明白。 思考:这种方式的ResultMap默认是强绑在一个TableName上的,如果是某个聚合查询或者查询的结果并非对应一个真实的表怎么办呢?有没有更优雅的方式?自定义AutoResultMap注解 基于上面的思考,我做了下面简单的实现:自定义AutoResultMap注解importjava。lang。annotation。;使用AutoResultMap注解的实体类自动生成{auto。mybatisplus类名}为id的resultMap{linkMybatisPlusConfiginitAutoResultMap()}DocumentedRetention(RetentionPolicy。RUNTIME)Target(ElementType。TYPE)publicinterfaceAutoResultMap{}启动时扫描AutoResultMap注解的实体类packagecom。bytesfly。mybatis。importcn。hutool。core。util。ClassUimportcn。hutool。core。util。ReflectUimportcom。baomidou。mybatisplus。annotation。DbTimportcom。baomidou。mybatisplus。core。metadata。TableIimportcom。baomidou。mybatisplus。core。metadata。TableInfoHimportcom。baomidou。mybatisplus。extension。plugins。MybatisPlusIimportcom。baomidou。mybatisplus。extension。plugins。inner。PaginationInnerIimportcom。baomidou。mybatisplus。extension。toolkit。JdbcUimportcom。bytesfly。mybatis。annotation。AutoResultMimportlombok。extern。slf4j。Slf4j;importorg。apache。ibatis。builder。MapperBuilderAimportorg。mybatis。spring。SqlSessionTimportorg。mybatis。spring。annotation。MapperSimportorg。springframework。beans。factory。annotation。Aimportorg。springframework。boot。autoconfigure。jdbc。DataSourcePimportorg。springframework。context。annotation。Bimportorg。springframework。context。annotation。Cimportorg。springframework。transaction。annotation。EnableTransactionMimportjavax。annotation。PostCimportjava。util。S可添加一些插件ConfigurationEnableTransactionManagement(proxyTargetClasstrue)MapperScan(basePackagescom。bytesfly。mybatis。mapper)Slf4jpublicclassMybatisPlusConfig{AutowiredprivateSqlSessionTemplatesqlSessionT分页插件(根据jdbcUrl识别出数据库类型,自动选择适合该方言的分页插件)相关使用说明:https:baomidou。comguidepage。htmlBeanpublicMybatisPlusInterceptormybatisPlusInterceptor(DataSourcePropertiesdataSourceProperties){StringjdbcUrldataSourceProperties。getUrl();DbTypedbTypeJdbcUtils。getDbType(jdbcUrl);MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();interceptor。addInnerInterceptor(newPaginationInnerInterceptor(dbType));}AutoResultMap注解的实体类自动构建resultMap并注入PostConstructpublicvoidinitAutoResultMap(){try{log。info(startregisterAutoResultMap);SStringpackageNamecom。bytesfly。mybatis。model。db。SetC?classesClassUtil。scanPackageByAnnotation(packageName,AutoResultMap。class);org。apache。ibatis。session。ConfigurationconfigurationsqlSessionTemplate。getConfiguration();for(Classclazz:classes){MapperBuilderAssistantassistantnewMapperBuilderAssistant(configuration,);assistant。setCurrentNamespace(namespace);TableInfotableInfoTableInfoHelper。initTableInfo(assistant,clazz);if(!tableInfo。isAutoInitResultMap()){设置tableInfo的autoInitResultMap属性为trueReflectUtil。setFieldValue(tableInfo,autoInitResultMap,true);调用tableInfoinitResultMapIfNeed()方法,自动构建resultMap并注入ReflectUtil。invoke(tableInfo,initResultMapIfNeed);}}log。info(finishregisterAutoResultMap);}catch(Throwablee){log。error(initAutoResultMaperror,e);System。exit(1);}}} 关键代码其实没有几行,耐心看下应该不难懂。使用AutoResultMap注解 还是用例子来说明更直观。 下面是一个聚合查询:MapperRepositorypublicinterfaceBookMapperextendsBaseMapperBook{ResultMap(auto。mybatisplusBookAgg)Select({SELECTtbbook。id,max(tbbook。name)asname,arrayagg(distincttbhero。idorderbytbhero。idasc)asheroidsFROMtbheroINNERJOINtbbookONtbhero。bidtbbook。idGROUPBYtbbook。id})ListBookAggagg();} 其中BookAgg的定义如下,在实体类上使用了AutoResultMap注解:GetterSetterNoArgsConstructorAutoResultMappublicclassBookAgg{TableId(id)privateLongbookId;TableField(name)privateStringbookNTableField(heroids)privateObjectheroI}