自定义的 mybatis 类型处理器JSON版

自定义的 mybatis 类型处理器JSON版

  • JsonTypeHandle MyBatis Type Handler
    • 概述
    • 特性
    • 依赖
    • 使用方法
      • 1.配置 MyBatis
      • 2.创建映射文件
    • 验证示例
      • 1.配置数据库
    • 源码

JsonTypeHandle MyBatis Type Handler

概述

JsonTypeHandle 是一个自定义的 MyBatis 类型处理器,用于将 Java 对象序列化为 JSON 存储在数据库中,并在查询时反序列化为 Java 对象。该处理器使用 Jackson 库来实现序列化和反序列化。

特性

  • 支持将任何 Java 对象存储为 JSON 格式的 CLOB、VARCHAR、NVARCHAR 数据类型
  • 使用 Jackson 库进行对象到 JSON 的转换
  • 支持自定义的 JSON 配置

依赖

确保在 pom.xml 中包含以下依赖:

<dependencies>
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.16version>
    dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.coregroupId>
        <artifactId>jackson-databindartifactId>
        <version>2.17.1version>
    dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.coregroupId>
        <artifactId>jackson-coreartifactId>
        <version>2.17.1version>
    dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.coregroupId>
        <artifactId>jackson-annotationsartifactId>
        <version>2.17.1version>
    dependency>
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-lang3artifactId>
        <version>3.14.0version>
    dependency>
dependencies>

使用方法

1.配置 MyBatis

mybatis-config.xml

<configuration>
    <typeHandlers>
        <typeHandler handler="com.***.dbc.spec.mybatis.typehandle.JsonTypeHandle" />
    typeHandlers>
    <mappers>
        <mapper resource="TableMapper.xml"/>
    mappers>
configuration>

2.创建映射文件

在 MyBatis 映射文件中指定使用 JsonTypeHandle 进行类型转换

示例:

Mapper.xml

<mapper namespace="com.***.dbc.spec.mybatis.mapper.TableMapper">
    <resultMap id="TestTableResultMap" type="com.***.dbc.spec.mybatis.typehandle.TestTable">
        <id property="id" column="id" />
        <result property="data" column="data" typeHandler="com.***.dbc.spec.mybatis.typehandle.JsonTypeHandle"/>
    resultMap>

    <select id="selectTestTable" resultMap="TestTableResultMap">
        SELECT id, data
        FROM test_table
        WHERE id = #{id}
    select>

    <insert id="insertTestTable" parameterType="com.***.dbc.spec.mybatis.typehandle.TestTable">
        INSERT INTO test_table (id, data)
        VALUES (#{id}, #{data, typeHandler=com.***.dbc.spec.mybatis.typehandle.JsonTypeHandle})
    insert>
mapper>

Entity

public class TestTable {
    private Long id;
    private Person data;

    // getter and setter
}
public class Person {
    private String name;
    private int age;

    // getter and setter
}

验证示例

1.配置数据库

示例使用 H2 内存数据库,配置如下:
mybatis-config.xml

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="org.h2.Driver"/>
            <property name="url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"/>
            <property name="username" value="sa"/>
            <property name="password" value="sa"/>
        dataSource>
    environment>
environments>
class JsonTypeHandleTest {

    private static SqlSessionFactory sqlSessionFactory;

    @BeforeAll
    public static void setup() throws Exception {
        // 配置 MyBatis
        try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        }

        // 创建 H2 内存数据库表
        try (SqlSession session = sqlSessionFactory.openSession()) {
            Connection connection = session.getConnection();
            try (Statement stmt = connection.createStatement()) {
                stmt.execute("CREATE TABLE test_table (id INT PRIMARY KEY, data CLOB)");
            }
            session.commit();
        }
    }

    @Test
    void testInsertAndSelectWithJsonTypeHandle() {
        try (SqlSession session = sqlSessionFactory.openSession()) {

            Person person = new Person("Jane", 25);
            TestTable table = new TestTable(1, person);

            PersonMapper tableMapper = session.getMapper(PersonMapper.class);
            tableMapper.insertTestTable(table);
            TestTable resultTable = tableMapper.selectTestTable(1);
            String resultJson = tableMapper.selectTestTableNoResultMap(1);
            assertEquals(table, resultTable);
            assertEquals("{\"@class\":\"com.bosssoft.dbc.spec.mybatis.typehandle.Person\",\"age\":25,\"name\":\"Jane\"}", resultJson);
        }
    }
}

源码

@MappedJdbcTypes({JdbcType.CLOB, JdbcType.VARCHAR, JdbcType.NVARCHAR})
public class JsonTypeHandle extends BaseTypeHandler<Object> {

    private final ObjectMapper mapObjectMapper;

    public JsonTypeHandle() {
        this(new ObjectMapper());
    }

    public JsonTypeHandle(ClassLoader classLoader) {
        this(createObjectMapper(classLoader, new ObjectMapper()));
    }

    protected static ObjectMapper createObjectMapper(ClassLoader classLoader, ObjectMapper om) {
        TypeFactory tf = TypeFactory.defaultInstance().withClassLoader(classLoader);
        om.setTypeFactory(tf);
        return om;
    }

    public JsonTypeHandle(ObjectMapper mapObjectMapper) {
        this.mapObjectMapper = mapObjectMapper;
        init(mapObjectMapper);
        initTypeInclusion(mapObjectMapper);
    }

    protected void initTypeInclusion(ObjectMapper mapObjectMapper) {
        TypeResolverBuilder<?> mapTyper = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL, LaissezFaireSubTypeValidator.instance) {
            @Override
            public boolean useForType(JavaType t) {
                switch (_appliesFor) {
                    case NON_CONCRETE_AND_ARRAYS:
                        while (t.isArrayType()) {
                            t = t.getContentType();
                        }
                        // fall through
                    case OBJECT_AND_NON_CONCRETE:
                        return (t.getRawClass() == Object.class) || !t.isConcrete();
                    case NON_FINAL:
                        while (t.isArrayType()) {
                            t = t.getContentType();
                        }
                        // to fix problem with wrong long to int conversion
                        if (t.getRawClass() == Long.class) {
                            return true;
                        }
                        if (t.getRawClass() == XMLGregorianCalendar.class) {
                            return false;
                        }
                        return !t.isFinal(); // includes Object.class
                    default:
                        // case JAVA_LANG_OBJECT:
                        return (t.getRawClass() == Object.class);
                }
            }
        };
        mapTyper.init(JsonTypeInfo.Id.CLASS, null);
        mapTyper.inclusion(JsonTypeInfo.As.PROPERTY);
        mapObjectMapper.setDefaultTyping(mapTyper);

        // warm up codec
        try {
            byte[] s = mapObjectMapper.writeValueAsBytes(1);
            mapObjectMapper.readValue(s, Object.class);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    protected void init(ObjectMapper objectMapper) {
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
                .withFieldVisibility(JsonAutoDetect.Visibility.ANY).withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) {
        try {
            String json = mapObjectMapper.writeValueAsString(parameter);
            StringReader reader = new StringReader(json);
            ps.setCharacterStream(i, reader, json.length());
        } catch (Exception e) {
            ExceptionUtils.wrapAndThrow(e);
        }
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String value = "";
        Clob clob = rs.getClob(columnName);
        if (clob != null) {
            int size = (int) clob.length();
            value = clob.getSubString(1, size);
        }
        return getObject(value);
    }

    private Object getObject(String value) {
        try {
            if (value == null || value.isEmpty()) {
                return null;
            }
            return mapObjectMapper.readValue(value, Object.class);
        } catch (IOException e) {
            return ExceptionUtils.wrapAndThrow(e);
        }
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String value = "";
        Clob clob = rs.getClob(columnIndex);
        if (clob != null) {
            int size = (int) clob.length();
            value = clob.getSubString(1, size);
        }
        return getObject(value);
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String value = "";
        Clob clob = cs.getClob(columnIndex);
        if (clob != null) {
            int size = (int) clob.length();
            value = clob.getSubString(1, size);
        }
        return getObject(value);
    }
}

你可能感兴趣的:(mybatis,json,java)