IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> MyBatis配置------typeHandler 类型转换器 -> 正文阅读

[大数据]MyBatis配置------typeHandler 类型转换器

目录

1.?系统定义的 typeHandler

2.?自定义 typeHandler

3.?枚举 typeHandler


1.?系统定义的 typeHandler

????????MyBatis 内部定义了许多有用 typeHandler。

????????这些就是 MyBatis 系统已经创建好的 typeHandler 。在大部分的情况下无须显式地声明 jdbcType 和 javaType ,或者用 typeHandler 去指定对应的 typeHandler 实现数据类型转换, 因为 MyBatis 系统会自己探测。有时候需要修改一些转换规则,比如枚举类往往需要自己去编写规则。

? ? ? ? 在MyBatis 中 typeHandler 都要实现接口 org.apache.ibatis.type.TypeHandler, 首先先看看这个接口的定义。

public interface TypeHandler<T> {
  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;
}

????????这里稍微说明一下它的定义。

  • 其中 T 是泛型,专指 javaType,比如需要 String 的时候,那么实现类可以写为 implements TypeHandler<String>。
  • setParameter 方法,是使用 typeHandler 通过 PreparedStatement 对象进行设 SQL 参数的时候使用的具体方法,其中 i 是参数在 SQL 的下标, parameter 是参数, jdbcType 是数据库类型。
  • 其中有3个 getResult 方法,它的作用是从 JDBC 结果集中获取数据进行转换,要么使用列名( columnName )要么使用下标( columnlndex )获取数据库的数据,其中最后一个?getResult 方法是存储过程专用的。

????????在编写 typeHandler 前, 先来研究一下 MyBatis 系统的 typeHandler 是如何实现的,所以有必要先研究 MyBatis 系统的 typeHandler 。打开源码,就可以发现它们都继承了 org.apache.ibatis.type.BaseTypeHandler。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  @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;

}

????????简单分析 BaseTypeHandler 的源码

  • BaseTypeHandler 是个抽象类,需要子类去实现其定义的4个抽象方法,而它本身实现了 typeHandler 接口的4个方法。
  • getResult 方法 非空结果集是通过 getNullableResult 方法获取的。如果判断为空,则返回 null。
  • setParameter 方法,当参数 parameter 和 jdbcType 同时为空时,MyBatis 将抛出异常。如果能明确 jdbcType ,则会进行空设置;如果参数不为空,那么它将采用 setNonNullParameter? 方法设置参数。
  • getNullableResult 方法用于存储过程。

????????MyBatis 使用最多的 typeHanlder 之一-?StringTypeHandler 。它用于字符串转换, 源码如下:

public class StringTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

????????显然它实现了 BaseTypeHandler 象方法,代码非常简单。

????????在这里,MyBatis 把 javaType 和 jdbcType 相互转换, 那么它们是如何注册的呢? 在MyBatis 中采用 org.apache.ibatis.type.TypeHandlerRegist 类对象的 register 方法进行注册。

  public TypeHandlerRegistry(Configuration configuration) {
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    ....
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    ....
  }

????????这样就实现了用代码的形式注 typeHandler 。注意,自定义的 typeHandler 一般不会使用代码注册,而是通过配置或扫描,下面开始学习它的自定义。

2.?自定义 typeHandler

????????在大部分的场景下, MyBatis 的 typeHandler 就能应付一般的场景,但是有时候不够用。 比如使用枚举的时候,枚举有特殊的转化规则,这个时候需要自定义 typeHandler 进行处理它。

????????从系统定义的 typeHandler 可以知道,要实现 typeHandler 就需要去实现接口 typeHandler ,或者继承 BaseTypeHandler (实际上, BaseTypeHandler 实现了 typeHandler 接口)。这里仿造 StringTypeHandler 来实现一个自定义的 typeHandler -- MyTypeHandler ,它只是用于实现接口typeHandler。

public class MyTypeHandler implements TypeHandler<String> {

    Logger logger = Logger.getLogger(MyTypeHandler.class);

    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        logger.info("设置string参数[" + parameter + "]");
        ps.setString(i, parameter);
    }

    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        String result = rs.getString(columnName);
        logger.info("读取string参数1[" + result + "]");
        return result;
    }

    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("读取string参数2[" + result + "]");
        return result;
    }

    @Override
    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("读取string参数3[" + result + "]");
        return result;
    }
}

????????定义 typeHandler 泛型为 String ,?显然要把数据库的数据类型转 String 型, 然后实现设置参数和获取结果集的方法。但是这个时候还没有启用 typeHandler ,它还需要做以下的配置。

<typeHandlers>
    <typeHandler jdbcType="VARCHAR" javaType="string" handler="com.learn.ssm.chapter4.typehandler.MyTypeHandler">
</typeHandlers>

????????配置完成后系统才会读取它,这样注册后,jdbcType 和 javaType 能与 MyTypeHandler 对应的时候,它就会启动 MyTypeHandler 。有时候还可以显式启用 typeHandler ,一般而言启用这个 typeHandler 有两种方式。

<resultMap id="roleMap" type="com.learn.ssm.chapter4.pojo.Role">
    <id column="id" property="id"/>
    <!--两种方式-->
    <result column="role_name" property="roleName" jdbcType="VARCHAR" javaType="string"/>
    <result column="note" property="note" typeHandler="com.learn.ssm.chapter4.typehandler.MyTypeHandler"/>
</resultMap>

<select id="findRoles" parameterType="string" resultMap="roleMap">
    SELECT ID, ROLE_NAME, NOTE FROM T_ROLE WHERE ROLE_NAME = #{roleName, jdbcType=VARCHAR, javaType=string}
</select>

<select id="findRoles2" parameterType="string" resultMap="roleMap">
    SELECT ID, ROLE_NAME, NOTE FROM T_ROLE WHERE ROLE_NAME = #{roleName, typeHandler=com.learn.ssm.chapter4.typehandler.MyTypeHandler}
</select>

????????注意,要么指定了与自定义 typeHandler 一致 jdbcType 和 javaType, 要么直接使用typeHandler 指定具体的实现类。在一些因为数据库返回为空导致无法断定采用哪个 typeHandler 来处理,而又没有注册对应的 javaType 和 typeHandler 时, MyBatis 无法知道使用哪个? typeHandler 转换数据,可以采用这样的方式来确定采用哪个 typeHandle 处理, 这样就不会有异常出现了。运行代码查看日志结果:

public static void main(String[] args) {
    Logger log = Logger.getLogger(Chapter4Main.class);

    try (SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession()) {
        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        List<Role> roles = roleMapper.findRoles("admin");
        log.info(roles.get(0));
    }
}

DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [827084938, URL=jdbc:mysql://localhost:3306/ssm?useSSL=false&amp;characterEncoding=utf8&amp;useUnicode=true, MySQL Connector Java]
DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> ?Preparing: SELECT ID, ROLE_NAME, NOTE FROM T_ROLE WHERE ROLE_NAME = ?
?INFO com.learn.ssm.chapter4.typehandler.MyTypeHandler: 设置string参数[admin]
DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: admin(String)
?INFO com.learn.ssm.chapter4.typehandler.MyTypeHandler: 读取string参数1[admin]
?INFO com.learn.ssm.chapter4.typehandler.MyTypeHandler: 读取string参数1[管理员]
DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== ?????Total: 1
DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [827084938, URL=jdbc:mysql://localhost:3306/ssm?useSSL=false&amp;characterEncoding=utf8&amp;useUnicode=true, MySQL Connector Java]
DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [827084938, URL=jdbc:mysql://localhost:3306/ssm?useSSL=false&amp;characterEncoding=utf8&amp;useUnicode=true, MySQL Connector Java]

????????显然配置的 MyTypeHandler 经启用了。

????????有时候由于枚举类型很多,系统需要的 typeHandler 也会很多,如果采用配置会很麻烦,这个时候可以考虑使用包扫描的形式,那么就需要按照下面的方式配置了。

<typeHandlertype> 
    <package name="com.learn.ssm.chapter4.typehandler"/>
</typeHandlertype>

????????只是这样就没法指定 jdbcType 和?javaType 了,不过可以使用注解来处理它们 。把 MyTypeHandler 声明修改一下。

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(String.class)
public class MyTypeHandler implements TypeHandler<String> {
......
}

3.?枚举 typeHandler

????????先来建一个性别枚举类-SexEnum。

public enum SexEnum {
    MALE(1, "男"),
    FEMALE(0, "女");

    private int id;
    private String name;

    /** setter and getter */

    SexEnum(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static SexEnum getSexById(int id) {
        for (SexEnum sex : SexEnum.values()) {
            if (sex.getId() == id) {
                return sex;
            }
        }
        return null;
    }
}

????????为了使用这个关于性别的枚举,以用户表为例。在讨论它们之前先创建一个用户 POJO。

public class User {

    private Long id;
    private String userName;
    private String password;
    private SexEnum sex;
    private String mobile;
    private String tel;
    private String email;
    private String note;

    /** setter and getter */
}

????????MyBatis 内部提供的两种转换的 typeHandler 但是它们有很大的局限性,更多的时候希望使用自定义的 typeHandler。此时,按 SexEnum 的定义, sex=1 为男性, sex=0为女性。为了满足这个规则,自定义 SexEnumTypeHandler。

@MappedTypes(SexEnum.class) 
@MappedjdbcTypes(jdbcType.INTEGER)
public class SexEnumTypeHandler implements TypeHandler<SexEnum> {

    @Override
    public void setParameter(PreparedStatement ps, int i, SexEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getId());
    }

    @Override
    public SexEnum getResult(ResultSet rs, String columnName) throws SQLException {
        int id = rs.getInt(columnName);
        return SexEnum.getSexById(id);
    }

    @Override
    public SexEnum getResult(ResultSet rs, int columnIndex) throws SQLException {
        int id = rs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }

    @Override
    public SexEnum getResult(CallableStatement cs, int columnIndex) throws SQLException {
        int id = cs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }

}

????????然后使用 SexEnumTypeHandler?修改 UserMapper.xml。?

<mapper namespace="com.learn.ssm.chapter4.mapper.UserMapper">

    <resultMap id="userMap" type="com.learn.ssm.chapter4.pojo.User">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
        <result column="password" property="password"/>
        <result column="sex" property="sex" typeHandler="com.learn.ssm.chapter4.typehandler.SexEnumTypeHandler"/>
        <result column="mobile" property="mobile"/>
        <result column="tel" property="tel"/>
        <result column="email" property="email"/>
        <result column="note" property="note"/>
    </resultMap>

    <select id="getUser" parameterType="_int" resultMap="userMap">
        select id, user_name, password, sex, mobile, tel, email, note from t_user where id = #{id}
    </select>

</mapper>

? ? ? ? 运行测试程序:

public static void main(String[] args) {
    Logger log = Logger.getLogger(Chapter4Main.class);

    try (SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession()) {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUser(1);
        log.info(user);
    }
}

? ? ? ? ?执行结果如下:

DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [827084938, URL=jdbc:mysql://localhost:3306/ssm?useSSL=false&amp;characterEncoding=utf8&amp;useUnicode=true, MySQL Connector Java]
DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> ?Preparing: select id, user_name, password, sex, mobile, tel, email, note from t_user where id = ? and 1 = 1
DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== ? ? ?Total: 1
?INFO com.learn.ssm.chapter4.main.Chapter4Main: User(id=1, userName=zhangsan, password=123456, sex=FEMALE, mobile=10086, tel=10086, email=10086@10086.com, note=10086)
DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [827084938, URL=jdbc:mysql://localhost:3306/ssm?useSSL=false&amp;characterEncoding=utf8&amp;useUnicode=true, MySQL Connector Java]
DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [827084938, URL=jdbc:mysql://localhost:3306/ssm?useSSL=false&amp;characterEncoding=utf8&amp;useUnicode=true, MySQL Connector Java]

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-11-26 08:55:06  更:2021-11-26 08:55:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:38:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码