Mybatis 学习笔记

Mybatis 学习笔记

1IAqS0.png

1. 简介

  1. MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
  2. 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  3. 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

2. 一个简单的 MyBatis Demo

2.1 项目结构

1IATFs.png

  • JAVA
    • POJO 层:用于存放 POJO 对象,是对数据库中表的映射
    • DAO 层:用于存放对于数据库中数据的操作
      • 接口:每一个 POJO 类都有一个与之对应的 Mapper 接口,定义进行的操作
      • XML:每一个 Mapper接口都会有一个同名的 XML 文件,来定义操作的具体实现
    • UTILS 层:用于存放工具类,每次进行的操作都需要调用工具类的方法来初始化
  • RESOURCE
    • 用于存放 MyBatis 的核心配置文件以及与数据库相关的属性文件,etc.
  • TEST
    • 会建立一个与 JAVA 目录下目录结构相同的目录

2.2 组成文件

  • 在父项目中导入依赖

    <dependencies>
      <!--  MyBatis  -->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
      </dependency>
    
      <!--  数据库连接  -->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.18</version>
      </dependency>
    
      <!--  单元测试  -->
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13-rc-2</version>
        <scope>test</scope>
      </dependency>
    
      <!--  pojo类简化  -->
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
      </dependency>
    
      <!--  日志  -->
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
      </dependency>
    </dependencies>
    
    <!--  文件过滤  -->
    <build>
      <resources>
        <resource>
          <directory>src/main/java</directory>
          <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
          </includes>
          <filtering>false</filtering>
        </resource>
        <resource>
          <directory>src/main/resources</directory>
          <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
          </includes>
          <filtering>false</filtering>
        </resource>
      </resources>
    </build>
    
  • MyBatis 核心配置文件 -- 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  :核心配置文件  -->
    <configuration>
      <environments default="development">
        <environment id="development">
    
          <transactionManager type="JDBC"/>
          <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="biggun@815"/>
          </dataSource>
        </environment>
      </environments>
    
      <mappers>
        <mapper resource="com/biggun/dao/userMapper.xml"/>
      </mappers>
    </configuration>
    
  • 工具类

    package com.biggun.utils;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.jdbc.SQL;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    // sqlSessionFactory:用来获取 sqlSession
    public class MyBatisUtils {
      private static SqlSessionFactory sqlSessionFactory;
    
      static {
        try {
          // 使用MyBatis第一步:创建 SqlSessionFactory 对象
          String resource = "mybatis-config.xml";
          InputStream inputStream = Resources.getResourceAsStream(resource);
          sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    
      /** 既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
       * SqlSession 完全包含了面向数据库执行 org.apache.ibatis.jdbc.SQL 命令所需的所有方法。
       * 故可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
       */
      public static SqlSession getSqlSession() {
        // sqlSessionFactory.openSession() 方法存在重载
        // 默认事务不会自动提交,默认值为 false
        // 可加入参数 true 打开事务的自动提交
        return sqlSessionFactory.openSession();
      }
    }
    
  • POJO 类 -- User

    package com.biggun.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
      private int id;
      private String name;
      private String pwd;
    }
    
  • POJO 类映射接口 -- UserMapper

    package com.biggun.dao;
    
    import com.biggun.pojo.User;
    
    import java.util.List;
    import java.util.Map;
    
    public interface UserMapper {
      List<User> getAllUser();
      User getUser(int id);
      User getUser_FuzzyMatching(String name);
    
      int insertUser(User user);
    
      // 非常规方法,使用 Map 进行自定义插入,要求配置自定义名称与使用名称一致
      int insertUser1(Map<String, Object> map);
    
      int updateUser(User user);
    
      int deleteUser(int id);
    }
    
  • 接口配置文件 -- 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.UserMapper">
        <select id="getAllUser" resultType="com.User">
            select * from mybatis.user
        </select>
    
        <select id="getUser_FuzzyMatching" parameterType="java.lang.String" resultType="com.User">
            select * from mybatis.user WHERE name LIKE #{name};
        </select>
    
        <select id="getUser" resultType="com.User" parameterType="int">
            select * from mybatis.user WHERE id = #{id}
        </select>
    
        <insert id="insertUser" parameterType="com.User">
            INSERT INTO mybatis.user(id, pwd) VALUES (#{id}, #{name},#{pwd})
        </insert>
    
        <insert id="insertUser1" parameterType="map">
            INSERT INTO mybatis.user(id, pwd) VALUES (#{Id},#{Password})
        </insert>
    
        <update id="updateUser" parameterType="com.User">
            UPDATE mybatis.user SET name = #{name},pwd = #{pwd} WHERE id = #{id}
        </update>
    
        <delete id="deleteUser" parameterType="int">
            DELETE FROM mybatis.user WHERE id = #{id}
        </delete>
    </mapper>
    
  • 测试代码

    package com.biggun.dao;
    
    import com.biggun.pojo.User;
    import com.biggun.utils.MyBatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class UserMapperTest {
      @Test
      public void test1() {
        // 1. 获取 SqlSession 对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        // 2. 执行sql语句
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        for (User user : mapper.getAllUser()) {
          System.out.println("user = " + user);
        }
        // 3. 关闭连接
        sqlSession.close();
      }
    
      @Test
      public void getUser() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        System.out.println("mapper.getUser(1) = " + mapper.getUser(1));
        sqlSession.close();
      }
    
      @Test
      public void getUser_FuzzyMatching() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(mapper.getUser_FuzzyMatching("%" + "F" + "%"));
        sqlSession.close();
      }
    
      @Test
      // 进行增、删、改的操作的时候,需要提交事务,操作才会执行
      public void insert() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.insertUser(new User(4, "哈哈", "123456"));
        sqlSession.commit();
        sqlSession.close();
      }
    
      @Test
      public void insert1() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("Id", 6);
        map.put("Password", "123456");
        mapper.insertUser1(map);
        sqlSession.commit();
        sqlSession.close();
      }
    
      @Test
      public void update() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        System.out.println("mapper.getUser(1) = " + mapper.updateUser(new User(4, "达瓦", "134679")));
        sqlSession.commit();
        sqlSession.close();
      }
    
      @Test
      public void delete() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        System.out.println("mapper.getUser(1) = " + mapper.deleteUser(5));
        sqlSession.commit();
        sqlSession.close();
      }
    }
    

3. XML 属性

3.1 核心配置文件属性

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

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

下面是官网提供的最基础的核心文件

<?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>
  <environments default="development">
    <environment id="development">
      <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>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

3.2 配置分析 -- mybatis-config.xml

1. properties(属性)
  • 属性都是可以通过应用外部配置文件实现灵活替换的
  • 例如通过将标签内 resource 属性设置为 db.properties 实现灵活配置数据库连接
<properties resource="db.properties"/>

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

  • 在 properties 元素体内指定的属性首先被读取。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
2. settings(设置)
  • 此处只记录常用类型,以及常用有效值,详见官网
属性名描述默认值有效值
cacheEnabled全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存truetrue|false
mapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。falseFalse
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。未设置LOG4J|STUDOUT_LOGGIN
  • 一个配置完整的 setting 元素的示例如下
<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>

使用Log4J日志的配置文件如下

# 将等级为 DEBUG 的日志输出到 console 和 file 这两个目的地,console 和 file 的定义在下面的代码
log4j.rootLogger = DEBUG, console, file
# 控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPatten = [%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/biggun.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3. typeAliases(类型别名)
  • 一般在描述结果集映射时,其返回值类型 resultType 或者参数类型 parameterType 需要精确到类型的确切包名,如

    <insert id="insertUser" parameterType="com.biggun.pojo.User">
      INSERT INTO mybatis.user(id, pwd) VALUES (#{id}, #{name},#{pwd})
    </insert>
    
  • 为了加快开发速度,可在 typeAliases 下的属性中设置别名,当再次被引用时只需填写别名即可

  • 别名可以通过纯 XML 文件设置,也可以通过 XML + 注解 的形式设置

    • 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>
    
    • XML + 注解
    <typeAliases>
      <package name="domain.blog"/>
    </typeAliases>
    
    @Alias("author")
    public class Author {
        ...
    }
    

下面列举了常用的类型及其别名

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

4. environments(环境配置)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。有许多类似的使用场景。

  • 存在属性 default 用于选择测试环境
<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>

5. transactionManager(事务管理器)

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

  • JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
  • MANAGED - 这个配置几乎没做什么。
  • 总之只要知道这个玩意不止有一个就可以了

如果你正在使用 Spring+ MyBatis,则没有必要配置事务管理器, 因为 Spring 模块会使用自带的管理器来覆盖前面的配置。


6. dataSource(数据源)

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

  • 许多 MyBatis 的应用程序会按示例中的例子来配置数据源。虽然这是可选的,但为了使用延迟加载,数据源是必须配置的。

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

主要看一下 POOLED
1IA7Yn.png
即是一个连接池,可设置来连接池的相关属性

其余的 UNPOOLED 即为无连接池,还有一个可以不用管


7. 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>
    
    • 使用这种方式的时候 接口映射器 需要和 接口 需要放在同一个包下
  • 方法二

    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    
    • 使用这种方法开启注解,就可以使用注解代替 XML 对接口定义的操作进行实现
    @Select("SELECT * FROM mybatis.user WHERE id = ${id}")
    User getUser(int id);
    
    // 若方法存在多个参数,则必须加上 @Param(value = String)
    // 且注解方式查询不支持方法重载
    @Select("SELECT * FROM mybatis.user WHERE id = ${id} AND name = '${name}'")
    User getUser1(@Param("id") int id, @Param("name") String name);
    
    // 编辑 SQL 语句时
    // 使用 #{name} 可以无需再在语句中加'',直接使用
    // 而使用 ${name} 时则需要加上 '',变成 '${name}'
    @Insert("INSERT INTO mybatis.user(id, name, pwd) VALUES (#{id}, #{name}, #{pwd})")
    boolean insertUser(@Param("id") int id, @Param("name") String name, @Param("pwd") String pwd);
    
    @Delete("DELETE FROM mybatis.user WHERE id = #{id}")
    boolean deleteUser(@Param("id") int id);
    
    List<User> userList(Map<String, Integer> map);
    

4. XML映射文件

MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。

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

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

4.1 SELECT

查询语句是 MyBatis 中最常用的元素之一,光能把数据存到数据库中价值并不大,只有还能重新取出来才有用,多数应用也都是查询比修改要频繁。

下面列举一下几种常见情况

POJO类如下

package com.biggun.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
  private int id;
  private String name;
  private String pwd;
}
1. 属性
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
resultType从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。
resultMap外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache将其设置为 true 后,将会导致本条语句的结果被==二级缓存==缓存起来,默认值:对 select 元素为 true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
fetchSize这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
statementTypeSTATEMENT,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这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。
2. 按照 id 查找对象
public interface UserMapper {
  User getUser(@Para("id") int id);
}
<select id="getUser" parameterType="_int" resultType="com.biggun.pojo.User">
  select * from mybatis.user WHERE id = #{id}
</select>
3. 按照字符串进行模糊查询
public interface UserMapper {
	User getUser_FuzzyMatching(String name);
}
<select id="getUser_FuzzyMatching" parameterType="java.lang.String" resultType="com.biggun.pojo.User">
  select * from mybatis.user WHERE name LIKE #{name};
</select>

测试代码:

@Test
public void getUser_FuzzyMatching() {
  SqlSession sqlSession = MyBatisUtils.getSqlSession();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  System.out.println(mapper.getUser_FuzzyMatching("%" + "F" + "%"));
  sqlSession.close();
}

结果如下:

1IA4eg.png

4. 使用注解进行插入
  • 优点:无需编写 xml ,直接编写 SQL
  • 缺点:只能执行简单的 SQL 语句,对于需要结果集映射的查询就无能为力了
public interface UserMapper {
  @Select("SELECT * FROM mybatis.user WHERE id = ${id} AND name = '${name}'")
  User getUser1(@Param("id") int id, @Param("name") String name);
}

4.2 INSERT、UPDATE、DELETE

1、属性
属性描述
id命名空间中的唯一标识符,可被用来代表这条语句。
parameterType将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
flushCache将其设置为 true 后,==只要语句被调用,都会导致本地缓存和二级缓存被清空==,默认值:true(对于 insert、update 和 delete 语句)。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
statementTypeSTATEMENT,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 的语句;如果带或者不带的语句都有,则不带的会被忽略。
2. 插入用户
public interface UserMapper {
	boolean insertUser(User user);
}

MyBatis 可以自动匹配对象的属性与 SQL 语句中的值

<insert id="insertUser" parameterType="user">
  INSERT INTO mybatis.user(id, pwd) VALUES (#{id}, #{name},#{pwd})
</insert>
3. 使用 map 进行插入

当我们想要插入的值既不包括对象的所有属性,又不是单个属性时,就可以使用 map 进行插入

public interface UserMapper {
	boolean insertUser(Map<String, Object> map);
}
<insert id="insertUser1" parameterType="map">
  INSERT INTO mybatis.user(id, pwd) VALUES (#{Id},#{Password})
</insert>

测试代码:

@Test
public void insert1() {
  SqlSession sqlSession = MyBatisUtils.getSqlSession();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  Map<String, Object> map = new HashMap<>();
  map.put("Id", 6);
  map.put("Password", "123456");
  mapper.insertUser1(map);
  sqlSession.commit();
  sqlSession.close();
}

4.3 结果映射

1. 简单的结果集映射

当数据库表中的字段名与 POJO 类中的属性名不一致的时候,就需要进行结果映射

用例如下

public class User {
  private int id;
  private String name;
  // 更改 pojo 类属性 pwd --> password
  private String password;
}

则在编写 XML 的时候,需要使用 resultMap 属性来映射结果,且只需要设置需要启用别名的属性即可

  • 属性
    • property :对应 POJO 类中的属性
    • column :对应 SQL 语句中搜索出来的字段名
<resultMap id="User" type="user">
  <!--<id column="id" property="id"/>-->
  <!--<id column="name" property="name"/>-->
  <!--  只需要设置需要启用别名的属性即可  -->
  <id column="pwd" property="password"/>
</resultMap>

<select id="getUser" resultMap="User" parameterType="_int">
  select * from mybatis.user WHERE id = #{id}
</select>
2. 多对一结果集映射

当我们需要进行联表查询的时候常常需要用到结果集映射,此次实验用例复杂一些,且介绍两种方法

  • POJO 类
public class Student {
  private int id;
  private String name;
  private Teacher teacher;
}
public class Teacher {
  private int id;
  private String name;
}
  • 映射器接口
public interface StudentMapper {
  // 查询所有学生的信息以及对应的老师的信息
  List<Student> getStudent();
}
  • 根据结果查询

    • association
      • 匹配对象类型的属性
    • 在匹配的时候需要将匹配对象的所有属性都加入子匹配,否则对象的该属性将为默认值
    <select id="getStudent" resultMap="teacherStudent">
      SELECT student.id AS sid, student.name AS sname, teacher.id AS tid
      #              , teacher.name AS tname
      FROM mybatis.student
      INNER JOIN mybatis.teacher ON student.tid = teacher.id
    
```
  • 根据目标查询

    <select id="getStudent" resultMap="teacherInStudent">
      SELECT * FROM mybatis.student
    </select>
    
    <resultMap id="teacherInStudent" type="com.biggun.pojo.Student">
      <result property="id" column="id"/>
      <result property="name" column="name"/>
      <association property="teacher" column="tid" javaType="com.biggun.pojo.Teacher" select="getTeacher"/>
    </resultMap>
    
    <select id="getTeacher" resultType="com.biggun.pojo.Teacher">
      SELECT * FROM mybatis.teacher WHERE id = #{id}
    </select>
    
3. 一对多结果集映射

同样是两种查询方式

  • 映射器接口
public interface TeacherMapper {
  // 获取老师
  Teacher getTeachers(@Param("teacherId") int id);
}
  • 根据结果查询
    • 复杂的属性单独处理 -->
      • association:对象
        • javaType:指定对象的类型
      • collection:集合
        • ofType:指定集合中对象的类型
<select id="getTeachers" resultMap="map1">
  SELECT teacher.id AS tid, teacher.name AS tname, student.id AS sid, student.name AS sname
  FROM mybatis.teacher
  INNER JOIN mybatis.student ON teacher.id = student.tid
  WHERE teacher.id = #{teacherId}
</select>

<resultMap id="map1" type="com.biggun.pojo.Teacher">
  <result property="id" column="tid"/>
  <result property="name" column="tname"/>
  <!--  复杂的属性单独处理  -->
  <!--  association:对象 javaType:指定对象的类型  -->
  <!--  collection:集合  ofType:指定集合中对象的类型  -->
  <collection property="studentList" ofType="com.biggun.pojo.Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
  </collection>
</resultMap>
  • 根据目标查询
<select id="getTeachers" resultMap="map">
  SELECT * FROM mybatis.teacher WHERE id = #{teacherId}
</select>

<resultMap id="map" type="com.biggun.pojo.Teacher">
  <collection property="studentList" javaType="ArrayList" ofType="Student" select="getStudentList" column="id"/>
</resultMap>

<select id="getStudentList" resultType="com.biggun.pojo.Student">
  SELECT * FROM mybatis.student WHERE tid = #{id}
</select>

5. 动态 SQL

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

动态 SQL 主要使用以下几个标签

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

1. if

动态 SQL 通常要做的事情是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了一种可选的查找文本功能。如果没有传入“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”,那么就会对“title”一列进行模糊查找并返回 BLOG 结果(细心的读者可能会发现,“title”参数值是可以包含一些掩码或通配符的)。

如果希望通过“title”和“author”两个参数进行可选搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

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

2. choose, when, otherwise

有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找的情形,若两者都没有提供,就返回所有符合条件的 BLOG(实际情况可能是由管理员按一定策略选出 BLOG 列表,而不是返回大量无意义的随机结果)。

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

3. trim, where, set

前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在回到“if”示例,这次我们将“ACTIVE = 1”也设置成动态的条件,看看会发生什么。

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

如果这些条件没有一个能匹配上会发生什么?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单地用条件句式来解决,如果你也曾经被迫这样写过,那么你很可能从此以后都不会再写出这种语句了。

MyBatis 有一个简单的处理,这在 90% 的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作。一处简单的修改就能达到目的:

<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 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。

如果 where 元素没有按正常套路出牌,我们可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

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

prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容。

类似的用于动态更新语句的解决方案叫做 setset 元素可以用于动态包含需要更新的列,而舍去其它的。比如:

<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 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号。(译者注:因为用的是“if”元素,若最后一个“if”没有匹配上而前面的匹配上,SQL 语句的最后就会有一个逗号遗留)

若你对 set 元素等价的自定义 trim 元素的代码感兴趣,那这就是它的真面目:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意这里我们删去的是后缀值,同时添加了前缀值。

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 是值。

6. 缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

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

<cache/>

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

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

6.1 一级缓存

一级缓存是缓存在会话进行期间的,当 SqlSession 被关闭时, 一级缓存将被清空。

而在此期间,执行查询所获得的数据将进入一级缓存,再次查询相同内容的时候将直接从缓存中读取数据,而不从数据库读取。

@Test
public void getTeacher() {
  SqlSession sqlSession = MybatisUtils.getSqlSession();
  TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
  for (Student student : mapper.getTeacher(1)) {
    System.out.println("student = " + student);
  }
  System.out.println("========================================");
  for (Student student : mapper.getTeacher(1)) {
    System.out.println("student = " + student);
  }
  sqlSession.close();
}

1IAHWq.png

6.2 二级缓存

二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

即当一级缓存清空之前会提交到二级缓存,其中的数据由二级缓存保存

如何使用二级缓存?

二级缓存默认是关闭的,要使用时只需要在映射文件中加上一个

<cache/>

此外,当使用二级缓存未配置 readOnly = true 时 POJO 类还需要序列化

public class User implements Serializable {
  private int id;
  private String name;
  private String pwd;
}

属性

这些属性可以通过 cache 元素的属性来修改。比如:

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

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

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。