mybatis plus开发过程中遇到的问题记录及解决
作者:唱跑雨淋淋
本文主要记录本人使用 mybatis plus 开发过程中碰到的问题,以及解决方案
## 以下 mybatis plus 统一简称 mp
一、使用 mp 生成代码时
1、使用saveOrUpdateBatch或者saveBatch等新增修改方法时
问题情况:
MybatisPlusException: error: can not execute. because can not find column for id from entity
原因:
不能执行。因为无法从实体中找到id列使用 mp 自动生成代码时,可能有这一行代码,会导致不生成主键 ID,变成自定义基础的Entity类,公共字段
strategy.setSuperEntityColumns("id")
解决方法:
将上面这行代码注释即可
可能生成 ID 之后任旧无法执行,检查下面的原因
可能会存在实体类属性与数据库字段不一致的情况,所以我们在使用 mp 生成实体类时,最好在生成策略中加上这个行
strategy.setEntityTableFieldAnnotationEnable(true);
目的:生成之后的实体类中的每个属性会多出一个注解,来用于属性和数据库字段的对应
@TableField("id")
2、生成实体类时
数据库中的 int 类型 ID 变成了 String
问题情况:
原因及解决方法:
我这里是 Mysql 数据库,生成代码时,做数据库类型转换时,原本选择的是 Oracle 数据库,改成 Mysql 数据的类型就可以
3、引用第2钟错误,当我们使用 mp 生成代码的时候,只想生成实体类
问题情况:
引用第2钟错误,当我们使用 mp 生成代码的时候,可能实体类或者某一个文件中的代码生成的有问题,需要重新生成,但是又不想覆盖其他 Controller 、Mapper 等文件,我们如何选择只生成实体类?
解决方法:
我百度搜过,大佬们生成代码时,基本上都是生成所有的文件,所以我就自己开始研究。
请看下面这段代码,大家应该都明白这是什么意思,就是让我们生成的 mapper.xml 生成到我们指定的 resource 的文件夹下面,那么它原本的 xml 为什么不会生成了呢?
// 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapperWorkDiscovery/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); //让默认生成 mapper 的目录不再生成 templateConfig.setXml(null); mpg.setTemplate(templateConfig);
关键点在这一行代码
templateConfig.setXml(null);
在配置模板的时候,我们设置了模板的 xml 为null,那么在我们改变 xml 的生成路径之后,原本的xml 就不会自己生成了,所以我想会不会有 .setMapper(null) 、 .setController(null) 的方法呢,于是我就自己尝试了一下,加上这几行代码
templateConfig.setMapper(null); templateConfig.setController(null); templateConfig.setService(null); templateConfig.setServiceImpl(null);
这样重新生成之后,就只会生成 Entity 实体类了。
二、使用 mp 封装的方法时
1、使用修改方法,修改对象某一字段为null
问题情况:开发过程中,我们不可避免的会碰到这样的一个场景,需要修改对象某一个字段的值为 null,原本我们用 mybatis 时,修改方法会有两个
- updateByPrimaryKey 对你注入的字段全部更新(不判断是否为Null);我们可以用这个来更新 null 值
- updateByPrimaryKeySelective 会对字段进行判断再更新(如果为Null就忽略更新)
但是 mp 中的 update 方法是默认只更新不为 null 的值
例如:这样是更新不了对应字段为 null 的
TSysUseraccount tSysUseraccount = sysUseraccountMapper.selectById(84); tSysUseraccount.setfExtensionnumber(null); sysUseraccountMapper.updateById(tSysUseraccount); //执行的 sql 语句 UPDATE T_Sys_UserAccount SET F_UserCode='cs', F_DeleteFlag=0 WHERE F_UserId=84
解决方法:
使用 LambdaUpdateWrapper 强制设置字段值为 null
TSysUseraccount tSysUseraccount = sysUseraccountMapper.selectById(84); LambdaUpdateWrapper<TSysUseraccount> userUpdateWrapper = new UpdateWrapper<TSysUseraccount>().lambda() .eq(TSysUseraccount::getfUserid,tSysUseraccount.getfUserid()) .set(TSysUseraccount::getfExtensionnumber,null); sysUseraccountMapper.update(tSysUseraccount,userUpdateWrapper); // 执行的 sql 语句 UPDATE T_Sys_UserAccount SET F_UserCode='cs', F_DeleteFlag=0, F_ExtensionNumber=null WHERE F_UserId = 84
三、自定义 sql 查询
1、mapper 接口中注解查询
@Select({"<script>", "SELECT IFNULL(sum( IFNULL(detail.amount,0) ),0) as total FROM T_Lhgy_Work_Plan plan, T_Lhgy_Work_Plan_Detail detail WHERE plan.id = detail.plan_id AND plan.delete_flag = 0 AND detail.delete_flag = 0 AND plan.object_id = #{yhCompanyId}", "<when test='startTime!=null'>", "AND date_format(plan.create_date,'%Y-%m-%d') >= #{startTime}", "</when>", "<when test='endTime!=null'>", "AND date_format(plan.create_date,'%Y-%m-%d') <= #{endTime}", "</when>", "</script>"}) List<Map<String, Object>> selectSumAmountByYhCompany(@Param("yhCompanyId") Integer yhCompanyId,@Param("startTime") String startTime,@Param("endTime") String endTime);
问题情况:
mybatis 报The content of elements must consist of well-formed character data or markup. 语法格式错误
问题原因:
原来在xml中使用“<” “>” “&” 等一些这样的操作符时,xml会把它当成一个新的元素开始;
解决方法:
使用< ![CDATA[" 标记开始,以"]]> 包裹在< ![CDATA[" 标记开始,以"]]> 里包裹的元素,在xml解析时会被解析器忽略
比如 >= 可以写成 <![CDATA[ >= ]]>
<= 可以写成 <![CDATA[ <= ]]>
@Select({"<script>", "SELECT IFNULL(sum( IFNULL(detail.amount,0) ),0) as total FROM T_Lhgy_Work_Plan plan, T_Lhgy_Work_Plan_Detail detail WHERE plan.id = detail.plan_id AND plan.delete_flag = 0 AND detail.delete_flag = 0 AND plan.object_id = #{yhCompanyId}", "<when test='startTime!=null'>", "AND date_format(plan.create_date,'%Y-%m-%d') <![CDATA[ >= ]]> #{startTime}", "</when>", "<when test='endTime!=null'>", "AND date_format(plan.create_date,'%Y-%m-%d') <![CDATA[ <= ]]> #{endTime}", "</when>", "</script>"}) List<Map<String, Object>> selectSumAmountByYhCompany(@Param("yhCompanyId") Integer yhCompanyId,@Param("startTime") String startTime,@Param("endTime") String endTime);
四、使用 mybatis plus 性能分析插件
@Configuration public class MybatisPlusConfig { /** * 打印 sql */ @Bean @Profile({"dev","pro"})// 设置 dev pro 环境开启日志打印 public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); //格式化sql语句 Properties properties = new Properties(); properties.setProperty("format", "false"); performanceInterceptor.setProperties(properties); return performanceInterceptor; } }
问题:3.2 无法使用此性能分析插件,导包时无法导入
原因:如果 mp 在3.1版本时使用是不会有问题的,但是如果你的 mp 是3.2的版本,就无法使用这个插件了,因为 mp 在3.2的版本已经移除了这个性能分析插件并推荐使用第三方插件。
3.1 mp 源码
3.2 mp 源码,已经没有了 PerformanceInterceptor
解决方案:
将 3.1 的源码复制出来,自定义一个 sql 性能分细插件
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package me.zhengjie.config; import cn.hutool.db.sql.SqlFormatter; import com.baomidou.mybatisplus.core.toolkit.Assert; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.SystemClock; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Statement; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.session.ResultHandler; /** * 由于 mybatis plus 3.2 升级之后移除了性能分析的插件,所以此处手动引入 3.1 的性能分析插件 */ @Intercepts({@Signature( type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class} ), @Signature( type = StatementHandler.class, method = "update", args = {Statement.class} ), @Signature( type = StatementHandler.class, method = "batch", args = {Statement.class} )}) public class PerformanceInterceptor implements Interceptor { private static final Log logger = LogFactory.getLog(PerformanceInterceptor.class); private static final String DruidPooledPreparedStatement = "com.alibaba.druid.pool.DruidPooledPreparedStatement"; private static final String T4CPreparedStatement = "oracle.jdbc.driver.T4CPreparedStatement"; private static final String OraclePreparedStatementWrapper = "oracle.jdbc.driver.OraclePreparedStatementWrapper"; private long maxTime = 0L; private boolean format = false; private boolean writeInLog = false; private Method oracleGetOriginalSqlMethod; private Method druidGetSQLMethod; private static final SqlFormatter SQL_FORMATTER = new SqlFormatter(); public PerformanceInterceptor() { } /** @deprecated */ @Deprecated public static String sqlFormat(String boundSql, boolean format) { if (format) { try { return SQL_FORMATTER.format(boundSql); } catch (Exception var3) { ; } } return boundSql; } public Object intercept(Invocation invocation) throws Throwable { Object firstArg = invocation.getArgs()[0]; Statement statement; if (Proxy.isProxyClass(firstArg.getClass())) { statement = (Statement)SystemMetaObject.forObject(firstArg).getValue("h.statement"); } else { statement = (Statement)firstArg; } MetaObject stmtMetaObj = SystemMetaObject.forObject(statement); try { statement = (Statement)stmtMetaObj.getValue("stmt.statement"); } catch (Exception var20) { ; } if (stmtMetaObj.hasGetter("delegate")) { try { statement = (Statement)stmtMetaObj.getValue("delegate"); } catch (Exception var19) { ; } } String originalSql = null; String stmtClassName = statement.getClass().getName(); Class clazz; Object stmtSql; if ("com.alibaba.druid.pool.DruidPooledPreparedStatement".equals(stmtClassName)) { try { if (this.druidGetSQLMethod == null) { clazz = Class.forName("com.alibaba.druid.pool.DruidPooledPreparedStatement"); this.druidGetSQLMethod = clazz.getMethod("getSql"); } stmtSql = this.druidGetSQLMethod.invoke(statement); if (stmtSql instanceof String) { originalSql = (String)stmtSql; } } catch (Exception var18) { var18.printStackTrace(); } } else if ("oracle.jdbc.driver.T4CPreparedStatement".equals(stmtClassName) || "oracle.jdbc.driver.OraclePreparedStatementWrapper".equals(stmtClassName)) { try { if (this.oracleGetOriginalSqlMethod != null) { stmtSql = this.oracleGetOriginalSqlMethod.invoke(statement); if (stmtSql instanceof String) { originalSql = (String)stmtSql; } } else { clazz = Class.forName(stmtClassName); this.oracleGetOriginalSqlMethod = this.getMethodRegular(clazz, "getOriginalSql"); if (this.oracleGetOriginalSqlMethod != null) { this.oracleGetOriginalSqlMethod.setAccessible(true); if (null != this.oracleGetOriginalSqlMethod) { Object stmtSql1 = this.oracleGetOriginalSqlMethod.invoke(statement); if (stmtSql1 instanceof String) { originalSql = (String)stmtSql1; } } } } } catch (Exception var17) { ; } } if (originalSql == null) { originalSql = statement.toString(); } originalSql = originalSql.replaceAll("[\\s]+", " "); int index = this.indexOfSqlStart(originalSql); if (index > 0) { originalSql = originalSql.substring(index); } long start = SystemClock.now(); Object result = invocation.proceed(); long timing = SystemClock.now() - start; Object target = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(target); MappedStatement ms = (MappedStatement)metaObject.getValue("delegate.mappedStatement"); StringBuilder formatSql = (new StringBuilder()).append(" Time:").append(timing).append(" ms - ID:").append(ms.getId()).append("\n").append("Execute SQL:").append(sqlFormat(originalSql, this.format)).append("\n"); if (this.isWriteInLog()) { if (this.getMaxTime() >= 1L && timing > this.getMaxTime()) { logger.error(formatSql.toString()); } else { logger.debug(formatSql.toString()); } } else { System.err.println(formatSql.toString()); Assert.isFalse(this.getMaxTime() >= 1L && timing > this.getMaxTime(), " The SQL execution time is too large, please optimize ! ", new Object[0]); } return result; } public Object plugin(Object target) { return target instanceof StatementHandler ? Plugin.wrap(target, this) : target; } public void setProperties(Properties prop) { String maxTime = prop.getProperty("maxTime"); String format = prop.getProperty("format"); if (StringUtils.isNotEmpty(maxTime)) { this.maxTime = Long.parseLong(maxTime); } if (StringUtils.isNotEmpty(format)) { this.format = Boolean.valueOf(format).booleanValue(); } } public Method getMethodRegular(Class<?> clazz, String methodName) { if (Object.class.equals(clazz)) { return null; } else { Method[] var3 = clazz.getDeclaredMethods(); int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { Method method = var3[var5]; if (method.getName().equals(methodName)) { return method; } } return this.getMethodRegular(clazz.getSuperclass(), methodName); } } private int indexOfSqlStart(String sql) { String upperCaseSql = sql.toUpperCase(); Set<Integer> set = new HashSet(); set.add(upperCaseSql.indexOf("SELECT ")); set.add(upperCaseSql.indexOf("UPDATE ")); set.add(upperCaseSql.indexOf("INSERT ")); set.add(upperCaseSql.indexOf("DELETE ")); set.remove(Integer.valueOf(-1)); if (CollectionUtils.isEmpty(set)) { return -1; } else { List<Integer> list = new ArrayList(set); list.sort(Comparator.naturalOrder()); return ((Integer)list.get(0)).intValue(); } } public PerformanceInterceptor setMaxTime(long maxTime) { this.maxTime = maxTime; return this; } public long getMaxTime() { return this.maxTime; } public PerformanceInterceptor setFormat(boolean format) { this.format = format; return this; } public boolean isFormat() { return this.format; } public PerformanceInterceptor setWriteInLog(boolean writeInLog) { this.writeInLog = writeInLog; return this; } public boolean isWriteInLog() { return this.writeInLog; } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。