Mybatis学习(二):Mybatis入门教程和简单应用

Mybatis学习(二):Mybatis入门教程和简单应用

  • 前言
  • 一、 Mybatis简介
    • 1.1 ORM简介
    • 1.2 Mybatis简介
  • 二、基本应用
    • 2.1 demo入门应用
      • 2.1.1 MyBatis官方地址
      • 2.1.2 环境搭建
        • 2.1.2.1 引入相关maven依赖
        • 2.1.2.2 本地数据库新建user用户表
        • 2.1.2.3 新建user实体类
        • 2.1.2.4 新建UserMapper映射配置文件UserMapper.xml
        • 2.1.2.5 新增MyBatis核心配置文件sqlMapConfig.xml
        • 2.1.2.6 测试
      • 2.1.3 简单的CRUD栗子
        • 2.1.3.1 查询
        • 2.1.3.2 新增
        • 2.1.3.3 修改
        • 2.1.3.4 删除
      • 2.1.4 Dao层实现的方式
        • 2.1.4.1 传统的实现方式(实现类调用SqlSession)
        • 2.1.4.2 代理的实现方式
    • 2.2 配置文件属性说明
      • 2.2.1 XML配置文件(sqlMapConfig.xml)
        • 2.2.1.1 properties(属性)
          • 2.2.1.1.1 如何设置属性
          • 2.2.1.1.2 MyBatis 属性加载顺序
          • 2.2.1.1.3 占位符指定一个默认值
        • 2.2.1.2 settings(设置)
        • 2.2.1.3 typeAliases(类型别名)
        • 2.2.1.4 typeHandlers(类型处理器)
        • 2.2.1.5 objectFactory(对象工厂)
        • 2.2.1.6 plugins(插件)
        • 2.2.1.7 environments(环境配置)
          • 2.2.1.7.1 transactionManager(事务管理器)
          • 2.2.1.7.2 dataSource(数据源)
          • 2.2.1.7.3 databaseIdProvider(数据库厂商标识)
        • 2.2.1.8 mappers(映射器)
      • 2.2.2 XML映射文件(Mapper.xml)
        • 2.2.2.1 select标签
        • 2.2.2.2 insert, update 和 delete标签
        • 2.2.2.3 sql标签
        • 2.2.2.4 参数
          • 2.2.2.4.1 字符串替换
        • 2.2.2.5 结果映射
        • 2.2.2.6 自动映射
        • 2.2.2.7 缓存
          • 2.2.2.7.1 设置缓存
          • 2.2.2.7.2 使用自定义缓存
          • 2.2.2.7.3 cache-ref
    • 2.3 动态 SQL
      • 2.3.1 if 标签
      • 2.3.2 choose、when、otherwise 标签
      • 2.3.3 trim、where、set 标签
      • 2.3.4 foreach 标签
      • 2.3.5 bind 标签
      • 2.3.6 多数据库支持
      • 2.3.7 动态 SQL 中的插入脚本语言
  • 三、彩蛋

前言

接上一篇Mybatis学习(一):基础概念和简单自定义持久层框架demo

一、 Mybatis简介

1.1 ORM简介

ORM(Object/Relation Mapping):对象—关系映射的缩写。

      ORM完成面向对象的编程语言到数据库的映射。ORM把关系数据库包装成面向对象的模型。采用orm框架后,应用程序不再直接访问数据库,而是用面向对象的方式操作持久化对象,orm框架本身把这些操作转变成数据库底层SQL操作。

1.2 Mybatis简介

      Mybatis是一款优秀的基于ORM的半自动化轻量级持久层框架,它支持定制化SQL、存储过程和高级映射。Mybatis可以用简单的xml或者注解来配置和映射原生类型、接口和实体类。

二、基本应用

2.1 demo入门应用

2.1.1 MyBatis官方地址

MyBatis官方地址:https://mybatis.org/mybatis-3/zh/index.html
Mybatis学习(二):Mybatis入门教程和简单应用_第1张图片

2.1.2 环境搭建

新建一个maven项目

2.1.2.1 引入相关maven依赖

<!--mybatis依赖-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.4</version>
</dependency>
<!--mysql驱动依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>

2.1.2.2 本地数据库新建user用户表

Mybatis学习(二):Mybatis入门教程和简单应用_第2张图片

2.1.2.3 新建user实体类

public class User {

    private int id;
    private String name;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

2.1.2.4 新建UserMapper映射配置文件UserMapper.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">

>
    
    
>

2.1.2.5 新增MyBatis核心配置文件sqlMapConfig.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">

>

    <!--environments:运行环境-->
    
        
            
            
            
            
                
                
                
                
            
        
    

    <!--引入映射配置文件-->
    
        
    

>

注意: 驱动class注意mysql版本的区别(8.0以上是"com.mysql.cj.jdbc.Driver")

2.1.2.6 测试

@org.junit.Test
    public void test1() throws IOException {
        //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.解析了配置文件,并创建了sqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.生产sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
        //在进行增删改操作时,要手动提交事务
        //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
        List<User> users = sqlSession.selectList("com.learn.dao.UserDao.findAll");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

测试结果:
Mybatis学习(二):Mybatis入门教程和简单应用_第3张图片

2.1.3 简单的CRUD栗子

2.1.3.1 查询

前边的例子已经展示过查询了

2.1.3.2 新增

  • 映射文件UserMapper.xml中新增插入方法:

    <insert id="add" parameterType="com.learn.pojo.User">
        insert into user values(#{id},#{name},#{password})
    </insert>
    
  • 编写测试方法:

        @org.junit.Test
    public void test2() throws IOException {
        //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.解析了配置文件,并创建了sqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.生产sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
        //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
        User user = new User();
        user.setId(2);
        user.setName("lisi");
        user.setPassword("123456");
        int insert = sqlSession.insert("com.learn.dao.UserDao.add", user);
        System.out.println(insert);
        //手动提交事务
        sqlSession.commit();
        sqlSession.close();
    }
    
  • 测试结果
    Mybatis学习(二):Mybatis入门教程和简单应用_第4张图片
    注意:

  1. 在映射文件中插入使用的是insert标签;
  2. 在映射文件中要使用parameterType属性指定插入的数据类型;
  3. 在映射文件的具体插入sql语句中#{实体类的属性名称}的方式引用入参;
  4. 使用sqlSession操作时,新增需要注意手动提交事务。

2.1.3.3 修改

  • 映射文件UserMapper.xml中新增修改方法:

    <update id="update" parameterType="com.learn.pojo.User">
        update user set name=#{name},password=#{password} where id=#{id}
    </update>
    
  • 编写测试方法:

       @org.junit.Test
    public void test3() throws IOException {
        //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.解析了配置文件,并创建了sqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.生产sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
        //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
        User user = new User();
        user.setId(2);
        user.setName("lisi2");
        user.setPassword("123456");
        int update = sqlSession.update("com.learn.dao.UserDao.update", user);
        //手动提交事务
        sqlSession.commit();
        sqlSession.close();
        System.out.println(update);
    }
    
  • 测试结果
    Mybatis学习(二):Mybatis入门教程和简单应用_第5张图片

2.1.3.4 删除

  • 映射文件UserMapper.xml中新增删除方法:

     <delete id="delete" parameterType="int">
        delete from user where id=#{id}
    </delete>
    
  • 编写测试方法:

      @org.junit.Test
    public void test4() throws IOException {
        //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.解析了配置文件,并创建了sqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.生产sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
        //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
        int delete = sqlSession.delete("com.learn.dao.UserDao.delete", 2);
        //手动提交事务
        sqlSession.commit();
        sqlSession.close();
        System.out.println(delete);
    }
    
  • 测试结果
    Mybatis学习(二):Mybatis入门教程和简单应用_第6张图片

2.1.4 Dao层实现的方式

2.1.4.1 传统的实现方式(实现类调用SqlSession)

  1. 新建UserDao接口类
    public interface UserDao {
    
        List<User> findAll();
    }
    
  2. 新建UserDaoImpl接口实现类
    public class UserDaoImpl implements UserDao {
    
        @Override
        public List<User> findAll() throws Exception {
            //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            //2.解析了配置文件,并创建了sqlSessionFactory工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            //3.生产sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
            //在进行增删改操作时,要手动提交事务
            //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
            List<User> users = sqlSession.selectList("com.learn.dao.UserDao.findAll");
            sqlSession.close();
            return users;
        }
    }
    
  3. 测试
        @org.junit.Test
        public void test5() throws Exception {
            UserDao userDao = new UserDaoImpl();
            List<User> userList = userDao.findAll();
            System.out.println(userList);
        }
    
  4. 测试结果
    Mybatis学习(二):Mybatis入门教程和简单应用_第7张图片

2.1.4.2 代理的实现方式

  1. 新建UserMapper接口类
    public interface UserMapper{
    
        List<User> findAll();
    }
    
  2. 修改UserMapper.xml文件
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.learn.dao.UserMapper">
        <!--namespace : 名称空间:与id组成sql的唯一标识
            resultType: 表明返回值类型-->
    
        <!--查询用户-->
        <select id="findAll" resultType="com.learn.pojo.User">
           select * from user
        </select>
    
        <insert id="add" parameterType="com.learn.pojo.User">
            insert into user values(#{id},#{name},#{password})
        </insert>
    
        <update id="update" parameterType="com.learn.pojo.User">
            update user set name=#{name},password=#{password} where id=#{id}
        </update>
    
        <delete id="delete" parameterType="int">
            delete from user where id=#{id}
        </delete>
    </mapper>
    
  3. 测试
        @org.junit.Test
        public void test6() throws Exception {
            //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            //2.解析了配置文件,并创建了sqlSessionFactory工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            //3.生产sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> users = userMapper.findAll();
            sqlSession.close();
            System.out.println(users);
        }
    
  4. 测试结果
    Mybatis学习(二):Mybatis入门教程和简单应用_第8张图片

注意:

  • Mapper.xml映射文件里的namespace必须与mappe接口的全限定名保持一致;
  • Mapper接口的每个方法名必须和Mapper.xml映射文件里的每个statement的id属性的值保持一致;
  • Mapper接口里方法的入参必须和mapper.xml映射文件里每个sql的parameterType类型保持一致;
  • Mapper接口里方法的返回值必须和mapper.xml映射文件里每个sql的resultType类型保持一致;

2.2 配置文件属性说明

2.2.1 XML配置文件(sqlMapConfig.xml)

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

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

2.2.1.1 properties(属性)

2.2.1.1.1 如何设置属性
  1. 在典型的 Java 属性文件中配置这些属性
  2. 在 properties 元素的子元素中设置

例子:

	<properties resource="config.properties">
        <property name="username" value="dev"/>
        <property name="password" value="dev"/>
    </properties>

    <!--environments:运行环境-->
    <environments default="dev">
        <environment id="dev">
            <!--当前事务交由JDBC进行管理-->
            <transactionManager type="JDBC"/>
            <!--当前使用mybatis提供的连接池-->
            <dataSource type="POOLED">
                <!--<property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
                <property name="driver" value="${driver}"/>
                <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false"/>
                <!--<property name="username" value="root"/>-->
                <!--<property name="password" value="xijian"/>-->
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

说明:

  1. 在外部配置文件中配置的属性或者在properties 元素的子元素中设置的属性,在整个配置文件中我们可以用来替换需要动态配置的属性值;
  2. Java中SqlSessionFactoryBuilder类提供了对应的api方法,可以在Java代码中配置属性。
    	public SqlSessionFactory build(Reader reader, Properties properties) {
            //。。。。。
        }
        public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        	//。。。。。
        }
    
2.2.1.1.2 MyBatis 属性加载顺序

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

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

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

如果属性 ‘username’ 没有被配置,‘username’ 属性的值将为 ‘ut_user’。

  1. 这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性。
    <properties resource="config.properties">
      <!-- 启用默认值特性 -->
      <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> 
    </properties>
    
  2. 如果在属性名中使用了 “:” 字符(如:db:username),或者在 SQL 映射中使用了 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : ‘global_constants’}),就需要设置特定的属性来修改分隔属性名和默认值的字符。
    <properties resource="org/mybatis/example/config.properties">
    	<!-- 修改默认值的分隔符 -->
    	<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> 
    </properties>
    
    <dataSource type="POOLED">
    	<!-- ... -->
    	<property name="username" value="${db:username?:ut_user}"/>
    </dataSource>
    

2.2.1.2 settings(设置)

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

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

具体说明参考:Mybatis官方文档

2.2.1.3 typeAliases(类型别名)

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

  1. 举个栗子:

        <typeAliases>
            <typeAlias alias="user" type="com.learn.pojo.User"/>
        </typeAliases>
    
  2. 也可以指定一个包下所有的Bean类

        <typeAliases>
            <!--<typeAlias alias="user" type="com.learn.pojo.User"/>-->
            <package name="com.learn.pojo"/>
        </typeAliases>
    

    com.learn.pojo包下所有的bean类如果没有注解,那么默认使用首字母小写的非限定类名来作为它的别名。有注解,则别名为其注解值。

    @Alias("user")
    public class User {
    	//。。。。
    }
    

2.2.1.4 typeHandlers(类型处理器)

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

MyBatis中有很多默认支持的类型处理器,具体参考:官方文档

我们可以重写已有的类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
具体做法为:
        实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。

  1. 创建自定义的类型处理器j
    // ExampleTypeHandler.java
    @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);
      }
    }
    
  2. 在mybatis-config.xml(mybatis的配置文件)中增加配置
    <typeHandlers>
      <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>
    

注意:

  1. 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType=“String”)MyBatis 可以得知该类型处理器处理的 Java 类型;在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType=“VARCHAR”)指定与其关联的 JDBC 类型列表。

  2. 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表;在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。

     ==上述的1,2两种配置方式,1的优先级高于2。==
    
  3. 我们可以通过指定包名的方式让 MyBatis 帮我们查找类型处理器

    <!-- mybatis-config.xml -->
    <typeHandlers>
      <package name="org.mybatis.example"/>
    </typeHandlers>
    

    注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。

2.2.1.5 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的配置文件中增加对应配置

<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
	<property name="someProperty" value="100"/>
</objectFactory>

objectFactory 元素体中定义的属性会被传递给 setProperties方法

2.2.1.6 plugins(插件)

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

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

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

@Intercepts({@Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {

    private Properties properties = new Properties();

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

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

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

在Mybatis配置文件中新增配置:

    <plugins>
        <plugin interceptor="com.learn.interceptor.ExamplePlugin">
            <property name="someProperty" value="100"/>
        </plugin>
    </plugins>

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

注意一些关键点:

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。
    默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
2.2.1.7.1 transactionManager(事务管理器)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    
2.2.1.7.2 dataSource(数据源)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

  • UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

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

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

  • POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
    除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

    • poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
    • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
    • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
    • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
    • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
    • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
    • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
    • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
  • JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:

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

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

2.2.1.7.3 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;
}

2.2.1.8 mappers(映射器)

告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

  1. 使用相对于类路径的资源引用
    <mappers>
      	<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    </mappers>
    
  2. 使用完全限定资源定位符(URL)
    	<mappers>
    		<mapper url="file:///var/mappers/AuthorMapper.xml"/>
    	</mappers>
    
  3. 使用映射器接口实现类的完全限定类名
    <mappers>
    	<mapper class="org.mybatis.builder.AuthorMapper"/>
    </mappers>
    
  4. 将包内的映射器接口实现全部注册为映射器
    <mappers>
    	<package name="org.mybatis.builder"/>
    </mappers>
    

2.2.2 XML映射文件(Mapper.xml)

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

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

2.2.2.1 select标签

Select 元素的属性

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

2.2.2.2 insert, update 和 delete标签

数据变更语句 insert,update 和 delete 的实现非常接近:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

Insert, Update, Delete 元素的属性:

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
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 的语句;如果带和不带的语句都有,则不带的会被忽略。

注意:

  1. 如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。
  2. 对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。
    <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 元素的属性:
    属性 描述
    keyProperty selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。
    keyColumn 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
    resultType 结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。
    order 可以设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
    statementType 和前面一样,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement, PreparedStatement 和 CallableStatement 类型。

2.2.2.3 sql标签

这个元素可以用来定义可重用的 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>

也可以在 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>

2.2.2.4 参数

2.2.2.4.1 字符串替换

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

提示:
用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。

2.2.2.5 结果映射

在没有显式的指定 resultMap时,可以使用resultType 属性指定,将所有的列映射到 HashMap的键上。

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

我们也可以显式使用外部的 resultMap,然后在引用它的语句中设置 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>

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

注意:
高级结果映射 请跳转 官方文档—结果映射

2.2.2.6 自动映射

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

例如:

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

例子中,id 和 userName 列将被自动映射,hashed_password 列将根据配置进行映射。

有三种自动映射等级:

  • NONE - 禁用自动映射。仅对手动映射的属性进行映射。
  • PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射
  • FULL - 自动映射所有属性。
    默认值是PARTIAL,这是有原因的。当对连接查询的结果使用 FULL 时,连接查询会在同一行中获取多个不同实体的数据,因此可能导致非预期的映射。

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

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

2.2.2.7 缓存

2.2.2.7.1 设置缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

这个简单语句的效果如下:

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

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

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

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

  1. 可用的清除策略有:默认的清除策略是 LRU。
    • LRU – 最近最少使用:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  2. flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  3. size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  4. readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
2.2.2.7.2 使用自定义缓存
<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
  1. type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。
  2. 为了对自定义的缓存进行配置,只需要简单地自定义的缓存实现中添加公有的 JavaBean属性,然后通过 cache 元素传递属性值,例如,上边的例子将在自定义的缓存实现上调用一个名为 setCacheFile(String file) 的方法。
  3. 也可以使用占位符(如 ${cache.file}),以便替换成在配置文件属性中定义的值。

提示:

  1. 上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。
  2. 缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。 因此,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。 默认情况下,语句会这样来配置:
    <select ... flushCache="false" useCache="true"/>
    <insert ... flushCache="true"/>
    <update ... flushCache="true"/>
    <delete ... flushCache="true"/>
    
2.2.2.7.3 cache-ref

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

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

2.3 动态 SQL

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

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

在 where 后的条件语句中根据多个参数动态判断时,使用多个if标签完成。

2.3.2 choose、when、otherwise 标签

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

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

在上面的例子中:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就使用featured = 1的条件。

2.3.3 trim、where、set 标签

  1. where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
  2. 如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
      ...
    </trim>
    
  3. 用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如下边两种等价的语句:
    <update id="updateAuthorIfNecessary">
      update Author
        <set>
          <if test="username != null">username=#{username},
          <if test="password != null">password=#{password},
          <if test="email != null">email=#{email},
          <if test="bio != null">bio=#{bio}
        </set>
      where id=#{id}
    </update>
    
    <trim prefix="SET" suffixOverrides=",">
      ...
    </trim>
    

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

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。

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

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

2.3.5 bind 标签

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

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

2.3.6 多数据库支持

如果配置了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>

2.3.7 动态 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)所提供。

三、彩蛋

本篇笔者描述了一下MyBatis的demo入门应用,配置文件属性说明及动态SQL。
下一篇笔者将讲解传统XML配置开发、注解开发、MyBatis缓存、MyBatis插件。

你可能感兴趣的:(orm框架,Java-base,#,Mybatis)