嘘~ 正在从服务器偷取页面 . . .

Mybatis(二)配置


Mybatis(二)配置

关于Mybatis配置,其实官网都有,本文是学习《深入浅出Mybatis技术原理与实战》的笔记,同时参考官网相关说明,进行阅读整理。

一、配置configuration

1、MyBatis 的配置文件层级结构。

configuration(配置)
    properties(属性)
    settings(设置)
    typeAliases(类型别名)
    typeHandlers(类型处理器)
    objectFactory(对象工厂)
    plugins(插件)
    environments(环境配置)
        environment(环境变量)
            transactionManager(事务管理器)
            dataSource(数据源)
    databaseIdProvider(数据库厂商标识)
    mappers(映射器)

说明:层次结构不能随意颠倒顺序。

二、属性 properties

properties是配置属性的元素。

主要配置方式有3种:

(1)直接配置properties子元素。

(2)使用properties配置文件。

(3)程序参数传递。

1、配置properties子元素

在典型的 Java 属性文件中配置这些属性 :

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

从 MyBatis 3.4.2 开始,也可以为占位符指定一个默认值。 如:

<dataSource type="POOLED">
   <!-- ... 其他省略-->
   <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
  <property name="username" value="${username:ut_user}"/>
</dataSource>

这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性。例如:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 表示启用默认值特性 -->
</properties>

如果你在属性名中使用了 ":" 字符(如:db:username),或者在 SQL映射中使用了 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : 'global_constants'}),就需要设置特定的属性来修改分隔属性名和默认值的字符。例如:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- 修改默认值的分隔符 -->
</properties>
<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${db:username?:ut_user}"/>
</dataSource>

2、使用properties配置文件

dbconfig.properties

validationQuery.sqlserver=SELECT 1
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useSSL=false&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
jdbc.dbType=mysql

在配置文件中引入

<properties resouce="dbconfig.properties" />

3、程序参数传入

如用户名密码加密,需要解密时,在 SqlSessionFactoryBuilder.build() 方法中传入属性值。

package com.ssm.web.demo.test;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Properties;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.xmlbeans.impl.util.Base64;
import org.springframework.beans.propertyeditors.InputStreamEditor;

import com.mysql.jdbc.util.Base64Decoder;

public class PropertiesTest {

    private static final String ClASS_LOCK = "_lock_";
    static SqlSessionFactory sqlSessionFactory;

    public static void main(String[] args) {

         InputStream cfgStream = null;
         Reader cfgReader = null ;
         InputStream proStream = null;
         Reader proReader = null;
         Properties properties = null;
         try {
             //读入配置文件流
             cfgStream = Resources.getResourceAsStream("mybatis-config.xml");

             cfgReader = new InputStreamReader(cfgStream);
            //读取配置文件
             proStream = Resources.getResourceAsStream("dbconfig.properties");
             proReader = new InputStreamReader(proStream);

             properties.load(proStream);

             //解密
             properties.setProperty("username",Base64.decode(properties.getProperty("username").getBytes()).toString());
         properties.setProperty("password",Base64.decode(properties.getProperty("password").getBytes()).toString());

        } catch (Exception e) {
            // TODO: handle exception
        }

        synchronized (ClASS_LOCK) {

            if(sqlSessionFactory == null){
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(cfgReader, properties);
            }
        }
    }
}

4、配置properties的优先级

MyBatis 将按照下面的顺序来加载:

(1)在properties元素体内的指定的属性优先读取。

(2)根据properties的resouce属性读取类路径下的属性文件,或者url属性指定的路径读取属性文件,并覆盖已读取的同名属性。

(3)读取作为方法参数传递的属性,并覆盖已读取的同名属性。

因此,优先级从高到底依次为:

方法参数传递属性 >  resource/url指定配置文件 > properties属性中指定的属性

实际使用时需注意:

(A)尽可能不要使用混合方式

(B)首选方式properties

(B)进行加密或其他加工时参考示例,今天使用同一个配置文件,方便管理。

三、设置setting

一个配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

大部分时候不需要配全,如需配置参考Mybatis官网-设置说明参数说明。

四、类型别名 typeAliases

typeAliases主要分两类

(A)系统定义别名typeAliases

(B)自定义别名typeAliases

1、系统定义别名

系统定义的别名,可以查看Mybatis源码,org.apache.ibatis.type.TypeAliasRegistry类中定义。部分代码如下:

package org.apache.ibatis.type;

import java.util.*;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.io.Resources;

public class TypeAliasRegistry {

  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);
    //....其他省略
   }
}

2、自定义别名

类型别名可为 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>

如果你的POJO非常多,还可以这样配置:

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

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author。那有注解的话,按注解别名使用。

@Alias("author")
public class Author {
    //some code 省略
}

五、类型处理器 typeHandler

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

数据库不同,设置参数可能不同,数据库也可以自定义数据类型,typeHandler可以自定义设置Java传递到数据库的参数,或从数据库读取数据,也可以进行特殊处理,都可以在自定义typeHandler中处理。特别是枚举类型需要使用typeHandler进行转换。

typeHandler常用的配置为Java类型(javaType)、JDBC类型(jdbcType)。主要数据库的Java类型和JDBC类型对应关系,可以参考《常见数据库字段的Java类型和JDBC类型对应关系》

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

typeHandler主要分两类

(A)系统定义typeHandler

(B)自定义的typeHandler

1、系统定义类型处理器

Mybatis系统内定义了一系列的类型处理器,可以查看Mybatis源码,org.apache.ibatis.type.TypeHandlerRegistry类中定义。部分代码如下:

public final class TypeHandlerRegistry {
     // some code
     public TypeHandlerRegistry() {
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
        // some code

        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());

        // some code
     }
}

比如源码中StringTypeHandler大概长这个样子:

 package org.apache.ibatis.type;

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

/**
 * @author Clinton Begin
 */
public class StringTypeHandler extends BaseTypeHandler<String> {
 //对PreparedStatement设置参数
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }
 //ResultSet使用下表columnName来获取结果数据 
  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }
    //ResultSet使用下表columnIndex来获取结果数据
  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }

  //CallableStatement存储过程获取结果及数据
  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

StringTypeHandler继承了BaseTypeHandler。BaseTypeHandler实现了接口typeHandler,并且自己定义了4个抽象方法,StringTypeHandler需要实现其定义的4个抽象方法。@Override注解已经注明。

更多有关Mybatis系统注册的typeHandler更多说明参考Mybatis官网说明。

需要注意:

(1)数值类型的精度,数据库int、double、decimal这些类型和java的精度、长度都是不一样的。

(2)时间精度,取数据到日用DateOnlyTypeHandler,用到精度为秒的用SqlTimestampTypeHandler等

2、自定义类型处理器

可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。

具体做法为:

实现org.apache.ibatis.type.TypeHandler接口,或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler,并且可以(可选地)将它映射到一个 JDBC 类型。

定义一个typeHandler,如使用ExampleTypeHandler覆盖原有的StringTypeHandler:

// ExampleTypeHandler.java
@MappedType({String.class}) 
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    System.out.print("---ExampleTypeHandler设置参数----")
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
      System.out.print("---ExampleTypeHandler 获取列名字符串----")
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
      System.out.print("---ExampleTypeHandler 获取列名字符串----")
      return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
     System.out.print("---ExampleTypeHandler----")
    return cs.getString(columnIndex);
  }
}

@MappedType - 定义的是JavaType类型,可以指定哪些Java类型被拦截。如果不用注解,也可以在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String") 。

@MappedJdbcTypes - 定义的是JdbcType类型,它需要满足枚举类org.apache.ibatis.type.JdbcType所列举的类型。如果不用注解,也可以在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR")。

配置 mybatis-config.xml使用ExampleTypeHandler:

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler" />
</typeHandlers>

需要注意:使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。

如果有多个类型转换器,可以使用扫描式:

让 MyBatis 帮你查找类型处理器(ExampleTypeHandler在包):

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="com.ssm.web.typehandle"/>
</typeHandlers>

在mybatis-config.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.ssm.web.demo.dao.emp.EmployeeMapper">
  <resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
    <id column="emp_id" jdbcType="INTEGER" property="empId" />
    <result column="emp_name" jdbcType="VARCHAR" property="empName" />
    <result column="sex" jdbcType="CHAR" property="sex" />
    <result column="email" property="email" typeHandler="com.ssm.web.demo.ExampleTypeHandler"/>
    <result column="dept_id" jdbcType="VARCHAR" property="did" />
  </resultMap>
    // some code 省略
</mapper>    

3、枚举类型处理器

Mybatis系统自带枚举类型处理器:

(A)org.apache.ibatis.type.EnumTypeHandler

(B) org.apache.ibatis.type.EnumOrdinalTypeHandler

EnumTypeHandler是使用枚举字符串名称作为参数传递的,EnumOrdinalTypeHandler是使用整数下标作为参数传递的。枚举和数据库字典保持一致就可以使用。

EnumOrdinalTypeHandler的使用

使用自定义枚举类型,比如性别枚举:

package com.ssm.web.myenum;

public enum Sex {

    MALE(1,"男"), FEMALE(2,"女");

    private int index;
    private String name;

    private Sex(){}

    private Sex(int index, String name) {
        this.index = index;
        this.name = name;
    }
    //getter setter method ...
}

全局配置加上类型转换器以让Mybatis认识枚举类型:

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.ssm.web.myenum.Sex"/>
</typeHandlers>

最后在需要的字段上指定即可:

<?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.ssm.web.demo.dao.emp.EmployeeMapper">
  <resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
    <id column="emp_id" jdbcType="INTEGER" property="empId" />
    <result column="emp_name" jdbcType="VARCHAR" property="empName" />
    <result column="sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
    <result column="email" property="email" typeHandler="com.ssm.web.demo.ExampleTypeHandler"/>
    <result column="dept_id" jdbcType="VARCHAR" property="did" />
  </resultMap>
    // some code 省略
</mapper>    

当这样配置时,sex为整数型,数据库插入的是序号(下标)值,1或2。

自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler 来处理枚举类型。

EnumTypeHandler的使用

EnumTypeHandler使用枚举名称去处理Java枚举类型。EnumTypeHandler对应的是一个字符串。

将sex类型变为VARCHAR类型,之前是int类型。

在xml文件中使用,在insert的时候需要显示的指明自定义类型转换器:

<?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.ssm.web.demo.dao.emp.EmployeeMapper">
  <resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
    <id column="emp_id" jdbcType="INTEGER" property="empId" />
    <result column="emp_name" jdbcType="VARCHAR" property="empName" />
    <result column="sex" property="sex" typeHandler="com.ssm.web.typehandle.EnumTypeHandler" />
    <result column="email" property="email" typeHandler="com.ssm.web.demo.ExampleTypeHandler"/>
    <result column="dept_id" jdbcType="VARCHAR" property="did" />
  </resultMap>
    // some code 省略

    <insert id="insert" parameterType="com.ssm.web.demo.entity.emp.Employee">
    insert into tbl_emp (emp_id, emp_name, sex, 
      email,dept_id)
    values (#{empId,jdbcType=INTEGER}, #{empName,jdbcType=VARCHAR}, #{sex,typeHandler="com.ssm.web.typehandle.EnumTypeHandler"}, 
      #{email,jdbcType=VARCHAR},#{did,jdbcType=INTEGER},)
  </insert>
</mapper> 

数据库中sex列写入的是 MALE或FAMALE。

自定义类型转换器使用:

定义自己的typeHandler,比如SexEnumTypeHandler类:

package com.ssm.web.typehandle;

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

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import com.ssm.web.myenum.Sex;

public class SexEnumTypeHandler implements TypeHandler<Sex>{

    @Override
    public void setParameter(PreparedStatement ps, int i, Sex sex, JdbcType jdbcType) throws SQLException {
        // TODO Auto-generated method stub
        ps.setInt(i, sex.getIndex());
    }

    @Override
    public Sex getResult(ResultSet rs, String columnName) throws SQLException {
        // TODO Auto-generated method stub
        int id = rs.getInt(columnName);
        return Sex.getSext(id);
    }

    @Override
    public Sex getResult(ResultSet rs, int columnIndex) throws SQLException {
        int id = rs.getInt(columnIndex);
        return Sex.getSext(id);
    }

    @Override
    public Sex getResult(CallableStatement cs, int columnIndex) throws SQLException {
        int id = cs.getInt(columnIndex);
        return Sex.getSext(id);
    }
}

最后配置mybatis-config.xml(也可以不配,在指定字段处配置),使用自定义的类型处理器SexEnumTypeHandler映射自定义枚举类Sex:

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="com.ssm.web.typehandle.SexEnumTypeHandler" javaType="com.ssm.web.myenum.Sex"/>
</typeHandlers>

使用:

<?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.ssm.web.demo.dao.emp.EmployeeMapper">
  <resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
    <id column="emp_id" jdbcType="INTEGER" property="empId" />
    <result column="emp_name" jdbcType="VARCHAR" property="empName" />
    <result column="sex" property="sex" typeHandler="com.ssm.web.typehandle.SexEnumTypeHandler" />
    <result column="email" property="email" typeHandler="com.ssm.web.demo.ExampleTypeHandler"/>
    <result column="dept_id" jdbcType="VARCHAR" property="did" />
  </resultMap>
    // some code 省略

    <insert id="insert" parameterType="com.ssm.web.demo.entity.emp.Employee">
    insert into tbl_emp (emp_id, emp_name, sex, 
      email,dept_id)
    values (#{empId,jdbcType=INTEGER}, #{empName,jdbcType=VARCHAR}, #{sex,typeHandler="com.ssm.web.typehandle.SexEnumTypeHandler"}, 
      #{email,jdbcType=VARCHAR},#{did,jdbcType=INTEGER},)
  </insert>
</mapper> 

六、对象工厂objectFactory

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。

如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:

// MyObjectFactory.java
public class MyObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
       //自定义工厂调用不带参数的构造方法
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
      //自定义工厂调用带参数的构造方法
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
       //自定义工厂设置属性
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}

配置 mybatis-config.xml:

<!-- mybatis-config.xml -->
<objectFactory type="com.ssm.web.objectFactorytest.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

七、插件

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。

默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。

MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="com.ssm.web.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。

提示覆盖配置类

除了用插件来修改 MyBatis 核心行为以外,还可以通过完全覆盖配置类来达到目的。只需继承配置类后覆盖其中的某个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。特别说明,这可能会极大影响 MyBatis 的行为,务请慎之又慎。

涉及Mybatis内部运行原理。

参考《Mybatis运行原理》

八、配置环境

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

也就是可以注册多个数据源(DataSource),每个数据源分两个部分:数据源配置和数据库事务(transactionManager)配置。

注意:每个 SqlSessionFactory 实例只能选择一种环境。每个数据库对应一个 SqlSessionFactory 实例

1、环境配置

environments 元素定义了如何配置环境:

<environments default="development">
  <environment id="development">
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
    <transactionManager type="JDBC">
      <property name="autoCommit" value="false"/>
    </transactionManager>
  </environment>
</environments>

(1)environments元素的default属性表示缺省时,启用的数据源配置。

(2)environment元素是配置数据源的开始,id是这个数据源的标志,方便Mybatis上下文使用。

(3)transactionManager配置数据库事务,其type属性有3种配置方式:

​ (a)JDBC,采用JDBC方式管理事务,直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。 独立编码常用。

​ (b)MANAGED,采用容器方式管理事务,它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期 。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。 在JNDI数据源源中常用。

​ (c)自定义,自定义数据库事务管理办法,适用于特殊应用。

transactionManager元素下的properties 元素可以配置数据源的各类属性,这里配置autoCommit的值false,表示要求数据源不自动提交。

2、数据源

标签元素是配置数据源连接信息,type 属性表示数据库连接方式配置,常见的配置方式有:

​ (a)UNPOOLED :非连接池数据库(UnpooledDataSource)。Mybatis使用org.apache.ibatis.datasource.unpooled.UnpooledDataSource类实现。

​ (b)POOLED :连接池数据库(PooledDataSource)。Mybatis使用org.apache.ibatis.datasource.pooled.PooledDataSource类实现。

​ (c)JNDI :JNDI数据源(JNDIDateSource)。Mybatis使用org.apache.ibatis.datasource.jndi.JndiDataSourceFactory获取数据源。

​ (d)自定义数据源。使用自定义数据源,必须实现``接口,如

3、数据库事务

Mybatis的数据库事务是由SqlSession控制,通过SqlSession提交(commit)或者回滚(rollback)。大部分情况会使用spring的中的事务管理器覆盖Mybatis的事务管理器transactionManager。

如果要使用,直接用TransactionFactory 接口实现类的全限定名或类型别名代替,同时实现还需要创建一个 Transaction 接口的实现类。使用这两个接口,可以完全自定义 MyBatis 对事务的处理。

3、数据源

UNPOOLED,POOLED,JNDI除了常见通用的基本属性,还有属于自己的额外属性,实际使用时,具体参考Mybatis官网说明。

主要说明一下 自定义数据源(或第三方数据源)。使用自定义数据源要求必须实现接口 org.apache.ibatis.datasource.DataSourceFactory

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}

如:自定义DBCP数据源:

package com.ssm.web.example;

import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.tomcat.dbcp.dbcp.BasicDataSource;
import org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory;

public class DBCPDataSource extends BasicDataSource implements DataSourceFactory {

    private Properties props = null;

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public void setProperties(Properties props) {
        this.props = props ;
    }

    @Override
    public DataSource getDataSource() {
        DataSource dataSource = null;
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }
}

配置自定义数据源:

<dataSource type="com.ssm.web.example.DBCPDataSource">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>

如果不实现接口也可以,使用org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器。

package com.ssm.web.example;

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DataSource extends UnpooledDataSourceFactory {

    public C3P0DataSource() {
         this.dataSource = new ComboPooledDataSource();
    }
}

九、数据库厂商标识

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。

如要使用在mybatis-config.xml中配置即可。

<databaseIdProvider type="DB_VENDOR" />

1、系统默认规则

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

type=”DB_VENDOR”是启动Mybatis内部注册的策略器。Mybatis将配置信息写入Configuration类中,在数据库连接后调用DatabaseMetaData的getDatabaseProductName() 方法获取数据库信息,然后跟配置的name值是做匹配,得到DatabaseId。

用代码也可以得到数据库ID:

String dbId = sqlSession.getConfiguration().getDatabaseId();

也可以指定SQL在某个数据库厂商执行,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.ssm.web.demo.dao.emp.EmployeeMapper">
  <resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
     // some code 省略
  </resultMap>
    // some code 省略

    <insert id="insert" parameterType="com.ssm.web.demo.entity.emp.Employee" databaseId="mysql">
    insert into tbl_emp (emp_id, emp_name, sex, 
      email,dept_id)
    values (#{empId,jdbcType=INTEGER}, #{empName,jdbcType=VARCHAR}, #{sex,typeHandler="com.ssm.web.typehandle.EnumTypeHandler"}, 
      #{email,jdbcType=VARCHAR},#{did,jdbcType=INTEGER},)
  </insert>
</mapper> 

在insert标签中多了databaseId属性时,Mybatis提供如下规则:

(1)若没有配置databaseIdProvider标签,databaseId会返回null。

(2)若配置了databaseIdProvider标签,Mybatis会用配置的name值去匹配数据库信息,匹配上就会设置databaseId,否则值为null。

(3)若Configuration的databaseId不为空,则它只能找到配置databaseId的SQL语句。

(4)MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者(不带 databaseId 的语句)会被舍弃。

2、不使用默认规则

Mybatis允许自定义规则,只要

(A)实现org.apache.ibatis.mapping.DatabaseIdProvider接口,

(B)在mybatis-config.xml中配置DatabaseIdProvider

如自定义MyDbIdProvider:

package com.ssm.web.example;

import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.mapping.DatabaseIdProvider;

public class MyDbIdProvider implements DatabaseIdProvider {

    private Properties props = null;
    @Override
    public void setProperties(Properties p) {
        this.props = p ;
    }

    @Override
    public String getDatabaseId(DataSource dataSource) throws SQLException {
        String dbName = dataSource.getConnection().getMetaData().getDatabaseProductName();
        String dbId = this.props.getProperty(dbName);
        return dbId;
    }
}

然后配置自定义的MyDbIdProvider :

<databaseIdProvider type="com.ssm.web.example.MyDbIdProvider">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

十、映射器引入(Mappers)

映射器是Mybatis的核心组件。

引入映射器首先需要定义映射器接口:

package com.ssm.web.demo.dao.emp;

import java.util.List;
import com.ssm.web.demo.entity.emp.Employee;
import com.ssm.web.demo.entity.emp.EmployeeExample;

public interface EmployeeMapper {

    int insert(Employee record);
    List<Employee> selectByExample(EmployeeExample example);
    Employee selectByPrimaryKey(Integer empId);
    int updateByPrimaryKey(Employee record);
    //some other code ...
}

对应的EmployeeMapper.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.ssm.web.demo.dao.emp.EmployeeMapper">
  <resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
    <id column="emp_id" jdbcType="INTEGER" property="empId" />
    <result column="emp_name" jdbcType="VARCHAR" property="empName" />
    <result column="sex" property="sex" typeHandler="com.ssm.web.typehandle.EnumTypeHandler" />
    <result column="email" property="email" typeHandler="com.ssm.web.demo.ExampleTypeHandler"/>
    <result column="dept_id" jdbcType="VARCHAR" property="did" />
  </resultMap>
    // some code 省略

    <insert id="insert" parameterType="com.ssm.web.demo.entity.emp.Employee">
    insert into tbl_emp (emp_id, emp_name, sex, 
      email,dept_id)
    values (#{empId,jdbcType=INTEGER}, #{empName,jdbcType=VARCHAR}, #{sex,typeHandler="com.ssm.web.typehandle.EnumTypeHandler"}, 
      #{email,jdbcType=VARCHAR},#{did,jdbcType=INTEGER},)
  </insert>

引入映射器的方式有几种:

1、文件相对路径引入映射器:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="com/ssm/web/demo/dao/emp/EmployeeMapper.xml"/>
</mappers>

2、使用包名引入映射器:

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="com.ssm.web.demo.dao.emp"/>
</mappers>

3、使用类注册引入映射器:

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="com.ssm.web.demo.dao.emp.AuthorMapper"/>
</mappers>

4、文件绝对路径引入映射器:

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/com/ssm/web/demo/dao/emp/EmployeeMapper.xml"/>
</mappers>

相关文章:

文章名称
《Mybatis(一)主要组件》
《Mybatis(二)配置》
《Mybatis(三)动态SQL》
《Mybtis(四)工作原理》
《Mybtis(五)Mapper映射器》
《Mybtis(六)Mapper级联》


文章作者: Small-Rose /张小菜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明来源 Small-Rose /张小菜 !
评论
  目录