郑州媒体网站定制开发,工商查名字能不能注册,wordpress改变端口,网页设计与制作(第2版)课件#x1f3ae; 作者主页#xff1a;点击 #x1f381; 完整专栏和代码#xff1a;点击 #x1f3e1; 博客主页#xff1a;点击 文章目录 原始 JDBC 存在的问题自定义 TypeHandler 实现TypeHandler详解BaseTypeHandler类TypeReference类型参考器43个类型处理器类型注册表 作者主页点击 完整专栏和代码点击 博客主页点击 文章目录 原始 JDBC 存在的问题自定义 TypeHandler 实现TypeHandler详解BaseTypeHandler类TypeReference类型参考器43个类型处理器类型注册表3个注解类枚举类 原始 JDBC 存在的问题
MyBatis 之所以设计了 TypeHandler类型处理器是为了解决 JDBC 在处理 数据类型映射 时存在的问题简化开发者操作并提供更灵活的类型转换机制。在 JDBC 中我们需要手动将数据库字段与 Java 对象的属性进行映射。通常涉及调用 ResultSet.getXXX() 和 PreparedStatement.setXXX() 方法如
PreparedStatement stmt connection.prepareStatement(SELECT * FROM user WHERE id ?);
stmt.setInt(1, 123);
ResultSet rs stmt.executeQuery();
if (rs.next()) {int id rs.getInt(id);String name rs.getString(name);Date birthDate rs.getDate(birth_date);
}
这种映射需要针对每种数据类型手动写 get 和 set 方法代码繁琐且容易出错。 数据库中的数据类型如 VARCHAR、DATE、TIMESTAMP、NUMERIC 等与 Java 的数据类型如 String、int、double、Date 等不完全对应导致在处理数据时需要进行额外的类型转换。例如数据库中的 DATE 类型可能需要转换为 Java 的 LocalDate 或 java.util.Date。 如果数据库字段的数据类型发生变化开发者需要修改所有相关的 JDBC 代码非常容易出错且维护成本高。原生 JDBC 无法直接处理数据库中的复杂数据类型如 JSON、XML、枚举等。如果需要支持这些类型需要在代码中手动解析和转换增加了开发难度。
自定义 TypeHandler 实现
在 MyBatis 中我们可以通过自定义 TypeHandler 来处理 特殊的数据类型映射。一个常见的应用场景是将数据库中的 JSON 字符串与 Java 对象之间进行自动转换。下面将介绍如何自定义 TypeHandler 实现 JSON 类型的转换。 t_user表的jsonInfo存储的是json文本字符串我希望在查询t_user数据的时候可以直接解析出来这个json文本信息为对象。
public class UserBean {private Integer id;private String name;private String mobile_no;private MyObject jsonInfo;
}
Data
public class MyObject {private String name;private Integer age;
}实现自定义 TypeHandler
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zy.client.bean.MyObject;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;import java.sql.*;public class JsonTypeHandler extends BaseTypeHandlerMyObject {private final ObjectMapper objectMapper new ObjectMapper();Overridepublic void setNonNullParameter(PreparedStatement ps, int i, MyObject parameter, JdbcType jdbcType) throws SQLException {try {// 将 Java 对象序列化为 JSON 字符串String json objectMapper.writeValueAsString(parameter);ps.setString(i, json);} catch (JsonProcessingException e) {throw new SQLException(Failed to convert JSON string., e);}}Overridepublic MyObject getNullableResult(ResultSet rs, String columnName) throws SQLException {String json rs.getString(columnName);return parseJson(json);}Overridepublic MyObject getNullableResult(ResultSet rs, int columnIndex) throws SQLException {String json rs.getString(columnIndex);return parseJson(json);}Overridepublic MyObject getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {String json cs.getString(columnIndex);return parseJson(json);}private MyObject parseJson(String json) throws SQLException {try {if (json ! null !json.isEmpty()) {return objectMapper.readValue(json, MyObject.class);}} catch (JsonProcessingException e) {throw new SQLException(Failed to convert JSON string to MyObject., e);}return null;}
}
在 mybatis-config.xml 中注册自定义的 TypeHandler typeHandlerstypeHandler handlercom.zy.client.test.JsonTypeHandler javaTypejava.util.List//typeHandlers或者通过 Mapper 的 TypeHandler 注解 Select(select * from t_user where id#{id})Result(column jsonInfo, property jsonInfo, typeHandler JsonTypeHandler.class)UserBean selectDataById(int id);测试类 Testpublic void test2() throws SQLException, IOException {InputStream resource Resources.getResourceAsStream(MybatisTest.class.getClassLoader(), mybatis-config.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(resource);Configuration configuration sqlSessionFactory.getConfiguration();// 手动注册mapperconfiguration.addMapper(UserMapper.class);configuration.setProxyFactory(new MyLoggingProxyFactory());SqlSession sqlSession sqlSessionFactory.openSession();UserMapper mapper sqlSession.getMapper(UserMapper.class);UserBean userBean mapper.selectDataById(1);System.out.println(userBean);}输出结果
UserBean(id1, namezhangSan, mobile_no188, jsonInfoMyObject(nameAlice, age18))通过自定义 TypeHandlerMyBatis 可以方便地处理复杂的数据类型避免繁琐的手动类型转换操作。尤其在处理 JSON 数据时通过 TypeHandler 实现自动序列化和反序列化可以极大简化代码逻辑提高开发效率。
TypeHandler详解
public interface TypeHandlerT {void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;T getResult(ResultSet rs, String columnName) throws SQLException;T getResult(ResultSet rs, int columnIndex) throws SQLException;T getResult(CallableStatement cs, int columnIndex) throws SQLException;}TypeHandler接口中定义了4个方法setParameter()方法用于为PreparedStatement对象参数的占位符设置值另外3个重载的getResult()方法用于从ResultSet对象中获取列的值或者获取存储过程调用结果。
BaseTypeHandler类
MyBatis中的BaseTypeHandler类实现了TypeHandler接口对调用setParameter()方法参数为Null的情况做了通用的处理。对调用getResult()方法从ResultSet对象或存储过程调用结果中获取列的值出现的异常做了处理。因此当我们需要自定义TypeHandler时只需要继承BaseTypeHandler类即可。 在类型处理器相关类的设计中采用了模板模式BaseTypeHandlerT作为所有类型处理器的基类定义了模板的框架。而在各个具体的实现类中则实现了具体的细节。
以BaseTypeHandler中 getResultResultSetString方法为例该方法完成了异常处理等统一的工作而与具体类型相关的 getNullableResultResultSetString操作则通过抽象方法交给具体的类型处理器实现。这就是典型的模板模式。 Overridepublic 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);}}public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;BaseTypeHandlerT交给具体的类型处理器实现的抽象方法一共只有四个。在每个类型处理器都需要实现这四个方法。 · void setNonNullParameterPreparedStatementintTJdbcType向PreparedStatement对象中的指定变量位置写入一个不为 null的值 · T getNullableResultResultSetString从 ResultSet 中按照字段名读出一个可能为null的数据 · T getNullableResultResultSetint从 ResultSet 中按照字段编号读出一个可能为null的数据 · T getNullableResultCallableStatementint从 CallableStatement中按照字段编号读出一个可能为 null的数据。
因为上面的抽象方法跟具体的类型相关因此存在泛型参数 T。在每种类型处理器中都给出了泛型参数的值。以 IntegerTypeHandler 为例它设置泛型参数值为Integer。
public class IntegerTypeHandler extends BaseTypeHandlerIntegerTypeReference类型参考器
43个类型处理器可以处理不同 Java类型的数据而这些类型处理器都是 TypeHandler接口的子类因此可以都作为 TypeHandler来使用。
那会不会遇到一个问题当 MyBatis取到某一个 TypeHandler 时却不知道它到底是用来处理哪一种 Java类型的处理器 为了解决这一问题MyBatis 定义了一个 TypeReference 类。它能够判断出一个TypeHandler用来处理的目标类型。而它判断的方法也很简单取出 TypeHandler实现类中的泛型参数 T的类型这个值的类型也便是该 TypeHandler能处理的目标类型。该功能由getSuperclassTypeParameter方法实现该方法能将找出的目标类型存入类中的 rawType属性。 Type getSuperclassTypeParameter(Class? clazz) {Type genericSuperclass clazz.getGenericSuperclass();if (genericSuperclass instanceof Class) {// try to climb up the hierarchy until meet something usefulif (TypeReference.class ! genericSuperclass) {return getSuperclassTypeParameter(clazz.getSuperclass());}throw new TypeException( getClass() extends TypeReference but misses the type parameter. Remove the extension or add a type parameter to it.);}Type rawType ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];if (rawType instanceof ParameterizedType) {rawType ((ParameterizedType) rawType).getRawType();}return rawType;}TypeReference 类是 BaseTypeHandler 的父类因此所有的类型处理器都继承了TypeReference 的功能。这意味着对任何一个类型处理器调用getSuperclassTypeParameter方法都可以得到该处理器用来处理的目标类型。
43个类型处理器
type 包共有 43 个类型处理器这些类型处理器的名称也均以“TypeHandler”结尾。而 TypeHandler和 BaseTypeHandler则分别是类型处理器接口和类型处理器基类。 MyBatis 提供了多种内置的 TypeHandler如 StringTypeHandler、IntegerTypeHandler 等也可以自定义 TypeHandler 来满足特定需求。 StringTypeHandler 用于处理 String 类型的数据在以下场景中会自动使用 • 当 MyBatis 映射的 Java 类型为 String而数据库字段的类型为 VARCHAR、CHAR、TEXT 等字符串类型时。 • 当没有为 String 类型字段显式配置 TypeHandler 时MyBatis 会默认使用 StringTypeHandler。
public class StringTypeHandler extends BaseTypeHandlerString {Overridepublic void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {if (parameter null) {ps.setNull(i, Types.VARCHAR);} else {ps.setString(i, parameter);}}Overridepublic String getResult(ResultSet rs, String columnName) throws SQLException {return rs.getString(columnName);}Overridepublic String getResult(ResultSet rs, int columnIndex) throws SQLException {return rs.getString(columnIndex);}Overridepublic String getResult(CallableStatement cs, int columnIndex) throws SQLException {return cs.getString(columnIndex);}
}
类型注册表3个
SimpleTypeRegistry基本类型注册表内部使用 Set 维护了所有 Java 基本数据类型的集合-TypeAliasRegistry类型别名注册表内部使用 HashMap维护了所有类型的别名和类型的映射关系 TypeHandlerRegistry类型处理器注册表内部维护了所有类型与对应类型处理器的映射关系。
定义了大量的类型处理器之后MyBatis 还需要在遇到某种类型的数据时快速找到与数据的类型对应的类型处理器。这个过程就需要各种类型注册表的帮助。 type 包中的类型注册表有三个SimpleTypeRegistry、TypeAliasRegistry 和TypeHandlerRegistry。SimpleTypeRegistry 是一个非常简单的注册表其内部使用一个 SIMPLE_TYPE_SET变量维护所有 Java基本类型。SIMPLE_TYPE_SET中的赋值是在 static代码块中进行的。这说明在 SimpleTypeRegistry初始化结束后就已经将所有的Java基本类型维护到了 SIMPLE_TYPE_SET中。
public class SimpleTypeRegistry {private static final SetClass? SIMPLE_TYPE_SET new HashSet();static {SIMPLE_TYPE_SET.add(String.class);SIMPLE_TYPE_SET.add(Byte.class);SIMPLE_TYPE_SET.add(Short.class);SIMPLE_TYPE_SET.add(Character.class);SIMPLE_TYPE_SET.add(Integer.class);SIMPLE_TYPE_SET.add(Long.class);SIMPLE_TYPE_SET.add(Float.class);SIMPLE_TYPE_SET.add(Double.class);SIMPLE_TYPE_SET.add(Boolean.class);SIMPLE_TYPE_SET.add(Date.class);SIMPLE_TYPE_SET.add(Class.class);SIMPLE_TYPE_SET.add(BigInteger.class);SIMPLE_TYPE_SET.add(BigDecimal.class);}TypeAliasRegistry是一个类型别名注册表其内部使用 typeAliases变量维护类型的别名与类型的对应关系。有了这个注册表我们就可以在很多场合使用类型的别名来指代具体的类型。
public class TypeAliasRegistry {private final MapString, Class? typeAliases new HashMap();public TypeAliasRegistry() {registerAlias(string, String.class);registerAlias(byte, Byte.class);registerAlias(long, Long.class);registerAlias(short, Short.class);registerAlias(int, Integer.class);registerAlias(integer, Integer.class);registerAlias(double, Double.class);registerAlias(float, Float.class);registerAlias(boolean, Boolean.class);registerAlias(byte[], Byte[].class);registerAlias(long[], Long[].class);registerAlias(short[], Short[].class);registerAlias(int[], Integer[].class);registerAlias(integer[], Integer[].class);registerAlias(double[], Double[].class);registerAlias(float[], Float[].class);registerAlias(boolean[], Boolean[].class);registerAlias(_byte, byte.class);registerAlias(_long, long.class);registerAlias(_short, short.class);registerAlias(_int, int.class);registerAlias(_integer, int.class);registerAlias(_double, double.class);registerAlias(_float, float.class);registerAlias(_boolean, boolean.class);registerAlias(_byte[], byte[].class);registerAlias(_long[], long[].class);registerAlias(_short[], short[].class);registerAlias(_int[], int[].class);registerAlias(_integer[], int[].class);registerAlias(_double[], double[].class);registerAlias(_float[], float[].class);registerAlias(_boolean[], boolean[].class);registerAlias(date, Date.class);registerAlias(decimal, BigDecimal.class);registerAlias(bigdecimal, BigDecimal.class);registerAlias(biginteger, BigInteger.class);registerAlias(object, Object.class);registerAlias(date[], Date[].class);registerAlias(decimal[], BigDecimal[].class);registerAlias(bigdecimal[], BigDecimal[].class);registerAlias(biginteger[], BigInteger[].class);registerAlias(object[], Object[].class);registerAlias(map, Map.class);registerAlias(hashmap, HashMap.class);registerAlias(list, List.class);registerAlias(arraylist, ArrayList.class);registerAlias(collection, Collection.class);registerAlias(iterator, Iterator.class);registerAlias(ResultSet, ResultSet.class);}TypeHandlerRegistry 是这三个注册表中最为核心的一个数据类型和相关处理器的对应关系就是由它维护的。
public final class TypeHandlerRegistry {private final MapJdbcType, TypeHandler? jdbcTypeHandlerMap new EnumMap(JdbcType.class);private final MapType, MapJdbcType, TypeHandler? typeHandlerMap new ConcurrentHashMap();private final TypeHandlerObject unknownTypeHandler;private final MapClass?, TypeHandler? allTypeHandlersMap new HashMap();private static final MapJdbcType, TypeHandler? NULL_TYPE_HANDLER_MAP Collections.emptyMap();private Class? extends TypeHandler defaultEnumTypeHandler EnumTypeHandler.class;/*** The default constructor.*/public TypeHandlerRegistry() {this(new Configuration());}/*** The constructor that pass the MyBatis configuration.** param configuration a MyBatis configuration* since 3.5.4*/public TypeHandlerRegistry(Configuration configuration) {this.unknownTypeHandler new UnknownTypeHandler(configuration);register(Boolean.class, new BooleanTypeHandler());register(boolean.class, new BooleanTypeHandler());register(JdbcType.BOOLEAN, new BooleanTypeHandler());register(JdbcType.BIT, new BooleanTypeHandler());register(Byte.class, new ByteTypeHandler());register(byte.class, new ByteTypeHandler());register(JdbcType.TINYINT, new ByteTypeHandler());register(Short.class, new ShortTypeHandler());register(short.class, new ShortTypeHandler());register(JdbcType.SMALLINT, new ShortTypeHandler());register(Integer.class, new IntegerTypeHandler());register(int.class, new IntegerTypeHandler());register(JdbcType.INTEGER, new IntegerTypeHandler());register(Long.class, new LongTypeHandler());register(long.class, new LongTypeHandler());register(Float.class, new FloatTypeHandler());register(float.class, new FloatTypeHandler());register(JdbcType.FLOAT, new FloatTypeHandler());register(Double.class, new DoubleTypeHandler());register(double.class, new DoubleTypeHandler());register(JdbcType.DOUBLE, new DoubleTypeHandler());}注解类
Alias使用该注解可以给类设置别名设置后别名和类型的映射关系便存入TypeAliasRegistry中 MappedJdbcTypes有时我们想使用自己的处理器来处理某些JDBC 类型只需创建 BaseTypeHandler 的子类然后在上面加上该注解声明它要处理的JDBC类型即可 MappedTypes有时我们想使用自己的处理器来处理某些Java类型只需创建BaseTypeHandler的子类然后在上面加上该注解声明它要处理的 Java类型即可。
枚举类
JdbcType在 Enum中定义了所有的 JDBC类型类型来源于java.sql.Types。
public enum JdbcType {ARRAY(Types.ARRAY),BIT(Types.BIT),TINYINT(Types.TINYINT),SMALLINT(Types.SMALLINT),INTEGER(Types.INTEGER),BIGINT(Types.BIGINT),FLOAT(Types.FLOAT),REAL(Types.REAL),DOUBLE(Types.DOUBLE),NUMERIC(Types.NUMERIC)
....
}