随意吧 發表於 2025-12-15 23:55:00

MyBatis 扩展BaseTypeHandler 转换泛型 JSON 列表

<p>最近发现一个mybatis里面json转换的bug, 写了这么多年Java这方面还是没有理清楚, 把正确的处理方法记录一下.</p>
<h1 id="一-对象json转换">一. 对象JSON转换</h1>
<p>这个是比较简单的情况, 有通用的处理方法, 例如</p>
<p>用Jackson实现一个通用的 TypeHandler</p>
<pre><code class="language-java">@Slf4j
public class JacksonTypeHandler&lt;T&gt; extends BaseTypeHandler&lt;T&gt; {
    private static ObjectMapper OBJECT_MAPPER;
    private final Class&lt;T&gt; clazz;

    public JacksonTypeHandler(Class&lt;T&gt; clazz) {
      if (log.isTraceEnabled()) {
            log.trace("JacksonTypeHandler[{}]", clazz);
      }
      this.clazz = clazz;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
      ps.setString(i, toJson(parameter));
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
      return parse(rs.getString(columnName));
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
      return parse(rs.getString(columnIndex));
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
      return parse(cs.getString(columnIndex));
    }

    protected T parse(String json) {
      if (json == null || json.isEmpty()) return null;
      try {
            return getObjectMapper().readValue(json, clazz);
      } catch (IOException e) {
            throw new RuntimeException(e);
      }
    }

    protected String toJson(T obj) {
      try {
            return getObjectMapper().writeValueAsString(obj);
      } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
      }
    }

    private static ObjectMapper getObjectMapper() {
      if (null == OBJECT_MAPPER) {
            OBJECT_MAPPER = new ObjectMapper();
            OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
      }
      return OBJECT_MAPPER;
    }
}
</code></pre>
<p>使用时直接指定 typeHandler 就行</p>
<pre><code class="language-xml">&lt;result property="groupFilter" column="group_filter" typeHandler="com.somewhere.mybatis.JacksonTypeHandler"/&gt;
</code></pre>
<p>以及</p>
<pre><code>#{groupFilter, typeHandler=com.somewhere.mybatis.JacksonTypeHandler},
</code></pre>
<h1 id="二-列表json-array转换">二. 列表JSON Array转换</h1>
<p>字段中更常见的是 List<t> 类型的JSON Array结构, 这种情况通过扩展 BaseTypeHandler 没有通用的处理方法, 有两种实现途径</t></p>
<h2 id="通用的typehandler-需要指定javatype">通用的TypeHandler, 需要指定javaType</h2>
<p>用 Jackson 实现一个通用的 TypeHandler, 注意构造函数中的 clazz, 如果不指定, 在默认情况下 mybatis 传进来的是一个不带泛型参数的 List</p>
<pre><code class="language-java">@Slf4j
public class JacksonListTypeHandler&lt;T&gt; extends BaseTypeHandler&lt;List&lt;T&gt;&gt; {
    private static ObjectMapper OBJECT_MAPPER;
    private final TypeReference&lt;List&lt;T&gt;&gt; type;

    public JacksonListTypeHandler(Class&lt;T&gt; clazz) {
      log.info("JacksonListTypeHandler[{}]", clazz);
      this.type = new TypeReference&lt;&gt;() {
            @Override
            public Type getType() {
                // 返回参数化类型 List&lt;T&gt;
                return new ParameterizedType() {
                  @Override
                  public Type[] getActualTypeArguments() {
                        return new Type[]{clazz};
                  }

                  @Override
                  public Type getRawType() {
                        return List.class;
                  }

                  @Override
                  public Type getOwnerType() {
                        return null;
                  }
                };
            }
      };
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List&lt;T&gt; parameter, JdbcType jdbcType) throws SQLException {
      ps.setString(i, toJson(parameter));
    }

    @Override
    public List&lt;T&gt; getNullableResult(ResultSet rs, String columnName) throws SQLException {
      return parse(rs.getString(columnName));
    }

    @Override
    public List&lt;T&gt; getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
      return parse(rs.getString(columnIndex));
    }

    @Override
    public List&lt;T&gt; getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
      return parse(cs.getString(columnIndex));
    }

    private List&lt;T&gt; parse(String json) {
      if (json == null || json.isEmpty()) return null;
      try {
            return getObjectMapper().readValue(json, type);
      } catch (IOException e) {
            throw new RuntimeException(e);
      }
    }

    protected String toJson(List&lt;T&gt; obj) {
      try {
            return getObjectMapper().writeValueAsString(obj);
      } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
      }
    }

    public static ObjectMapper getObjectMapper() {
      if (null == OBJECT_MAPPER) {
            OBJECT_MAPPER = new ObjectMapper();
            OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
      }
      return OBJECT_MAPPER;
    }
}
</code></pre>
<p>除了用 TypeReference, 还可以用 JavaType</p>
<pre><code class="language-java">@Slf4j
public class ListJacksonTypeHandler&lt;T&gt; extends BaseTypeHandler&lt;List&lt;T&gt;&gt; {
    private final JavaType type;

    public ListJacksonTypeHandler(Class&lt;T&gt; clazz) {
      log.info("ListJacksonTypeHandler[{}]", clazz);
      // 创建 List&lt;T&gt; 类型的 JavaType
      this.type = JacksonUtil.getObjectMapper()
                .getTypeFactory()
                .constructCollectionType(List.class, clazz);
    }

    // 其它一样
}
</code></pre>
<p>需要在 mapper 中强制指定javaType, 如果是MyBatis自带的简单类型, 可以直接用 alias(见下面的表格), 如果是自定义的对象, 则需要用对象类的完整包路径</p>
<pre><code class="language-xml">&lt;result property="ips" column="ips" javaType="string" typeHandler="com.somewhere.mybatis.JacksonListTypeHandler"/&gt;
</code></pre>
<p>以及</p>
<pre><code>#{users,javaType=string,typeHandler=com.somewhere.mybatis.JacksonListTypeHandler},
</code></pre>
<p>这样启动后, 如果初始化的type是正确的string 才能正确解析</p>
<pre><code>2025-12-15T10:33:42.896+08:00INFO 1 --- c.somewhere.mybatis.JacksonListTypeHandler: JacksonListTypeHandler
</code></pre>
<h2 id="抽象类-根据对象类型实现具体的-typehandler">抽象类, 根据对象类型实现具体的 TypeHandler</h2>
<p>如果不在 mapper 中指定类型, 就需要在 TypeHandler 中指定, 这样就不是通用的了</p>
<p>写一个基类 AbstractListTypeHandler<t></t></p>
<pre><code class="language-java">@Slf4j
public abstract class AbstractListTypeHandler&lt;T&gt; extends BaseTypeHandler&lt;List&lt;T&gt;&gt; {
    private static ObjectMapper OBJECT_MAPPER;

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List&lt;T&gt; parameter, JdbcType jdbcType) throws SQLException {
      ps.setString(i, toJson(parameter));
    }

    @Override
    public List&lt;T&gt; getNullableResult(ResultSet rs, String columnName) throws SQLException {
      return parse(rs.getString(columnName));
    }

    @Override
    public List&lt;T&gt; getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
      return parse(rs.getString(columnIndex));
    }

    @Override
    public List&lt;T&gt; getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
      return parse(cs.getString(columnIndex));
    }

    protected List&lt;T&gt; parse(String json) {
      if (json == null || json.isEmpty()) return null;
      try {
            return getObjectMapper().readValue(json, specificType());
      } catch (IOException e) {
            throw new RuntimeException(e);
      }
    }

    protected String toJson(List&lt;T&gt; obj) {
      try {
            return getObjectMapper().writeValueAsString(obj);
      } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
      }
    }

    private static ObjectMapper getObjectMapper() {
      if (null == OBJECT_MAPPER) {
            OBJECT_MAPPER = new ObjectMapper();
            OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
      }
      return OBJECT_MAPPER;
    }

    protected abstract TypeReference&lt;List&lt;T&gt;&gt; specificType();
}
</code></pre>
<p>根据具体的使用场景, 创建对应的实现类</p>
<pre><code class="language-java">public class ListLongTypeHandler extends AbstractListTypeHandler&lt;Long&gt; {
    @Override
    protected TypeReference&lt;List&lt;Long&gt;&gt; specificType() {
      return new TypeReference&lt;&gt;() {};
    }
}
</code></pre>
<p>使用时, 直接用 typeHandler 指定, 不需要指定类型</p>
<pre><code>#{setIds, typeHandler=com.somewhere.mybatis.ListLongTypeHandler},
...
@Result(column="set_ids",                     property="setIds", typeHandler= ListLongTypeHandler.class),
</code></pre>
<h1 id="附-mybatis-内建的-javatype-alias">附: MyBatis 内建的 javaType Alias</h1>
<p>链接: https://mybatis.org/mybatis-3/configuration.html#typeAliases</p>
<table>
<thead>
<tr>
<th>alias</th>
<th>javaType</th>
</tr>
</thead>
<tbody>
<tr>
<td>_byte</td>
<td>byte</td>
</tr>
<tr>
<td>_char (since 3.5.10)</td>
<td>char</td>
</tr>
<tr>
<td>_character (since 3.5.10)</td>
<td>char</td>
</tr>
<tr>
<td>_long</td>
<td>long</td>
</tr>
<tr>
<td>_short</td>
<td>short</td>
</tr>
<tr>
<td>_int</td>
<td>int</td>
</tr>
<tr>
<td>_integer</td>
<td>int</td>
</tr>
<tr>
<td>_double</td>
<td>double</td>
</tr>
<tr>
<td>_float</td>
<td>float</td>
</tr>
<tr>
<td>_boolean</td>
<td>boolean</td>
</tr>
<tr>
<td>string</td>
<td>String</td>
</tr>
<tr>
<td>byte</td>
<td>Byte</td>
</tr>
<tr>
<td>char (since 3.5.10)</td>
<td>Character</td>
</tr>
<tr>
<td>character (since 3.5.10)</td>
<td>Character</td>
</tr>
<tr>
<td>long</td>
<td>Long</td>
</tr>
<tr>
<td>short</td>
<td>Short</td>
</tr>
<tr>
<td>int</td>
<td>Integer</td>
</tr>
<tr>
<td>integer</td>
<td>Integer</td>
</tr>
<tr>
<td>double</td>
<td>Double</td>
</tr>
<tr>
<td>float</td>
<td>Float</td>
</tr>
<tr>
<td>boolean</td>
<td>Boolean</td>
</tr>
<tr>
<td>date</td>
<td>Date</td>
</tr>
<tr>
<td>decimal</td>
<td>BigDecimal</td>
</tr>
<tr>
<td>bigdecimal</td>
<td>BigDecimal</td>
</tr>
<tr>
<td>biginteger</td>
<td>BigInteger</td>
</tr>
<tr>
<td>object</td>
<td>Object</td>
</tr>
<tr>
<td>date[]</td>
<td>Date[]</td>
</tr>
<tr>
<td>decimal[]</td>
<td>BigDecimal[]</td>
</tr>
<tr>
<td>bigdecimal[]</td>
<td>BigDecimal[]</td>
</tr>
<tr>
<td>biginteger[]</td>
<td>BigInteger[]</td>
</tr>
<tr>
<td>object[]</td>
<td>Object[]</td>
</tr>
<tr>
<td>map</td>
<td>Map</td>
</tr>
<tr>
<td>hashmap</td>
<td>HashMap</td>
</tr>
<tr>
<td>list</td>
<td>List</td>
</tr>
<tr>
<td>arraylist</td>
<td>ArrayList</td>
</tr>
<tr>
<td>collection</td>
<td>Collection</td>
</tr>
<tr>
<td>iterator</td>
<td>Iterator</td>
</tr>
</tbody>
</table><br><br>
来源:https://www.cnblogs.com/milton/p/19351380
頁: [1]
查看完整版本: MyBatis 扩展BaseTypeHandler 转换泛型 JSON 列表