zoukankan      html  css  js  c++  java
  • 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字符串的方式保存到数据库

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

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

    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,接下来设计数据库和数据结构就会方便和灵活很多了。

  • 相关阅读:
    golang删除数组某个元素
    golang用通道实现信号量,控制并发个数
    什么是ScaleIO中的forwards rebuild和backwards rebuild?
    SQL Server中的database checkpoint
    如何将thick provision lazy zeroed的VMDK文件转换为thick provision eager zeroed?
    LoadTestAgentResultsLateException in VS2010
    SQL Server Instance无法启动了, 因为TempDB所在的分区没有了, 怎么办?
    VMware vCenter中, 如何辩认虚机上Raw Device Mapping过了的一块物理磁盘?
    SQL Server AlwaysOn Setup Step-By-Step Guide
    TPC-E在populate测试Database时需要注意的一些事项
  • 原文地址:https://www.cnblogs.com/lxl57610/p/6805241.html
Copyright © 2011-2022 走看看