• Mybatis 基础篇(四)-- 深入了解 XML 配置(typeAliases、typeHandlers)


    原文链接:Mybatis 基础篇(四)-- 深入了解 XML 配置(typeAliases、typeHandlers)

    类型别名(typeAliases)

    还记得 Mybatis 基础篇(二)-- 深入了解 XML 配置(environments)

    <datasource type="UNPOOLED">
    	...
    </datasource> 
    

    其中 UNPOOLED 是类 UnpooledDataSourceFactory

    类型别名介绍

    类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

    <typeAliases>
      <typeAlias alias="Author" type="domain.blog.Author"/>
      <typeAlias alias="Blog" type="domain.blog.Blog"/>
      <typeAlias alias="Comment" type="domain.blog.Comment"/>
      <typeAlias alias="Post" type="domain.blog.Post"/>
      <typeAlias alias="Section" type="domain.blog.Section"/>
      <typeAlias alias="Tag" type="domain.blog.Tag"/>
    </typeAliases>
    

    当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

    也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

    <typeAliases>
      <package name="domain.blog"/>
    </typeAliases>
    

    每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

    @Alias("author")
    public class Author {
        ...
    }
    

    下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

    别名 映射的类型
    _byte byte
    _long long
    _short short
    _int int
    _integer int
    _double double
    _float float
    _boolean boolean
    string String
    byte Byte
    long Long
    short Short
    int Integer
    integer Integer
    double Double
    float Float
    boolean Boolean
    date Date
    decimal BigDecimal
    bigdecimal BigDecimal
    object Object
    map Map
    hashmap HashMap
    list List
    arraylist ArrayList
    collection Collection
    iterator Iterator

    类型别名实践

    通过新建一个新的数据源类型,加深 datasource type 的理解和别名理解。

    step1: 新建类 C3p0DataSourceFactory

    package tech.ibit.mybatis.learning.configuration.demo.datasource.c3p0;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
    
    public class C3p0DataSourceFactory extends UnpooledDataSourceFactory {
    
        public C3p0DataSourceFactory() {
            this.dataSource = new ComboPooledDataSource();
        }
    
    }
    

    step2: 指定别名 C3P0

    <typeAliases>
        <!--定义数据源别名-->
        <typeAlias type="tech.ibit.mybatis.learning.configuration.demo.datasource.c3p0.C3p0DataSourceFactory" alias="C3P0"/>
    </typeAliases>
    

    step3: 引用别名 C3P0

    <dataSource type="C3P0">
        <property name="driverClass" value="${driver}" />
        <property name="jdbcUrl" value="${url}" />
        <property name="user" value="${username}" />
        <property name="password" value="${password}" />
    </dataSource>
    

    这样就完成了新的数据源类型的创建、别名指定和别名引用。

    注意ComboPooledDataSourceUnpooledDataSource 属性名称有差异。

    类型处理器(typeHandlers)

    类型处理器介绍

    MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。

    提示 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API)。

    类型处理器 Java 类型 JDBC 类型
    BooleanTypeHandler java.lang.Boolean,boolean 数据库兼容的 BOOLEAN
    ByteTypeHandler java.lang.Byte,byte 数据库兼容的 NUMERIC 或 BYTE
    ShortTypeHandler java.lang.Short,short 数据库兼容的 NUMERIC 或 SMALLINT
    IntegerTypeHandler java.lang.Integer,int 数据库兼容的 NUMERIC 或 INTEGER
    LongTypeHandler java.lang.Long,long 数据库兼容的 NUMERIC 或 BIGINT
    FloatTypeHandler java.lang.Float,float 数据库兼容的 NUMERIC 或 FLOAT
    DoubleTypeHandler java.lang.Double,double 数据库兼容的 NUMERIC 或 DOUBLE
    BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC 或 DECIMAL
    StringTypeHandler java.lang.String CHAR,VARCHAR
    ClobReaderTypeHandler java.io.Reader -
    ClobTypeHandler java.lang.String CLOB,LONGVARCHAR
    NStringTypeHandler java.lang.String NVARCHAR,NCHAR
    NClobTypeHandler java.lang.String NCLOB
    BlobInputStreamTypeHandler java.io.InputStream -
    ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
    BlobTypeHandler byte[] BLOB,LONGVARBINARY
    DateTypeHandler java.util.Date TIMESTAMP
    DateOnlyTypeHandler java.util.Date DATE
    TimeOnlyTypeHandler java.util.Date TIME
    SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
    SqlDateTypeHandler java.sql.Date DATE
    SqlTimeTypeHandler java.sql.Time TIME
    ObjectTypeHandler Any OTHER 或未指定类型
    EnumTypeHandler Enumeration Type VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
    EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。
    SqlxmlTypeHandler java.lang.String SQLXML
    InstantTypeHandler java.time.Instant TIMESTAMP
    LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
    LocalDateTypeHandler java.time.LocalDate DATE
    LocalTimeTypeHandler java.time.LocalTime TIME
    OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
    OffsetTimeTypeHandler java.time.OffsetTime TIME
    ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
    YearTypeHandler java.time.Year INTEGER
    MonthTypeHandler java.time.Month INTEGER
    YearMonthTypeHandler java.time.YearMonth VARCHAR 或 LONGVARCHAR
    JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

    你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口,或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。比如:

    // ExampleTypeHandler.java
    @MappedJdbcTypes(JdbcType.VARCHAR)
    public class ExampleTypeHandler 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);
      }
    }
    
    <!-- mybatis-config.xml -->
    <typeHandlers>
      <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>
    

    使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。 要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

    通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

    • 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String");
    • 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。

    可以通过两种方式来指定关联的 JDBC 类型:

    • 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");
    • 在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。

    当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null 的组合来选择一个类型处理器。 这意味着使用 @MappedJdbcTypes 注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap 中将不会生效。 如果希望能在 ResultMap 中隐式地使用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。 然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true,那么这个类型处理器也会是 ResultMap 使用 Java 类型时的默认处理器。

    最后,可以让 MyBatis 帮你查找类型处理器:

    <!-- mybatis-config.xml -->
    <typeHandlers>
      <package name="org.mybatis.example"/>
    </typeHandlers>
    

    注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。

    你可以创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例的时候传入一个具体的类。

    //GenericTypeHandler.java
    public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
    
      private Class<E> type;
    
      public GenericTypeHandler(Class<E> type) {
        if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.type = type;
      }
      ...
    }  
    

    EnumTypeHandlerEnumOrdinalTypeHandler 都是泛型类型处理器,我们将会在接下来的部分详细探讨。

    处理枚举类型

    若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选择一个来使用。

    比如说我们想存储取近似值时用到的舍入模式。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

    注意 EnumTypeHandler 在某种意义上来说是比较特别的,其它的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。
    不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样简单:在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形数值。

    <!-- mybatis-config.xml -->
    <typeHandlers>
      <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
    </typeHandlers>
    

    但要是你想在一个地方将 Enum 映射成字符串,在另外一个地方映射成整形值呢?

    自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler 来处理枚举类型, 所以如果我们想用普通的 EnumTypeHandler,就必须要显式地为那些 SQL 语句设置要使用的类型处理器。

    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
    	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
    		<id column="id" property="id"/>
    		<result column="name" property="name"/>
    		<result column="funkyNumber" property="funkyNumber"/>
    		<result column="roundingMode" property="roundingMode"/>
    	</resultMap>
    
    	<select id="getUser" resultMap="usermap">
    		select * from users
    	</select>
    	<insert id="insert">
    	    insert into users (id, name, funkyNumber, roundingMode) values (
    	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode}
    	    )
    	</insert>
    
    	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
    		<id column="id" property="id"/>
    		<result column="name" property="name"/>
    		<result column="funkyNumber" property="funkyNumber"/>
    		<result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
    	</resultMap>
    	<select id="getUser2" resultMap="usermap2">
    		select * from users2
    	</select>
    	<insert id="insert2">
    	    insert into users2 (id, name, funkyNumber, roundingMode) values (
    	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
    	    )
    	</insert>
    
    </mapper>
    

    注意,这里的 select 语句必须指定 resultMap 而不是 resultType。

    实践:自定义枚举类实现

    实现enumint之间的转化。

    step1: 定义 enum

    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 通用枚举类定义(如果需要将类型转换为枚举类的字段,枚举类都要继承此类)
     *
     * @author IBIT程序猿
     */
    public interface CommonEnum {
    
        /**
         * 获取枚举值
         *
         * @return 枚举值
         */
        int getValue();
    
        /**
         * 获取枚举值对应的枚举
         *
         * @param enumClass 枚举类
         * @param enumValue 枚举值
         * @param <E>       枚举类型
         * @return 枚举
         */
        static <E extends CommonEnum> E getEnum(final Class<E> enumClass, final Integer enumValue) {
            if (enumValue == null) {
                return null;
            }
            try {
                return valueOf(enumClass, enumValue);
            } catch (final IllegalArgumentException ex) {
                return null;
            }
        }
    
        /**
         * 获取枚举值对应的枚举
         *
         * @param enumClass 枚举类
         * @param enumValue 枚举值
         * @param <E>       枚举类型
         * @return 枚举
         */
        static <E extends CommonEnum> E valueOf(Class<E> enumClass, Integer enumValue) {
            if (enumValue == null) {
                throw new NullPointerException("EnumValue is null");
            }
            return getEnumMap(enumClass).get(enumValue);
        }
    
        /**
         * 获取枚举键值对
         *
         * @param enumClass 枚举类型
         * @param <E>       枚举类型
         * @return 键值对
         */
        static <E extends CommonEnum> Map<Integer, E> getEnumMap(Class<E> enumClass) {
            E[] enums = enumClass.getEnumConstants();
            if (enums == null) {
                throw new IllegalArgumentException(enumClass.getSimpleName() + " does not represent an enum type.");
            }
            Map<Integer, E> map = new HashMap<>(2 * enums.length);
            for (E t : enums) {
                map.put(t.getValue(), t);
            }
            return map;
        }
    }
    

    step2: 定义类型处理器

    import org.apache.ibatis.type.BaseTypeHandler;
    import org.apache.ibatis.type.JdbcType;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * 通用枚举类处理器(在ResultMap定义映射枚举字段,typeHandler类型)
     *
     * @author IBIT程序猿
     */
    public class CommonEnumTypeHandler<E extends CommonEnum> extends BaseTypeHandler<E> {
    
        /**
         * 枚举类
         */
        private Class<E> type;
    
        /**
         * 构造函数
         *
         * @param type 枚举类
         */
        public CommonEnumTypeHandler(Class<E> type) {
            if (type == null) {
                throw new IllegalArgumentException("Type argument cannot be null");
            }
            this.type = type;
            E[] enums = type.getEnumConstants();
            if (enums == null) {
                throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
            }
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
            ps.setInt(i, parameter.getValue());
        }
    
        @Override
        public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
            return rs == null ? null : CommonEnum.getEnum(type, rs.getInt(columnName));
        }
    
        @Override
        public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            return rs == null ? null : CommonEnum.getEnum(type, rs.getInt(columnIndex));
        }
    
        @Override
        public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            return cs == null ? null : CommonEnum.getEnum(type, cs.getInt(columnIndex));
        }
    }
    

    step3: 定义枚举实现

    public enum UserType implements CommonEnum {
        NORMAL(0),
        VIP(1),
        ;
    
        private int value;
    
        UserType(int value) {
            this.value = value;
        }
    
        @Override
        public int getValue() {
            return value;
        }
    }
    

    step4: 指定 Enum 使用的默认 TypeHandler

    <settings>
        <setting name="defaultEnumTypeHandler" value="tech.ibit.mybatis.learning.configuration.demo.type.CommonEnumTypeHandler"/>
    </settings>
    

    注意: 若不指定 Enum 使用的默认 TypeHandler,则 CommonEnum 的每种实现,都需要配置 typeHandler

    <typeHandlers>
        <typeHandler handler="tech.ibit.mybatis.learning.configuration.demo.type.CommonEnumTypeHandler"
                     javaType="tech.ibit.mybatis.learning.configuration.demo.type.CommonEnum"/>
    </typeHandlers>
    

    那么自定义的 UserType 字段就能实现自动映射。

    实例代码:configuration-demo

    其他思考?

    typeHandlers 是否也能像 typeAlias 那样指定 package,查找 package 下的类,然后自动映射?

    公众号

    喜欢我的文章,请关注公众号

    IBIT程序猿

  • 相关阅读:
    Baskets of Gold Coins_暴力
    Inversion_树状数组***
    萌新的旅行-
    KI的斐波那契_DFS
    牛吃草_二分法
    See you~_树状数组
    Bellovin_树状数组
    Bubble Sort_树状数组
    [Python] numpy.ndarray.shape
    [Python] numpy.sum
  • 原文地址:https://www.cnblogs.com/javaDeveloper/p/13180916.html
Copyright © 2020-2023  润新知