JsonTypeHandle
是一个自定义的 MyBatis 类型处理器,用于将 Java 对象序列化为 JSON 存储在数据库中,并在查询时反序列化为 Java 对象。该处理器使用 Jackson 库来实现序列化和反序列化。
确保在 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>
mybatis-config.xml
<configuration>
<typeHandlers>
<typeHandler handler="com.***.dbc.spec.mybatis.typehandle.JsonTypeHandle" />
typeHandlers>
<mappers>
<mapper resource="TableMapper.xml"/>
mappers>
configuration>
在 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
}
示例使用 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);
}
}