typeHander就是mybatis處理Java類型和Jdbc類型時轉換策略。mybatis在查詢設置參數和從查詢結果映射到Java類型時會調用相應的typeHander的方法完成轉換。這裏我寫了一個小demo以完成自定義List到數據庫的轉換關係,mybatis自動註冊了不少typeHander,好比Java中的基本類型,包裝類型,mybatis都爲咱們註冊了,當咱們不指定具體的typeHander時,mybatis會自動選擇合適的typeHander以完成java類型和jdbc類型的相互轉換。前端
/** * @author chenzhicong * @time 2019/8/13 20:37 * @description */ public class ListTypeHandler extends BaseTypeHandler<List<String>> { @Override public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException { if (jdbcType == null) { ps.setString(i, StringUtils.collectionToCommaDelimitedString(parameter)); } else { ps.setObject(i, parameter, jdbcType.TYPE_CODE); } } @Override public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException { String s = rs.getString(columnName); return s==null ? null : new ArrayList<String>(Arrays.asList(s.split(","))); } @Override public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String s = rs.getString(columnIndex); return s==null ? null : new ArrayList<>(Arrays.asList(s.split(","))); } @Override public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String s = cs.getString(columnIndex); return s==null ? null : new ArrayList<>(Arrays.asList(s.split(","))); } }
首先繼承BaseTypeHandler,指定其泛型參數爲咱們須要轉換的java類型,BaseTypeHandler運用了模板方法模式,封裝了不少空值判斷,異常處理。說明一下:這裏這個類型轉換器沒有指定具體的jdbcType,因此mybatis會自動判斷當java類型爲List的時候就匹配這個這個轉換器。java
註冊到TypeHandlerRegistry是爲了全局自定義效果,若是不註冊,須要在Mapper,xml裏面的resultMap去指定typeHander如:spring
<resultMap id="personMap" type="person"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <result property="hobbys" column="hobbys" typeHandler="com.sankuai.lkl.typeHandler.ListTypeHandler"/> <result property="date" column="data_time"/> </resultMap>
註冊的思路是直接在spring容器加載完bean的時候從容器裏面取出TypeHandlerRegistry,而後註冊咱們自定義的轉換器:sql
/** * @author chenzhicong * @time 2019/8/13 22:19 * @description */ @Component public class CustomTypeHandlerParser implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { //從spring容器獲取sqlSessionFactory SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class); //獲取typeHandler註冊器 TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry(); //註冊List的typeHandler typeHandlerRegistry.register(List.class, ListTypeHandler.class); } }
@Test public void test() { UserJpaTest userJpaTest = new UserJpaTest(); List<String> list = new ArrayList<>(); list.add("phoneNumber1"); list.add("phoneNumber2"); userJpaTest.setPhoneNumber(list); userJpaTestMapper.insert(userJpaTest); }
能夠看到,在數據庫中已經把集合轉換爲了逗號分隔的字符串。數據庫
TypeHander還常常應用於枚舉與數據庫字符串的轉換策略,業務中,咱們枚舉一般有三個變量,一個是給前端的展現字符,一個是存入數據庫的字符,另外一個是咱們在java系統的字符。爲了讓數據庫只識別咱們其中存入數據庫的字符,同時把數據庫中字符轉換爲咱們想要的枚舉。咱們能夠用自定義的轉換器來便利的實現。下面介紹下過程:mybatis
首先咱們能夠把全部枚舉抽象爲一個接口。以下,咱們抽象了全部枚舉爲一個接口枚舉的EntityEnumIFace接口,接口的getDbCode方法爲須要轉換的數據庫字符串,咱們經過這個轉換器去處理這一類枚舉抽象的轉換:app
public final class EntityEnumIFaceHandler<T extends Enum<T> & EntityEnumIFace> extends BaseTypeHandler<T> { private Class<T> type; public EntityEnumIFaceHandler(Class<T> type) { if (null == type) { throw new IllegalArgumentException("type參數不能爲空"); } else { this.type = type; } } @Override public void setNonNullParameter(PreparedStatement preparedStatement, int i, T t, JdbcType jdbcType) throws SQLException { if (jdbcType == null) { preparedStatement.setString(i, ((EntityEnumIFace)t).getDbCode()); } else { preparedStatement.setObject(i, ((EntityEnumIFace)t).getDbCode(), jdbcType.TYPE_CODE); } } @Override public T getNullableResult(ResultSet resultSet, String s) throws SQLException { String value = resultSet.getString(s); return value == null ? null : valueOf(this.type, value); } @Override public T getNullableResult(ResultSet resultSet, int i) throws SQLException { String value = resultSet.getString(i); return value == null ? null : valueOf(this.type, value); } @Override public T getNullableResult(CallableStatement callableStatement, int i) throws SQLException { String value = callableStatement.getString(i); return value == null ? null : valueOf(this.type, value); } private static <E extends Enum<E> & EntityEnumIFace> E valueOf(Class<E> enumClass, String dbCode) { E[] enumConstants = enumClass.getEnumConstants(); if (null != enumConstants) { for (E e : enumConstants) { if (e.getDbCode().equals(dbCode)) { return e; } } } throw new BusinessException("ENUM_NOT_EXIST", enumClass.getSimpleName() + "枚舉中沒有" + dbCode, false); } }
可能看了上面會奇怪,爲何前面定義的轉換器實例化沒有有參構造方法,這裏卻定義了有參構造方法?這個實例化過程又是怎麼樣的?別急,等下面咱們再看,這裏咱們先將其註冊到容器,咱們怎麼註冊到容器勒?這裏能夠經過掃描指定包路徑而後獲取全部枚舉的class對象再用typeHandlerRegistry.register註冊到mybatis,如:ide
try { Set<Class<?>> set = ClassUtil.listClass(BUSINESS_ENUM_PACKAGE); set.forEach(o->{ if(EntityEnumIFace.class.isAssignableFrom(o.getClass())){ typeHandlerRegistry.register(o.getClass(),EntityEnumIFaceHandler.class); } }); }catch (Exception e){ log.error(e.getMessage(),e); }
其中ClassUtil.listClass方法邏輯就是掃描包路徑,而後用類加載器加載class文件獲取class對象。測試
咱們能夠看到這裏和註冊ListTypeHandler沒有兩樣都是調用的同一個方法register(register(Class<?> javaTypeClass, Class<?> typeHandlerClass)),咱們先進入這個方法看一看源碼,源碼調用了register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass),咱們在進去getInstance方法看一看,怎麼實例化的typeHandler,源碼以下:this
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) { if (javaTypeClass != null) { try { Constructor<?> c = typeHandlerClass.getConstructor(Class.class); return (TypeHandler<T>) c.newInstance(javaTypeClass); } catch (NoSuchMethodException ignored) { // ignored } catch (Exception e) { throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); } } try { Constructor<?> c = typeHandlerClass.getConstructor(); return (TypeHandler<T>) c.newInstance(); } catch (Exception e) { throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); } }
咱們能夠看到,實例化typeHandler會首先調用有參構造方法,將typeHandlerRegistry.register參數的javaTypeClass傳入typeHandlerClass建立typeHandlerClass的實例,若沒有反射獲取到構造方法即拋了NoSuchMethodException異常,將進行忽略,進而調用無參的構造方法建立typeHandlerClass實例。因此咱們ListTypeHandler和EntityEnumIFaceHandler都經過此方法成功實例化了,我想這個方法之因此這麼設計應該是想讓TypeHandlerClass可以知曉須要處理的具體Class類型而後去作更通用的抽象去處理這一類Class的轉換方式吧。