Mybatis(四): 类型转换器模块详解 |
您所在的位置:网站首页 › mybatis日期类型转换 › Mybatis(四): 类型转换器模块详解 |
在上篇文章中我们介绍了下解析器模块的源码,在今天的文章中我们介绍下Mybatis的另一个基础模块-解析器模块。 类型转换器的作用是负责Java类和jdbc类型之间的转换。 该模块对应源码中的org.apache.ibatis.type包,这个里面的类挺多,但是别慌,大部分类都是TypeHandler的子类,只是负责处理不同的类型罢了,在本篇文章中我们介绍的类如下: TypeHandler 类型转换器的顶层接口 BaseTypeHandler 抽象类继承自TypeHandler,Mybatis中所有的类型转换器实现均继承他。 TypeHandlerRegistry 类型转换器注册器,负责存储类型转换器。 TypeAliasRegistry 类型别名转换器,用来存储类型与别名的对应关系。 1 TypeHandlerTypeHandler是类型转换器的顶层接口,其定义了类型转换器应该具有的功能,其源码如下: public interface TypeHandler { // 为sql语句绑定参数 操作PreparedStament void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 获取数据 操作ResultSet T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; } 2 BaseTypeHandlerBaseTypeHandler是一个抽象类,改类实现了TypeHandler中的方法并实现了异常捕获。继承改类我们可以很容易的实现一个自定义类型转换器,其源码如下: public abstract class BaseTypeHandler extends TypeReference implements TypeHandler { @Deprecated protected Configuration configuration; @Deprecated public void setConfiguration(Configuration c) { this.configuration = c; } @Override public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) { throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { ps.setNull(i, jdbcType.TYPE_CODE); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + e, e); } } else { try { setNonNullParameter(ps, i, parameter, jdbcType); } catch (Exception e) { throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different configuration property. " + "Cause: " + e, e); } } } @Override public T getResult(ResultSet rs, String columnName) throws SQLException { try { return getNullableResult(rs, columnName); } catch (Exception e) { throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e); } } @Override public T getResult(ResultSet rs, int columnIndex) throws SQLException { try { return getNullableResult(rs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e); } } @Override public T getResult(CallableStatement cs, int columnIndex) throws SQLException { try { return getNullableResult(cs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e); } } public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException; public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException; public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException; } 3 自定义类型转换器在上面我们介绍了类型转换器的父接口,接下来我们演示下当我们需要定义类型转换器时应该如何操作。 该代码已提交至github 有需要的可以自取:github.com/xiehuaa/myb… 3.1 功能介绍实现的功能也比较简单哈,目前有张employee表,其数据如下: 对于role字段,我想添加时传递List对象,获取时也自动转换为List。 3.2 编写类型转换器我们定义一个RoleTypeHandler继承BaseTypeHandler,其源码如下 /** * Create By IntelliJ IDEA * * @author: XieHua * @date: 2021-08-30 20:25 */ public class RoleTypeHandler extends BaseTypeHandler { @Override public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { if (parameter == null || parameter.isEmpty()) { ps.setString(i, ""); } else { StringBuilder paramStr = new StringBuilder(); for (int j = 0; j < parameter.size(); j ++) { paramStr.append(parameter.get(j)); if (j != parameter.size() - 1) { paramStr.append(","); } } ps.setString(i, paramStr.toString()); } } @Override public List getNullableResult(ResultSet rs, String columnName) throws SQLException { return this.convertToList(rs.getString(columnName)); } @Override public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return this.convertToList(rs.getString(columnIndex)); } @Override public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return this.convertToList(cs.getString(columnIndex)); } // 字符串转换为list private List convertToList(String rolesStr) { List roleList = new ArrayList(); if (!"".equals(rolesStr)) { String[] roleIdStrArray = rolesStr.split(","); for (String roleIdStr : roleIdStrArray) { roleList.add(Integer.parseInt(roleIdStr)); } } return roleList; } } 3.3 实体及Mapper如下 /** * Create By IntelliJ IDEA * * @author: XieHua * @date: 2021-08-30 20:23 */ @Setter @Getter public class Employee { private Integer id; private String name; private List role; } /** * Create By IntelliJ IDEA * * @author: XieHua * @date: 2021-08-02 15:05 */ public interface EmployeeMapper { int insert(@Param("employee") Employee employee); Employee getById(@Param("id") Integer id); } 3.3 配置文件在配置文件中加入该类型转换器,增加如下代码: Mapper映射文件如下: INSERT INTO employee (name, role) VALUES (#{employee.name}, #{employee.role,jdbcType=VARCHAR,typeHandler=com.xh.sample.mybatis.type.RoleTypeHandler}) SELECT * FROM employee WHERE id = #{id} 3.4 测试测试代码如下: public static void main(String[] args) throws IOException { // 配置文件路径 String resource = "mybatis.xml"; // 加载配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // insertEmployee(sqlSession); getEmployee(sqlSession); } private static void insertEmployee (SqlSession sqlSession) { EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = new Employee(); employee.setName("张三"); employee.setRole(Arrays.asList(1, 2)); employeeMapper.insert(employee); // 提交事务 sqlSession.commit(); sqlSession.close(); } private static void getEmployee(SqlSession sqlSession) { EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = employeeMapper.getById(2); System.out.println(JSON.toJSONString(employee)); sqlSession.close(); } 4 TypeHandlerRegistry了解了TypeHandler后,我们学习下Mybatis是如何对TypeHandler进行管理的。在TypeHandlerRegistry中定义了存储TypeHandler的容器及注册和获取的方法。 其属性如下: // jdbc类型和TypeHandler的关系 private final Map>> typeHandlerMap = new ConcurrentHashMap(); // UnknowTypeHandler对象 private final TypeHandler unknownTypeHandler; // 存储了TypeHandler类型与其对象的关系 private final Map> allTypeHandlersMap = new HashMap(); // 空TypeHandler private static final Map> map = typeHandlerMap.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap(); } // 添加到map中 map.put(jdbcType, handler); // 向typeHandlerMap中添加数据 typeHandlerMap.put(javaType, map); } // 维护typeHandler类型与其对应关系 allTypeHandlersMap.put(handler.getClass(), handler); }通过源码可以看到,这两个底层的方法就是操作TypeHandlerRegistry中的属性字段。再往上一层的方法有register(Type javaType, TypeHandler typeHandlerClass)和register(String packageName)三个方法。 public void register(TypeHandler typeHandler) { boolean mappedTypeFound = false; // 获取TypeHandler类上的MappedTypes注解 该注解用于表示该TypeHandler适用的Java类型 MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); // 如果注解存在 if (mappedTypes != null) { // 遍历value值 获取到使用的java类型 for (Class handledType : mappedTypes.value()) { // 进行注册 register(handledType, typeHandler); mappedTypeFound = true; } } // MappedTypes注册不存在或者没有配置对应的属性 且 typeHandler属于TypeReference if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference typeReference = (TypeReference) typeHandler; // 注册类型转换器 typeReference.getRawType获取到的是java类型 register(typeReference.getRawType(), typeHandler); mappedTypeFound = true; } catch (Throwable t) { // maybe users define the TypeReference with a different type and are not assignable, so just ignore it } } // 如果上面的逻辑都没有注册类型转换器 if (!mappedTypeFound) { // 这个逻辑会操作allTypeHandlersMap register((Class) null, typeHandler); } } public void register(Class typeHandlerClass) { boolean mappedTypeFound = false; MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class javaTypeClass : mappedTypes.value()) { register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { register(getInstance(null, typeHandlerClass)); } } public void register(String packageName) { // Mybatis中io工具 ResolverUtil>> handlerSet = resolverUtil.getClasses(); for (Class type : handlerSet) { //Ignore inner classes and interfaces (including package-info.java) and abstract classes if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { // 注册类型转换器 register(type); } } } 4.2 查找类型转换器TypeHandlerRegistry中获取类型转换器的方法也有多个方法,这里我们可以将其划分为三类 通过类型转换器类型查找 通过Jdbc类型查找 通过Java类型的查找 4.2.1 通过类型转换器类型查找通过类型转换器类型查找对应的对象的方法是getMappingTypeHandler,该方法比较简单,直接从allTypehandlersMap中进行查找,其源码如下: public TypeHandler getMappingTypeHandler(Class> handlerType) { return allTypeHandlersMap.get(handlerType); } 4.2.2 通过JdbcType查找通过JdbcType查找对应的类型转换器方法是getTypeHandler(JdbcType jdbcType),这个方法也没什么逻辑,直接从jdbcTypeHandlerMap中进行查找,其源码如下: public TypeHandler getTypeHandler(JdbcType jdbcType) { return jdbcTypeHandlerMap.get(jdbcType); } 4.2.3 通过Java类型查找通过Java类型查找的方法有几个,但他们最终都调用到getTypeHandler(Type type, JdbcType jdbcType)方法,该方法的逻辑如下: private TypeHandler getTypeHandler(Type type, JdbcType jdbcType) { if (ParamMap.class.equals(type)) { return null; } // 获取Java类型对应的类型转换器集合 Map handler = null; if (jdbcHandlerMap != null) { // 再根据JdbcType查询对应的类型转换器 handler = jdbcHandlerMap.get(jdbcType); // 没找到 if (handler == null) { // 获取null对应的类型转换器 handler = jdbcHandlerMap.get(null); } if (handler == null) { // #591 // 如果只注册了一个类型转换器则使用该类型转换器 handler = pickSoleHandler(jdbcHandlerMap); } } // type drives generics here return (TypeHandler) handler; }通过Java类型查找对应的类型转换器列表的逻辑如下: private Map> jdbcHandlerMap = typeHandlerMap.get(type); // 如果是空直接返回null if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) { return null; } // 类型转换器列表为空 if (jdbcHandlerMap == null && type instanceof Class) { Class clazz = (Class) type; // 是否为枚举 if (Enum.class.isAssignableFrom(clazz)) { Class enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz; // 递归枚举的父接口 查找对应的类型转换器 jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass); if (jdbcHandlerMap == null) { // 注册类型转换器 register(enumClass, getInstance(enumClass, defaultEnumTypeHandler)); // 返回类型转换器 return typeHandlerMap.get(enumClass); } } else { // 查找父类的类型转换器集合 jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz); } } // 存入到typeHandlerMap中 typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap); // 返回类型转换器集合 return jdbcHandlerMap; } private Map> jdbcHandlerMap = typeHandlerMap.get(iface); if (jdbcHandlerMap == null) { // 递归查找父接口 jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz); } // 父接口存在对应的类型转换器 if (jdbcHandlerMap != null) { // Found a type handler registered to a super interface // 新建一个map 存储Java类型对应的类型转换器 HashMap> getJdbcHandlerMapForSuperclass(Class clazz) { // 获取改类的父类 Class superclass = clazz.getSuperclass(); // 父类为Object或者为null if (superclass == null || Object.class.equals(superclass)) { return null; } // 从typeHandlerMap中查找对应的类型转换器 Map> typeAliases = new HashMap()属性,这个属性用来存储别名和类型之间的对应关系。在TypeAliasRegistry中提供了注册和通过别名获取类型的方法,接下来我们看看这两部分内容的逻辑 5.1 注册别名在TypeAliasRegistry中提供了如下几个注册方法 registerAliases(String packageName) 注册包下的类 registerAliases(String packageName, Class superType) 注册包下指定父类的类 registerAlias(Class type) 注册某个类 registerAlias(String alias, Class value) 指定别名的方式注册某个类 registerAlias(String alias, String value) 通过别名和类全名注册这些方法的逻辑都比较简单,源码如下: public void registerAliases(String packageName) { registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class superType) { ResolverUtil>> typeSet = resolverUtil.getClasses(); for (Class type : typeSet) { // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 // 忽略内部类 接口 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } } public void registerAlias(Class type) { // 别名默认为类名 String alias = type.getSimpleName(); // 获取类上的Alias注解 Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { // 获取注解的value属性 alias = aliasAnnotation.value(); } registerAlias(alias, type); } public void registerAlias(String alias, Class value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // issue #748 // 别名全部为小写 String key = alias.toLowerCase(Locale.ENGLISH); // 别名已经存在抛出异常 if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'."); } // 添加到map中 typeAliases.put(key, value); } public void registerAlias(String alias, String value) { try { registerAlias(alias, Resources.classForName(value)); } catch (ClassNotFoundException e) { throw new TypeException("Error registering type alias " + alias + " for " + value + ". Cause: " + e, e); } } 5.2 通过别名获取对应的类TypeAliasRegistry.resolveAlias用于获取别名对应的类型,其源码如下: public Class resolveAlias(String string) { try { // 别名为空直接返回null if (string == null) { return null; } // issue #748 // 转为小写 String key = string.toLowerCase(Locale.ENGLISH); Class value; // 该别名已经注册 if (typeAliases.containsKey(key)) { // 获取对应的类型 value = (Class) typeAliases.get(key); } else { // 未注册过,通过反射获取类 value = (Class) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } } 6 总结至此我们今天的文章就结束了,在本篇文章中我们介绍了Mybatis的类型转换器模块,这个模块是不是也很简单呢?希望大家能通过本文掌握这个模块的工作原理及如何自定义类型转换器。 主要涉及的类如下: TypeHandler 类型转换器顶层接口 BaseTypeHandler 一个抽象类,可以简化我们自定义类型转换器 TypeHandlerRegistry 类型转换器注册器 用于管理项目中的类型转换器,提供了注册和获取类型转换器的方法 TypeAliasRegistry 类型别名注册器 用于管理项目中的别名 @MappedTypes 这个注解用于指定一个类转换器可以使用的Java类型 @MappedJdbcTypes 这个注解用户指定一个类型转换器可以使用的Jdbc类型 @Alias 可以通过这个注解为类提供别名感谢您的阅读,如果感觉对您有所帮助,欢迎关注本人公众号"Bug搬运小能手",或者扫描下面二维码进行关注 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |