深入理解MyBatis的原理:配置文件用法(续)

Posted 不忘初心

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解MyBatis的原理:配置文件用法(续)相关的知识,希望对你有一定的参考价值。

前言:前文讲解了 MyBatis 的配置文件一部分用法,本文将继续讲解 MyBatis 的配置文件的用法。

目录

1、typeHandler 类型处理器

2、ObjectFactory

3、插件

4、environments 配置环境

5、databaseIdProvider 数据库厂商标识

6、引入映射器的方法

1、typeHandler 类型处理器

  MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,或者从结果集(ResultSet)中取出一个值时,都会用注册了的 typeHandle 进行处理。

  由于数据库厂商不同,字段类型就不同,所以从 java 传递参数到数据库,或者从数据库中读取数据到 java,我们都需要进行字段的处理。typeHandle 的作用,就是将参数从 javaType 转化为 jdbcType,或者从数据库中取出结果时把 jdbcType 转化为 javaType。

  typeHandle 和别名一样,也分为 MyBatis 系统定义和用户自定义两种。  

1.1 dtd 规则

  常用的配置:java 类型(javaType)、JDBC 类型(jdbcType)。

1.2 系统定义的 typeHandle

  MyBatis 系统内部就定义了一系列的 typeHandle,源码在 org.apache.ibatis.type.TypeHandlerRegistry,如下:

public TypeHandlerRegistry() {
        this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
        this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
        this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler()));
        this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler()));
        this.register((Class)Byte.class, (TypeHandler)(new ByteTypeHandler()));
        this.register((Class)Byte.TYPE, (TypeHandler)(new ByteTypeHandler()));
        this.register((JdbcType)JdbcType.TINYINT, (TypeHandler)(new ByteTypeHandler()));
        this.register((Class)Short.class, (TypeHandler)(new ShortTypeHandler()));
        this.register((Class)Short.TYPE, (TypeHandler)(new ShortTypeHandler()));
        this.register((JdbcType)JdbcType.SMALLINT, (TypeHandler)(new ShortTypeHandler()));
        this.register((Class)Integer.class, (TypeHandler)(new IntegerTypeHandler()));
        this.register((Class)Integer.TYPE, (TypeHandler)(new IntegerTypeHandler()));
        this.register((JdbcType)JdbcType.INTEGER, (TypeHandler)(new IntegerTypeHandler()));
        this.register((Class)Long.class, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)Long.TYPE, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)Float.class, (TypeHandler)(new FloatTypeHandler()));
        this.register((Class)Float.TYPE, (TypeHandler)(new FloatTypeHandler()));
        this.register((JdbcType)JdbcType.FLOAT, (TypeHandler)(new FloatTypeHandler()));
        this.register((Class)Double.class, (TypeHandler)(new DoubleTypeHandler()));
        this.register((Class)Double.TYPE, (TypeHandler)(new DoubleTypeHandler()));
        this.register((JdbcType)JdbcType.DOUBLE, (TypeHandler)(new DoubleTypeHandler()));
        this.register((Class)Reader.class, (TypeHandler)(new ClobReaderTypeHandler()));
        this.register((Class)String.class, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
        this.register((Class)String.class, JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
        this.register((Class)String.class, JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((Class)String.class, JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((Class)String.class, JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
        this.register((JdbcType)JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((JdbcType)JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((JdbcType)JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
        this.register((JdbcType)JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
        this.register((JdbcType)JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((JdbcType)JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((JdbcType)JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
        this.register((Class)Object.class, JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
        this.register((JdbcType)JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
        this.register((Class)BigInteger.class, (TypeHandler)(new BigIntegerTypeHandler()));
        this.register((JdbcType)JdbcType.BIGINT, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)BigDecimal.class, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.REAL, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.DECIMAL, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.NUMERIC, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((Class)InputStream.class, (TypeHandler)(new BlobInputStreamTypeHandler()));
        this.register((Class)Byte[].class, (TypeHandler)(new ByteObjectArrayTypeHandler()));
        this.register((Class)Byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
        this.register((Class)Byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
        this.register((Class)byte[].class, (TypeHandler)(new ByteArrayTypeHandler()));
        this.register((Class)byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
        this.register((Class)byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
        this.register((JdbcType)JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
        this.register((JdbcType)JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
        this.register(Object.class, this.UNKNOWN_TYPE_HANDLER);
        this.register(Object.class, JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
        this.register(JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
        this.register((Class)Date.class, (TypeHandler)(new DateTypeHandler()));
        this.register((Class)Date.class, JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
        this.register((Class)Date.class, JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
        this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));
        this.register((JdbcType)JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
        this.register((JdbcType)JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
        this.register((Class)java.sql.Date.class, (TypeHandler)(new SqlDateTypeHandler()));
        this.register((Class)Time.class, (TypeHandler)(new SqlTimeTypeHandler()));
        this.register((Class)Timestamp.class, (TypeHandler)(new SqlTimestampTypeHandler()));
        if(Jdk.dateAndTimeApiExists) {
            Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this);
        }

        this.register((Class)Character.class, (TypeHandler)(new CharacterTypeHandler()));
        this.register((Class)Character.TYPE, (TypeHandler)(new CharacterTypeHandler()));
    }
View Code

  其中 StringTypeHandle,源码如下

public class StringTypeHandler extends BaseTypeHandler<String> {
    public StringTypeHandler() {
    }

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

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

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

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

  StringTypeHandle 继承了 BaseTypeHandle。而 BaseTypeHandle 实现了接口 typeHandle,并且自己定义了 4 个抽象方法。

  setParameter 是 PreparedStatement 对象设置参数,它允许我们自己填写变换规则。

  getResult 则分为 ResultSet 用列名(columnName)或者使用列下标(columnIndex)来获取结果数据。其中还包括了 CallableStatement(存储过程)获取结果及数据的方法。

1.3 自定义 typeHandler

  从上面系统定义的 typeHandler 中来看,其实已经能够应付大部分的场景了,但是有一些特殊情况还是需要自定义 typeHandler。

  这里实现一个字符串参数的 typeHandler。首先配置 XML 文件,确定我们需要处理上面类型的参数和结果。

    <!--自定义类型处理器-->
    <typeHandlers>
        <typeHandler handler="com.yule.mybatis.typehandle.MyStringTypeHandler" javaType="string" jdbcType="VARCHAR"/>
    </typeHandlers>

  或者采取包扫描的方式

    <!--自定义类型处理器-->
    <typeHandlers>
        <package name="com.yule.mybatis.typehandler"/>
    </typeHandlers>

  源码里的 StringTypeHandler 使用的是继承 BaseTypeHandler,而这里采取实现 TypeHandler 接口来实现。

  自定义的 typeHandler 中配置的 jdbcType,javaType,需要使用注解在 handle 类中绑定。

package com.yule.mybatis.typehandler;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author yule
 * @date 2018/8/12 17:52
 */
@MappedTypes({String.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyStringTypeHandler implements TypeHandler<String> {
    private Logger logger = LoggerFactory.getLogger(MyStringTypeHandler.class);

    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
        logger.info("使用我的 typeHandler");
        preparedStatement.setString(i, s);
    }

    @Override
    public String getResult(ResultSet resultSet, String s) throws SQLException {
        logger.info("使用我的 typeHandler,ResultSet 列名获取字符串");
        return resultSet.getString(s);
    }

    @Override
    public String getResult(ResultSet resultSet, int i) throws SQLException {
        logger.info("使用我的 typeHandler,ResultSet 下标获取字符串");
        return resultSet.getString(i);
    }

    @Override
    public String getResult(CallableStatement callableStatement, int i) throws SQLException {
        logger.info("使用我的 typeHandler,CallableStatement 下标获取字符串");
        return callableStatement.getString(i);
    }
}

  注解 @MappedTypes 定义的是 JavaType 类型,可以指定哪些 Java 类型被拦截。

  注解 @MappedJdbcTypes 定义的是 JdbcType 类型,它需要满足枚举类 org.apache.ibatis.type.JdbcType 所列的枚举类型。

  到这里,我们还需要去 sql 里标注哪些参数或结果类型去用我们自定义的 TypeHandler 进行转换,因为在没有任何标注的情况,MyBatis 是不会启用自定义的 TypeHandler。可以通过配置 jdbcType 和 javaType,或者直接使用 typeHandler 属性指定。

    <select id="queryUserByName" resultType="user">
        select t.id, t.name, t.age from t_user t
        where t.name = #{name,javaType=String,jdbcType=VARCHAR,typeHandler=com.yule.mybatis.typehandler.MyStringTypeHandler}
    </select>

  这时运行 sql,就可以了。

1.4 枚举类型 typeHandler

2、ObjectFactory

  当 MyBatis 在构建一个结果返回的时候,会使用 ObjectFactory(对象工厂)去创建 POJO,在 MyBatis 中可以定制自己的对象工厂。

  一般情况下,我们使用默认的对象工厂即可,MyBatis 中默认的对象工厂是由 org.apache.ibatis.reflection.factory.DefaultObjectFactory 来提供服务的。

  如果特殊情况下,需要定制特定的工厂,xml 中配置如下: 

    <!--自定义对象工厂-->
    <objectFactory type="com.yule.mybatis.objectfactory.MyObjectFactory">
        <property name="name" value="MyObjectFactory"/>
    </objectFactory>

  MyObjectFactory 可以参考 DefaultObjectFactory 的写法来,这里为了测试方便,直接继承 DefaultObjectFactory 来简化编程。

package com.yule.mybatis.objectfactory;

import org.apache.ibatis.reflection.factory.DefaultObjectFactory;

import java.util.List;
import java.util.Properties;

/**
 * @author yule
 * @date 2018/8/12 19:34
 */
public class MyObjectFactory extends DefaultObjectFactory {

    private static final long serialVersionUID = -8855120656740914948L;
    
    @Override
    public void setProperties(Properties properties) {
        System.out.println("使用我自己的对象工厂 定制属性");
        super.setProperties(properties);
    }

    @Override
    public <T> T create(Class<T> type) {
        System.out.println("使用我自己的对象工厂 构建单个对象");
        return super.create(type);
    }

    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        System.out.println("使用我自己的对象工厂 构建对象列表");
        return super.create(type, constructorArgTypes, constructorArgs);
    }

    @Override
    public <T> boolean isCollection(Class<T> aClass) {
        return false;
    }
}

  这样我们配置的对象工厂就已经生效。

  大部分情况下,我们使用系统默认的对象工厂即可。

3、插件

4、environments 配置环境

4.1 概述

  配置环境可以注册多个数据源(dataSource),每一个数据源分为两大部分:数据源的配置和数据库事务(transactionManager)的配置。

    <!--配置环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="autoCommit" value="false"/>
            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${datasourceurl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

  environments 中的属性 default,标明在缺省的情况下,我们将启用哪个数据源配置。

  environment 元素是配置一个数据源,属性 id 是设置这个数据源的唯一标识,以便 MyBatis 上下文使用它。

  transactionManager 配置的是数据库事务,其中 type 属性有 3 中配置方式:

  (1)JDBC,采用 JDBC 方式管理事务,在独立编码中我们常常使用。

  (2)MANAGED,采用容器方式管理事务,在 JNDI 数据源中常用。

  (3)自定义,由使用者自定义数据库事务管理方式,适用于特殊应用。

  property 元素则是可以配置数据源的各类属性,我们这个配置了 autoCommit = false,则是要求数据源不自动提交。

  dataSource 标签,是配置数据源连接的信息,type 属性是提供我们队数据库连接方式的配置,同样 MyBatis 提供几种配置方式:

  (1)UNPOOLED,非连接池数据库(UnpooledDataSource)。

  (2)POOLED,连接池数据库(PooledDataSource)。

  (3)自定义数据源。

  其中,配置的 property 元素,就是定义数据库的各类参数。

4.2 数据源

  MyBatis 内部提供了 3 中数据源的实现方式:

  (1)UNPOOLED,非连接池,使用org.apache.ibatis.datasource.unpooled.UnpooledDataSource 实现。

  (2)POOLED,连接池,使用 org.apache.ibatis.datasource.pooled.PooledDataSource 实现。

  (3)JNDI,使用 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 来获取数据源。

  这 3 中方式实现比较简单,只需要在 dataSource 标签的 type定义为 UNPOOLED,POOLED,JNDI 即可。

  如果还需要其他的数据源,可以自定义一个类 MyDataSourceFactory,实现 org.apache.ibatis.datasource.DataSourceFactory 接口,可以参考 JndiDataSourceFactory,然后在 dataSource 标签中配置 type="xxx.xxx.MyDataSourceFactory",即可。

5、databaseIdProvider 数据库厂商标识

  如果 MyBatis 需要运行在不同厂商的数据库中,会需要使用到数据库厂商标识,它为此提供一个数据库标识,并提供自定义,作用在于指定 SQL 到对应的数据库厂商提供的数据库中运行。

  用的很少,所以就不先不在这里讲解。

6、引入映射器的方法

  引入映射器的几种方式:

    <!-- 引入映射器 的几种方式 -->
    <mappers>
        <!--使用类注册引入-->
        <!--<mapper class="com.yule.user.dao.UserDao"/>-->
        <!--使用文件路径引入-->
        <mapper resource="com/yule/user/dao/UserDao.xml"/>
        <!--使用包名引入映射器-->
        <!--<package name="com.yule.user.dao"/>-->
        <!--使用 userDao.xml 引入-->
        <!--<mapper url="file:F:\\IDEAworkspace\\sdemo\\src\\main\\java\\com\\yule\\user\\dao\\UserDao.xml"/>-->
    </mappers>

 

以上是关于深入理解MyBatis的原理:配置文件用法(续)的主要内容,如果未能解决你的问题,请参考以下文章

《深入理解mybatis原理2》 Mybatis初始化机制详解

《深入理解mybatis原理》 Mybatis初始化机制详解

深入理解Mybatis原理与技术

深入理解java:4.3.1. 框架编程之MyBatis---SQL语句执行的完整流程

深入理解V8的垃圾回收原理

《深入理解mybatis原理7》 MyBatis的二级缓存的设计原理