MyBatis 如何正确设置 Map 为 javaType in <arg>?

How to properly set Map as javaType in <arg> in MyBatis?

问题描述:

当我尝试使用 MyBatis 从数据库中获取 immutable Foo objects 时,我有 java.lang.NoSuchMethodException: bar.Foo.<init>(java.lang.Long, java.lang.String, java.util.Map) 并且无法弄清楚出了什么问题:(

我有一个用@Value注解结果object的不成功案例和用@Data注解的成功案例(它们在下面相应的headers下)。所以,我想使用 @Value 但不要理解我做错了什么..

不成功案例:

所以,我得到的结果是 class,即 immutable:

@Value
public class Foo {
    long entryId;
    String description;
    Map<String, String> params;
}

它存储在postgres中的一个tablefoos中,params字段值以JSON格式存储在相同table的相应列中(类型为VARCHAR,在 params = Map.of("key", "value") 的情况下条目为 "{key=value}"。 我想从 DB 实例化我的 object 这个 class ,所以我使用以下 MyBatis 代码:

<resultMap id="foo" type="bar.Foo">
     <constructor>
         <arg column="entry_id" javaType="java.lang.Long"/>
         <arg column="description" javaType="java.lang.String"/>
         <arg column="params" javaType="java.util.Map" jdbcType="VARCHAR" typeHandler="bar.MapToJsonStringHandler"/>
     </constructor>
</resultMap>

这是我的类型处理程序(它已经在另一个案例中成功使用了 @Data object 而不是 @Value,所以我想它本身是正确的):

public class MapToJsonStringHandler implements TypeHandler<Map<String, String>> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @SneakyThrows(IOException.class)
    @Override
    public void setParameter(PreparedStatement ps, int i, Map<String, String> parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
        ps.setNull(i, Types.VARCHAR);
    } else {
        ps.setString(i, objectMapper.writeValueAsString(parameter));
    }
}

@Override
public Map<String, String> getResult(ResultSet rs, String columnName) throws SQLException {
    String result = rs.getString(columnName);
    return convertToMap(result);
}


@Override
public Map<String, String> getResult(ResultSet rs, int columnIndex) throws SQLException {
    String result = rs.getString(columnIndex);
    return convertToMap(result);
}

@Override
public Map<String, String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
    String result = cs.getString(columnIndex);
    return convertToMap(result);
}

@SneakyThrows(IOException.class)
private Map<String, String> convertToMap(String result) {
    return result == null ? Collections.emptyMap() : objectMapper.readValue(result, Map.class);
}

}

这里是SQL:

<select id="getFoos" resultMap="foo">
     SELECT *
     FROM foos
     <![CDATA[WHERE needed_tm < now()]]>
</select>

当我得到我的 Foos 时,我有 java.lang.NoSuchMethodException: bar.Foo.<init>(java.lang.Long, java.lang.String, java.util.Map)

有趣的是,当我使用@Data 而不是@Value 时,它​​就像一个魅力:

成功案例:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Foo {
    long entryId;
    String description;
    Map<String, String> params;
}

<resultMap id="foo" type="bar.Foo">
     <result column="entry_id" property="entryId"/>
     <result column="description" property="description"/>
     <result column="params" property="params" typeHandler="bar.MapToJsonStringHandler"/>
</resultMap>

SQL 和类型处理程序与不成功的情况相同。

能否请您指点一下我可能遗漏了什么?提前致谢!

更新 1:

@Value
@AllArgsConstructor
public class Foo {
     long entryId;
     String description;
     Map<String, String> params;
}

给出了同样的例外。无论如何,据我了解,@Value 已经设计为 @AllArgsConstructor (https://projectlombok.org/features/Value)

java.lang.NoSuchMethodException: bar.Foo.(java.lang.Long, java.lang.String, java.util.Map) <--- 表示没有定义构造函数。

当你使用 @AllArgsConstructor,您是在告诉 lombok 生成一个包含所有参数(Long、String、Map)的构造函数,这就是为什么它对您有用。

根据文档@Data:

@数据 现在全部在一起:@ToString、@EqualsAndHashCode、所有字段上的@Getter、所有非最终字段上的@Setter 和@RequiredArgsConstructor

的快捷方式

https://projectlombok.org/features/Data

这就是为什么此时它会找到构造函数并且在您使用 @Data 时不会给您错误 (NoSuchMethod)。


<resultMap id="foo" type="bar.Foo">
     <constructor>
         <arg column="entry_id" javaType="java.lang.Long"/>
         <arg column="description" javaType="java.lang.String"/>
         <arg column="params" javaType="java.util.Map" jdbcType="VARCHAR" typeHandler="bar.MapToJsonStringHandler"/>
     </constructor>
</resultMap>

由于您将数据类型指定为 java.lang.Long,它希望构造函数被定义为具有该参数类型。

我相信@Data,帮助你的不是构造函数,而是 Setters (boxing) long To Long。

尝试使用@Value,将你的class中的long改为Long。看看是否可行。

Lombok 将生成一个带有 (long, String, Map) 的构造函数,而不是 (Long, String, Map)

想知道您使用的 java 版本。