mybatis 比 ibatis 改進了不少,特別是支持了註解,支持了plugin inteceptor,也給開發者帶來了更多的靈活性,相比其餘ORM,我仍是挺喜歡mybatis的。java
閒言碎語不要講,今天研究了下mybatis的typeHandler:spring
先看這樣一張表(postgresql)sql
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[] 數組類型apache
2. address 咱們但願保存爲json格式的數據,查詢時返回json字符串,mybatis orm 以後能夠還原爲一個數據對象VO。json
完成這2個需求,則須要咱們標題中提到的 JsonTypeHandler & ArrayTypeHandler數組
先看第一個typHandler: ArrayTypeHandler數據結構
咱們先準備VO的代碼:mybatis
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不會變。app
接下來咱們看一下 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,接下來設計數據庫和數據結構就會方便和靈活不少了。