MyBatis全局配置详解

全局配置详解

配置文件结构

MyBatis全局配置文件包含了会深深影响MyBatis行为的设置和属性信息,配置文件使用xml文档进行配置,其文档结构如下:

  • configuration(根元素)
    • properties(属性变量)
      • property
    • settings(设置)
      • setting
    • typeAliases(类型别名)
      • typeAlias
      • package
    • plugins(插件)
      • plugin
        • property
    • objectFactory(对象工厂)
      • property
    • objectWrapperFactory(对象包装器工厂)
    • reflectorFactory(反射器工厂)
    • environments(环境变量)
      • environment
        • transactionManager
          • property
        • dataSource
          • property
    • databaseIdProvider(数据库厂商)
      • property
    • typeHandlers(类型处理器)
      • typeHandler
    • mappers(SQL映射)
      • mapper
      • package

MyBatis在解析这些元素的过程中,大至上会按照顺序解析完后封装保存到configuration对象中。

properties元素

在properties中可以定义应用程序或者MyBatis需要使用到的变量信息,比如数据库连接信息等。properties元素是MyBatis最先解析的一个元素。可以通过类路径resource或网络路径url属性指定外部属性文件加载属性信息,也可以通过properties的子元素property进行定义,可以只是用一种方式定义属性,也可以同时使用两种方式(resource和property或url和property,resource和url不能同时使用)进行定义。定义属性时,如果属性重复定义,前面定义的属性值将被覆盖,属性的值将以后面定义的为准。一个简单的示例如下


  
  
  
  
  

其中,使用resource属性指定加载当前资源目录下的db.properties文件,比如db.properties的内容如下

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.101.28:3306/mybatis?characterEncoding=utf8
jdbc.username=mybatis
jdbc.password=123456
user.name=sqcheng
user.password=20200111

上面的例子中使用了resource加载外部属性文件的同时,也使用了propety子元素定义了一些属性,部分property元素的value中还使用了占位符${}引用其它的属性。以上例子中,会发现jdbc.username和jdbc.password属性被重复定义了(在db.properties和property元素),这种情况下,相同属性名的属性将会出现覆盖。MyBatis按照下面的顺序来读取属性信息

  • 首先读取properties元素体内的property属性
  • 然后读取properties元素中的resource或url属性指定的属性文件,并覆盖已读取的同名属性
  • 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性

从这个顺序可以了解,以上例子中使用property定义的jdbc.username和jdbc.password将会被db.properties属性文件中定义的jdbc.username和jdbc.password覆盖。另外,username和password分别引用了user.name和user.password。MyBatis解析完成后的最终结果如下

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.101.28:3306/mybatis?characterEncoding=utf8
jdbc.username=mybatis
jdbc.password=123456
user.name=sqcheng
user.password=20200111
username=sqcheng
password=20200111
otherPropertyName=otherPropertyValue

settings元素

这是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为。MyBatis提供了非常多的配置项,总共有28项之多,下表比较常接触到的一些设置项、默认值及其描述

设置项 描述 取值范围 默认值
cacheEnabled 缓存开关(全局),开启或关闭配置文件中的所有映射器已经配置的缓存。 true | false true
lazyLoadingEnabled 延迟加载开关(全局)。开启时,所有关联对象都会延迟加载。 true | false false
lazyLoadTriggerMethods 指定对象的哪个方法触发一次延迟加载。 用逗号分隔的方法列表 equals,clone,hashCode,toString
defaultExecutorType 缺省执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 SIMPLE | REUSE | BATCH SIMPLE
defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 任意正整数 null(未设置)
defaultFetchSize 查询结果最大记录数 任意正整数 null(未设置)
localCacheScope 本地缓存(一级缓存)作用域或生命周期。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 SESSION | STATEMENT SESSION
defaultScriptingLanguage 指定动态 SQL 生成的默认语言。 一个类型别名或完全限定类名。 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找,未查找到将关闭日志。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工厂。 CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3+)

用到比较多的如数据库查询或执行超时等待时长defaultStatementTimeout、一级缓存声生命周期localCacheScope、日志logImpl等,很多设置MyBatis已经提供的默认的配置,甚至可以零配置。更多的设置可以参考Mybatis官方网站
。以下是官网提供的一个完整的settings配置示例


  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

typeAliases元素

类型别名是为Java类型设置一个短的名字,配置之后,在各个XML文件中原来需要使用完全限定名的地方,可以改为使用较短的别名,用来减少类完全限定名的冗余。Mybatis默认提供或内置了很多类型别名,包括JAVA中8种基本类型、String类型、集合类型(Conllection)、数组,以及事务管理器工厂、数据源工厂等类型别名。

类型别名可以通过package或typeAlias元素进行定义,下面是一个简单示例


  
  
  
  

以上例子中,typeAlias元素的type属性指定Java类型的完全限定名,alias属性定义别名。package元素的name属性指定了一个包路径,MyBatis将扫描该包路径下的JavaBean,在没有注解的情况下,会使用 Bean 的简单类名小写来作为它的别名。假设该包下有一个BookMark类即com.fandou.sqlmapper.mapper.book.BookMark,在没有注解的情况下,其类型别名为bookmark;如果在BookMark类上加了别名注解如

@Alias("Book")
public class BookMark implements Serializable {
    ...
}

将使用注解的名字Book小写后的book作为别名,即使这里使用了首字母大写进行了注解。类型别名解析后,会被注册到configuration对象持有的类型别名注册表中,注册时,如果该别名已经被其它类型注册,解析过程将会抛出异常,即一个别名不允许被多个类型注册,但一个类型可以注册多个别名。
如果typeAlias元素的没有配置alias属性和Javabean没有添加@Alias注解的情况时一样的,都会使用简单类名小写被注册到类型别名注册表中。

通过测试或查看MyBatis的源码,实际上,所有类型别名都会被转为小写后再注册到类型别名注册表中,即类型别名不区分大小写,大写小写都一样,都会被认为是同一个类型,所以,最佳的做法是,在配置或引用类型别名的时候,统一使用小写。

Mybatis默认提供或内置了的Java类型别名见下表,一共有27种,其中基本类型和其包装类被注册为不同类型

别名 映射的类型
_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

plugins元素

MyBatis 允许开发人员在已映射语句执行过程中的某一点进行拦截调用,比如在调用方法的前后打印日志或统计执行时间等。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

拦截接口 可拦截的方法列表
Executor update | query | flushStatements | commit | rollback | getTransaction | close | isClosed
ParameterHandler getParameterObject | setParameters
ResultSetHandler handleResultSets | handleOutputParameters
StatementHandler prepare | parameterize | batch | update | query

通过MyBatis提供的强大机制,使用插件是非常简单的,只需实现Interceptor接口,并指定想要拦截的方法签名即可。接着在plugins元素声明实现的插件即可。比如想要监控某条查询语句的消耗的时间,可以监控Executor接口的query方法

 List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

首先,创建一个插件TimeCounterPlugin,使用@Intercepts注解声明一个拦截器,然后使用@Signature注解拦截的接口和方法签名信息,包括拦截的接口类型type、方法method、方法的参数args等,TimeCounterPlugin实现类大致如下

package com.fandou.sqlmapper;

import java.lang.reflect.Method;
import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class }) })
public class TimeCounterPlugin implements Interceptor {

    // 可以额外配置一些属性
    private Properties properties = new Properties();

    /**
     * 拦截Executor接口的query方法
     */
    public Object intercept(Invocation invocation) throws Throwable {

        System.out.println(" 通过插件元素配置的属性 => ");
        Set propertyNames = properties.stringPropertyNames();
        for (String key : propertyNames) {
            System.out.println("   " + key + " => " + properties.getProperty(key));
        }

        //配置一些属性控制插件开关和打印结果
        boolean startup = Boolean.valueOf(properties.getProperty("startup"));
        boolean printResult = Boolean.valueOf(properties.getProperty("printResult"));

        long start = 0L;
        long end = 0L;

        //如果开启了插件
        if (startup) {
            Object executor = invocation.getTarget();
            Method method = invocation.getMethod();
            Object[] args = invocation.getArgs();

            System.out.println(" 拦截的接口类型 => " + executor.getClass().getTypeName());
            System.out.println(" 拦截的方法名 => " + method.getName());
            System.out.println(" 拦截的方法参数信息 => ");
            for (Object object : args) {
                //参数可能为null
                if(null == object) {
                    System.out.println("   => null");
                }
                else {
                    System.out.println("   => " + object.getClass().getTypeName());
                }
            }

            // query方法执行前
            start = System.currentTimeMillis();
        }

        // 执行方法
        Object returnObject = invocation.proceed();

        //如果开启了插件
        if (startup) {
            // query方法执行后
            end = System.currentTimeMillis();

            System.out.println(" 执行查询消耗的时间 => " + ((end - start)) + " 毫秒...");

            //如果配置了打印结果属性
            if(printResult) {
                System.out.println(" returnObject => " + returnObject);
            }
        }

        // 返回执行结果
        return returnObject;
    }

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

接着在全局配置文件plugins元素中声明插件plugin



  
    
    
  

plugin元素的interceptor属性值为插件的完全限定名,可以按照需求自定添加一些属性,如例子定义了开关以及是否打印查询结果的控制。编写测试,正常将会输出如下内容

通过插件元素配置的属性 =>
  printResult => true
  startup => true
拦截的接口类型 => org.apache.ibatis.executor.CachingExecutor
拦截的方法名 => query
拦截的方法参数信息 =>
  => org.apache.ibatis.mapping.MappedStatement
  => com.fandou.sqlmapper.mapper.User
  => org.apache.ibatis.session.RowBounds
  => null
执行查询消耗的时间 => 32 毫秒...
returnObject => [User [id=3, username=sqcheng, password=$2a$10$.SyCzQodO1Qy0kQh31UfOOeF4Z.YEAHEq/dJtoOWpneq.wDLimET2, address=广东连州, identityCard=null, books=null], User [id=4, username=chengshangqian, password=$2a$10$VCk3NUj3cZZPtxOk2tODcesbtY33GPRijCQMEyYSzSnLiYOdX8jGa, address=连州市西江镇, identityCard=null, books=null]]

objectFactory、objectWrapperFactory、reflectorFactory三个工厂元素

MyBatis每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为,开发人员可以通过创建自己的对象工厂,然后在objectFactory元素中配置指定使用新建的工厂对象类型来实现。比如:

public class MyObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }

  public Object create(Class type, List constructorArgTypes, List constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }

  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }

  public  boolean isCollection(Class type) {
    return Collection.class.isAssignableFrom(type);
  }
}

然后声明


  

objectFactory元素的type属性指定要使用的新的对象工厂类型。另外objectWrapperFactory、reflectorFactory元素类似。

environments元素

MyBatis支持配置多个环境变量,比如开发环境、测试环境、产品环境,以满足不同场合的SQL映射需求。虽然MyBatis支持配置多个环境变量,但每个SqlSessionFactory实例只能选择一种环境。如果应用程序想连接两个或以上数据库,就需要创建两个或以上的SqlSessionFactory实例,每个数据库对应一个SqlSessionFactory。



  
  
    
    
    
    
      
      
      
      
    
  

上面的示例中,首先,使用environment元素配置了一个dev环境,使用default属性指定使用的缺省环境变量。接着,transactionManager定义了MyBatis使用的事务管理器,其类型type为JDBC,这种类型的事务管理器依赖于从数据源得到的连接来管理事务作用域;MyBatis还提供了被容器管理的事务管理器的类型MANAGED,它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文),默认情况下它会关闭连接。这两种类型对应的是JdbcTransactionFactory和ManagedTransactionFactory事务工厂类。最后,使用了dataSource定义了一个类型type为POOLED的数据源,内部包括若干数据库连接属性(数据库驱动driver、数据库连接url、数据库用户username、数据库密码password)的定义。对于数据源dataSource的类型type,MyBatis提供了三种JNDI(Java命名和目录接口)、POOLED(连接池)、UNPOOLED(非连接池,相当于直接使用JDBC)。下面对这三种使用方式做各简单的表格对比

数据源类型 说明 属性列表 属性说明
UNPOOLED 非连接池类型数据源。数据源每次被请求时打开和关闭连接。数据库连接较慢,适用对可用性或性能方面没有太高要求的简单应用程序种。 driver JDBC 驱动的 Java 类的完全限定名
url 数据库连接地址
username 登录数据库的用户名
password 登录数据库的密码
defaultTransactionIsolationLevel 默认的连接事务隔离级别
defaultNetworkTimeout 默认的连接超时时间(毫秒)
driver. 传递属性给数据库驱动,只需在属性名加上“driver.”前缀即可,比如driver.encoding=UTF8,它将通过 DriverManager.getConnection(url,driverProperties)方法传递值为UTF8的encoding属性给数据库驱动
POOLED 连接池类型数据源。通过“池”,避免了创建新的连接时所必需的初始化和认证时间。 UNPOOLED的所有属性 见UNPOOLED类型
poolMaximumActiveConnections 任意时间最大活动连接数,默认值:10
poolMaximumIdleConnections 任意时间最大空闲连接数
poolMaximumCheckoutTime 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
poolTimeToWait 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)
poolMaximumLocalBadConnectionTolerance 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值:3
poolPingEnabled 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false
poolPingQuery 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息
poolPingConnectionsNotUsedFor 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
JNDI Java命名和目录接口类型,主要在EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。这种数据源配置只需要两个属性initial_context和data_source。 initial_context 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
data_source 这是引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
env. 和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给初始上下文。比如:env.encoding=UTF8,这就会在初始上下文(InitialContext)实例化时往它的构造方法传递值为UTF8的encoding属性

databaseIdProvider元素

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:


DB_VENDOR 对应的 databaseIdProvider 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。 由于通常情况下这些字符串都非常长而且相同产品的不同版本会返回不同的值,所以你可能想通过设置属性别名来使其变短,如下:


  
  
  

在提供了属性别名时,DB_VENDOR 的 databaseIdProvider 实现会将 databaseId 设置为第一个数据库产品名与属性中的名称相匹配的值,如果没有匹配的属性将会设置为 “null”。 在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。
开发可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider:

public interface DatabaseIdProvider {
  default void setProperties(Properties p) { // Since 3.5.2, change to default method
    // NOP
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}

typeHandlers元素

MyBatis在预处理语句(PreparedStatement)中设置一个参数时,或从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成Java类型。下表描述了一些默认的类型处理器,一共有37种。

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERIC 或 SMALLINT
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERIC 或 BIGINT
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER 或未指定类型
EnumTypeHandler Enumeration Type VARCHAR 或任何兼容的字符串类型,用以存储枚举的名称(而不是索引值)
EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的序数值(而不是名称)。
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHAR 或 LONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

开发人员可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。比如:

@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}

然后配置类型处理器元素


  

使用上述的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR 参数及结果的类型处理器。 要注意 MyBatis 不会通过窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是 VARCHAR 类型的字段, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

mappers元素

mappers元素定义了MyBatis解析器如何查找定义sql语句的xml文件。使用mapper元素的resource|url|class属性(三选一)指定sql映射文件的资源位置或定义的Mapper接口,比如以下三个例子


  


  


  

另外,还可以使用package指定Mapper接口所在的包,MyBatis会完成扫描并解析SQL语句信息。


  
  
  

上一步MyBatis快速入门

下一步MyBatis之SQL映射文件详解(待完成)

你可能感兴趣的:(MyBatis全局配置详解)