Mybatis - 核心配置解读

序言

大部分时候我们只需要很少的配置就可以让 MyBatis 运行起来。其实 MyBatis 里面提供的配置项非常多,我们没有配置的时候使用的是系统的默认值而已。这一节来具体介绍一下Mybatis的核心配置,深入一下。
中文官方文档地址
Mybatis3中文官方文档地址

configuration

configuration 是整个配置文件的根标签,实际上也对应着 MyBatis 里面最重要的配置类 Configuration。它贯穿 MyBatis 执行流程的每一个环节。我们打开这个类看一下,这里面有很多的属性,跟其他的子标签也能对应上。
注意:MyBatis 全局配置文件顺序是固定的,否则启动的时候会报错。

属性(properties)

用来配置参数信息,比如最常见的数据库连接信息。
为了避免直接把参数写死在 xml 配置文件中,我们可以把这些参数单独放在properties 文件中,用 properties 标签引入进来,然后在 xml 配置文件中用${}引用就可以了。
可以用 resource 引用应用里面的相对路径,也可以用 url 指定本地服务器或者网络的绝对路径

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

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);

// ... 或者 ...

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
设置(settings)

setttings 里面是 MyBatis 的一些核心配置,我们最后再看,先看下其他的以及标签。

typeAliases

TypeAlias 是类型的别名,跟 Linux 系统里面的 alias 一样,主要用来简化全路径类名的拼写。比如我们的参数类型和返回值类型都可能会用到我们的 Bean,如果每个地方都配置全路径的话,那么内容就比较多,还可能会写错。我们可以为自己的 Bean 创建别名,既可以指定单个类,也可以指定一个 package,自动转换。配置了别名以后,只需要写别名就可以了。

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

    registerAlias("byte", Byte.class);
    registerAlias("char", Character.class);
    registerAlias("character", Character.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);

    registerAlias("byte[]", Byte[].class);
    registerAlias("char[]", Character[].class);
    registerAlias("character[]", Character[].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);

    registerAlias("_byte", byte.class);
    registerAlias("_char", char.class);
    registerAlias("_character", char.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_char[]", char[].class);
    registerAlias("_character[]", char[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

注册别名源码


  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    typeAliases.put(key, value);
  }
typeHandlers

由于 Java 类型和数据库的 JDBC 类型不是一一对应的(比如 String 与 varchar),所以我们把 Java 对象转换为数据库的值,和把数据库的值转换成 Java 对象,需要经过一定的转换,这两个方向的转换就要用到 TypeHandler。

我没有做任何的配置,为什么实体类对象里面的一个 String属性,可以保存成数据库里面的 varchar 字段,或者保存成 char 字段
这是因为 MyBatis 已经内置了很多 TypeHandler(在 type 包下),它们全部全部注册在 TypeHandlerRegistry 中,他们都继承了抽象类 BaseTypeHandler,泛型就是要处理的 Java 数据类型。

基类接口

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Column name, when configuration useColumnLabel is false
   * @return the result
   * @throws SQLException
   *           the SQL exception
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the nullable result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Column name, when configuration useColumnLabel is false
   * @return the nullable result
   * @throws SQLException
   *           the SQL exception
   */
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

当我们做数据类型转换的时候,就会自动调用对应的 TypeHandler 的方法。

如果我们需要自定义一些类型转换规则,或者要在处理类型的时候做一些特殊的动作,就可以编写自己的 TypeHandler,跟系统自定义的 TypeHandler 一样,继承抽象类BaseTypeHandler。有 4 个抽象方法必须实现,就是上面的4个,可以看出来,是set 和 get两类,根据参数的不同,分别是 根据列名 根据列下标,根据存储过程。还有一个是设置参数。

objectFactory

当我们把数据库返回的结果集转换为实体类的时候,需要创建对象的实例,由于我们不知道需要处理的类型是什么,有哪些属性,所以不能用 new 的方式去创建。在MyBatis 里面,它提供了一个工厂类的接口,叫做 ObjectFactory,专门用来创建对象的实例,里面定义了 4 个方法。

public interface ObjectFactory {

  /**
   * Sets configuration properties.
   * @param properties configuration properties
   */
  default void setProperties(Properties properties) {
    // NOP
  }

  /**
   * Creates a new object with default constructor.
   *
   * @param 
   *          the generic type
   * @param type
   *          Object type
   * @return the t
   */
  <T> T create(Class<T> type);

  /**
   * Creates a new object with the specified constructor and params.
   *
   * @param 
   *          the generic type
   * @param type
   *          Object type
   * @param constructorArgTypes
   *          Constructor argument types
   * @param constructorArgs
   *          Constructor argument values
   * @return the t
   */
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

  /**
   * Returns true if this object can have a set of other objects.
   * It's main purpose is to support non-java.util.Collection objects like Scala collections.
   *
   * @param 
   *          the generic type
   * @param type
   *          Object type
   * @return whether it is a collection or not
   * @since 3.1.0
   */
  <T> boolean isCollection(Class<T> type);

}

ObjectFactory 有一个默认的实现类 DefaultObjectFactory,创建对象的方法最终都调用了 instantiateClass(),是通过反射来实现的。如果想要修改对象工厂在初始化实体类的时候的行为,就可以通过创建自己的对象工厂,继承 DefaultObjectFactory 来实现(不需要再实现 ObjectFactory 接口)。

  private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        try {
          return constructor.newInstance();
        } catch (IllegalAccessException e) {
          if (Reflector.canControlMemberAccessible()) {
            constructor.setAccessible(true);
            return constructor.newInstance();
          } else {
            throw e;
          }
        }
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
      try {
        return constructor.newInstance(constructorArgs.toArray(new Object[0]));
      } catch (IllegalAccessException e) {
        if (Reflector.canControlMemberAccessible()) {
          constructor.setAccessible(true);
          return constructor.newInstance(constructorArgs.toArray(new Object[0]));
        } else {
          throw e;
        }
      }
    } catch (Exception e) {
      String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
          .stream().map(Class::getSimpleName).collect(Collectors.joining(","));
      String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
          .stream().map(String::valueOf).collect(Collectors.joining(","));
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
  }
plugins

插件是 MyBatis 的一个很强大的机制,跟很多其他的框架一样,MyBatis 预留了插件的接口,让 MyBatis 更容易扩展。
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 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 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();

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }

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

如果配置的是 JDBC,则会使用 Connection 对象的 commit()、rollback()、close()
管理事务。

如果配置成 MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。因为我们跑的是本地程序,如果配置成 MANAGE 不会有任何事务。

如果是Spring + MyBatis, 则没有必要配置 , 因 为 我们会直接在applicationContext.xml 里面配置数据源,覆盖MyBatis的配置。

结束

大致就这么多核心配置,更多信息可用去官网查看。

你可能感兴趣的:(Mybatis深入解析源码,mybatis,java,mysql)