惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
The Last Watchdog
The Last Watchdog
Cyberwarzone
Cyberwarzone
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cyber Attacks, Cyber Crime and Cyber Security
L
Lohrmann on Cybersecurity
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
The Cloudflare Blog
V
V2EX
博客园_首页
博客园 - 聂微东
Vercel News
Vercel News
人人都是产品经理
人人都是产品经理
G
GRAHAM CLULEY
T
Tenable Blog
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
L
LINUX DO - 最新话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
SecWiki News
SecWiki News
博客园 - 三生石上(FineUI控件)
S
Secure Thoughts
N
News | PayPal Newsroom
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
T
Troy Hunt's Blog
博客园 - 【当耐特】
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
A
About on SuperTechFans
B
Blog RSS Feed
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
D
DataBreaches.Net
P
Privacy & Cybersecurity Law Blog
Schneier on Security
Schneier on Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Jina AI
Jina AI
D
Docker
P
Proofpoint News Feed

博客园 - kylindai

Spark installation for windows java cpu load - kylindai remove all .git files and directories use one command - kylindai Android adb not responsing putty ssh login linux nodejs 实现 http proxy 透明转发 一次PostgreSql数据迁移,使用nodejs来完成 nodejs 安装 postgresql module mongodb 安装为windows服务 memcached linux / win32 1.4.13 learning nodejs 2 - connect middleware learning nodejs 1 - stream.pipe javascript的变量、作用域和内存问题 javascript编程的最佳实践推荐 android download host jetty-distribution-7.6.x 部署 Quartz Cron 表达式 ImageMagick 批量处理图片脚本 常用 LINUX 命令
mybatis 处理数组类型及使用Json格式保存数据 JsonTypeHandler and ArrayTypeHandler
kylindai · 2014-02-23 · via 博客园 - kylindai

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