MyBatis(二): 配置文件

MyBatis 的配置文件即mybatis-config.xml中的configuration标签下有settings、properties等属性。本文对其中常用属性的配置做简单介绍



properties 属性

properties 属性可以从properties文件中获得,也可以直接写到配置文件中。
属性文件里config.properties中内容为

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test
username=root
password=root

配置文件mybatis-config.xml中有这样的内容

<!-- properties配置 -->
<properties resource="config.properties">
    <property name="username" value="shisong"/>
    <property name="password" value="123456"/>
</properties>

<!-- properties使用 -->
<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

上面例子中的password重复指定了且不一样,而数据库的正确密码是root,上面的例子是能够成功的; 当把配置中的password改为root,而把porperties中password改为123456时则会失败; 所以当属性在不只一个地方进行了配置时,要有一个加载顺序,后加载的会覆盖先加载的。
另外属性加载的时候,还可以通过SqlSessionBuilder.build()方法来加载

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props);
// ... or ...
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, props);

当属性在不只一个地方进行了配置时,加载顺序是这样的

  1. 先加载XML文件中在properties元素体内property标签指定的属性
  2. 然后加resourceurl指定的资源文件中的属性,并覆盖已读取的同名属性
  3. 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性

typeAliases 类型别名

类型别名是为Java类型命名的一个短的名字,意义仅在于用来减少类完全限定名的冗余。
可以像下面这样起别名:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

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

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

包 domain.blog 中所有的的 Java Bean,在没有注解的情况下,会使用 Bean 的类名来作为它的别名。 比如 domain.blog.Author 的别名为 Author(大小写不敏感);若有注解,则别名为其注解值。如下:

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

MyBatis 已经为普通的 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

typeHandlers 类型转换处理器

数据库中的类型和Java的类型是不一样的,但是有一定的对应关系。
无论是 MyBatis 在预处理语句(PreparedStatement)中设置参数时,还是从结果集中取出值时,都会用类型处理器以合适的方式进行转换。 typeHandler就是进行jdbcTypejavaType之间的转换的。

自定义TypeHandler

MyBatis已经自动注册了一些TypeHandler,我们仍然可以写自己的TypeHandler,具体方法是:实现org.apache.ibatis.type.TypeHandler接口,或继承org.apache.ibatis.type.BaseTypeHandler抽象类并实现其中的抽象方法,然后指明自己的TypeHandler是哪个jdbcTypejavaType之间的转换。
推荐使用BaseTypeHandler,因为它对null值进行了过滤,而且它继承了TypeReference抽象类,通过TypeReferencegetRawType()方法可以获取到当前TypeHandler所使用泛型的原始类型。这对Mybatis在注册TypeHandler的时候是非常有好处的。在没有指定javaType的情况下,Mybatis在注册TypeHandler时可以通过它来获取当前TypeHandler所使用泛型的原始类型作为要注册的TypeHandler的javaType类型。
假设有一个User类,其中有一个属性interests是String数组类型

public class User {  

    private int id;  
    private String name;  
    private int age;  
    private String[ ] interests;  
    ...
}

要为String 数组定义一个转换类StringArrayTypeHandler

// StringArrayTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class StringArrayTypeHandler extends BaseTypeHandler<String[ ]> {
    /**
     * 用于定义在Mybatis设置参数时该如何把Java类型的参数转换为对应的数据库类型
     * @param ps 当前的PreparedStatement对象
     * @param i 当前参数的位置
     * @param parameter 当前参数的Java对象
     * @param jdbcType 当前参数的数据库类型
     * @throws SQLException
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String[ ] parameter, JdbcType jdbcType) throws SQLException {
        //由于BaseTypeHandler中已经把parameter为null的情况做了处理,所以这里我们就不用再判断parameter是否为空了,直接用就可以了  
       StringBuffer result = new StringBuffer();  
       for (String value : parameter)  
           result.append(value).append(",");  
       result.deleteCharAt(result.length()-1);  
       ps.setString(i, result.toString());  
    }  
    }


    /**
     * 用于在Mybatis获取数据结果集时如何把数据库类型转换为对应的Java类型
     * @param rs 当前的结果集
     * @param columnName 当前的字段名称
     * @return 转换后的Java对象
     * @throws SQLException
     */
    @Override
    public String[ ] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return this.getStringArray(rs.getString(columnName));
    }

    /**
     * 用于在Mybatis通过字段位置获取字段数据时把数据库类型转换为对应的Java类型
     * @param rs 当前的结果集
     * @param columnIndex 当前字段的位置
     * @return 转换后的Java对象
     * @throws SQLException
     */
    @Override
    public String[ ] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return this.getStringArray(rs.getString(columnIndex));
    }

    /**
     * 用于Mybatis在调用存储过程后把数据库类型的数据转换为对应的Java类型
     * @param cs 当前的CallableStatement执行后的CallableStatement
     * @param columnIndex 当前输出参数的位置
     * @return
     * @throws SQLException
     */
    @Override
    public String[ ] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return this.getStringArray(cs.getString(columnIndex));
    }

    private String[ ] getStringArray(String columnValue) {
        if (columnValue == null)
            return null;
        return columnValue.split(",");
    }
}

注册TypeHandler

MyBatis 在TypeHandlerRegistry类中已经注册了许多TypeHandler。
我们建立了自己的TypeHandler之后就需要把它注册到Mybatis的配置文件中,让Mybatis能够识别并使用它。注册TypeHandler主要有两种方式:

  • 一种是通过typeHandler来注册,这样一次只能注册一个TypeHandler。
    它有一个必须的属性handler来指明当前要注册的TypeHandler的全名称。另外还有两个可选属性,一个是javaType,用以指定对应的java类型;另一个是jdbcType,用以指定对应的jdbc类型。
<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.StringArrayTypeHandler" javaType="[Ljava.lang.String;" jdbcType="VARCHAR"/>
</typeHandlers>

注意上面的javaType要写全类名,String[]的全类名为"[Ljava.lang.String;",所以上面注册的javaType属性为javaType="[Ljava.lang.String;"

  • 另一种是通过package来注册,MyBatis将会自动检索该包下所有的TypeHandler
    注意:这种方式只能通过注解方式来指定 JDBC 的类型
<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

总结
MyBatis会通过窥探属性的原始类型来推断由类型处理器处理的 Java 类型(前面说的getRawType()方法),可以通过两种方式显式指定被关联的 Java 类型:

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

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

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

Mybatis注册TypeHandler就是建立一个javaTypejdbcTypeTypeHandler的对应关系。根据上面所讲的情况,MyBatis注册我们写的TypeHandler时可分为下面这几种情况:

  1. javaTypejdbcType,那么Mybatis会注册对应javaTypedbcTypeTypeHandler
  2. javaTypejdbcType,那么Mybatis会注册对应javaTypenullTypeHandler
  3. javaTypejdbcType,若当前的TypeHandler继承了TypeReference抽象类,Mybatis会利用TypeReferencegetRawType()方法取到当前TypeHandler泛型对应的javaType类型以方式1注册。
  4. javaTypejdbcType,且当前的TypeHandler未继承TypeReference抽象类,那么Mybatis会注册对应nullnullTypeHandler

自动获取TypeHandler

上面是讲 MyBatis 是如何注册TypeHandler,下面讲 Mybatis 是如何获取对应的TypeHandler进行类型转换的。

我们这样定义UserMapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  

<mapper namespace="com.mybatis.example.mapper.UserMapper">  

    <resultMap id="UserResult" type="User">  
       <id column="id" property="id"/>  
       <result column="interests" property="interests" javaType="[Ljava.lang.String;" jdbcType="VARCHAR"/>  
    </resultMap>  

    <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyColumn="id">  
       insert into t_user(name, age, interests) values(#{name}, #{age}, #{interests, javaType=[Ljava.lang.String;, jdbcType=VARCHAR})  
    </insert>  

    <update id="updateUser" parameterType="User">  
       update t_user set name=#{name}, age=#{age}, interests=#{interests} where id=#{id}  
    </update>  

    <select id="findById" parameterType="int" resultMap="UserResult">  
       select * from t_user where id=#{id}  
    </select>  

    <delete id="deleteUser" parameterType="int">  
       delete from t_user where id=#{id}  
    </delete>  
</mapper>

注意到UserResult中我们指定了javaTypejdbcType。获取TypeHandler的方式与注册TypeHandler中的总结完全一样。
或者我们可以通过typeHandler属性指定到底使用哪个TypeHandler,这样不指定javaTypejdbcType也可以达到同样效果。

<result column="interests" property="interests" typeHandler="org.mybatis.example.StringArrayTypeHandler"/>

mapper.xml文件的内容请参考后续文章中的动态SQL


mappers 映射器

MyBatis的配置文件mybtis-config.xml中有mappers元素,其作用是告诉MyBatis去哪里找SQL映射语句。
一般情况会有一个Maper接口和一个Mapper.xml对应(这里指xml文件的namespace与接口对应),注册Mapper的方法归纳如下:

<mappers>  
   <!-- 通过mapper元素的resource属性可以指定资源中的Mapper.xml文件 -->  
   <mapper resource="mapper/UserMapper.xml"/>  
   <!-- 通过mapper元素的url属性可以指定一个通过URL请求道的Mapper.xml文件 -->  
   <mapper url="file:///var/mappers/UserMapper.xml"/>  
   <!-- 通过mapper元素的class属性可以指定一个Mapper接口进行注册 -->  
   <mapper class="com.test.mybatis.mapper.UserMapper"/>  
   <!-- 通过package元素将会把指定包下面的所有Mapper接口进行注册 -->  
   <package name="com.test.mybatis.bean"/>  
</mappers>

注意

  1. 根据mybatis3的DTD,package元素必须在mapper元素之后
  2. xml文件的namespace如果与接口不对应,则相当于一个只有接口另一个只有xml文件,两者之间没有任何关联
  • 如果只有Mapper接口而无Mapper.xml,则接口中必须通过注解来实现,注册时只能通过package或者class方式注册,调用方式如下
//获取注解后接口对应的对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用接口中的方法
User user = userMapper.findById(1);
  • 如果只有Mapper.xml文件而无Mapper接口,则只能通过resourceurl来注册,调用方式也将变成:
//参数中的com.mybatis.example.mapper.UserMapper是xml文件中的namespace指定的类名
//参数中的findById为指定的类中的方法
User user = sqlSession.selectOne("com.mybatis.example.mapper.UserMapper.findById", 1);
  • 如果既有Mapper接口Mapper.xml(namespace对应才算是都有)则接口中注解不能与xml文件重复,注册最好通过resource(通过classpackage注册只会注册接口而不会关联xml文件,通过resource注册不仅会注册xml文件,还会关联对应的接口),调用方式可以采取上面两种的任意一种。