深入理解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)(newView CodeBooleanTypeHandler())); 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的原理(三):配置文件用法(續)