MyBatis学习笔记-基础使用篇

第一章 MyBatis快速入门

第一节 MyBatis简介

MyBatis 是 Apache 的一个开源项目 iBatis,是一款在持久层使用的SQL映射框架,可以将SQL语句单独写在XML配置文件中,或者使用带有注解的Mapper映射类来完成数据库记录到Java实体的映射。

  • 2002年,Clinton Begin开发了iBatis框架,并引入了SQL映射作为持久化层开发的一种方法,不久后Clinton Begin将iBatis 捐献给Apache软件基金会。
  • 2010年,这个项目由Apache 迁移到了Google Code,并改名为MyBatis。
  • 2013年11月,MyBatis 迁移到目前最大的源代码托管平台Github。

MyBatis与另一款主流的 ORM(Object Relational Mapping) 框架 Hibernate 不同,MyBatis 属于半自动的 ORM 框架,它虽然不能将不同数据库的影响隔离开,仍然需要自己编写SQL语句,但是可以灵活地控制SQL语句的构造,将SQL语句的编写和程序的运行分离开,使用更加便捷。

目前,Java实现的持久化框架比较多,名气相对较大的有Hibernate、Speedment、Spring Data JPA、ActivelJPA等。总结一下,MyBatis 能够流行起来的主要原因有以下几点:

  • 消除了大量的JDBC冗余代码,包括参数设置、结果集封装等。

  • SQL语句可控制,方便查询优化,使用更加灵活。

  • 学习成本比较低,对于新用户能够快速学习使用。

  • 提供了与主流IoC框架Spring的集成支持。

  • 引入缓存机制,提供了与第三方缓存类库的集成支持。

MyBatis这些优秀的特性使它成为目前最受欢迎的ORM框架之一。

第二节 MyBatis入门案例

某数据库有一张 User 表,定义如下,现要使用 MyBatis 对其进行查询操作。

create database test01 charset utf8;

create table if not exists user(
    id int(11) unsigned not null auto_increment comment '用户编号'
    ,username varchar(32) not null comment '用户名称'
    ,birthday varchar(38) not null comment '用户生日'
    ,sex char(2) not null comment '性别,0男1女'
    ,address varchar(255) null comment '用户地址'
    ,primary key(id)
    )engine=innodb default charset=utf8 comment='用户表';

insert into user(username, birthday, sex, address) values('hyx', '1999-09-13', '0', '深圳市南山区');
commit;

1. 前置准备

  • 假设你已经建立好一个普通的 maven 工程。

image-20210406225222922

  • 生成了 User 表对应的实体类。
package org.example.model;

import java.io.Serializable;
import java.sql.Date;

public class User implements Serializable {

  private Integer id;
  private String username;
  private Date birthday;
  private String sex;
  private String address;

	// 省略getter/setter和toString方法
    
}

  • 并且已经写好了 DAO 层接口
package org.example.dao;

import org.example.model.User;

import java.util.List;

public interface UserDao {
  List<User> findAll();
}

如果使用传统的 JDBC 或 JdbcTemplate 模板,那么你的下一步应该是实现 DAO 接口,编写具体的 CURD 方法。但是,使用 MyBatis 后,你无需自己手写这些繁琐的实现类,这些 MyBatis 都会使用动态代理技术帮你生成。Let’s go and have a look。

2. 依赖导入

MyBatis 有一些必须的依赖,你可以把它加入到 CLASSPATH 路径,或者在 pom 文件中定义它的坐标。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>demo01</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- MyBatis 依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        
        <!-- JDBC 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Junit 用于测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

如果你使用的是Oracle或SqlServer数据库,应该切换如下驱动:

<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>sqljdbc4</artifactId>
    <version>4.0</version>
</dependency>

<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc14</artifactId>
    <version>10.2.0.5.0</version>
</dependency>

3. 主配置文件

现在我们需要一个配置文件来描述 MyBatis 的相关属性和行为。如果你不太明白,没关系,我们在下一章将会详细讲解!

src\main\resources\mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置MyBatis的环境 -->
    <environments default="mysql">
        <!-- 配置mysql 的环境 -->
        <environment id="mysql">
            <!-- 配置事务的类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源(连接池) -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://42.192.223.129:3306/test01"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 告知MyBatis映射配置的位置 -->
    <mappers>
        <mapper resource="org/example/dao/UserDao.xml"/>
    </mappers>
</configuration>

如果你使用的是Oracle或SqlServer数据库,JDBC连接四要素应修改如下:

    <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:ORCLONE"/>
    <property name="username" value="kbssfms"/>
    <property name="password" value="kbssfms"/>

    <property name ="driverClassName" value ="com.microsoft.sqlserver.jdbc.SQLServerDriver"></property>
    <property name ="url" value ="jdbc:sqlserver://127.0.0.1:1433;DatabaseName=kbssfms"></property>
    <property name ="username" value ="sa"></property>
    <property name ="password" value ="Sa147741"></property>

4. 映射配置文件

除了主配置文件之外,还需要一个映射配置文件,告诉 MyBatis 执行什么SQL,以及参数和结果集如何处理。

src\main\resources\com\mybatis\demo\dao\UserDao.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="org.example.dao.UserDao">

    <!-- 查询所有用户 -->
    <select id="findAll" resultType="org.example.model.User">
    	SELECT * FROM user
    </select>
</mapper>

5. 简单测试

现在我们可以来进行测试了!

package org.example.dao.test;

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.example.dao.UserDao;
import org.example.model.User;

import java.io.InputStream;
import java.util.List;

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        //1.读取主配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

        //2.创建 SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

        //3. 构建 SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);

        //4. 打开会话
        SqlSession session = factory.openSession();

        //5. 获取代理对象
        UserDao userDao = session.getMapper(UserDao.class);

        //6. 使用代理对象执行查询所有方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }

        //7. 释放资源
        if (session != null) {
            session.close();
        }
        if (in != null) {
            in.close();
        }

    }
}

运行结果如下:

image-20210415231840857

上述案例的完整工程代码可参考附件 mybatis-demo/demo01 工程!

第三节 入门案例扩展

1. SqlSessionFactoryBuilder

要想创建一个 Session,需要借助 SqlSeesionFactory,而 SqlSeesionFactory 必须由 SqlSessionFactoryBuilder 来进行构建, SqlSessionFactoryBuilder 有五个 build() 方法,每一种都允许你从不同的资源中创建一个 SqlSessionFactory 实例。

SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
    
SqlSessionFactory build(Configuration config)

可以看到,前四种方式都是从一个 InputStream 流中获取配置文件(mybatis-config.xml),然后构建 SqlSessionFactory,并且提供了不同组合的默认参数供选择,我们入门案例正是使用了这种方式。

另外,也通过接收一个 Configuration 实例来构建 SqlSessionFactory 。Configuration 类包含了对一个 SqlSessionFactory 实例你可能关心的所有内容。以下是一个简单的示例,演示如何手动配置 Configuration 实例,然后将它传递给 build() 方法来创建 SqlSessionFactory。

DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);

Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);

2. SqlSessionFactory

有了 SqlSessionFactory 之后,我们就可以用它来创建 SqlSession 了。它有 6 个重载的方法供我们使用,具体使用哪一个,这取决于你对事务、连接和执行器类型的选择。

SqlSession openSession()  
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level) 
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

无参数的opensseion()方法具有下列默认行为:

  • 事务作用域将会开启(也就是不自动提交)。
  • 事务隔离级别将会使用驱动或数据源的默认设置。
  • 将由当前环境配置的 DataSource 实例中获取 Connection 对象。
  • 预处理语句不会被复用,也不会批量处理更新(采用 SIMPLE 类型执行器)。

除此之外,SqlSessionFacotry还有一个方法:getConfiguration()。这个方法会返回一个 Configuration 实例,你可以在运行时使用它来检查 MyBatis 的配置。

事物的隔离级别

对于事务隔离级别,MyBatis 使用了一个 Java 枚举包装器来表示,称为 TransactionIsolationLevel,事务隔离级别支持 JDBC 的五个隔离级别(NONEREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE),并且与预期的行为一致

执行器类型

你可能对 ExecutorType 参数感到陌生。这个枚举类型定义了三个值:

  • ExecutorType.SIMPLE:该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。
  • ExecutorType.REUSE:该类型的执行器会复用预处理语句。
  • ExecutorType.BATCH:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句。

3. SqlSession

SqlSession 在 MyBatis 中是非常强大的一个类。它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。下面对其进行分类展示和介绍。

// 1. 语句执行方法
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter) // 游标形式:可以借助迭代器实现数据的惰性加载
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter) // insert、update 以及 delete 方法返回的值表示受该语句影响的行数
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

// 2. 语句执行方法(不需要参数的重载形式)
<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<T> Cursor<T> selectCursor(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)

// 3. select方法的高级版本:允许你限制返回行数的范围,或是提供自定义结果处理逻辑,通常在数据集非常庞大的情形下使用。
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)

// 4. 批量更新方法:当使用 BATCH 执行器时,可以使用这个方法清除(执行)缓存在 JDBC 驱动类中的批量更新语句。
List<BatchResult> flushStatements()

// 5. 事务控制方法:在使用由 Connection 实例控制的 JDBC 事务管理器时,用于控制事务作用域。注意:这些方法不适用于外部事务管理器。
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)

// 6. 清空本地缓存(会话缓存)
void clearCache()

// 7. 关闭会话
void close()
    
// 8. 获取 Configuration 实例
Configuration getConfiguration()
 
// 9. 获取映射器:映射器相对上述CURD方法,最大的优点是符合类型安全,并且对IDE和单元测试也更加友好。
<T> T getMapper(Class<T> type)
    

关于 RowBounds 和 ResultHandler 在官网有更详细的解释,可以点击这:https://mybatis.org/mybatis-3/zh/java-api.html。

4. 映射器实例

SqlSeesion中的各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐,它们并不符合类型安全,对你的 IDE 和单元测试也不是那么友好。因此,使用映射器类来执行映射语句是更常见的做法。

我们已经在之前的入门案例中见到过一个使用映射器的示例。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配方法的接口。下面的示映射器例展示了一些方法签名以及它们是如何映射到 SqlSession 上的。

public interface AuthorMapper {
  Author selectAuthor(int id); // (Author) selectOne("selectAuthor",5);

  List<Author> selectAuthors(); // (List<Author>) selectList(“selectAuthors”)

  @MapKey("id")
  Map<Integer, Author> selectAuthors(); // (Map<Integer,Author>) selectMap("selectAuthors", "id")

  int insertAuthor(Author author); // insert("insertAuthor", author)

  int updateAuthor(Author author); // updateAuthor("updateAuthor", author)

  int deleteAuthor(int id); // delete("deleteAuthor",5)
}

总之,每个映射器方法签名有与之相关联的 SqlSession 方法。

映射器接口可以继承自其他接口。在使用 XML 来绑定映射器接口时,保证语句处于合适的命名空间中即可。唯一的限制是,不能在两个具有继承关系的接口中拥有相同的方法签名(这是潜在的危险做法,不可取)。

你也可以传递多个参数给一个映射器方法。在多个参数的情况下,默认它们将会以 param 加上它们在参数列表中的位置来命名,比如:#{param1}、#{param2}等。如果你想(在有多个参数时)自定义参数的名称,那么你可以在参数上使用 @Param(“paramName”) 注解。

除外之外,你还可以给方法传递一个 RowBounds 实例来限制查询结果等。

5. 使用注解替换映射器配置

MyBatis 针对映射器配置,定义了一套注解,可以让我们更加方便的进行相关配置。

  • 在接口加上注解,同时删去 UserDao.xml 文件(删除对应方法配置亦可)。
public interface UserDao {
  @Select("SELECT * FROM USER")
  List<User> findAll();
}

  • 修改mybatis-config.xml,让 MyBatis 去扫描注解。
<!-- 告知MyBatis映射配置的位置 -->
<mappers>
    <mapper class="com.mybatis.demo.dao.UserDao"/>
</mappers>

注意
同一个方法,不能同时存在映射配置文件和注解两种配置方式,MyBatis将检测并抛出异常!

注解使用案例可以参考mybatis-demo02。

// TODO mybatis-demo02

6. 添加日志输出

MyBatis在运行时会按照下列顺序检测日志实现,如果没有找到这些实现时,将会禁用日志功能。

  • SLF4J
  • Apache Commons Logging (Tomcat 和 WebShpere 应用服务器的默认日志)
  • Log4j 2
  • Log4j
  • JDK logging

当然,你也可以在mybatis-config.xml配置文件中显示指定日志实现。

<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  </settings>
</configuration>
      

可选的值有:SLF4JLOG4JLOG4J2JDK_LOGGINGCOMMONS_LOGGINGSTDOUT_LOGGINGNO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log 接口,且构造方法以字符串为参数的类完全限定名。

你也可以在调用其它 MyBatis 方法之前调用以下任一方法来切换日志实现:

org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
org.apache.ibatis.logging.LogFactory.useJdkLogging();
org.apache.ibatis.logging.LogFactory.useCommonsLogging();
org.apache.ibatis.logging.LogFactory.useStdOutLogging();

注意,仅当运行时类路径中存在该日志实现时,日志实现的切换才会生效,否则会使用默认顺序来查找日志实现。

你可以通过在映射类的全限定名命名空间全限定语句名上开启日志功能,来查看 MyBatis 的日志语句。具体的配置步骤与日志实现有关,下面以 Log4J 作为示范。

首先,你应该确保应用的类路径下有 Log4J 的jar 包,或导入了相应的 maven 坐标。

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>

然后,添加 log4j.properties配置文件到应用类路径下,简单配置如下。

# 全局日志配置
log4j.rootLogger=ERROR, stdout

# MyBatis 日志配置
log4j.logger.org.mybatis.example.BlogMapper=TRACE

# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

上述配置将使 Log4J 详细打印 org.mybatis.example.BlogMapper 的日志,对于应用的其它部分,只打印错误信息。

你可以按需调整日志输出的粒度,参考如下。

# 只打印语句 selectBlog 的日志
log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE

# 打印一组映射器的日志
log4j.logger.org.mybatis.example=TRACE

# 只想查看SQL语句,而忽略结果集,一般用于返回大量数据的场景(因为SQL语句的日志级别是 DEBUG,而返回的结果集是 TRACE )
log4j.logger.org.mybatis.example=DEBUG

配置文件 log4j.properties 的余下内容用来配置输出器(appender),这一内容已经超出本文档的范围。关于 Log4J 的更多内容,可以参考 Log4J 的官方网站。

第二章 主配置文件详解

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

在开发过程中,可以参考配置文件头部的DTD约束来进行配置!

第一节 properties标签

1. property的定义和使用

property 是一个 key-value 键值对,可以直接在 properties 标签内部用 property 子标签定义,也可以从外部资源文件引入,并且可以进行动态替换。

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

tips:properties标签也可以使用 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>

这个例子中的占位符 ${username}${password} 将会由 property 标签中设置的相应值来替换。 d r i v e r ∗ ∗ 和 ∗ ∗ {driver}** 和 ** driver{url} 将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。

除此之外,也可以在 SqlSessionFactoryBuilder.build() 方法中传入属性值。例如:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props); 
// ... 或者 ...
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);

2. property的优先级

如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  • 首先读取在 properties 标签体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

3. 设置占位符的默认值

从 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">
  <!-- 如果属性 'db:username' 没有被配置,'db:username' 属性的值将为 'ut_user' -->
  <property name="username" value="${db:username?:ut_user}"/>
</dataSource>

第二节 settings标签

settings 标签是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

设置名描述有效值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | false
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
true | false
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。
否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。
true | false
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true | false
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,
具体可参考数据库驱动的相关文档,或通过对比测试来观察。
true | false
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。
如果设置为 true,将强制使用自动生成主键。
尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。
true | false
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。
NONE 表示关闭自动映射;
PARTIAL 只会自动映射没有定义嵌套结果映射的字段。
FULL 会自动映射任何复杂的结果集(无论是否嵌套)。
NONE, PARTIAL, FULL
autoMappingUnknown
ColumnBehavior
指定发现自动映射目标未知列(或未知属性类型)的行为。
NONE: 不做任何反应
WARNING: 输出警告日志
'org.apache.ibatis.session<br />.AutoMappingUnknownColumnBehavior'
的日志等级必须设置为 WARN
FAILING: 映射失败 (抛出 SqlSessionException)
NONE, WARNING, FAILING
defaultExecutorType配置默认的执行器。
SIMPLE 就是普通的执行器;
REUSE 执行器会重用预处理语句(PreparedStatement);
BATCH 执行器不仅重用语句还会执行批量更新。
SIMPLE REUSE BATCH
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。
此参数只可以在查询设置中被覆盖。
任意正整数
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY |
SCROLL_SENSITIVE |
SCROLL_INSENSITIVE |
DEFAULT(等同于未设置)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。
如果允许使用则设置为 false。
true | false
safeResultHandler
Enabled
是否允许在嵌套语句中使用结果处理器(ResultHandler)。
如果允许使用则设置为 false。
true | false
mapUnderscore
ToCamelCase
是否开启驼峰命名自动映射,即下划线列名映射小驼峰属性名。true | false
localCacheScopeMyBatis 利用本地缓存机制防止循环引用和加速重复的嵌套查询。
默认值为 SESSION,会缓存一个会话中执行的所有查询。
若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。
SESSION | STATEMENT
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。
某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
JdbcType 常量,常用值:
NULL、VARCHAR 或 OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。
equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。
org.apache.ibatis.scripting
.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。
org.apache.ibatis.type
.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。
注意基本类型(int、boolean 等)是不能设置成 null 的。
true | false
returnInstance
ForEmptyRow
当返回行的所有列都是空时,MyBatis默认返回 null
当开启这个设置时,MyBatis会返回一个空实例。
请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)
true | false
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 |
JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB | JAVASSIST
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。
useActualParamName允许使用方法签名中的名称作为语句参数名称。
为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)
true | false
configurationFactory指定一个提供 Configuration 实例的类。
这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。
这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)
一个类型别名或完全限定类名。
shrinkWhitespacesInSql从SQL中删除多余的空格字符。
请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5)
true | false
defaultSqlProviderTypeSpecifies an sql provider class that holds provider method (Since 3.5.6).
This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted.
A type alias or fully qualified class name

下面是一个配置完整的 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>

第三节 typeAliases标签

1. 配置JavaBean的别名

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

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

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

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

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

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

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

2. 内置的类型别名

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

第四节 typehandlers标签

1. 默认类型处理器

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

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERICBYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERICSMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERICINTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERICBIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERICFLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERICDECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERICDOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHARLONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE

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

2. 自定义类型处理器

你可以创建你自己的类型处理器或重写已有的类型处理器来处理不支持的或非标准的类型。只需要实现 org.apache.ibatis.type

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

// ExampleTypeHandler.java 将java类型 String 映射到JDBC类型 VARCHAR
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @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);
  }
}

如果想要这个类型处理器生效,则必须在主配置文件的typehandlers标签进行配置,这样拦截器链才会进行调用。

<!-- mybatis-config.xml -->
<typeHandlers>
  <!-- 注册自定义类型处理器 -->
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    
    <!-- 也可以使用自动查找类型处理器(注意:此时JDBC 的类型仅能通过注解方式来指定) -->
    <!-- <package name="org.mybatis.example"/> -->
</typeHandlers>

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

通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

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

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

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

当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null 的组合来选择一个类型处理器。 这意味着使用 @MappedJdbcTypes 注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap 中将不会生效。 如果希望能在 ResultMap 中隐式地使用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。 然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true,那么这个类型处理器也会是 ResultMap 使用 Java 类型时的默认处理器。

3. 泛型类型处理器

你可以创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例的时候传入一个具体的类。

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

  private Class<E> type;

  public GenericTypeHandler(Class<E> type) {
    if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
  }
  ...

EnumTypeHandlerEnumOrdinalTypeHandler 都是泛型类型处理器,我们将会在接下来的部分详细探讨。

若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选择一个来使用。

  • EnumTypeHandler

比如说我们想存储取近似值时用到的舍入模式。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

注意 EnumTypeHandler 在某种意义上来说是比较特别的,其它的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。

  • EnumOrdinalTypeHandler

不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样简单:在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形数值。

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>

但要是你想在一个地方将 Enum 映射成字符串,在另外一个地方映射成整形值呢?自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler 来处理枚举类型, 所以如果我们想用普通的 EnumTypeHandler,就必须要显式地为那些 SQL 语句设置要使用的类型处理器。

<!-- 在 resultMap 中指定枚举类型处理器 -->
<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="funkyNumber" property="funkyNumber"/>
    <result column="roundingMode" property="roundingMode" 
            typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
</resultMap>

<!-- 在 行内参数映射 指定枚举类型处理器 -->
<insert id="insert2">
    insert into users2 (id, name, funkyNumber, roundingMode) values (
    #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
    )
</insert>

第五节 objectFactory标签

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

// ExampleObjectFactory.java
public class ExampleObjectFactory 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 -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。

第六节 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 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 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="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

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

提示 覆盖配置类也可以修改mybatis的核心行为

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

第七节 environments 标签

1. 配置 environment

environments 标签定义了 SQL 执行环境相关的配置,并且支持多个 environment 子标签的定义。

<environments default="development">
  <!--开发环境:development-->
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
    
  <!-- 其他 environment 子标签-->
</environments>

尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择其中一种。为了指定创建哪种环境,必须将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名如下,如果忽略了环境参数,那么将会加载默认环境。

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

2. 事务管理器

在 MyBatis 中有两种类型的事务管理器:

  • JDBC 直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

  • MANAGED 使用容器来管理事务的整个生命周期。从不提交或回滚一个连接,但默认情况下它会关闭连接。

  1. 在使用 MANAGED 事务管理器的时候, 一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
  2. 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

JDBCMANAGED 其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。

public interface TransactionFactory {
  default void setProperties(Properties props) { 
    // 空实现(从 3.5.2 开始,该方法为默认方法)
  }
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 方法。你的实现还需要创建一个 Transaction 接口的实现类,这个接口也很简单:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。

3. 数据源

dataSource 标签使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型:

  • UNPOOLED 这个数据源的实现会每次请求时打开和关闭连接。
    • driver 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
    • url 这是数据库的 JDBC URL 地址。
    • username 登录数据库的用户名。
    • password 登录数据库的密码。
    • defaultTransactionIsolationLevel 默认的连接事务隔离级别。
    • defaultNetworkTimeout 等待数据库操作完成的默认网络超时时间(单位:毫秒)。

作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:driver.encoding=UTF8。这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8encoding 属性给数据库驱动。

  • POOLED 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

    • poolMaximumActiveConnections 最大活动连接数,默认值:10。
    • poolMaximumIdleConnections 最大空闲连接数。
    • poolMaximumCheckoutTime 在被强制返回之前,池中连接被检出时间,默认值:20000 毫秒。
    • poolTimeToWait 获取连接超时时间,超时后打印错误日志并尝试重新获取连接,默认值:20000 毫秒。
    • poolPingEnabled 是否启用侦测查询,默认值:false。
    • poolPingQuery 侦测查询语句,用来检验连接是否正常工作并准备接受请求,默认“NO PING QUERY SET”。
    • poolPingConnectionsNotUsedFor 侦测查询的频率,默认值:0(所有连接每一时刻都被侦测,一般与 poolTimeToWait 一致)。
    • poolMaximumLocalBadConnectionTolerance 坏连接容忍度底层设置,默认值:3(新增于 3.4.5)。
  • JNDI 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:

    • initial_context 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。
      这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
    • data_source 这是引用数据源实例位置的上下文路径。
      提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如:env.encoding=UTF8。这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8encoding 属性。

你可以通过实现接口 org.apache.ibatis.datasource.DataSourceFactory 来使用第三方数据源实现:

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

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0 数据源所必需的代码:

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

public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

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

为了令其工作,记得在配置文件中为每个希望 MyBatis 调用的 setter 方法增加对应的属性。 下面是一个可以连接至 PostgreSQL 数据库的例子:

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <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>

提示 数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

第八节 databaseIdProvider标签

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。

为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:

<databaseIdProvider type="DB_VENDOR" />

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

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

在提供了属性别名时,databaseIdProvider 的 DB_VENDOR 实现会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为 “null”。 在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。

你可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider:

public interface DatabaseIdProvider {
  default void setProperties(Properties p) { 
    // 空实现(从 3.5.2 开始,该方法为默认方法)
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}

第九节 mappers标签

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

这些配置会告诉 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了,也就是接下来我们要讨论的。

第三章 映射配置文件详解

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

下面将从语句本身开始来描述每个元素的细节。

第一节 语句定义标签

1. select 标签

select 标签是 MyBatis 中最常用的标签之一。下面是一个简单的 select 标签示例。

<!-- 根据ID查询用户 -->
<select id="findById" parameterType="int" resultType="org.example.model.User">
	SELECT * FROM user WHERE id = #{id}
</select>

这个语句名为 findById,接受一个 int(或 Integer)类型的参数,并返回一个 org.example.model.User 类型的对象。根据这些信息以及一些预设的默认值,MyBatis 会创建一个 PreparedStatement ,并用 ?来标识需传入的参数,等效的 JDBC 代码如下:

String findById = "SELECT * FROM user WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(findById);
ps.setInt(1,id);

当然,select 标签的功能远远不止如此。下面是一份 select 标签的属性配置列表,详细的对其进行了说明。

属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
resultType期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

下面是一个复杂的 select 标签属性配置实例,仅当演示,实际使用根据需要选择配置即可。

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

2. insert/update和delete标签

数据修改语句使用的 insert/updatedelete 标签实现非常接近,可配置的属性也极其类似。

属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

下面是一些 insert,update 和 delete 语句的配置示例:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
              values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author 
     set username = #{username},
         password = #{password},
         email = #{email},
         bio = #{bio}
   where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

3. 实现主键自增

在插入语句里面有一些额外的属性和子元素用来处理主键的生成,并且提供了多种生成方式。

  • 使用数据库的主键自增功能

首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:

<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
  insert into Author (username,password,email,bio)
       values (#{username},#{password},#{email},#{bio})
</insert>

如果你的数据库还支持多行插入, 你也可以传入一个 Author 数组或集合,并返回自动生成的主键。

<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>
  • 使用 selectKey 标签进行主键自增

对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。这里有一个简单(也很傻)的示例,它可以生成一个随机 ID作为主键。

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author (id, username, password, email,bio, favourite_section)
              values (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

在上面的示例中,首先会运行 selectKey 元素中的语句,并设置 Author 的 id,然后才会调用插入语句。这样就实现了数据库自动生成主键类似的行为,同时保持了 Java 代码的简洁。

selectKey 标签更详细的属性说明如下:

属性描述
keyPropertyselectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
resultType结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。
order可以设置为 BEFOREAFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType和前面一样,MyBatis 支持 STATEMENTPREPAREDCALLABLE 类型的映射语句,分别代表 Statement, PreparedStatementCallableStatement 类型。

4. sql 标签

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 片段可以在其它语句中使用,例如:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

也可以在 include 元素的 refid 属性或内部语句中使用属性值,例如:

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

第二节 参数映射配置

1. parameterType

之前见到的所有语句都使用了简单的参数形式。但实际上,参数是 MyBatis 非常强大的元素。对于大多数简单的使用场景,你都不需要使用复杂的参数,比如:

<select id="selectUsers" resultType="User">
  select id, username, password
    from users
   where id = #{id}
</select>

上面的这个示例说明了一个非常简单的命名参数映射。鉴于参数类型(parameterType)会被自动设置为 int,这个参数可以随意命名。原始类型或简单数据类型(比如 IntegerString)因为没有其它属性,会用它们的值来作为参数。

然而,如果传入一个复杂的对象,行为就会有点不一样了。比如:

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
             values (#{id}, #{username}, #{password})
</insert>

如果 User 类型的参数对象传递到了语句中,会查找 id、username 和 password 属性,然后将它们的值传入预处理语句的参数中。

  1. 如果SQL中只存在一个参数,那么无需关注参数名称。如本节第一个例子中的 #{id} 写成#{uid}也没问题。
  2. 参数支持OGNL表达式,可以使用 . 号来使用内部对象的成员。如 #{user.sex} 。

2. 行内参数设置

可以在#{}内对每一个参数进行更加详细的配置,如 java 类型、jdbc 类型、类型处理器以及参数的小数保留位数等。

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler,numericScale=2}

MyBatis 几乎总是可以根据参数对象的类型确定 javaType,除非该对象是一个 HashMap。这个时候,你需要显式指定 javaType 来确保正确的类型处理器(TypeHandler)被使用。

**提示 **JDBC 要求,如果一个列允许使用 null 值,并且会使用值为 null 的参数,就必须要指定 JDBC 类型(jdbcType)。阅读 PreparedStatement.setNull()的 JavaDoc 来获取更多信息。

除以上配置之外,还可以使用 mode 属性对每个参数指明为INOUTINOUT 参数。

  • 如果参数的 modeOUTINOUT,将会修改参数对象的属性值,以便作为输出参数返回。
  • 如果 modeOUT(或 INOUT),而且 jdbcTypeCURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 引用来将结果集 ResultMap 映射到参数的类型上。要注意这里的 javaType 属性是可选的,如果留空并且 jdbcType 是 CURSOR,它会被自动地被设为 ResultMap
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

MyBatis 也支持很多高级的数据类型,比如结构体(structs),但是当使用 out 参数时,你必须显式设置类型的名称。比如(再次提示,在实际中要像这样不能换行):

#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

尽管上面这些选项很强大,但大多时候,你只须简单指定属性名,顶多要为可能为空的列指定 jdbcType,其他的事情交给 MyBatis 自己去推断就行了。下面一些才是通常所用的实例。

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}

3. ${} 的使用

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:

ORDER BY ${columnName}

这样,MyBatis 就不会修改或转义该字符串了。

当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

其中 ${column} 会被直接替换,而 #{value} 会使用 ? 预处理。这种方式也同样适用于替换表名的情况。

警告 用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。

第三节 结果映射配置

1. ResultType

  • 映射到基本类型。当SQL返回单列结果集时,可以直接映射到基本类型。
<!-- 获取用户的总记录条数 -->
<select id="findTotal" resultType="int">
    select count(id) from user;
</select>

  • 映射到 HashMap 对象。大部分情况下都支持将结果集映射到 HashMap 对象。
<select id="selectUsers" resultType="map">
  select id, username, hashedPassword from some_table where id = #{id}
</select>
  • 映射到 Javabean。映射到 JavaBean 是一种更好的选择。
<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword from some_table where id = #{id}
</select>

你可以在 mybatis-config.xml 文件中的 TypeAliases 标签配置类型别名,这样可以不用输入类的全限定名了。

<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

在这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。比如:

<select id="selectUsers" resultType="User">
  select user_id as "id", user_name as "userName", hashed_password as "hashedPassword" 
    from some_table where id = #{id}
</select>

2. ResultMap

resultMap 标签用来应对复杂的结果映射,属性列表和子标签结构如下。

属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名,或者一个类型别名,将结果集映射到该 Java 类或该 Java 类的集合。
autoMapping开启或者关闭自动映射,默认值:未设置(unset)。设置后覆盖全局属性 autoMappingBehavior。
  • 我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。

    <resultMap id="userResultMap" type="User">
      <id property="id" column="user_id" />
      <result property="username" column="user_name"/>
      <result property="password" column="hashed_password"/>
    </resultMap>
    

    然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。

    <select id="selectUsers" resultMap="userResultMap">
      select user_id, user_name, hashed_password from some_table where id = #{id}
    </select>
    

    上面案例中出现的 resultid 标签是结果映射的基础。idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。两个元素具有相同属性:

    属性描述
    property映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
    column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
    javaType一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
    jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
    typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

    这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

    下面是所有支持的JDBC类型,为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。

    image-20210408215914135

    3. 结果对象构建

    一般来说,MyBatis 通过无参构造创建结果对象,再通过 getter/setter 方法赋值。但对于某些特殊需求,如映射到不提供 getter/setter 方法的不可变类等,希望通过构造方法来初始化,MyBatis 为这种场景提供了 constructor 标签。 下面是 constructor 标签的属性列表:

    属性描述
    column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
    javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
    jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
    typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
    select用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句。具体请参考关联元素。
    resultMap结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 将会将包含重复或部分数据重复的结果集。为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许你 “串联”结果映射,以便解决嵌套结果集的问题。想了解更多内容,请参考下面的关联元素。
    name构造方法形参的名字。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素。参看上面的解释。

    假如,我们要将结果集通过构造方法映射到下面 User 对象中。

    public class User {
       //...
       public User(Integer id, String username, int age) {
         //...
      }
    //...
    }
    

    那么需要在 resultMap中做如下配置,并且 arg 标签的顺序必须与构造方法参数一致。

    <constructor>
       <idArg column="id" javaType="int"/>
       <arg column="username" javaType="String"/>
       <arg column="age" javaType="_int"/>
    </constructor>
    

    提示 MyBatis 通过暴力反射支持了私有属性的注入,但这是不提倡的行为。

    当你在处理一个带有多个形参的构造方法时,很容易搞乱 arg 元素的顺序。 在 3.4.3 版本之后,你可以通过名称来引用构造方法参数,前提是你添加了 @Param 注解,或者使用 ‘-parameters’ 编译选项并启用 useActualParamName 选项(默认开启)来编译项目。

    <constructor>
       <idArg column="id" javaType="int" name="id" />
       <arg column="age" javaType="_int" name="age" />
       <arg column="username" javaType="String" name="username" />
    </constructor>
    

    如果存在名称和类型相同的属性,那么 javaType 也可以省略 。

    4. 一对一映射

    **关联(association)**标签处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。

    关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:

    • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
    • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

    首先,先让我们来看看这个元素的属性。你将会发现,和普通的结果映射相比,它只在 select 和 resultMap 属性上有所不同。

    属性描述
    property映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
    javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
    jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
    typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
    嵌套Select 查询
    属性描述
    column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
    select用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
    fetchType可选的。有效值为 lazyeager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

    示例:

    <resultMap id="blogResult" type="Blog">
      <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
    </resultMap>
    
    <select id="selectBlog" resultMap="blogResult">
      SELECT * FROM BLOG WHERE ID = #{id}
    </select>
    
    <select id="selectAuthor" resultType="Author">
      SELECT * FROM AUTHOR WHERE ID = #{id}
    </select>
    

    就是这么简单。我们有两个 select 查询语句:一个用来加载博客(Blog),另外一个用来加载作者(Author),而且博客的结果映射描述了应该使用 selectAuthor 语句加载它的 author 属性。

    其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。

    这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:

    • 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
    • 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。

    这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。

    好消息是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。

    所以还有另外一种方法。

    嵌套结果映射
    属性描述
    resultMap结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。
    columnPrefix当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 详细说明请参考后面的例子。
    notNullColumn默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。
    autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 selectresultMap 元素使用。默认值:未设置(unset)。

    之前,你已经看到了一个非常复杂的嵌套关联的例子。 下面的例子则是一个非常简单的例子,用于演示嵌套结果映射如何工作。 现在我们将博客表和作者表连接在一起,而不是执行一个独立的查询语句,就像这样:

    <select id="selectBlog" resultMap="blogResult">
      select
        B.id            as blog_id,
        B.title         as blog_title,
        B.author_id     as blog_author_id,
        A.id            as author_id,
        A.username      as author_username,
        A.password      as author_password,
        A.email         as author_email,
        A.bio           as author_bio
      from Blog B left outer join Author A on B.author_id = A.id
      where B.id = #{id}
    </select>
    

    注意查询中的连接,以及为确保结果能够拥有唯一且清晰的名字,我们设置的别名。 这使得进行映射非常简单。现在我们可以映射这个结果:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
    </resultMap>
    
    <resultMap id="authorResult" type="Author">
      <id property="id" column="author_id"/>
      <result property="username" column="author_username"/>
      <result property="password" column="author_password"/>
      <result property="email" column="author_email"/>
      <result property="bio" column="author_bio"/>
    </resultMap>
    

    在上面的例子中,你可以看到,博客(Blog)作者(author)的关联元素委托名为 “authorResult” 的结果映射来加载作者对象的实例。

    重要提示 id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。 虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。

    现在,上面的示例使用了外部的结果映射元素来映射关联。这使得 Author 的结果映射可以被重用。 然而,如果你不打算重用它,或者你更喜欢将你所有的结果映射放在一个具有描述性的结果映射元素中。 你可以直接将结果映射作为子元素嵌套在内。这里给出使用这种方式的等效例子:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
      </association>
    </resultMap>
    

    那如果博客(blog)有一个共同作者(co-author)该怎么办?select 语句看起来会是这样的:

    <select id="selectBlog" resultMap="blogResult">
      select
        B.id            as blog_id,
        B.title         as blog_title,
        A.id            as author_id,
        A.username      as author_username,
        A.password      as author_password,
        A.email         as author_email,
        A.bio           as author_bio,
        CA.id           as co_author_id,
        CA.username     as co_author_username,
        CA.password     as co_author_password,
        CA.email        as co_author_email,
        CA.bio          as co_author_bio
      from Blog B
      left outer join Author A on B.author_id = A.id
      left outer join Author CA on B.co_author_id = CA.id
      where B.id = #{id}
    </select>
    

    回忆一下,Author 的结果映射定义如下:

    <resultMap id="authorResult" type="Author">
      <id property="id" column="author_id"/>
      <result property="username" column="author_username"/>
      <result property="password" column="author_password"/>
      <result property="email" column="author_email"/>
      <result property="bio" column="author_bio"/>
    </resultMap>
    

    由于结果中的列名与结果映射中的列名不同。你需要指定 columnPrefix 以便重复使用该结果映射来映射 co-author 的结果。

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <association property="author"
        resultMap="authorResult" />
      <association property="coAuthor"
        resultMap="authorResult"
        columnPrefix="co_" />
    </resultMap>
    
    多结果集
    属性描述
    column当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),以识别关系中的父类型与子类型。
    foreignColumn指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
    resultSet指定用于加载复杂类型的结果集名字。

    从版本 3.2.3 开始,MyBatis 提供了另一种解决 N+1 查询问题的方法。

    某些数据库允许存储过程返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集。 我们可以利用这个特性,在不使用连接的情况下,只访问数据库一次就能获得相关数据。

    在例子中,存储过程执行下面的查询并返回两个结果集。第一个结果集会返回博客(Blog)的结果,第二个则返回作者(Author)的结果。

    SELECT * FROM BLOG WHERE ID = #{id}
    
    SELECT * FROM AUTHOR WHERE ID = #{id}
    

    在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。

    <select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
      {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
    </select>
    

    现在我们可以指定使用 “authors” 结果集的数据来填充 “author” 关联:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="id" />
      <result property="title" column="title"/>
      <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="email" column="email"/>
        <result property="bio" column="bio"/>
      </association>
    </resultMap>
    

    你已经在上面看到了如何处理“有一个”类型的关联。但是该怎么处理“有很多个”类型的关联呢?这就是我们接下来要介绍的。

    5. 一对多映射

    集合标签和关联标签几乎是一样的,它们相似的程度之高,以致于没有必要再介绍集合标签的相似部分。 所以让我们来关注它们的不同之处吧。我们来继续上面的示例,一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。 在博客类中,这可以用下面的写法来表示:

    private List<Post> posts;
    

    要像上面这样,映射嵌套结果集合到一个 List 中,可以使用集合元素。 和关联元素一样,我们可以使用嵌套 Select 查询,或基于连接的嵌套结果映射集合。

    嵌套Select查询

    首先,让我们看看如何使用嵌套 Select 查询来为博客加载文章。

    <resultMap id="blogResult" type="Blog">
      <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
    </resultMap>
    
    <select id="selectBlog" resultMap="blogResult">
      SELECT * FROM BLOG WHERE ID = #{id}
    </select>
    
    <select id="selectPostsForBlog" resultType="Post">
      SELECT * FROM POST WHERE BLOG_ID = #{id}
    </select>
    

    你可能会立刻注意到几个不同,但大部分都和我们上面学习过的关联元素非常相似。 首先,你会注意到我们使用的是集合元素。 接下来你会注意到有一个新的 “ofType” 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。 所以你可以按照下面这样来阅读映射:

    <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
    

    读作: “posts 是一个存储 Post 的 ArrayList 集合”

    在一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写。所以很多时候你可以简略成:

    <collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
    
    嵌套结果映射

    现在你可能已经猜到了集合的嵌套结果映射是怎样工作的——除了新增的 “ofType” 属性,它和关联的完全相同。

    首先, 让我们看看对应的 SQL 语句:

    <select id="selectBlog" resultMap="blogResult">
      select
      B.id as blog_id,
      B.title as blog_title,
      B.author_id as blog_author_id,
      P.id as post_id,
      P.subject as post_subject,
      P.body as post_body,
      from Blog B
      left outer join Post P on B.id = P.blog_id
      where B.id = #{id}
    </select>
    

    我们再次连接了博客表和文章表,并且为每一列都赋予了一个有意义的别名,以便映射保持简单。 要映射博客里面的文章集合,就这么简单:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <result property="body" column="post_body"/>
      </collection>
    </resultMap>
    

    再提醒一次,要记得上面 id 元素的重要性,如果你不记得了,请阅读关联部分的相关部分。

    如果你喜欢更详略的、可重用的结果映射,你可以使用下面的等价形式:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
    </resultMap>
    
    <resultMap id="blogPostResult" type="Post">
      <id property="id" column="id"/>
      <result property="subject" column="subject"/>
      <result property="body" column="body"/>
    </resultMap>
    
    多结果集

    像关联元素那样,我们可以通过执行存储过程实现,它会执行两个查询并返回两个结果集,一个是博客的结果集,另一个是文章的结果集:

    SELECT * FROM BLOG WHERE ID = #{id}
    
    SELECT * FROM POST WHERE BLOG_ID = #{id}
    

    在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。

    <select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
      {call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
    </select>
    

    我们指定 “posts” 集合将会使用存储在 “posts” 结果集中的数据进行填充:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="id" />
      <result property="title" column="title"/>
      <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
        <id property="id" column="id"/>
        <result property="subject" column="subject"/>
        <result property="body" column="body"/>
      </collection>
    </resultMap>
    

    注意 对关联或集合的映射,并没有深度、广度或组合上的要求。但在映射时要留意性能问题。 在探索最佳实践的过程中,应用的单元测试和性能测试会是你的好帮手。 而 MyBatis 的好处在于,可以在不对你的代码引入重大变更(如果有)的情况下,允许你之后改变你的想法。

    高级关联和集合映射是一个深度话题。文档的介绍只能到此为止。配合少许的实践,你会很快了解全部的用法。下面是一个非常复杂的查询映射案例,可以作为学习参考。

    <!-- 
    	把这个查询结果映射到一个智能的对象模型。
    	这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。
    -->
    <select id="selectBlogDetails" resultMap="detailedBlogResultMap">
      select
           B.id as blog_id,
           B.title as blog_title,
           B.author_id as blog_author_id,
           A.id as author_id,
           A.username as author_username,
           A.password as author_password,
           A.email as author_email,
           A.bio as author_bio,
           A.favourite_section as author_favourite_section,
           P.id as post_id,
           P.blog_id as post_blog_id,
           P.author_id as post_author_id,
           P.created_on as post_created_on,
           P.section as post_section,
           P.subject as post_subject,
           P.draft as draft,
           P.body as post_body,
           C.id as comment_id,
           C.post_id as comment_post_id,
           C.name as comment_name,
           C.comment as comment_text,
           T.id as tag_id,
           T.name as tag_name
      from Blog B
           left outer join Author A on B.author_id = A.id
           left outer join Post P on B.id = P.blog_id
           left outer join Comment C on P.id = C.post_id
           left outer join Post_Tag PT on PT.post_id = P.id
           left outer join Tag T on PT.tag_id = T.id
      where B.id = #{id}
    </select>
    
    <!-- 结果映射写法参考 -->
    <resultMap id="detailedBlogResultMap" type="Blog">
      <constructor>
        <idArg column="blog_id" javaType="int"/>
      </constructor>
      <result property="title" column="blog_title"/>
      <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
        <result property="favouriteSection" column="author_favourite_section"/>
      </association>
      <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <association property="author" javaType="Author"/>
        <collection property="comments" ofType="Comment">
          <id property="id" column="comment_id"/>
        </collection>
        <collection property="tags" ofType="Tag" >
          <id property="id" column="tag_id"/>
        </collection>
        <discriminator javaType="int" column="draft">
          <case value="1" resultType="DraftPost"/>
        </discriminator>
      </collection>
    </resultMap>
    

    6. 鉴别器

    有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)标签就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。

    一个鉴别器的定义需要指定 column 和 javaType 属性。column 指定了 MyBatis 查询被比较值的地方。 而 javaType 用来确保使用正确的相等测试(虽然很多情况下字符串的相等测试都可以工作)。例如:

    <resultMap id="vehicleResult" type="Vehicle">
      <id property="id" column="id" />
      <result property="vin" column="vin"/>
      <result property="year" column="year"/>
      <result property="make" column="make"/>
      <result property="model" column="model"/>
      <result property="color" column="color"/>
      <discriminator javaType="int" column="vehicle_type">
        <case value="1" resultMap="carResult"/>
        <case value="2" resultMap="truckResult"/>
        <case value="3" resultMap="vanResult"/>
        <case value="4" resultMap="suvResult"/>
      </discriminator>
    </resultMap>
    

    在这个示例中,MyBatis 会从结果集中得到每条记录,然后比较它的 vehicle type 值。 如果它匹配任意一个鉴别器的 case,就会使用这个 case 指定的结果映射。 这个过程是互斥的,也就是说,剩余的结果映射将被忽略(除非它是扩展的,我们将在稍后讨论它)。

    如果不能匹配任何一个 case,MyBatis 就只会使用鉴别器块外定义的结果映射。 所以,如果 carResult 的声明如下:

    <resultMap id="carResult" type="Car">
      <result property="doorCount" column="door_count" />
    </resultMap>
    

    那么只有 doorCount 属性会被加载。这是为了即使鉴别器的 case 之间都能分为完全独立的一组,尽管和父结果映射可能没有什么关系。

    在上面的例子中,我们当然知道 cars 和 vehicles 之间有关系,也就是 Car 是一个 Vehicle。因此,我们希望剩余的属性也能被加载。而这只需要一个小修改。

    <resultMap id="carResult" type="Car" extends="vehicleResult">
      <result property="doorCount" column="door_count" />
    </resultMap>
    

    现在 vehicleResult 和 carResult 的属性都会被加载了。

    可能有人又会觉得映射的外部定义有点太冗长了。 因此,对于那些更喜欢简洁的映射风格的人来说,还有另一种语法可以选择。例如:

    <resultMap id="vehicleResult" type="Vehicle">
      <id property="id" column="id" />
      <result property="vin" column="vin"/>
      <result property="year" column="year"/>
      <result property="make" column="make"/>
      <result property="model" column="model"/>
      <result property="color" column="color"/>
      <discriminator javaType="int" column="vehicle_type">
        <case value="1" resultType="carResult">
          <result property="doorCount" column="door_count" />
        </case>
        <case value="2" resultType="truckResult">
          <result property="boxSize" column="box_size" />
          <result property="extendedCab" column="extended_cab" />
        </case>
        <case value="3" resultType="vanResult">
          <result property="powerSlidingDoor" column="power_sliding_door" />
        </case>
        <case value="4" resultType="suvResult">
          <result property="allWheelDrive" column="all_wheel_drive" />
        </case>
      </discriminator>
    </resultMap>
    
    

    7. 自动映射

    在简单的场景下,MyBatis 可以为你自动映射查询结果。但如果遇到复杂的场景,你需要构建一个结果映射。 但是在本节中,你将看到,你可以混合使用这两种策略。

    • MyBatis会使用结果中返回的列名在 Java 类中匹配相同名字的属性(忽略大小写),并进行赋值,这个过程为自动映射。

    • 自动映射处理完毕后,进行手动映射处理。如下案例,id 和 userName 列将被自动映射, hashed_password 列将根据配置进行映射。

    <resultMap id="userResultMap" type="User">
      <result property="password" column="hashed_password"/>
    </resultMap>
    
    <select id="selectUsers" resultMap="userResultMap">
      select user_id as "id", user_name as "userName", hashed_password from some_table where id = #{id}
    </select>
    

    提示 开启全局设置 mapUnderscoreToCamelCase ,可以自动将下划线分隔的字段名映射到小驼峰式的 Java 属性。

    自动映射有三种等级,分别为NONE(禁用自动映射)、PARTIAL(对除在内部定义了嵌套结果映射以外的属性进行映射)、FULL(自动映射所有属性),默认值为 PARTIAL

    警告 当对连接查询的结果使用 FULL 时,连接查询会在同一行中获取多个不同实体的数据,因此可能导致非预期的映射。 下面的例子将展示这种风险:

    <resultMap id="blogResult" type="Blog">
      <association property="author" resultMap="authorResult"/>
    </resultMap>
    
    <resultMap id="authorResult" type="Author">
      <result property="username" column="author_username"/>
    </resultMap>
    
    <select id="selectBlog" resultMap="blogResult">
      select B.id, B.title, A.username, from Blog B 
        left outer join Author A on B.author_id = A.id
       where B.id = #{id}
    </select>
    

    在该结果映射中,Blog 和 Author 均将被自动映射。但是注意 Author 有一个 id 属性,在 ResultSet 中也有一个名为 id 的列,所以 Author 的 id 将填入 Blog 的 id,这可不是你期望的行为。 所以,要谨慎使用 FULL

    无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置 autoMapping 属性来为指定的结果映射设置启用/禁用自动映射。

    <resultMap id="userResultMap" type="User" autoMapping="false">
      <result property="password" column="hashed_password"/>
    </resultMap>
    

    第四节 缓存配置

    和大多数的持久化框架一样,MyBatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。

    image-20210417222425728

    1. MyBatis的一级缓存

    一级缓存是SqlSession 范围的缓存,默认是开启的,每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。本地缓存将会在做出修改、事务提交或回滚,以及关闭 session 时清空。

    默认情况下,本地缓存数据的生命周期等同于整个 session 的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。

    //TODO 验证一级缓存的存在和清空,可以参考mybatis-demo0x

    2. MyBatis的二级缓存

    二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession 的。

    要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行。(注意:默认开启的 cacheEnabled 全局设置不能被关闭)

    <cache/> 
    

    基本上就是这样。这个简单语句的效果如下:

    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    警告 *缓存只作用于 cache 标签所在的映射文件中的语句。*如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

    二级缓存的行为可以通过 cache 标签的属性来修改。比如:

    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    

    这个更高级的配置创建了一个 FIFO 缓存,每隔 60000 毫秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

    提示 eviction(清除策略)的其它取值还有SOFTWEAK

    二级缓存还可以使用 select/update/insert/delete 标签的属性进行更细粒度的行为控制。

    • useCache 为 true 时表示配置的 SQL 语句使用二级缓存,否则表示不进行缓存。默认值为true。
    • flushCache 为true时表示执行该语句时进行缓存刷新,否则表示无需刷新缓存。select 标签的默认值为false,其他三者为true。

    对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。

    <cache-ref namespace="com.someone.application.data.SomeMapper"/>
    

    3. 使用自定义缓存

    除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

    <cache type="com.domain.something.MyCustomCache"/>
    

    这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。

    public interface Cache {
      String getId();
      int getSize();
      void putObject(Object key, Object value);
      Object getObject(Object key);
      boolean hasKey(Object key);
      Object removeObject(Object key);
      void clear();
    }
    

    为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file) 的方法:

    <cache type="com.domain.something.MyCustomCache">
      <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
    </cache>
    

    你可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。 你也可以使用占位符(如 ${cache.file}),以便替换成在配置文件属性中定义的值。

    从版本 3.4.2 开始,MyBatis 已经支持在所有属性设置完毕之后,调用一个初始化方法。 如果想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口。

    public interface InitializingObject {
      void initialize() throws Exception;
    }
    

    提示 上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。

    第五节 动态SQL

    动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

    1. if 标签

    使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如,希望通过 “title” 和 “author” 两个参数进行可选搜索。

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG WHERE 1 = 1
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>
    

    2. choose(when、otherwise)标签

    有时候,我们想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

    <select id="findActiveBlogLike" resultType="Blog">
      SELECT * FROM BLOG WHERE 1 = 1
      <choose>
        <when test="title != null">
          AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
          AND author_name like #{author.name}
        </when>
        <otherwise>
          AND featured = 1
        </otherwise>
      </choose>
    </select>
    

    3. trim(where、set)标签

    回到之前的 “if” 示例,仔细思考发现,我们不得不在where语句后加上 1 = 1,否则就会出现SQL拼接错误的问题。

    MyBatis为这种场景提供了一个更好的解决方案,那就是使用 where 标签。

    • where 标签只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。
    • 若拼接的子句的开头为 “AND ” 或 “OR ”,where 元素也会将它们去除。
    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG
      <where>
        <if test="state != null">
             state = #{state}
        </if>
        <if test="title != null">
            AND title like #{title}
        </if>
        <if test="author != null and author.name != null">
            AND author_name like #{author.name}
        </if>
      </where>
    </select>
    

    同样的,对于动态更新语句,MyBatis也有类似的解决方案,那就是 set 标签。set 标签可以用于动态包含需要更新的列,忽略其它不更新的列。

    <update id="updateAuthorIfNecessary">
      update Author
        <set>
          <if test="username != null">username=#{username},</if>
          <if test="password != null">password=#{password},</if>
          <if test="email != null">email=#{email},</if>
          <if test="bio != null">bio=#{bio}</if>
        </set>
      where id=#{id}
    </update>
    

    这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉末尾额外的逗号。

    如果上述的 whereset 标签都不能满足你的需求,那么你可以尝试更加强大的 trim 标签。

    • prefix 属性为子句不为空的场景下插入的前缀。

    • prefixOverrides/suffixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。

    下面分别是与 where 标签和 trim 标签等价的 trim标签。

    <!-- MyWhere -->
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
      ...
    </trim>
    
    <!-- MySet -->
    <trim prefix="SET" suffixOverrides=",">
      ...
    </trim>
    

    上述例子会移除所有 prefixOverrides/suffixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

    4. foreach标签

    动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

    <select id="selectPostIn" resultType="domain.blog.Post">
      SELECT *
      FROM POST P
      WHERE ID in
      <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
            #{item}
      </foreach>
    </select>
    

    你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach

    • 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。
    • 当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

    5. script标签

    要在带注解的映射器接口类中使用动态 SQL,可以使用 script 标签。

        @Update({"<script>",
          "update Author",
          "  <set>",
          "    <if test='username != null'>username=#{username},</if>",
          "    <if test='password != null'>password=#{password},</if>",
          "    <if test='email != null'>email=#{email},</if>",
          "    <if test='bio != null'>bio=#{bio}</if>",
          "  </set>",
          "where id=#{id}",
          "</script>"})
        void updateAuthorValues(Author author);
    

    6. bind标签

    bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。

    <select id="selectBlogsLike" resultType="Blog">
      <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
      SELECT * FROM BLOG
      WHERE title LIKE #{pattern}
    </select>
    

    7. 更细化的多数据库支持

    如果配置了 databaseIdProvider,你就可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句。

    <insert id="insert">
      <selectKey keyProperty="id" resultType="int" order="BEFORE">
        <if test="_databaseId == 'oracle'">
          select seq_users.nextval from dual
        </if>
        <if test="_databaseId == 'db2'">
          select nextval for seq_users from sysibm.sysdummy1"
        </if>
      </selectKey>
      insert into users values (#{id}, #{name})
    </insert>
    

    8. 动态 SQL 中的插入脚本语言

    MyBatis 从 3.2 版本开始支持插入脚本语言,这允许你插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。可以通过实现以下接口来插入一种语言:

    public interface LanguageDriver {
      ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
      SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
      SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
    }
    

    实现自定义语言驱动后,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

    <typeAliases>
      <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
    </typeAliases>
    <settings>
      <setting name="defaultScriptingLanguage" value="myLanguage"/>
    </settings>
    

    或者,你也可以使用 lang 属性为特定的语句指定语言:

    <select id="selectBlog" lang="myLanguage">
      SELECT * FROM BLOG
    </select>
    

    或者,在你的 mapper 接口上添加 @Lang 注解:

    public interface Mapper {
      @Lang(MyLanguageDriver.class)
      @Select("SELECT * FROM BLOG")
      List<Blog> selectBlog();
    }
    

    你前面看到的所有 xml 标签都由默认 MyBatis 语言提供,而它由语言驱动 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver(别名为 xml)所提供。

    提示 可以使用 Apache Velocity 作为动态语言,更多细节请参考 MyBatis-Velocity 项目。

    第六节 映射器注解

    1. 映射器注解概述

    设计初期的 MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,映射语句也是定义在 XML 中的。而 MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。它是 XML 和注解配置的基础。注解提供了一种简单且低成本的方式来实现简单的映射语句。

    下面是所有映射器注解列表,我们将会一一介绍。

    注解XML等价形式
    @Insert
    @Update
    @Delete
    @Select
    @ParamN/A
    @Results
    @Result
    @ConstructorArgs

    2. 语句定义注解

    • **@Insert/@Update/@Delete/@Select:**用于定义执行的SQL语句。
      • value SQL语句字符串
      • databaseId 指定SQL语句的数据库厂商。
    @Select(value = "SELECT SYS_GUID() FROM dual", databaseId = "oracle") // oracle
    @Select(value = "SELECT uuid_generate_v4()", databaseId = "postgres") //postgres
    @Select("SELECT RANDOM_UUID()") //others
    String generateId();
    
    

    3. 参数/结果集映射注解

    • **@Param:**自定义映射参数名称。默认情况下,MyBatis 通过反射获取的形参名是 arg0、arg1…(RowBounds参数除外)。

      • value 指定参数名称。如果使用了 @Param("person"),参数就会被命名为 #{person}
    • **@Results:**定义一个结果集映射。描述了结果集如何映射到Java对象。

      • value 一个 @Result 数组。
      • id 结果映射的名称(从版本 3.5.4 开始,该注解变为可重复注解)。
    • **@Result:**定义某列的映射。描述了对某个特定结果列,映射到某个属性或字段的方式。

      • id 布尔值,表示该属性是否用于唯一标识和比较对象。
      • column 结果集列名。
      • javaType Java对象的某个属性或字段。
      • jdbcType JDBC类型。
      • typeHandler 指定类型处理器。
      • one 关联映射,和<association>标签类似。
      • many 集合映射,和<collection>标签类似。
    @Results(id = "userResult", value = {
      @Result(property = "id", column = "uid", id = true),
      @Result(property = "firstName", column = "first_name"),
      @Result(property = "lastName", column = "last_name")
    })
    @Select("select * from users where id = #{id}")
    User getUserById(Integer id);
    
    • **@ConstructorArgs:**收集一组结果以传递给一个结果对象的构造方法。

      • value 它是一个 @Arg 数组。
    • **@Arg:**代表一个构造方法参数。

      • id 布尔值,表示该属性是否用于唯一标识和比较对象(从版本 3.5.4 开始,该注解变为可重复注解)。
      • column 结果集列名。
      • javaType Java对象的某个属性或字段。
      • jdbcType JDBC类型。
      • typeHandler 指定类型处理器。
      • select
      • resultMap
    @Results(id = "companyResults")
    @ConstructorArgs({
      @Arg(column = "cid", javaType = Integer.class, id = true),
      @Arg(column = "name", javaType = String.class)
    })
    @Select("select * from company where id = #{id}")
    Company getCompanyById(Integer id);
    
    • **@One:**复杂类型的单个属性映射。
      • select 指定可加载合适类型实例的映射语句(也就是映射器方法)全限定名。
      • fetchType 指定在该映射中覆盖全局配置参数lazyLoadingEnabled
      • resultMap 结果映射的全限定名。(available since 3.5.5)。
      • columnPrefix 用于在嵌套结果集对列进行分组的列前缀(available since 3.5.5)。

    注解 API 不支持联合映射。这是由于 Java 注解不允许产生循环引用。

    • **@Many:**复杂类型的集合属性映射。
      • select 指定可加载合适类型实例集合的映射语句(也就是映射器方法)全限定名。
      • fetchType 指定在该映射中覆盖全局配置参数lazyLoadingEnabled
      • resultMap 结果映射的全限定名。(available since 3.5.5)。
      • columnPrefix 用于在嵌套结果集对列进行分组的列前缀(available since 3.5.5)。

    • **@ResultType:**在使用了结果处理器的情况下,需要使用此注解。

    由于此时的返回类型为 void,所以 Mybatis 需要有一种方法来判断每一行返回的对象类型。如果在 XML 有对应的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的 元素中指定了,就不需要使用其它注解了。否则就需要使用此注解。比如,如果一个标注了 @Select 的方法想要使用结果处理器,那么它的返回类型必须是 void,并且必须使用这个注解(或者 @ResultMap)。这个注解仅在方法返回类型是 void 的情况下生效。

    • @ResultMap: 为 @Select 标注的方法指定 /@Results 的 id 值,复用已存在的结果集映射。

    如果标注的 select 注解中存在 @Results 或者 @ConstructorArgs 注解,这两个注解将被此注解覆盖。

    • **@TypeDiscriminator:**决定使用何种结果映射的一组取值(case)。

      • cases 一个 @Case 的数组
      • column
      • javaType
      • jdbcType
      • typeHandler
    • **@Case:**表示某个值的一个取值以及该取值对应的映射。

      • value
      • type
      • results 一个 @Results 的数组,因此这个注解实际上和 ResultMap 很相似,由 @Results 注解指定。

    4. 缓存相关注解

    • **@CacheNamespace:**为映射器配置缓存。

      • 属性有:implemetation、eviction、flushInterval、size、readWrite、blocking、properties。
    • **@CacheNamespaceRef:**引用另外一个命名空间的缓存以供使用。

      • value 指定能够表示该命名空间的全限定名。
      • name 直接指定了命名空间的名字(这个属性仅在 MyBatis 3.4.2 以上可用)。

    注意,即使共享相同的全限定类名,在 XML 映射文件中声明的缓存仍被识别为一个独立的命名空间。

    5. 其它类型注解

    • @Flush: 在调用被注解的方法时,触发 SqlSession 的 flushStatements() 方法(Mybatis 3.3 以上可用)。

    • **@SelectKey:**在执行SQL语句之前

      • statement 将会被执行的 SQL 语句
      • keyProperty 指定作为参数传入的对象对应属性的名称,该属性将会更新成新的值。
      • before 布尔值,指明 SQL 语句应被在插入语句的之前还是之后执行。
      • resultType 指定 keyProperty 的 Java 类型。
      • statementType 用于选择语句类型,可以选择 STATEMENT、PREPARED 或 CALLABLE 之一,默认值是 PREPARED。
      • databaseId 指明该SQL的数据库厂商类型(Available since 3.5.5)。
    1. 该注解只能在 @Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider 标注的方法上使用,否则将会被忽略。
    2. 如果标注了 @SelectKey 注解,MyBatis 将会忽略掉由 @Options 注解所设置的生成主键或设置(configuration)属性。

    这个例子展示了如何使用 @SelectKey 注解来在插入前读取数据库序列的值。

    @Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
    @SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
    int insertTable3(Name name);
    

    这个例子展示了如何使用 @SelectKey 注解来在插入后读取数据库自增列的值。

    @Insert("insert into table2 (name) values(#{name})")
    @SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
    int insertTable2(Name name);
    
    • **@Options:**该注解允许你指定大部分开关和配置选项,它们通常在映射语句上作为属性出现。下面是一些常用的属性及默认值。

    注意,Java 注解无法指定 null 值。因此,一旦你使用了 Options 注解,你的语句就会被上述属性的默认值所影响。要注意避免默认值带来的非预期行为。

    • **@Property:**指定参数值或占位符,该占位符能被 mybatis-config.xml 内的配置属性替换(仅在 MyBatis 3.4.2 以上可用)。

      • name
      • value
    • **@MapKey:**供返回值为 Map 的方法使用的注解。它使用对象的某个属性作为 key,将对象 List 转化为 Map。

      • value 指定作为 Map 的 key 值的对象属性名
    • **@InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider:**引用 Sql 构建类构建的动态 SQL。

    第四章 MyBatis-Spring

    其他

    1. mybatis-demo案例
    2. mybatis-spring + 案例
    3. mybatis源码分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值