java mybatis resultMapping在字段名称的顺序更改时不起作用

cyvaqqii  于 12个月前  发布在  Java
关注(0)|答案(2)|浏览(128)

我在一个SQLite数据库中存储播放列表信息。这是我的实体类;

@Data
@Builder
public class Playlist {
    private String id;
    private String playlistName;
    private PlaylistType type;
}

字符串
PlaylistType是(“TYPE_A”,“TYPE_B”)的enum
首先,在我的Map器XML中,我尝试使用resultType="Playlist",当数据库列名和Playlist属性的顺序相同时,它可以工作。但是如果我改变属性的顺序,例如如下所示,

public class Playlist {
    private String id;
    private PlaylistType type; // just moved PlaylistType property 
    private String playlistName;
}


它给了我这个错误:

Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'name' from result set.  Cause: java.lang.IllegalArgumentException: No enum constant com.example.demo.PlaylistType.p1


然后一些线程建议使用resultMap,但问题仍然存在;
这是我使用resultMap的Map器XML

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.PlaylistRepository">

    <resultMap id="toPlaylist" type="com.example.demo.Playlist">
        <id column="id" property="id"/>
        <result column="name" property="playlistName"/>
        <result column="type" property="type"/>
    </resultMap>
    <select id="findById" resultMap="toPlaylist">
        select * from playlist where id = #{id}
    </select>
</mapper>


有没有更好的方法来Map数据库列?它不应该在愚蠢的事情上有太多的限制,比如实体类中属性的顺序。或者我错过了什么?

iezvtpos

iezvtpos1#

添加@NoArgsConstructor是最简单的解决方案。
这个答案是给那些不想给不可变类添加无参数构造函数的人准备的。
当您将@Builder添加到类中时,Lombok生成以下构造函数。

Playlist(String id, String playlistName, PlaylistType type) {
  this.id = id;
  this.playlistName = playlistName;
  this.type = type;
}

字符串
虽然它是包私有的,但MyBatis可以(必须;因为没有其他构造函数)使用反射[1]使用这个构造函数。
为了将结果Map到这个构造函数,MyBatis提供了四个不同的方法。
1.基于顺序的构造函数自动Map
1.基于参数名的构造函数自动Map
1.具有<constructor>但不具有name属性的结果Map
1.带有name属性的<constructor>结果Map

tl;dr

对于大多数简单的情况,方法2应该足够了。
使用方法3或4进行高级Map或在需要最佳性能时使用。

1.基于顺序的构造函数自动Map

这是不使用<resultMap>时的默认行为。
在您的示例中,构造函数接受三个参数,因此结果集中的第一、第二和第三列分别Map到idplaylistNametype
当你改变类中的字段顺序时,构造函数参数的顺序也会改变,你也必须改变列的顺序。
就我个人而言,我不推荐这种基于顺序的构造函数自动Map,因为有一个已知的问题可能会让人挠头。
如果你有兴趣的话,我在answer中解释了它。

2.基于参数名的构造函数自动Map

这是当您1)在配置中启用argNameBasedConstructorAutoMapping和2)不使用<resultMap>时的行为。使用此方法,MyBatis会查找与构造函数参数[2]同名的列。
列的顺序无关紧要。
请注意,在您的示例中,列名name与目标参数名playlistName不匹配,因此您可能必须在SELECT语句中指定列别名。
argNameBasedConstructorAutoMapping是在3.5.10版本中添加的。

3.带<constructor>不带name属性的结果图

使用结果Map时,需要<constructor><idArg><arg>元素来执行构造函数Map。

<resultMap>
  <constructor>
    <idArg column="id" javaType="string" />
    <arg column="name" javaType="string" />
    <arg column="type" javaType="pkg.PlaylistType" />
  </constructor>
</resultMap>


为了完整起见,这里是在JavaMap器中声明的相同结果Map[3]。

@Arg(id = true, column = "id", javaType = String.class)
@Arg(column = "name", javaType = String.class)
@Arg(column = "type", javaType = PlaylistType.class)
Playlist findById(String id);


使用此方法时,列顺序无关紧要,但XML元素顺序必须与构造函数参数顺序匹配。
如果您很难维护XML元素的顺序(例如,目标类经常更新),请参阅下一节。

4. name属性为<constructor>的结果图

当指定name属性时,XML元素的顺序不必与实际构造函数参数的顺序相匹配[4]。

<resultMap>
  <constructor>
    <idArg column="id" name="id" javaType="string" />
    <arg column="name" name="playlistName" javaType="string" />
    <arg column="type" name="type" javaType="pkg.PlaylistType" />
  </constructor>
</resultMap>


在您的例子中,构造函数参数类型总是匹配字段类型,因此javaType可以省略。

<resultMap>
  <constructor>
    <idArg column="id" name="id" />
    <arg column="name" name="playlistName" />
    <arg column="type" name="type" />
  </constructor>
</resultMap>


这是使用注解的相同结果图。

@Arg(id = true, column = "id", name = "id")
@Arg(column = "name", name = "name")
@Arg(column = "type", name = "type")
Playlist findById(String id);


使用上面的结果Map,MyBatis搜索具有以下三个参数的构造函数,但顺序任意。

  • 名称:id,类型= java.lang.String
  • 名称:playlistName,类型= java.lang.String
  • 名称:type,类型= pkg.PlaylistType

当有多个构造函数符合条件时,您需要将@AutomapConstructor添加到正确的构造函数。
找到构造函数后,指定列的值将Map到每个构造函数参数。
因此,使用此方法,XML元素顺序和列顺序都无关紧要,但如果更改字段名,则可能需要编辑name值。
此方法需要3.4.3或更高版本。
[1]如果您使用Java平台模块系统(JPMS),您可能必须允许MyBatis访问此构造函数。
[2]要在二进制文件中包含参数名称,您必须1)指定-parameters编译器选项或2)为每个参数添加@Param注解。
[3]如果您使用的版本早于3.5.4,则可能需要@ConstructorArgs
[4]<idArg>必须在<arg>之前写入,因为它是由DTD强制执行的。

lhcgjxsq

lhcgjxsq2#

我不敢相信我用一种稍微不同的方式制作了same mistake。为了我和其他人的利益,让我分享一些见解。
这个问题看起来像是从mybatis中出现的,但实际上不是。Lombok注解是导致问题的原因。在这种情况下,实际的Map操作可以追溯到

private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
    for (int i = 0; i < constructor.getParameterTypes().length; i++) {
      Class<?> parameterType = constructor.getParameterTypes()[i];
      String columnName = rsw.getColumnNames().get(i);
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
      constructorArgTypes.add(parameterType);
      constructorArgs.add(value);
      foundValues = value != null || foundValues;
    }
    return foundValues;
  }

字符串

  • 来自org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java#L783*

可以看出,构造函数参数是一个接一个的,因此实体类中属性的顺序很重要。这是因为@Builder注解将创建一个全参数构造函数。因此mybatis不能使用reflection来Map参数,因为它没有默认的构造函数(此检查在DefaultResultSetHandler.java的第686行完成)。相反,它会遍历构造函数参数并设置值。这就是为什么顺序很重要。
解决方案很简单,只需在Playlist类的顶部添加@NoArgsConstructor@AllArgsConstructor

相关问题