深入理解MyBatis的原理:配置文件用法(续)
Posted 不忘初心
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解MyBatis的原理:配置文件用法(续)相关的知识,希望对你有一定的参考价值。
前言:前文讲解了 MyBatis 的配置文件一部分用法,本文将继续讲解 MyBatis 的配置文件的用法。
目录
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())); }
其中 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初始化机制详解