MyBatis文档
阅读原文时间:2023年07月09日阅读:6

MyBatis 学习笔记

什么是Mybatis

MyBatis 是一款优秀的持久层框架,是Apache的一个Java开源项目 ,它支持自定义 SQL、存储过程以及高级映射, 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。

  • 原名 iBatis,在由 Apache 迁移到了谷歌,更名为 MyBatis。因此,MyBatis 指的就是 iBatis 3.x 后的版本
  • 采用 ORM(Object Relational Mapping,对象关系映射)思想实现的持久化层框架。而之前学习的 JDBC 只是一种规范,JdbcTemplate 和 DBUtil 都是简单封装了 JDBC 的工具类

Mybatis和Hibernate的关联

  1. Hibernate是全自动全映射的ORM框架,使用HQL来定制SQL;而Mybatis是半自动的
  2. Hibernate会自动映射对象属性和数据库字段,自动生产SQL语句,供直接调用,但同样因为此,Hibernate难以优化SQL,且学习HQL的成本较高
  3. Mybatis以配置文件或注解的方式编写SQL,仅封装了预编译、设置参数、执行SQL、封装结果等操作, 只需要掌握 SQL 和 MyBatis 相关配置就可以使用,学习成本相对更低

安装

目前Mybatis能够在githup上直接下载

https://github.com/mybatis/mybatis-3/releases

要使用Mybatis只需要将 mybatis-xxx.jar 文件置于类路径(classpath)中即可。

maven依赖如下(除了mybatis外,还需要导入和数据库版本适配的驱动包)

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

<!-- database connector  -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>

构建全局配置文件

创建 mybatis-config.xml 配置文件,一般直接置于resource目录下。

该文件包含了对Mybatis系统的核心设置,包括获取数据库连接实例的数据源(DataSource)、决定事务作用域和控制方式的事务管理器(TransactionManager)。

首先简单配置如下:

<?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>
    <!-- 和spring整合后 environments配置将废除 -->
    <!--环境配置,可以配置多个,根据id选择默认环境 -->
  <environments default="development">
    <environment id="development">
        <!-- 使用JDBC事务管理 -->
      <transactionManager type="JDBC"/>
        <!-- 设置数据库连接池 -->
      <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>
  </environments>
    <!-- 配置文件映射,指定配置文件的位置,每个mapper都需要在核心配置文件中注册后才可使用 -->
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>
  • environment 元素体中包含了事务管理和连接池的配置
  • mappers元素则包含了一组映射器(mapper),这些映射器的 XML文件包含了SQL代码和映射定义信息

通过配置文件构建Utils类

每个基于Mybatis的应用都是以一个SqlSessionFactory的实例为核心的。

而SqlSessionFactory的实例则可以通过SqlSessionFactoryBuilder获得。

而SqlSessionFactoryBuilder则可以从XML配置文件中构建

示例代码如下:

public class MybatisUtils {
    // 声明静态属性
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            // 获取核心配置文件路径
            String resource = "mybatis-config.xml";
            // 封装为字节流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 创建根据核心文件创建对象
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取SqlSession对象
     */
    public static SqlSession getSqlSession() {
           // 根据工厂类创建SqlSession对象并返回
        return sqlSessionFactory.openSession();
    }

创建测试类

创建POJO对象

public class User {
    private int id;
    private String name;
    private String pwd;

    /**
    *  加上对应的构造方法、getters&setters、toString()
    */
}

创建持久层接口

public interface UserMapper {
    User getUserById(int id);
}

创建对应的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">
<!-- namespace 命名空间,绑定一个对应的 Mapper 接口,且为全限定类名 -->
<mapper namespace="com.kuang.dao.UserMapper">
    <!-- id 为指定接口的方法,parameterType为传入参数类型,resultType为返回值类型 -->
    <select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
        select * from user user where id = #{id}
    </select>

</mapper>

在核心配置文件中注册

<mappers>
    <mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>

测试

使用Junit进行测试

@Test
public void test3() {
    // 调用自己编写的工具类获取SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    // 传入接口的字节码文件,创建代理对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // 执行方法,并传入参数
    User u = userMapper.getUserById(1);
    // 打印
    System.out.println(u);
    // 释放资源
    sqlSession.close();
}

详解

具体步骤如下:

  1. 通过字节流加载 核心配置文件(即 mybatis-config.xml)
  2. 使用SqlSessionFactoryBuilder读取输入流,并创建处SqlSessionFactory
  3. 使用SqlSessionFactory创建SqlSession
  4. 使用SqlSession创建代理对象(即 mapper)
  5. 通过代理对象调用方法并获取查询结果
  6. 释放资源

和传统JDBC写法比较

MyBatis

JDBC

连接池技术管理

手动地、频繁地创建和释放连接,浪费系统资源

SQL和代码分离,易于维护

SQL经常变动,字符串拼接繁琐且容易出错

自动将java对象映射到sql语句,自动实现结果封装

SQL语句的set内容、where条件都不一定,参数个数不明确,十分麻烦,且需要手动遍历封装

作用域和生命周期

SqlSessionFactoryBuilder

  • 这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory后就不需要它了
  • 最佳作用域是方法作用域(局部方法作用域)
  • 没必要长期保留该对象,以保证所有的XML解析资源可以被释放

SqlSessionFactory

  • 一旦被创建就应该在应用运行期间一直存在,不应该重复创建
  • 最佳作用域是应用作用域,可使用单例模式或静态单例模式创造

SqlSession

  • SqlSession的实例不是线程安全的,因此不能共享,即每个线程都应该有一个它自己的SqlSession实例
  • 它的最佳作用域是请求或方法作用域,绝不能将SqlSession实例的引用放在一个类的静态域或实例变量
  • 对SqlSession的关闭很重要,应该把关闭操作放到 finally块中,或者使用 try-with-source 方法获取

映射器实例

  • 映射器是一些绑定映射语句的接口,接口实例从SqlSession中获得

  • 合适的作用域是方法作用域,即映射器应该在调用它们的方法中被获取,使用完毕后即丢弃

MyBatis的配置文件十分重要!

结构

  • configuration(配置)

    • properties(属性)

    • settings(设置)

    • typeAliases(类型别名)

    • typeHandlers(类型处理器)

    • objectFactory(对象工厂)

    • plugins(插件)

    • environments(环境配置)

      • environment(环境变量)

        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)

    • mappers(映射器)

Properties(属性)

外部文件

<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="zhao"/>
</properties>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值,比如:

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

​ 其中,username和password都会被替换

属性加载顺序

  1. 首先读取在 properties 元素体内指定的属性
  2. 然后根据 properties 元素中的 resource属性或url属性读取外部文件,并覆盖掉之前读取过的同名属性
  3. 最后读取作为方法参数传递的属性,覆盖掉之前读取过的同名属性

综上,优先级排名:方法参数 > 外部文件 > 大于核心配置文件

设置默认值

从MyBatis3.4.2开始,可以为占位符指定一个默认的值,如:

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

该特性默认关闭,若要使用,需先配置,如:

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

但开启后,如果在属性命名中用到了 “:”,它就会被识别为默认值分隔符,鉴于 “:”,在三元运算等诸多场合都会被用到,所以需要修改默认值分隔符,也就是说,令找一种表示方式来代替 “:”,如

<properties resource="org/mybatis/example/config.properties">
      <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- 修改默认值的分隔符 -->
</properties>

<!-- 此时在属性获取内容处,应表达如下-->
<dataSource type="POOLED">
  <property name="username" value="${db:username?:ut_user}"/>
</dataSource>

Settings(设置)

这是MyBatis中极为重要的调整,会改变MyBatis的运行时行为。

设置名

描述

有效值

默认值

cacheEnabled

全局性地开启或关闭缓存

true | false

true

lazyLoadingEnabled

设置延迟加载,关联关系可通过设置 fetchType 属性来覆盖

true | false

false

aggressiveLazyLoading

任何方法调用都会加载该对象的所有延迟加载属性,否则延迟加载属性按需加载

true | false

false

multipleResultSetsEnabled

单个语句返回多个结果集

true | false

true

useColumnLabel

使用列标签代替列名

true | false

true

useGeneratedKeys

允许JDBC支持自动生成主键

true | false

false

autoMappingBehavior

自动将列映射到字段或属性
NONE:关闭自动映射
PARTIAL:自动映射没有定义嵌套结果映射的字段
FULL:自动映射任何复杂的结果集

NONE, PARTIAL, FULL

PARTIAL

autoMappingUnknownColumnBehavior

指定发现自动映射目标未知列的行为
NONE:不做任何反应
WARNING:输出警告日志
FAILING:映射失败

NONE, WARNING, FAILING

NONE

defaultExecutorType

配置默认执行器
SIMPLE:普通执行器
REUSE:重用预处理语句(PreparedStatement)
BATCH:既重用语句又批量更新

SIMPLE, REUSE, BATCH

SIMPLE

defaultStatementTimeout

超时时间

任意正整数

null

defaultFetchSize

为驱动的结果集获取数量(fetchSize)设定一个建议值

任意正整数

null

defaultResultSetType

指定语句默认的滚动策略

FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(即未设置)

null

safeRowBoundsEnabled

是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false

true | false

false

sageResultHandlerEnabled

是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false

true | false

false

mapUnderscoreToCamelCase

是否开启驼峰命名自动映射

true | false

false

localCacheScope

本地缓存机制的类型
SESSION:缓存一个会话中执行的所有查询
STATEMENT:仅对单个语句执行进行缓存,但对相同SqlSession的不同查询不会缓存

SESSION | STATEMENT

SESSION

jdbcTypeForNull

指定列的JDBC类型

JDBC常量,如:NULL, VARCHAR, OTHER

OTHER

lazyloadTriggerMethods

指定对象触发延迟加载的条件

用逗号分隔的方法列表

equals, clone, hashCode, toString

defaultScriptingLanguage

指定动态SQL生成使用的默认脚步语言

类型别名或全类名

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver

defaultEnumTypeHandler

指定 Enum使用的默认 TyperHandlder

类型别名或全类名

org.apache.ibatis.type.EnumTypeHandler

callSettersOnNulls

指定当结果集中值为null时是否调用映射对象的 setter 方法(基本类型不能设置为null)

true | false

false

returnInstanceForEmptyRow

当返回行的所有列都是空时,MyBatis默认返回空实例对象

true | false

false

logPrefix

指定MyBatis增加到日志名称的前缀

任何字符串

null

logImpl

指定MyBatis所用日志的具体实现,未指定时自动查找

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

null

proxyFactory

指定MyBatis创建可延迟加载对象多用到的代理工具

CGLIB | JAVASSIST

JAVASSIST

vfsImpl

指定 VFS 的实现

自定义 VFS 的实现类全类名,以逗号分隔

null

useActualParamName

允许使用方法签名中的名称作为语句参数名称

true | false

true

configurationFactory

指定一个提供 Configuration 实例的类,用来加载被反序列化对象的延迟加载属性值(该类必须包含一个 static Configuration getConfiguration()方法)

类的别名或全类名

null

shrinkWhitespacesInSql

从SQL中删除多余的空格(会影响SQL中的文字字符串)

true | false

false

defaultSqlProviderType

指定一个类作为SQL语句提供类,需使用 @SelectProvider注解

类的别名或全类名

null

<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(类型别名)

给Java类型设置一个缩写的名字,仅用于XML配置,可减少重复,例:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

也可以指定一个包名,MyBatis会在包名下面自动搜索需要的Java类型,此时会使用类的首字母小写的类名作为别名。例:

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

此时也可以单独给类设置别名,使用 @Alias注解

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

常见的Java类型别名(不区分大小写):

别名

映射类型

_byte

byte

_long

long

_short

short

_int | _integer

int

_double

double

_float

float

_boolean

boolean

string

String

byte

Byte

long

Long

short

Short

int | integer

Integer

double

Double

float

Float

boolean

Boolean

date

Date

decimal | bigdecimal

BigDecimal

object

Object

map

Map

hashmap

HashMap

list

List

arraylist

ArrayList

collection

Collection

iterator

Iterator

typeHandlers(类型处理器)

类型处理器用于将获取到的值转换为Java类型

类型处理器

Java类型

JDBC类型

BooleanTypeHandler

Boolean | boolean

BOOLEAN

ByteTypeHandler

Byte | byte

BYTE | NUMERIC

ShortTypeHandler

Short | short

SMALLINT | NUMERIC

IntegerTypeHandler

Integer | int

INTEGER | NUMERIC

LongTypeHandler

Long | long

BIGINT | NUMERIC

FloatTypeHandler

Float | float

FLOAT | NUMERIC

DoubleTypeHandler

Double | double

DOUBLE | NUMERIC

BigDecimalTypeHandler

BigDecimal

DECIMAL | NUMERIC

StringTypeHandler

String

CHAR | VARCHAR

ClobReaderTypeHandler

Reader

-

ClobTypeHandler

String

CLOB | LONGVARCHAR

NStringTypeHandler

String

NVARCHAR | NCHAR

NClobTypeHandler

String

NCLOB

BlobInputStreamTypeHandler

InputStream

-

ByteArrayTypeHandler

byte[ ]

数据库兼容的字节流类型

BlobTypeHandler

byte[ ]

BLOB | LONGVARBINARY

DateTypeHandler

Date

TIMESTAMP

DateOnlyTypeHandler

Date

DATE

TimeOnlyTypeHandler

Date

TIME

SqlTimestampTypeHandler

Timestamp

TIMESTAMP

SqlDateTypeHandler

Date

DATE

SqlTimeTypeHandler

Time

TIME

ObjectTypeHandler

Any

-

EnumTypeHandler

Enumeration Type

VARCHAR或任何能兼容的字符串类型,用来存储枚举的名称

EnumOrdinalTypeHandler

Enumeration Type

兼容 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值

SqlxmlTypeHandler

String

SQLXML

InstantTypeHandler

Instant

TIMESTAMP

LocalDateTimeTypeHandler

LocalDateTime

TIMESTAMP

LoalDateTypeHandler

LocalDate

DATE

LocalTimeTypeHandler

LocalTime

TIME

OffsetDateTimeTypeHandler

OffsetDateTime

TIMESTAMP

OffsetTimeTypeHandler

OffsetTime

TIME

ZonedDateTimeTypeHandler

ZonedDateTime

TIMESTAMP

YearTypeHandler

Year

INTEGER

MonthTypeHandler

Month

INTEGER

YearMonthTypeHandler

YearMonth

VARCHAR | LONGVARCHAR

JapaneseDateTypeHandler

JapaneseDate

DATE

objectFactory(对象工厂)

每次MyBatis创建结果对象的新实例时,都会使用一个对象工厂实例来完成实例化工作。

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

  • 创建实例的方法 create(),包括无参构造和有参构造

  • 配置工厂的方法 setProperties(),在初始化ObjectFactory后,元素体中定义的属性会作为参数传递过来

    public class ExampleObjectFactory 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);
    }
    }

    plugins(插件)

    MyBatis可使用插件在映射语句执行的过程中的某一点处进行拦截调用,方法包括:

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

    自定义插件时,要特别小心,有可能会破坏MyBatis的核心模块

    // 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>

    environments(环境配置)

    MyBatis内部可配置多种环境,便于SQL映射应用于多个数据库中。但是,对于每个SqlSessionFactory实例,只能选择一种环境。

    因此,如果想要连接多个数据库进行操作,就要创建多个SqlSessionFactory实例,每个数据库对应一个。

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

    environments元素内部定义:

    <environments default="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>
    </environments>

    其中

    • default=“development”表示默认使用的环境ID
    • id=“development”表示该environment元素定义的环境ID
    • type=“JDBC”表示事务管理器的配置
    • type=“POOLED”表示数据源的配置

    事务管理器配置(transactionManager)

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

    • JDBC
    • MANAGED

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

    MANAGER:这个配置几乎什么也不做

    这两种事务管理器都不需要设置任何属性,其实都是类型别名而已。

    数据源(dataSource)

    在MyBatis中有三种内建的数据源类型:

    • UNPOOLED
    • POOLED
    • JNDI

    UNPOOLED:会在每次请求时打开和关闭连接,速度慢,性能表现依赖于选用的数据库

    POOLED:使用数据库连接池组织JDBC连接对象,避免了创建新连接实例必须的初始化和认证时间,便于并发Web应用快速响应。

    JNDI:能在如EJB或应用服务器等容器中使用

    databaseIdProvider(数据库厂商标识)

    MyBatis可以通过设置databaseId属性,实现针对不同的数据库厂商执行不同语句的效果。

    MyBatis在实际工作中,回加载匹配databaseId和不带databaseId的所有语句,若同时找到了相同的语句,就会将不带databaseId的语句舍弃。

    例:

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

    mappers(映射器)

    需要在核心配置文件中注册每一个映射器文件,注册方法有四种:

    • 使用相对于类路径的资源引用

    • 使用完全限定资源定位符

    • 使用映射器接口类的全类名

    • 使用包名





      <!-- 使用完全限定资源定位符(URL) -->



      <!-- 使用映射器接口实现类的完全限定类名 -->



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

    映射器是MyBatis中最重要的部分!

    结构

    • cache:为该命名空间进行缓存配置
    • cache-ref:引用其他命名空间的缓存配置
    • resultMap:描述从数据库结果集加载数据并封装成对象的规则(很重要!!!)
    • sql:可复用的SQL语句块
    • insert:插入语句
    • update:更新语句
    • delete:删除语句
    • select:查询语句

    select

    可从数据库中查询结果,并根据具体配置实现自动的对象封装返回。例:

    <select id="selectPerson" parameterType="int" resultType="hashmap">
      SELECT * FROM PERSON WHERE ID = #{id}
    </select>

    参数详解:

    属性

    描述

    id

    命名空间中唯一的标识符,可以被用来引用整条语句

    parameterType

    方法的传入参数的属性类(别名或全类名),实际上MyBatis会通过TypeHandler自动推断出类型,因此默认为 unset

    resultType

    方法返回参数的类型,如果返回的是集合,就写泛型的类型(别名或全类名)

    resultMap

    对外部resultMap的命名引用(和resultType二者只能选择一个使用)

    flushCache

    设置为true后,会导致每次调用该语句都刷新一级、二级缓存(默认为 false)

    useCache

    设置为true后,会导致调用该语句后,结果会被保存到二级缓存中(默认为true)

    timeout

    驱动程序等待数据库返回结果的秒数

    fetchSize

    驱动程序每次批量返回结果的行数

    statementType

    可选STATEMENT, PREPARED, CALLABLE(默认为 PREPARED)

    resultSetType

    可选FORWARD_ONLY, SCROLL_SENSITIVE, SCROLL_INSENSITIVE, DEFAULT(即unset)

    databaseId

    数据库厂商标识

    resultOrdered

    设置为true后,会导致嵌套查询的内部结果不会产生对之前结果的引用,能节省内存(默认为false)

    resultSets

    为返回的结果集赋予名称,多个名称间用逗号隔开

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

    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>

    相比于select特殊的属性为:

    属性

    描述

    useGeneratedKeys

    仅适用于 insert 和 update,会调用getGeneratedKey()方法取出由数据库内部生成的主键(默认值为 false)

    keyProperty

    仅适用于 insert 和 update,指定能够唯一识别对象的属性,如果生成列不止一个就用逗号隔开

    keyColumn

    仅使用于 insert 和 update,设置生成键值在表中的列明,如果生成列不止一个就用逗号隔开

    具体用法:

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

    sql

    该元素可用来定义可重用的代码片段,例:

    <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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>

    也可以在sql内部调用sql,例:

    <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>

    字符串替换

    占位符:

    • { }:MyBatis会通过预处理的手段创建PreparedStatement参数占位符,安全且迅速

    • ${ }:MyBatis进行字符串拼接,会有潜在的SQL注入风险

    混合搭配小技巧:

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

    结果映射

    resultMap可以将数据从resultSet中提取出来,并且支持类似自定义一样的封装。非常重要!!!

    例:

    <!--  配置resultMap,并取名为userResultMap -->
    <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,并以此为依据进行数据封装 -->
    <select id="selectUsers" resultMap="userResultMap">
      select user_id, user_name, hashed_password
      from some_table
      where id = #{id}
    </select>

    但是,很多时候,我们会写复杂的查询语句,此时 resultMap的配置也会变得较为繁琐。一个对象可能关联多个其他对象,或关联一个集合,等等。

    <!-- 非常复杂的结果映射 -->
    <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>
    
    <!-- 非常复杂的语句 -->
    <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>

    结构

    • constructor:用于在实例化类时,调用构造方法

      • idArg:ID参数,标记出来可以提高整体的性能
      • arg:普通参数
    • id:一个ID结果,标记出来可以提高整体的性能

    • result:注入到字段或 JavaBean属性的普通结果

    • association:复杂的关联类型,内部可嵌套 resultMap

    • collection:复杂的集合类型,内部可以嵌套 resultMap

    • discriminator:根据结果值判断具体使用哪个resultMap

      • case:基于某些值的结果映射

    resultMap中可配置的属性

    属性

    描述

    id

    当前命名空间中的一个唯一标识,用于标识一个结果映射

    type

    类的别名或全类名

    autoMapping

    若设置为 true,则会开启自动映射(默认为 unset)

    元素详解

    id & result

    这两个元素是映射的基础

    • 二者都能将一个列的值映射到简单数据类型中
    • id元素对应的属性会被标记为对象的标识符,在进行缓存和嵌套映射的时候能提高性能

    id 和 result 的一些属性

    属性

    描述

    property

    Java类中的属性名

    column

    数据库中的列名或是查询语句中列的别名

    javaType

    Java类的类别名或全类名(MyBatis通常能够自动推断出来)

    jdbcType

    JDBC类型,如果对象列是可修改的可为空的列,就必须给出

    typeHandler

    类型处理器

    constructor

    针对一些很少改变或者基本不变类或表,建议通过 constructor 进行创建。

    MyBatis能通过某种方式定位到 Java类中相应的构造方法,并将结果注入其中。如果存在名称和类型相同的属性则可以省略 javaType

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

    constructor 的一些属性

    属性

    描述

    column

    数据库中的列名或是查询语句中列的别名

    javaType

    Java类的类别名或全类名(MyBatis通常能够自动推断出来)

    jdbcType

    JDBC类型,如果对象列是可修改的可为空的列,就必须给出

    typeHandler

    类型处理器

    select

    加载复杂类型属性的映射语句

    resultMap

    映射嵌套的结果集

    name

    构造方法形参的名字

    association

    关联元素能处理 “ has a ”的关系,如:一个用户对应一个博客,两个Java类内部存在关联。

    MyBatis有两种加载关联的形式:

    • 嵌套 select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型

    • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集

    association 的属性

    属性

    描述

    property

    Java类中的属性名

    javaType

    Java类的类别名或全类名(MyBatis通常能够自动推断出来)

    jdbcType

    JDBC类型,如果对象列是可修改的可为空的列,就必须给出

    typeHandler

    类型处理器

    关联嵌套的Select查询

    单独设置多个 select 查询语句,一个用来查询 A,一个用来查询 B ……

    其他的所有属性都会被自动加载,只要它们的列名和属性名想匹配

    但是,存在 ”N+1查询问题“:每一次执行一条单独的 SQL 语句获取一个列表,针对列表中的每一个记录,都会执行一个 select查询语句来加载详细信息。效率低!

    嵌套select查询的属性

    属性

    描述

    column

    数据库中的列名或别名

    select

    加载复杂类型属性的语句ID,会从 column属性指定的列中检索数据,并传递给目标 select语句

    fetchType

    可选 lazy 或 eager

    <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>

    关联的嵌套结果映射

    针对复杂的嵌套关联语句,我们可以选择连接查询。

    关联嵌套的属性:

    属性

    描述

    resultMap

    引用的嵌套结果集,可以实现 resultMap的复用

    columnPrefix

    给表中的每列设置相同的额外的前缀,来避免重复

    notNullColumn

    指定非空列(指定后,MyBaits只有在该列非空情况下才会创建对象)

    autoMapping

    自动映射

    <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>
    
    <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>

    注意:在嵌套查询中,id 元素非常重要,设置与否会严重影响性能!

    此外,针对结果列名和映射列名不同,设置 columnPrefix:

    <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>
    collection

    集合元素类似管理元素,针对 一个博客有多篇文章类型的问题。

    同样,集合中也可以进行 嵌套 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属性:指定泛型类型,即集合存储元素的类型

    其余属性含义和 association中相同

    嵌套结果查询

    <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>
    
    <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>

    再次强调 id元素对性能的重要性!

    discriminator

    有时候一个数据库拆线呢可能会返回多个不同的结果集,因此可以使用鉴别器来实现针对不同的值采用不同的resultMap承接。

    <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>

    discriminator的属性:

    属性

    描述

    column

    数据库中的列名或列别名

    javaType

    Java类的类别名或全类名(MyBatis通常能够自动推断出来)

    value

    查出来待判断的值

    resultMap

    针对不同值适用的不同结果集

    cache

    默认情况下MyBatis之开启本地缓存,即一级缓存,仅针对一个sqlSession中的数据进行缓存

    要开启二级缓存需要手动在映射器中设置:

    <cache/>

    二级缓存开启后,

    • 所有的select语句都会被缓存

    • 只有发生insert, update, delete语句时才会刷新缓存

    • 会使用LRU算法(最少使用算法)清除不必要的缓存

    • 最多缓存1024个对象

    • 对缓存的操作被视为 读/写操作,因此获取到的对象是不共享的

    cache的属性:

    属性

    描述

    eviction

    配置清理缓存的算法,可选项有:LRU、FIFO、SOFT、WEAK

    flushInterbal

    设置刷新间隔

    size

    设置最大缓存数目

    readOnly

    设置只读属性。如果只读,则调用缓存时返回同一个对象,高效;如果可读可写,返回缓存对象的拷贝,安全但低效。

    使用MyBatis最主要的接口就是SqlSession,我们可以通过这个接口来执行命令,获取映射器和管理事务。

    SqlSession由SqlSessionFactory实例创建;SqlSessionFactory则由SqlSessionFactoryBuilder读取配置文件或注解创建。

    String resource = "org/mybatis/builder/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(inputStream);

    这里我们调用Resources工具类,从类路径、文件系统或者一个 web URL中加载资源文件。内部API 如下:

    URL getResourceURL(String resource)
    URL getResourceURL(ClassLoader loader, String resource)
    InputStream getResourceAsStream(String resource)
    InputStream getResourceAsStream(ClassLoader loader, String resource)
    Properties getResourceAsProperties(String resource)
    Properties getResourceAsProperties(ClassLoader loader, String resource)
    Reader getResourceAsReader(String resource)
    Reader getResourceAsReader(ClassLoader loader, String resource)
    File getResourceAsFile(String resource)
    File getResourceAsFile(ClassLoader loader, String resource)
    InputStream getUrlAsStream(String urlString)
    Reader getUrlAsReader(String urlString)
    Properties getUrlAsProperties(String urlString)
    Class classForName(String className)

    SqlSessionFactoryBuilder

    SqlSessionFactoryBuilder 有五个 build() 方法

    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)

    其中environment决定加载哪种环境,properties决定加载哪些属性,优先级最高。

    Configuration类中包含了对所有配置的设置,并以 Java API 的形式展示出来:

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

    SqlSessionFactory

    SqlSessionFactory 中有六个方法创建 SqlSession 实例。

    SqlSession openSession()
    SqlSession openSession(boolean autoCommit) // 传 true将开启自动提交事务
    SqlSession openSession(Connection connection) // 使用自己的 Connection 实例,MyBaits会根据 Connection来自动判断是否启用 autoCommit
    SqlSession openSession(TransactionIsolationLevel level) // 设置事务隔离级别(NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE)
    SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
    SqlSession openSession(ExecutorType execType) // 设置执行器类别(SIMPLE, REUSE, BATCH)
    SqlSession openSession(ExecutorType execType, boolean autoCommit)
    SqlSession openSession(ExecutorType execType, Connection connection)
    Configuration getConfiguration();

    通过不同方法的选择,可以实现对事务处理、数据库连接、语句执行的设置。

    默认的空参方法会创建具有如下特性的 SqlSession:

    • 开启事务作用域,但不自动提交
    • 从当前环境配置的 DataSource 实例中获取 Connection对象
    • 事务隔离级别将会使用驱动或数据源的默认设置
    • 采用 SimpleExecutor,预处理语句不会复用也不会批量处理更新

    SqlSession

    SqlSession包含所有执行语句、提交或回滚事务以及获取映射器的方法。

    直接执行语句(老式的用法)

    这些方法用来执行定义在SQL映射文件中的语句,具体参数可以是原始类型、JavaBean、POJO或Map

    <T> T selectOne(String statement, Object parameter) // 若返回多个对象就会报错,若未查到则返回 null
    <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) //  返回字典,对象的一个属性作为key,对象作为value
    int insert(String statement, Object parameter) // 返回影响行数
    int update(String statement, Object parameter)
    int delete(String statement, Object parameter)

    分页查询方法:

    <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)

    关于 RowBounds类,它是不可变类:

    int offset = 100;
    int limit = 25;
    RowBounds rowBounds = new RowBounds(offset, limit);

    当我们将 MyBatis的执行器类型设置为 BATCH时,可以使用以下方法执行缓存在 JDBC驱动类中的批量更新语句。

    List<BatchResult> flushStatements()

    使用映射器

    在MyBatis中定义一个接口和同名的映射文件,通过反射的方法来操作数据库。

    public interface AuthorMapper {
    
      Author selectAuthor(int id);
    
      List<Author> selectAuthors();
    
      @MapKey("id")
      Map<Integer, Author> selectAuthors();
    
      int insertAuthor(Author author);
    
      int updateAuthor(Author author);
    
      int deleteAuthor(int id);
    }

    映射器接口不需要实现任何方法或继承自任何类,且不能在两个具有继承关系的接口中拥有相同的方法签名

    可以选择给接口中的方法配置注解来替代或者方便映射文件的设置

    注解

    使用对象

    XML等价形式

    描述

    @CacheNamespace

    为给定的命名空间配置缓存,属性同标签类似

    @Property

    N/A

    指定参数值或占位符,属性同标签

    @CacheNamespaceRef

    引用另一个命名空间的缓存配置

    @ConstructorArgs

    方法

    收集一组结果来传给构造方法,属性:Value数组

    @Arg

    N/A

    |

    表示构造方法的参数,是一个布尔值,表示该属性是否用于唯一标识

    @TypeDiscriminator

    方法

    根据不同取值决定使用何种映射集

    @Case

    N/A

    表示某个值和对应情况下的映射

    @Results

    方法

    定义一组映射结果集

    @Result

    N/A

    |

    在属性和字段之间设置单个结果映射

    @One

    N/A

    复杂类型的单个属性映射

    @Many

    N/A

    复杂类型的集合属性映射

    @MapKey

    方法

    供返回值为Map的方法使用,使得注释的属性成为key,将对象转化为Map

    @Options

    方法

    映射语句的属性

    可设置大部分的开关和配置项

    @Insert | @Update | @Delete | @Select

    方法

    | | |

    分别代表将被执行的SQL语句

    @InsertProvider | @UpdateProvider | @DeleteProvider | @SelectProvider

    方法

    | | |

    分别代表构建的动态SQL语句

    @Param

    参数

    自定义每个参数的名字

    @SelectKey

    方法

    查询主键

    @Flush

    方法

    使用该注解后就能调用flushStatement()方法

    示例:

    @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);
    
    @Insert("insert into table2 (name) values(#{name})")
    @SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
    int insertTable2(Name name);
    
    @Flush
    List<BatchResult> flush();
    
    @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);
    
    @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);
    
    @SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
    List<User> getUsersByName(String name);
    
    class UserSqlBuilder {
      public static String buildGetUsersByName(final String name) {
        return new SQL(){{
          SELECT("*");
          FROM("users");
          if (name != null) {
            WHERE("name like #{value} || '%'");
          }
          ORDER_BY("id");
        }}.toString();
      }
    }
    
    @SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
    List<User> getUsersByName(
        @Param("name") String name, @Param("orderByColumn") String orderByColumn);
    
    class UserSqlBuilder {
    
      // 如果不使用 @Param,就应该定义与 mapper 方法相同的参数
      public static String buildGetUsersByName(
          final String name, final String orderByColumn) {
        return new SQL(){{
          SELECT("*");
          FROM("users");
          WHERE("name like #{name} || '%'");
          ORDER_BY(orderByColumn);
        }}.toString();
      }
    
      // 如果使用 @Param,就可以只定义需要使用的参数
      public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
        return new SQL(){{
          SELECT("*");
          FROM("users");
          WHERE("name like #{name} || '%'");
          ORDER_BY(orderByColumn);
        }}.toString();
      }
    }

    动态SQL是MyBatis的强大特性,能够方便SQL语句的拼接

    主要标签:

    • if
    • choose(when, otherwise)
    • trim(where, set)
    • foreach

    if

    提供可选的查找功能,如果传入了某个参数,就将该参数作为查找标准之一。

    <select id="findActiveBlogLike" resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <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>

    choose, when, otherwise

    类似Java中的 switch, case, default

    <select id="findActiveBlogLike"  resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <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>

    trim, where, set

    解决 where语句中 and拼接 和 update语句中 逗号拼接的问题

    <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>

    元素会在子元素返回内容的情况下才插入 WHERE子句,并且若子句以 AND或OR开头也会根据情况删除或保留

    <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关键字,并且会根据情况删掉或保留逗号

    此外,也可以自定义类似的元素标签

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

    foreach

    可以动态的遍历集合,在构造条件或插入语句时特别有用

    <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>
    • collection:集合
    • item:集合项
    • index:索引
    • separator:分隔符
    • open:开头
    • close:结尾