Mybatis技巧之LOB对象处理

LOB代表大对象数据,包括BLOB和CLOB两种类型。前者用于存储大块的二进制数据,如图片数据,视频数据等(一般应该把文件存储到相应的文件服务器中),后者则用于存储长文本数据。
值得注意的是,不同数据库中,大对象对应的字段类型往往并不相同。

1. 概述

正如之前文章Mybatis源码研究之TypeHandler里已经提及过的,Mybatis构建在对JDBC流程的深刻理解之上。涉及到数据库操作前的参数设置,以及数据库操作完毕结果集的提取的部分都被Mybatis抽象为TypeHandler接口。

本文中,我们将细化上一篇文章,以实际的BLOB / CLOB操作再次探究一些TypeHandler的细节。

2. BLOB操作

网上的相关例子都是以POJO为例;所以为了展示本文的必要性,这里我以map类型为例子,给出一个快速的startup。

2.1 BLOB操作之存储
  1. Mybatis映射文件

    "insert_blob" parameterType="map">               
        INSERT INTO  da_affix (                 
                 ax_ident, 
                 ax_data 
            ) VALUES ( 
                '123321', 
                #{blobData, jdbcType=BLOB}
        )
    
  2. Java端调用

    // 下面这种方式是不需要第三步的额外配置的
    String sqlid = NAMESPACE.concat("insert_blob");
    crud.insert(sqlid, Collections.singletonMap("blobData", FileUtil.readBytes(new File("D:/1.txt"))));
    
    // 以下方法和上面的方法等价, 区别只是传入参数的差别, 需要下面第三步配置的支持
    // 以下两行代码模拟了工程中的不同参数类型
    Object blobDataParam = new File("D:/1.txt");
    // Object blobDataParam = FileUtils.openInputStream(new File("D:/1.txt"));
    crud.insert(sqlid, Collections.singletonMap("blobData", blobDataParam));
    
  3. 额外配置
    为了减少调用层的重复性代码(将流/文件转换为byte[],这步操作可能因为程序员的经历而选择不同的实现方式),我们特意加入了如下Mybatis的TypeHandler。

    <typeHandlers>
        
        <typeHandler handler="mybatis.theory.typehandler.BlobAndFileParameterSetterTypeHandler" jdbcType="BLOB"  />
        <typeHandler handler="mybatis.theory.typehandler.BlobAndInputStreamParameterSetterTypeHandler" javaType="java.io.FileInputStream"  jdbcType="BLOB" />
    typeHandlers>

    值得注意的以下三点:

    1. 因为Mybatis在挑选typeHandler时, 是根据传入参数的实际类型, 比如这里的FileInputStream, 而非我们在定义时的InputStream; 所以如果有其他InputStream子类需求, 我们需要在这里额外再注册一次
    2. 为了防止二义性, 我在这里进行了完全约束, 同时约定了javaType, jdbcType。
    3. 我们在这里注册的两个typeHandler,只有对数据库操作前的参数处理,对于操作完毕的返回值直接注明不支持

    补充两个自定义的typehandler

    // ----------------------------------------- BlobAndFileParameterSetterTypeHandler
    /**
     * 执行Mybatis操作时, 数据库操作前的参数设置, 如果传入参数类型为File, 将调用本TypeHandler
     * @author LQ
     *
     */
    public class BlobAndFileParameterSetterTypeHandler extends BaseTypeHandler<File> {
    
        private static final String ERROR_INFO = "本类只负责参数设置部分, BLOB提取参见本人博客";
    
        @Override
        public File getNullableResult(ResultSet rs, String columnName) throws SQLException {
            throw new UnsupportedOperationException(ERROR_INFO);
        }
    
        @Override
        public File getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            throw new UnsupportedOperationException(ERROR_INFO);
        }
    
        @Override
        public File getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            throw new UnsupportedOperationException(ERROR_INFO);
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, File parameter, JdbcType jdbcType)
                throws SQLException {
            ByteArrayInputStream bis;
            try {
                bis = new ByteArrayInputStream(FileUtils.readFileToByteArray(parameter));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            ps.setBinaryStream(i, bis);
        }
    }
    
    // ----------------------------------------- BlobAndInputStreamParameterSetterTypeHandler
    /**
     * 执行Mybatis操作时, 数据库操作前的参数设置, 如果传入参数类型为Inputstream, 将调用本TypeHandler
     * @author LQ
     *
     */
    public class BlobAndInputStreamParameterSetterTypeHandler extends BaseTypeHandler<InputStream> {
    
        private static final String ERROR_INFO = "本类只负责参数设置部分, BLOB提取参见本人博客";
    
        @Override
        public InputStream getNullableResult(ResultSet rs, String columnName) throws SQLException {
            throw new UnsupportedOperationException(ERROR_INFO);
        }
    
        @Override
        public InputStream getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            throw new UnsupportedOperationException(ERROR_INFO);
        }
    
        @Override
        public InputStream getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            throw new UnsupportedOperationException(ERROR_INFO);
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, InputStream parameter,
                JdbcType jdbcType) throws SQLException {
            ps.setBinaryStream(i, parameter);
        }
    
    }
2.2 BLOB操作之读取
  1. Mybatis映射文件

    
    <resultMap type="map" id="blobResultMap"> 
        
        <result property="AX_D" column="AX_DATA" jdbcType="BLOB" javaType = "_byte[]"/> 
    resultMap>
    
    <select id="select_blob" parameterType="map" resultMap ="blobResultMap">    
        SELECT
            ax_data,
            ax_ident
        FROM
            affix       
        WHERE
            ax_ident = '123321'     
    select>
  2. Java端调用

    String sqlid =NAMESPACE.concat("select_blob");  
    Map<String, Object> resultMap = crud.String, Object>>selectOne(sqlid, null);
    byte[] ax_d = (byte[])resultMap.get("AX_D");
    System.out.println(new String(convert));
  3. 这里我需要提醒如下几点:
    1. 查询的SQL语句中, 是针对两个字段; 但在resultMap的映射配置中, 我们只设置了一个,也就是只配置了特殊情况——BLOB映射,其他字段的匹配最终还是交给了Mybatis自主完成。