• mybatis 处理数组类型及使用Json格式保存数据 JsonTypeHandler and ArrayTypeHandler



    mybatis 处理数组类型及使用Json格式保存数据 JsonTypeHandler and ArrayTypeHandler

    mybatis 比 ibatis 改进了很多,特别是支持了注解,支持了plugin inteceptor,也给开发者带来了更多的灵活性,相比其他ORM,我还是挺喜欢mybatis的。

    闲言碎语不要讲,今天研究了下mybatis的typeHandler:

    先看这样一张表(postgresql)

    复制代码
    create table user (
    
      id serial not null
    
      name character varchar(100),
    
      age integer,
    
      emails character varchar[],  -- varchar 数组 表示可以多个email
    
      address character varchar(2000) -- 因为地址内容为非结构化的数据,我们希望保存json格式描述的地址信息,以增加灵活性
    
    );
    复制代码

    这个表有2个字段值得我们注意:

    1. emails 为 character varchar[] 数组类型

    2. address 我们希望保存为json格式的数据,查询时返回json字符串,mybatis orm 之后可以还原为一个数据对象VO。

    完成这2个需求,则需要我们标题中提到的 JsonTypeHandler & ArrayTypeHandler

     

    先看第一个typHandler: ArrayTypeHandler

    我们先准备VO的代码:

    复制代码
    public class UserVO {
    
        private long id;
        private String name;
        private int age;
        private String[] emails;
        Private Object address;
    
        ......
    }
    复制代码

    其中 emails 对应数据库的 emails,address 对应数据库的 address,为什么用Object类型呢,这是因为以后我们可能会有个 AddressVO 这样的对象,也可能会有 AddressVO2 extends AddressVO 这样的对象,但是数据库的schame不会变。

    接下来我们看一下 UserDao.xml 中的片段:

    复制代码
    <resultMap type="com.kylin.test.userVO" id="userVO">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="emails" column="emails" typeHandler="com.kylin.test.util.mybatis.handler.ArrayTypeHandler"/>
        <result property="address" column="address" typeHandler="com.kylin.test.util.mybatis.handler.JsonTypeHandler"/>
    </resultMap>
    复制代码

    上面的resultMap中配置了2个typeHandler,关于typeHandler的配置,mybatis有多种方法,这里简单示意一下。

     

    再看UserDao.xml中的2个方法,需要使用到这2个handler

    复制代码
    <insert id="addUser" parameterType="com.kylin.test.UserVO">    
        INSERT INTO user (
            name, 
            age, 
            emails, 
            address)
        VALUES (
            #{name, jdbcType=VARCHAR},
            #{age, jdbcType=INTEGER},
            #{emails, jdbcType=ARRAY, typeHandler=com.kylin.test.util.mybatis.handler.ArrayTypeHandler},
            #{address, jdbcType=VARCHAR, typeHandler=com.kylin.test.util.mybatis.handler.JsonTypeHandler})
    </insert>
    
    <select id="getUserById" resultMap="userVO">
        SELECT * 
        FROM user
        WHERE id = #{0}
    </select>
    复制代码

     

    上述的addUser方法,传入了字符串数组,和Object对象,保存到了数据库中,见下面的代码:

    复制代码
        UserVO user = new UserVO();
    
        user.setName("kylin");
        user.setAge(30);
        user.setEmails(new String[] { "kylin@163.com", "kylin@263.com" });
    
        Map<String, Object> address = new HashMap<String, Object>();
        address.put("country", "china");
        address.put("province", "guangdong");
        address.put("city", "shenzhen");
        user.setAddress(address);
    
        // 调用dao.addUser方法
        userDao.addUser(user);
    复制代码

    上面这个方法,将emails 字符串数组保存入数据库,将Map address,以json字符串的方式保存到数据库

    select * from user;
    
    id name     age   emails                                               address
    -------------------------------------------------------------------------------
     1 kylin        30   ["kylin@163.com","kylin@126.com"]  {"contry":"china","province":"guangdong","city":"shenzhen"}

    看到输入按期望的存入到了数据库中,稍后我们看从数据库读取出后是什么样子,我们先看看ArrayTypeHandler.java

     

    mybatis 已经实现了 BaseTypeHandler<T> 这个抽象类,并将公共的逻辑实现好了,我们只需要继承BaseTypeHandler就好,只需要处理简单数据即可。

    复制代码
    package com.kylin.test.util.mybatis.handler;
    
    import java.sql.Array;
    import java.sql.CallableStatement;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.apache.ibatis.type.BaseTypeHandler;
    import org.apache.ibatis.type.JdbcType;
    import org.apache.ibatis.type.TypeException;
    
    // 继承自BaseTypeHandler<Object[]> 使用时传入的参数一定要是Object[],例如 int[]是 Object, 不是Object[],所以传入int[] 会报错的
    public class ArrayTypeHandler extends BaseTypeHandler<Object[]> {
    
        private static final String TYPE_NAME_VARCHAR = "varchar";
        private static final String TYPE_NAME_INTEGER = "integer";
        private static final String TYPE_NAME_BOOLEAN = "boolean";
        private static final String TYPE_NAME_NUMERIC = "numeric";
        
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, Object[] parameter,
                JdbcType jdbcType) throws SQLException {
    
            /* 这是ibatis时的做法
            StringBuilder arrayString = new StringBuilder("{");
    
            for (int j = 0, l = parameter.length; j < l; j++) {
                arrayString.append(parameter[j]);
                if (j < l - 1) {
                    arrayString.append(",");
                }
            }
    
            arrayString.append("}");
            
            ps.setString(i, arrayString.toString());
            */
            
            String typeName = null;
            if (parameter instanceof Integer[]) {
                typeName = TYPE_NAME_INTEGER;
            } else if (parameter instanceof String[]) {
                typeName = TYPE_NAME_VARCHAR;
            } else if (parameter instanceof Boolean[]) {
                typeName = TYPE_NAME_BOOLEAN;
            } else if (parameter instanceof Double[]) {
                typeName = TYPE_NAME_NUMERIC;
            }
            
            if (typeName == null) {
                throw new TypeException("ArrayTypeHandler parameter typeName error, your type is " + parameter.getClass().getName());
            }
            
            // 这3行是关键的代码,创建Array,然后ps.setArray(i, array)就可以了
            Connection conn = ps.getConnection();
            Array array = conn.createArrayOf(typeName, parameter);
            ps.setArray(i, array);
        }
    
        @Override
        public Object[] getNullableResult(ResultSet rs, String columnName)
                throws SQLException {
    
            return getArray(rs.getArray(columnName));
        }
    
        @Override
        public Object[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    
            return getArray(rs.getArray(columnIndex));
        }
    
        @Override
        public Object[] getNullableResult(CallableStatement cs, int columnIndex)
                throws SQLException {
    
            return getArray(cs.getArray(columnIndex));
        }
        
        private Object[] getArray(Array array) {
            
            if (array == null) {
                return null;
            }
    
            try {
                return (Object[]) array.getArray();
            } catch (Exception e) {
            }
            
            return null;
        }
    }
    复制代码

     

    JsonTypeHandler 我们需要用到处理Json的第三方包:jackson,这个包据说处理json是效率最快的,代价最小的。

    先封装一个JsonUtil,并提供JsonUtil.stringify(...) JsonUtil.parse(...) 这样2个方法出来

    复制代码
    package com.kylin.test.util.json;
    
    import java.io.OutputStream;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.codehaus.jackson.map.DeserializationConfig;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.SerializationConfig;
    import org.codehaus.jackson.map.annotate.JsonFilter;
    import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
    import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
    import org.springframework.core.annotation.AnnotationUtils;
    
    public class JsonUtil {
    
        private static Log log = LogFactory.getLog(JsonUtil.class);
        
        private static ObjectMapper objectMapper = null;
        
        static {
            
            objectMapper = new ObjectMapper();
            
            objectMapper.setDateFormat(new SimpleDateFormat(FormatUtil.DATE_FORMAT_LONG));
            objectMapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
            objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
            objectMapper.setFilters(new SimpleFilterProvider().setFailOnUnknownId(false));
        }
    
        /*
        public static JsonUtil getInstance() {
            
            if (instance == null) {
                synchronized (JsonUtil.class) {
                    if (instance == null) {
                        instance = new JsonUtil();
                    }
                }
            }
            
            return instance;
        }
        */
        
        public static String stringify(Object object) {
        
            try {
                return objectMapper.writeValueAsString(object);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            
            return null;
        }
        
        public static String stringify(Object object, String... properties) {
        
            try {
                return objectMapper
                        .writer(new SimpleFilterProvider().addFilter(
                                AnnotationUtils.getValue(
                                    AnnotationUtils.findAnnotation(object.getClass(), JsonFilter.class)).toString(), 
                                    SimpleBeanPropertyFilter.filterOutAllExcept(properties)))
                        .writeValueAsString(object);    
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            
            return null;
        }
        
        public static void stringify(OutputStream out, Object object) {
            
            try {
                objectMapper.writeValue(out, object);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        
        public static void stringify(OutputStream out, Object object, String... properties) {
            
            try {
                objectMapper
                    .writer(new SimpleFilterProvider().addFilter(
                            AnnotationUtils.getValue(
                                AnnotationUtils.findAnnotation(object.getClass(), JsonFilter.class)).toString(), 
                                SimpleBeanPropertyFilter.filterOutAllExcept(properties)))
                    .writeValue(out, object);    
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        
        public static <T> T parse(String json, Class<T> clazz) {
            
            if (json == null || json.length() == 0) {
                return null;
            }
            
            try {
                return objectMapper.readValue(json, clazz);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            
            return null;
        }
        
    }
    复制代码

     

    接着再看看JsonTypeHandler

    复制代码
    package com.kylin.test.util.mybatis.handler;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.apache.ibatis.type.BaseTypeHandler;
    import org.apache.ibatis.type.JdbcType;
    
    import com.kylin.test.util.json.JsonUtil;
    
    // 继承自BaseTypeHandler<Object> 使用Object是为了让JsonUtil可以处理任意类型
    public class JsonTypeHandler extends BaseTypeHandler<Object> {
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, Object parameter,
                JdbcType jdbcType) throws SQLException {
            
            ps.setString(i, JsonUtil.stringify(parameter));
        }
    
        @Override
        public Object getNullableResult(ResultSet rs, String columnName)
                throws SQLException {
            
            return JsonUtil.parse(rs.getString(columnName), Object.class);
        }
    
        @Override
        public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            
            return JsonUtil.parse(rs.getString(columnIndex), Object.class);
        }
    
        @Override
        public Object getNullableResult(CallableStatement cs, int columnIndex)
                throws SQLException {
            
            return JsonUtil.parse(cs.getString(columnIndex), Object.class);
        }
    
    }
    复制代码

     

    至此,JsonTypeHandler 和 ArrayTypeHandler 就分享介绍完了,

    如前面的 resultMap的配置,当调用 getUserById方法时,会返回 String[], 和Map<String, Object>对象回来,

    有了这2个基础TypeHandler,接下来设计数据库和数据结构就会方便和灵活很多了。

    关注“茶爸爸”微信公众号
    坚持最初的执着,从不曾有半点懈怠,为优秀而努力,为证明自己而活。

  • 相关阅读:
    关于JS动态切换样式表
    关于header()函数重定向的问题
    微信团队讲课笔记 Android 开发(二)UI设计
    Effective C++ 笔记:4设计与声明
    某面试算法题_最短时间找出十包粉末中的两蓝粉末。
    VS2015 配置opengl的一些库
    URAL 1225 Flags 简单DP,一重循环
    POJ 1384 Piggy-Bank 完全背包分析
    POJ 1651 Multiplication Puzzle DP 类似矩阵链
    URAL 1183 Brackets Sequence DP 路径输出
  • 原文地址:https://www.cnblogs.com/duyinqiang/p/5696637.html
Copyright © 2020-2023  润新知