wyl1924

1.Mybatis

MyBatis 原本是apache的一个开源项目iBatis,2010年这个项目由Apache Software Foundation迁移到了Google Code,并且改名为MyBatis,2013年11月迁移到GitHub。它是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射(ORM),支持XML或者注解来配置和映射原生类型、接口和java的POJO(Plain Old Java Objects,普通老式的Java对象)数据库中的记录。。

2.下载MyBatis

2.1.github

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

image-20210816153745328

2.2Maven仓库

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

3.入门

3.1驱动与依赖

采用mavenhttps://search.maven.org/ 查询相关的驱动。

image-20210816171241868

<dependencies>
    <!--dependency>
      <groupId>org.wyl</groupId>
      <artifactId>[the artifact id of the block to be mounted]</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency-->
    <!-- mysql驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.18</version>
    </dependency>
    <!-- mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.5</version>
    </dependency>
    <!-- 整合log4j -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.6.4</version>
    </dependency>
      <!-- 测试junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

右击pom.xml->meave 重新加载项目。包下载完即可。

将 mybatis-x.x.x.jar 文件置于 classpath 中即可。

3.2.MyBatis的功能架构

3.2.1.架构

img

API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

  • 参数映射(参数解析和参数绑定):获取并解析映射文件中的 Statement标签及其属性(parameterType)。也就是解析SQL语句并为 SQL语句准备需要进行绑定的参数
  • SQL解析:对 Statement【 <select><update><delete><insert>】标签中的内容进行解析、拼接、封装,最后得到一个完整的带有占位符的 SQL语句。也就是 JDBC中的准备 SQL语句的过程
  • 结果映射(结果集解析和结果集处理):获取配置文件中的结果集类型,并进行类型转换,将ResultSet进行结果映射。也就是 JDBC中处理结果集的步骤。

基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

  • 管理 Mybatis与数据库的连接方式

  • 管理 Mybatis的事务

  • 加载 配置文件

  • Mybatis 查询缓存

3.2.2.全局配置文件 mybatis-config

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- configuration标签 => 声明MyBatis核心配置 -->
<configuration>
    <!-- environments标签 => 设置MyBatis选用的环境信息 -->
    <environments default="development">
        <environment id="development">
            <!-- transactionManager标签 => 事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- dataSource标签 => 配置数据源属性 -->
            <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/WylMapper.xml"/>
    </mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- configuration标签 => 声明MyBatis核心配置 -->
<configuration>
    <!-- environments标签 => 设置MyBatis选用的环境信息 -->
    <environments default="development">
        <environment id="development">
            <!-- transactionManager标签 => 事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- dataSource标签 => 配置数据源属性 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/wyl?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=CST"/>
                <property name="username" value="nps"/>
                <property name="password" value="123.nps@zst"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mappers/userMapper.xml"/>
    </mappers>

</configuration>

3.2.3.SqlSessionFactory 会话工厂

  • 通过Mybatis的配置信息,使用 SqlSessionFactoryBuilder构建器,来构建会话工厂对象

  • SqlSessionFactory 创建了 Configuration对象,使用 Configuration对象来构建SqlSession会话工厂

  • SqlSessionFactoryBuilder构建器使用了 Builder构建者设计模式

  • SqlSessionFactory 会话工厂,使用了工厂设计模式

    public class MyBatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            try {
                // 定义XML核心配置文件路径信息
                String resource = "mybatis-config.xml";
                // 读取XML核心配置文件路径信息
                InputStream inputStream = Resources.getResourceAsStream(resource);
                // 获得实例化SQLSessionFactory
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    	//调用SqlSessionFactory.openSession()方法,返回SqlSession对象
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }
    

3.2.4.SqlSession接口方法

程序通过SqlSession来操作数据库

  • SqlSession对外提供了一整套的增删改查的api,通过api来操作数据库
  • SqlSession还能够获取动态的Mapper接口
  • SqlSession的作用域是方法级别的,也就是从创建到销毁必须保证在方法内完成,注意一定要在方法内部销毁sqlSession,千万不要忘记
  • sqlSession在使用完后一定要及时关闭,尤其是在Service层,会在一个方法中同时使用多个sqlSession,每个sqlSession在用完后最好在下一行代码就关闭,避免影响其他sqlSession。
//从 SqlSessionFactory 中获取 SqlSession
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UsersDao mapper = sqlSession.getMapper(UsersDao.class);
List<Users> usersInfo = mapper.getUsersInfo();

也可以

try (SqlSession session = sqlSessionFactory.openSession()) {
  User user = (User) session.selectOne("org.mybatis.example.WylMapper.getUsersById",12);
  }

3.2.5.pojo层

对应的数据库表的实体类

package com.wyl.mybatis.pojo;

/**
 * @创建人 王延领
 * @创建时间 2021/8/16
 * 描述
 **/
public class Users {
    private int id;
    private String username;
    private String password;
    private String email;
    private int gender;

    public Users() {
    }

    public Users(int id, String username, String password, String email, int gender) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Users{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                '}';
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }
}

快速生成构造函数get set 等的方法,空白处右击生成即可

image-20210823153814340

3.2.6. dao层

与数据库交互相关代码,对应Mybatis的mapper接口

package com.wyl.mybatis.dao;
import com.wyl.mybatis.pojo.Users;
import java.util.List;
import java.util.Map;
/**
 * @创建人 王延领
 * @创建时间 2021/8/18
 * 描述
 **/
public interface UserDao {
    // 【select】所有用户信息
    List<Users> getUsersInfo();

    // 【select】指定用户信息
    Users getUserInfoById(int id);

    // 【update】指定用户信息
    int updateUseInfoById(Users user);

    // 【insert】指定用户信息
    int insertUser(Users user);

    // 【delete】指定用户信息
    int deleteUserById(int id);

    // 【insert】 批量用户信息
    int insertManyUseList(List<Users> users);

    // 【select】 模糊查询
    List<Users> getUsersInfoByPhantomSelect(String username);
}

3.2.7.mapper

image-20210823155011994

userMapper.xml配置对应的数据库操作映射,在mybatis-config进行注册。

<!--如图所示配置-->
<mappers>
    <mapper resource="mappers/usersMapper.xml"/>
</mappers>

mapper如下。

<?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】: 指定dao层,绑定Dao -->
<mapper namespace="com.wyl.mybatis.dao.UserDao">
    <!-- select sql: 绑定getUsersInfo方法,返回所有用户信息【id】: 绑定Dao中的方法名
				【resultType】: 指定对应【类的形式】返回结果集的类型 -->
    <select id="getUsersInfo" resultType="com.wyl.mybatis.pojo.Users">
        select * from users
    </select>
  </mapper>

测试:

 @Test
    public void getUsersInfo() {
        // 调用MyBatisUtils.getSqlSession()方法,获取SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        // 调用获取到的SQLSession对象中的getMapper对象
        // 反射Dao接口,动态代理Dao接口中的方法,并将这些方法存在对象【mapper】中
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        // 调用mapper中对应方法,并设置对应的对象来接收其返回结果
        // 以下为测试方法getUsersInfo() => 获取所有Users表中信息,并用对应类接收
        List<Users> usersInfo = mapper.getUsersInfo();
        // for循环遍历输出List集合
        for (Users users : usersInfo) {
            System.out.println(users);
        }
        // 关闭sqlSession
        sqlSession.close();
    }

3.3.作用域与生命周期

image-20210823173846319

3.3.1.SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

3.3.2.SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。类似于数据库连接池。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

3.3.3.SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

3.3.4.Mapper

是一种创建的用于绑定映射语句的接口,Mapper 接口的实例是用 SqlSession 来获得的。同样,从技术上来说,最广泛的 Mapper 实例作用域像 SqlSession 一样,使用请求作用域。确切地说,在方法被调用的时候调用 Mapper 实例,然后使用后,就自动销毁掉不需要使用明确的注销。当一个请求执行正确无误的时候,像 SqlSession 一样,你可以轻而易举地操控这一切。保持简单性,保持 Mapper 在方法体作用域内。

4.xml 配置解析

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

<?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>
    <!-- 属性:定义配置外在化 -->
    <properties></properties>
    <!-- 设置:定义mybatis的一些全局性设置 -->
    <settings>
       <!-- 具体的参数名和参数值 -->
       <setting name="" value=""/> 
    </settings>
    <!-- 类型名称:为一些类定义别名 -->
    <typeAliases></typeAliases>
    <!-- 类型处理器:定义Java类型与数据库中的数据类型之间的转换关系 -->
    <typeHandlers></typeHandlers>
    <!-- 对象工厂 -->
    <objectFactory type=""></objectFactory>
    <!-- 插件:mybatis的插件,插件可以修改mybatis的内部运行规则 -->
    <plugins>
       <plugin interceptor=""></plugin>
    </plugins>
    <!-- 环境:配置mybatis的环境 -->
    <environments default="">
       <!-- 环境变量:可以配置多个环境变量,比如使用多数据源时,就需要配置多个环境变量 -->
       <environment id="">
          <!-- 事务管理器 -->
          <transactionManager type=""></transactionManager>
          <!-- 数据源 -->
          <dataSource type=""></dataSource>
       </environment> 
    </environments>
    <!-- 数据库厂商标识 -->
    <databaseIdProvider type=""></databaseIdProvider>
    <!-- 映射器:指定映射文件或者映射类 -->
    <mappers></mappers>
</configuration>

image-20210823161021441

​ 图片来自官网

4.1.属性(properties)

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置.

比如第三章的配置文件我们可以如下写法

<!-- properties标签 => 读取外部properties文件 -->
    <properties resource="dataSource.properties">
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </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将会由properties元素中设置的相应值来替换。driver和url属性将会由dataSource.properties文件中对应的值来替换。这样就为配置提供了诸多灵活选择。

image-20210823162924181

属性也可以被传递到SqlSessionBuilder.build()方法中。

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props);
// ... or ...
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, props);

但是,这也就涉及到了优先级的问题,如果属性不只在一个地方配置,那么mybatis将会按照下面的顺序来加载:
1.在properties元素体内指定的属性首先被读取。

  1. 然后根据properties元素中的resource属性读取类路径下属性文件或根据url属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  2. 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

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

从 MyBatis 3.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>

4.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 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

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

4.3.类型别名(typeAliases)

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

 <typeAlias alias="user" type="com.wyl.mybatis.pojo.Users"></typeAlias>

​ 当这样配置时,user 可以用在任何使用 com.wyl.mybatis.pojo.Users 的地方。

在使用注解的时候也可以指定一个包名,再使用其类的时候直接小写类名即可。.比如:

<!--除非使用注解,否则不支持自定义别名-->
<package name="com.wyl.mybatis.dao"/>

4.4.映射器(mappers)

定义SQL映射语句,指定MyBatis寻找SQL语句。

    1. 使用相对于类路径的资源引用 【推荐】:

      <mappers>
         <mapper resource="mappers/userMapper.xml"/>
      </mappers>
      
    2. 使用映射器接口实现类的完全限定类名

      <mappers>
        <mapper class="com.wyl.mybatis.dao.UsersDao"/>
      </mappers>
      
    3. 将包内的映射器接口实现全部注册为映射器(注意相对位置)

      <mappers>
        <package name="com.wyl.mybatis.dao"/>
      </mappers>
      
    4. 使用完全限定资源定位符(URL) 【不推荐使用】

      <mappers>
        <mapper url="file:///var/mappers/usersMapper.xml"/>
      </mappers>
      

4.5.环境配置(environments)

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

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。

 <environments default="oracle">
        <environment id="mysql">
         <!--mysql 配置-->
        </environment>
        <environment id="oracle">
             <!--oracle 配置-->
        </environment>
    </environments>

4.6.事务管理器(transactionManager)

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

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

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

  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    

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

这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。

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

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

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

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

4.7.数据源(dataSource)

  • 【官方声明】:dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 【官方声明】:大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

  • 【官方声明】:有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")

    • UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
    • POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
    • JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
  • MyBatis默认数据源类型 => 【POOLED】

  • 数据源类型: dbcp c3p0 druid hikari

4.8.对象工厂(objectFactory)

MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现.
自定义对象工厂
ObjectFactory是个接口类,其默认实现类是DefaultObjectFactory。在 MyBatis 中,默认的DefaultObjectFactory要做的就是实例化查询结果对应的目标类,有两种方式可以将查询结果的值映射到对应的目标类:一种是通过目标类的默认构造方法,另外一种就是通过目标类的有参构造方法。

4.8.1.自定义对象工厂

MyBatis允许注册自定义的ObjectFactory,只需要实现接口 org.apache.ibatis.reflection.factory.ObjectFactory即可。但是在大部分的情况下,我们都不需要自定义ObjectFactory对象工厂,只需要继承系统已经实现好的 DefaultObjectFactory ,通过一定的改写来完成我们所需要的工作。

有时候在新建一个新对象(构造方法或者有参构造方法),在得到对象之前需要处理一些逻辑,或者在执行该类的有参构造方法时,在传入参数之前,要对参数进行一些处理,这时就可以创建自己的 ObjectFactory 来加载该类型的对象。如下所示,增加了日志打印功能:

public class MyObjectFactory extends DefaultObjectFactory
{
    private static final long serialVersionUID = 1L;
    Logger log = Logger.getLogger(MyObjectFactory.class);
    private Object temp = null;
    @Override
    public void setProperties(Properties properties)
    {
        super.setProperties(properties);
    }
    @Override
    public <T> T create(Class<T> type)
    {
        T result = super.create(type);
        log.info("创建对象:" + result.toString());
        return result;
    }
    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
    {
        T result = super.create(type, constructorArgTypes, constructorArgs);
        log.info("创建对象:" + result.toString());
        return result;
    }
    @Override
    public <T> boolean isCollection(Class<T> type)
    {
        return super.isCollection(type);
    }

然后,需要在 SqlMapConfig.xml 全局配置文件中配置该自定义对象工厂即可,代码如下:

<objectFactory type="cn.mybatis.mydemo.MyObjectFactory">
    <property name="key" value="value" />
</objectFactory>

这样 MyBatis 就会采用配置的 MyObjectFactory 来生成结果集对象,采用下面的代码进行测试。

public class MyBatisDemo
{
    public static void main(String[] args) throws IOException
    {
        Logger log = Logger.getLogger(MyBatisDemo.class);
        InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
        SqlSession session = factory .openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.getUser(1L);  
    }
}

4.9.插件

4.9.1.插件的分类

第一类:插件是对系统的一种补充,例如在分布式系统中,可以使用插件的方式,实现内存插件、磁盘插件、线性网络插件、Paxos插件等。此类插件等同于组件。

第二类:插件是对系统默认功能的自定义修改,例如mybatis里面自定义插件,它实现的拦截器的功能。此类插件等同于拦截器。

4.9.2.MyBatis拦截器插件

MyBatis允许用户在已映射语句执行过程中的某一点进行拦截调用。MyBatis使用插件来拦截的方法调用,故此MyBatis插件通常称为:Mybatis拦截器。默认情况下,MyBatis允许使用插件来拦截的对象包括下面的四大金刚:

  • Executor:MyBatis的执行器,用于执行增删改查操作
  • ParameterHandler:处理SQL的参数对象
  • ResultSetHandler:处理SQL的返回结果集
  • StatementHandler:数据库的处理对象,用于执行SQL语句

在Java里面,我们想拦截某个对象,只需要把这个对象包装一下,用代码行话来说,就是重新生成一个代理对象。

4.9.2.注解实例

@Intercepts({
    @Signature(
            type=Executor.class,
            method="query",
            args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
    )
})
public class MyInterceptor implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }
 
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }
 
    public void setProperties(Properties arg0) {}
 

@Intercepts 拦截器注解,此注解声明此类是一个插件类。其中可以声明多个 @Signature 签名信息注解,type 为拦截的方法所属的接口类型,method 为拦截的方法名称,args 是参数信息。

intercept 方法是一个对目标方法进行拦截的抽象方法,而 plugin 方法的作用是将拦截器插入目标对象。

setProperties 方法的作用是将全局配置文件中的参数注入插件类中。

MyBatis 全局配置文件中配置该插件即可

<plugins>
    <plugin interceptor="com.wyl.mybatis.unit.MyInterceptor"></plugin>
</plugins>

4.10.typeHandlers(类型处理器)

在JDBC中,需要在PreparedStatement对象中设置那些已经预编译过的SQlL语句的参数。执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些在Mybatis是根据数据的类型通过typeHandler来实现的。

在typeHandler中,分为jdbcType和javatype,其中jdbcType用于定义数据库类型,而javaType用于定义Java类型,那么typeHandler的作用就是承担jdbcTypr和javaType之间的相互转换。

image-20210824103050041

和别名一样,在 MyBatis 中存在系统定义 typeHandler 和自定义 typeHandler。MyBatis 会根据 javaType 和数据库的 jdbcType 来决定采用哪个 typeHandler 处理这些转换规则。系统提供的 typeHandler 能覆盖大部分场景的要求,但是有些情况下是不够的,比如我们有特殊的转换规则,枚举类就是这样。

4.10.1.系统定义的typeHandler

image-20210824104446532

image-20210824104028080

这些就是MyBatis 系统已经创建好的typeHandler。在大部分的情况下无须显式地声明jdbcType 和javaType ,或者用typeHandler 去指定对应的typeHandler 来实现数据类型转换,因为MyBatis 系统会自己探测。有时候需要修改一些转换规则,比如枚举类往往需要自己去编写规则。

public interface TypeHandler<T> {
  // 使用typeHandler通过PreparedStatement对象进行设置SQL参数的时候使用的具体方法。其中:ps是PreparedStatement对象;i是参数在SQL语句中的下标;jdbcType是数据库类型
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  //JDBC结果集中获取数据进行转换,使用列名(columnName)或下标(columeIdex)获取数据库的数据
  T getResult(ResultSet rs, String columnName) throws SQLException;
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
   //存储过程专用
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

4.10.2.自定义TypeHandler

从系统定义的typeHandler中可以知道,要实现typeHandler就需要去实现接口Typehandler,或者继承BaseTypeHandler(实际上,BaseTypehandler实现了typeHandler接口)

实现TypeHandler

public class MyTypeHandler implements TypeHandler<String>{
	//定义一个日志
	Logger log = Logger.getLogger(MyTypeHandler.class);

	@Override
	public String getResult(ResultSet rs, String columnName) throws SQLException {
		String result = rs.getString(columnName);
		log.info("读取string参数1【"+result+"】");
		return result;
	}

	@Override
	public String getResult(ResultSet rs, int columnIdex) throws SQLException {
		String result = rs.getString(columnIdex);
		log.info("读取string参数2【"+result+"】");
		return result;
	}

	@Override
	public String getResult(CallableStatement cs, int columnIdex) throws SQLException {
		String result = cs.getString(columnIdex);
		log.info("读取string参数3【"+result+"】");
		return result;
	}

	@Override
	public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
		log.info("设置string参数【"+parameter+"】");
		ps.setString(i, parameter);
	}
}

在代码中使用自定义typeHandler

<!-- 类型转换器 -->
	<typeHandlers>
		<typeHandler handler="com.wyl.mybatis.utils.MyTypeHandler" jdbcType="VARCHAR" javaType="string"/>
	</typeHandlers>

or

<resultMap id="roleMapper" type="role">
    <result property="id" column="id"/>
    <result property="roleName" column="role_name" jdbcType="VARCHAR" javaType="string"/>
    <result property="note" column="note" typeHandler="com.wyl.mybatis.utils.MyTypeHandler"/>
</resultMap>

<select id="getRole" parameterType="long" resultMap="roleMapper">
    select id,role_name,note from t_role where id = #{id}
</select>

有时候配置的typeHandler太多,也可以使用包扫描的方式

<typeHandlers>      
    <package name="com.wyl.mybatis.unit.typeHandler"/>
</typeHandlers>
@MappedJdbcTypes(JdbcType.VARCHAR)   // 表示把数据库中的varchar类型转成java的String类型时使用该转换器
@MappedTypes(String.class)
public class MyTypeHandler implements TypeHandler<String>{
     
    Logger logger = Logger.getLogger(MyTypeHandler.class);
 
    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        String result = rs.getString(columnName);
        logger.info("读取string参数1【" + result +"】");
        return result;
    }
 
    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("读取string参数2【" + result +"】");
        return result;
    }
 
    @Override
    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("读取string参数3【" + result +"】");
        return result;
    }
 
    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        logger.info("设置string参数【" + parameter + "】");
        ps.setString(i, parameter);
    }
}

4.10.3.枚举TypeHandler

在绝大多数情况下,typeHandler 因为枚举而使用,MyBatis 已经定义了两个类作为枚举类型的支持,这两个类分别是:

  • EnumOrdinalTypeHandler。使用整数下标作为参数传递的(枚举类型的默认转换类)

        <resultMap id="userMapper" type="user">
            <result property="id" column="id" />
            <result property="userName" column="user_name" />
            <result property="password" column="passsword" />
            <result property="sex" column="sex"
                typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler" />
        </resultMap>
        <select id="getUser" resultMap="userMapper" parameterType="long">
            select id,user_name,password,sex,mobile,tel,email,note from myUser
            where id=#{id}
        </select>
    
    
  • EnumTypeHandler。使用枚举字符串名称作为参数传递的

    <resultMap id="userMapper" type="com.mybatis.po.User">
            <result property="id" column="id" />
            <result property="userName" column="user_name" />
            <result property="password" column="passsword" />
            <result property="sex" column="sex"
                typeHandler="org.apache.ibatis.type.EnumTypeHandler" />
        </resultMap>
        <select id="getUser" resultMap="userMapper" parameterType="long">
            select id,user_name,password,sex,mobile,tel,email,note from myUser
            where id=#{id}
        </select>
    

4.10.4.BlobTypeHandler读取Blob字段

MyBatis 对数据库的 Blob 字段也进行了支持,它提供了一个 BlobTypeHandler,为了应付更多的场景,它还提供了 ByteArrayTypeHandler,只是它不太常用.

create table file(
    id int(12) not null auto_increment,
    content blob not null,
    primary key(id)
);
//pojo
public class TestFile{
    long id;
    byte[] content;
    /** setter and getter **/
}
 <resultMap type="com.ssm.chapter5.pojo.TestFile" id="file">
        <id column="id" property="id"/>
        <id column="content" property="content" typeHandler="org.apache.ibatis.type.BlobTypeHandler"/>
    </resultMap>

4.11.数据库厂商标识(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;
}

5.映射器

5.1.Select

编写接口dao
User getUserById(int id);

编写对应的mapper中的sql语句,注意要写在对应的mapper下

<!--
id:对应的dao接口
resultType:sql语句执行的返回值
parameterType : 参数类型
User:为别名
-->
<select id="getUserById" parameterType="int" resultType="user">
        select * from mybatis.user where id=#{id}
</select>
@Test
    public void getUserInfoById(){
        // 调用MyBatisUtils.getSqlSession()方法,获取SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        // 调用获取到的SQLSession对象中的getMapper对象
        // 反射Dao接口,动态代理Dao接口中的方法,并将这些方法存在对象【mapper】中
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        Users user = mapper.getUserInfoById(2);
        System.out.println(user);
        // 关闭sqlSession
        sqlSession.close();
    }

查询所有

 // 【select】所有用户信息
    List<Users> getUsersInfo();
<!-- select sql: 绑定getUsersInfo方法,返回所有用户信息 -->
    <select id="getUsersInfo" resultType="com.camemax.com.camemax.pojo.Users">
        select * from school.users
    </select>
 @Test
    public void getUsersInfo(){

        // 调用MyBatisUtils.getSqlSession()方法,获取SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        // 调用获取到的SQLSession对象中的getMapper对象
        // 反射Dao接口,动态代理Dao接口中的方法,并将这些方法存在对象【mapper】中
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);

        // 调用mapper中对应方法,并设置对应的对象来接收其返回结果
        // 以下为测试方法getUsersInfo() => 获取所有Users表中信息,并用对应类接收
        List<Users> usersInfo = mapper.getUsersInfo();
        // for循环遍历输出List集合
        for (Users users : usersInfo) {
            System.out.println(users);
        }
        // 关闭sqlSession
        sqlSession.close();

    }

5.2.insert

插入单条细腻

  // 【insert】指定用户信息
    int insertUser(Users user);
 <!-- insert sql: 绑定insertUser方法,插入单个用户信息-->
    <insert id="insertUser" parameterType="user" >
        insert into users
        values (#{id},#{username},#{password},#{email},#{gender})
    </insert>
 @Test
    public void insertUsers(){
        // 调用MyBatisUtils.getSqlSession()方法,获取SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        // 调用获取到的SQLSession对象中的getMapper对象
        // 反射Dao接口,动态代理Dao接口中的方法,并将这些方法存在对象【mapper】中
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        int i = mapper.insertUser(
                new Users(2, "wyl", "123456", "171@qq.com", 0)         
        );
        //提交事务
        sqlSession.commit();
        if ( i > 0 ){
            System.out.println("Insert 成功!");
        }
        // 关闭sqlSession
        sqlSession.close();
    }

插入多条数据

    // 【insert】 批量用户信息
    int insertManyUseList(List<Users> users);
<!-- insert sql: 绑定insertManyUseMap,批量插入 -->
    <insert id="insertManyUseList" >
        insert into users values
        /* foreach 标签:
            -【item】属性: 表示集合中每一个元素进行迭代时的别名
            - 【collection】属性: 参数类型是一个List的时候,collection属性值为list
            - 【separator】属性: 表示在每次进行迭代之间以什么符号作为分隔符。
        */
        <foreach  item="user" collection="list" separator=",">
            (#{user.id},#{user.username},#{user.password},#{user.email},#{user.gender})
        </foreach>
    </insert>
 @Test
    public void insertManyUseList(){

        List<Users> users = new ArrayList<Users>();
        users.add(new Users(2, "wyl", "123", "123@qq.com", 20));
        users.add(new Users(3, "wjm", "123456", "123456@qq.com", 30));
        users.add(new Users(4, "王延领", "1123456", "wjm@qq.com", 41));
        users.add(new Users(5, "王经墨", "223", "wjm@qq.com", 51));
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        int i = mapper.insertManyUseList(users);
        if ( i > 0 ){
            System.out.println("插入成功过!");
        }
        sqlSession.commit();

        sqlSession.close();
    }

5.3.1.主键回填

如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了

 <insert id="insertUser" useGeneratedKeys="true"  keyProperty="id">
        insert into users
        values (#{id},#{username},#{password},#{email},#{gender})
    </insert>

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

5.3.2.自定义主键

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

<insert id="insertUser" useGeneratedKeys="true"  keyProperty="id">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
    select if(max(id)==null,1,max(id)+2) from users
  </selectKey>
        insert into users
        values (#{id},#{username},#{password},#{email},#{gender})
    </insert>

5.3.update

// 【update】指定用户信息
    int updateUseInfoById(Users user);
 <!-- update sql: 绑定updateUser方法,更新指定用户信息 -->
    <update id="updateUseInfoById" parameterType="users">
        update users
        set username = #{username},
            password = #{password},
            email = #{email},
            gender = #{gender}
        where id = #{id}
    </update>
@Test
    public void updateUseInfoById(){

        SqlSession session = MyBatisUtils.getSqlSession();
        UsersDao mapper = session.getMapper(UsersDao.class);
        int i = mapper.updateUseInfoById(new Users(1, "王延领", "123456", "171@qq.com", 1));
        if ( i > 0 ){
            System.out.println(mapper.getUserInfoById(1).getUsername() + " 修改成了!");
        }
        session.commit();
        session.close();
    }

5.4.delete

// 【delete】指定用户信息
    int deleteUserById(int id);
 <!-- delete sql: 绑定deleteUserById方法,删除指定用户信息 -->
    <delete id="deleteUserById" parameterType="int">
        delete from users
        where id = #{id}
    </delete>
@Test
    public void deleteUserInfoById(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        String willDeleteUsername = mapper.getUserInfoById(2).getUsername();
        int i = mapper.deleteUserById(2);

        if (i > 0){
            System.out.println(willDeleteUsername + " 已删除!");
        }
        sqlSession.commit();
        sqlSession.close();
    }

5.5.模糊查询like

// 【select】 模糊查询
    List<Users> getUsersInfoByPhantomSelect(String username);
<!-- select sql: 绑定getUsersInfoByPhantomSelect,模糊查询 -->
    <select id="getUsersInfoByPhantomSelect" resultType="Users">
        select * from users where username like #{username}
    </select>
 @Test
    public void getUsersInfoByPhantomSelect(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UsersDao mapper = sqlSession.getMapper(UsersDao.class);
        List<Users> users = mapper.getUsersInfoByPhantomSelect("%e%");
        for (Users user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

5.6.sql

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。

<sql id='userCols'> user_name,pwd</sql>
 <select id="getUsersInfoByPhantomSelect" resultType="Users">
        select <include refid='userCols' from school.users where username like #{username}
    </select>

5.7.映射结果(resultMap)

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

5.7.1结构

<!--元素的 type 属性表示需要的 POJO,id 属性是 resultMap 的唯一标识-->
<resultMap id="" type="">
    <constructor><!-- 类再实例化时用来注入结果到构造方法 -->
        <idArg/><!-- ID参数,结果为ID -->
        <arg/><!-- 注入到构造方法的一个普通结果 -->  
    </constructor>
    <id/><!-- 用于表示哪个列是主键 -->
    <result/><!-- 注入到字段或JavaBean属性的普通结果 -->
    <association property=""/><!-- 用于一对一关联 -->
    <collection property=""/><!-- 用于一对多、多对多关联 -->
    <discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
        <case value=""/><!-- 基于某些值的结果映射 -->
    </discriminator>
</resultMap>

5.7.2.使用 Map 存储结果集

任何 select 语句都可以使用 Map 存储结果,示例代码如下:

<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap" resultType="map">
    select * from user
</select>
@Test
// 查询所有用户信息存到Map中
List<Map<String, Object>> lmp = userDao.selectAllUserMap();
for (Map<String, Object> map : lmp) {
    System.out.println(map);
}

上述 Map 的 key 是 select 语句查询的字段名(必须完全一样),而 Map 的 value 是查询返回结果中字段对应的值

5.7.3.使用POJO存储结果集

Map 用起来很方便,但可读性稍差,有的开发者不太喜欢使用 Map,更多时候喜欢使用 POJO 的方式。

package com.wyl.mybatis.pojo;
public class User {
    private Integer m_uid;
    private String m_uname;
    private String m_usex;
    // 此处省略setter和getter方法
    @Override
    public String toString() {
        return "User[uid=" + m_uid + ",uname=" + m_uname + ",usex=" + m_usex
                + "]";
    }
}
 <!-- 查询指定用户信息 -->
    <resultMap id="com.wyl.mybatis.pojo.User" type="users">
        <!-- 类属性【userId】映射为数据库中的【id】字段 -->
        <id property="userId" column="id"/>
        <!-- 类属性【userName】映射为数据库中的【name】字段 -->
        <result property="userName" column="name" />
        <!-- 类属性【userPasswd】映射为数据库中的【password】字段 -->
        <result property="userPasswd" column="password" />
    </resultMap>

 <!-- 【resultMap】属性指向<resultMap>标签 -->
    <select id="getUsersInfo" resultType="MyBatisAliasUsers" >
        select * from users
    </select>
@Test
// 使用resultMap映射结果集
List<User> listResultMap = userDao.selectResultMap();
for (User myUser : listResultMap) {
    System.out.println(myUser);
}

5.8.分页

5.8.1.使用limit分页

//分页
List<User> getUserByLimit(Map<String,Integer> map);
<!--分页
语法:
SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3;  #[0,3)
-->
<select id="getUserByLimit" parameterType="map" resultMap="User">
    select * from user limit #{startIndex},#{pageSize}
 </select>
//分页
@Test
public void getUserByLimit(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper =sqlSession.getMapper(UserMapper.class);
    Map<String,Integer> map=new HashMap<String, Integer>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> userList=mapper.getUserByLimit(map);
    for (User user: userList) {
        System.out.println(user);
     }
    sqlSession.close();
}

5.8.2.RowBounds分页

//分页2
List<User> getUserByRowBounds();
<select id="getUserByRowBounds" resultMap="User">
    select * from user
</select>
//分页2
@Test
public void getUserByRowBounds(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    //RowBounds
    RowBounds rowBounds = new RowBounds(1, 2);
    List<User> userList = sqlSession.selectList("com.wyl.mybatis.dao.UserMapper.getUserByRowBounds",null,rowBounds);
    for (User user: userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

5.8.3.使用分页插件

官网地址:https://pagehelper.github.io/

image-20210824093737129

5.9.级联查询

级联关系是一个数据库实体的概念,有 3 种级联关系,分别是一对一级联、一对多级联以及多对多级联

5.9.1.一对一关联查询

在 MyBatis 中,通过 元素的子元素 处理这种一对一级联关系。

元素中通常使用以下属性。

  • property:指定映射到实体类的对象属性。
  • column:指定表中对应的字段(即查询返回的列名)。
  • javaType:指定映射到实体对象属性的类型。
  • select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
public class Teacher {
    private int tid;
    private String tname;
    private Classes classes;
     
    public int getTid() {
        return tid;
    }
    public void setTid(int tid) {
        this.tid = tid;
    }
    public String getTname() {
        return tname;
    }
    public void setTname(String tname) {
        this.tname = tname;
    }
    public Classes getClasses() {
        return classes;
    }
    public void setClasses(Classes classes) {
        this.classes = classes;
    }
    @Override
    public String toString() {
        return "Teacher [tid=" + tid + ", tname=" + tname + ", classes=" + classes + "]";
    }
     
     
}
public class Classes {
    private int cid;
    private String cname;
    private Teacher teacher;
     
    public int getCid() {
        return cid;
    }
    public void setCid(int cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    @Override
    public String toString() {
        return "Classes [cid=" + cid + ", cname=" + cname + ", teacher=" + teacher + "]";
    }
<?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="one.to.one.classesMapper">
    <!--
         方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
                 封装联表查询的数据(去除重复的数据)
         select * from classes c, teacher t where c.tid=t.tid and c.tid=#{tid}
     -->
    <select id="getClasses" resultMap="getClassesMap" parameterType="int">
        select * from classes c ,teacher t
            where c.tid=t.tid and c.tid=#{tid}
    </select>
    <resultMap type="one.to.one.Classes" id="getClassesMap">
        <id column="cid" property="cid"/>
        <result column="cname" property="cname"/>
        <association property="teacher" javaType="one.to.one.Teacher">
            <id column="tid" property="tid"></id>
            <result column="tname" property="tname"/>
        </association>
    </resultMap>
    <!--
         方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
                 封装联表查询的数据(去除重复的数据)
         select * from teacher t,classes c where t.cid = c.cid and t.cid=#{cid}
     -->
    <select id="getTeacher" resultMap="getTeacherMap" parameterType="int">
        select * from teacher t,classes c
            where t.cid = c.cid and t.cid=#{cid}
    </select>
    <resultMap type="one.to.one.Teacher" id="getTeacherMap">
        <id column="tid" property="tid"/>
        <result column="tname" property="tname"/>
        <association property="classes" javaType="one.to.one.Classes">
            <id column="cid" property="cid"/>
            <result column="cname" property="cname"/>
        </association>
    </resultMap>
     
     
    <!--
         方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型
         SELECT * FROM classes WHERE cid=1;
         SELECT * FROM teacher WHERE tid=1   //1 是上一个查询得到的tid的值
         property:别名(属性名)    column:列名 -->
          <!-- 把teacher的字段设置进去 -->
    <select id="getClasses2" resultMap="getClassesMap2">
        select * from classes c where c.cid = #{cid}
    </select>
    <resultMap type="one.to.one.Classes" id="getClassesMap2">
        <id column="cid" property="cid"/>
        <result column="cname" property="cname"/>
        <collection property="teacher" column="tid" select="getTeacherCollection">
        </collection>
    </resultMap>
    <select id="getTeacherCollection" resultType="one.to.one.Teacher">
        select tid tid,tname tname from teacher where tid=#{tid}
    </select>
   
</mapper>

我们这里一对一的关联操作,有两种方式:

    1、使用嵌套结果映射来处理重复的联合结果的子集

    2、通过执行另外一个SQL映射语句来返回预期的复杂类型

 //一对一嵌套结果方式:根据教师id查询班级信息
    @Test
    public void testGetClasses(){
        String statement = "one.to.one.classesMapper.getClasses";
        Classes c = session.selectOne(statement, 1);
        System.out.println(c);
    }
     
    //一对一嵌套结果方式:根据班级id查询教师信息
    @Test
    public void testGetTeacher(){
        String statement = "one.to.one.classesMapper.getTeacher";
        Teacher t = session.selectOne(statement, 1);
        System.out.println(t);
    }
     
    //一对一嵌套查询方式:根据教师id查询班级信息
    @Test
    public void testGetClasses2(){
        String statement = "one.to.one.classesMapper.getClasses2";
        Classes c = session.selectOne(statement, 1);
        System.out.println(c);
    }

5.9.2. 多对一查询

  • SQL返回的值需要使用到类时的处理方式

  • 模拟测试:多个学生对应一个老师

    1. MySQL测试表【Teachers】、【Students】
    2. 测试实体类【Teachers】、【Students】
    3. dao层【TeachersMapper】、【StudentsMapper】
    4. XML映射文件【teachersMapper.xml】、【studentsMapper.xml】
    5. 核心配置文件=>【mybatis-config.xml】绑定dao接口、注册XML映射文件
    6. 输出测试
  • 整体目录结构

    image-20200829123326705

5.9.2.1 环境搭建

MySQL创建测试数据

use school;

#教师表
DROP TABLE IF exists teachers;
create table teachers(
	`tid` int(10),
	`tname` varchar(20) DEFAULT NULL,
	PRIMARY KEY (`tid`)
	)ENGINE=INNODB DEFAULT CHARSET=utf8;

#学生表
DROP TABLE IF exists students;
create table students(
	`id` int(10) ,
	`name` varchar(20) DEFAULT NULL,
	`tid` int(10) DEFAULT NULL,
	PRIMARY KEY (`id`),
	CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teachers` (`tid`)	
	)ENGINE=INNODB DEFAULT CHARSET=utf8;
	
	insert into teachers (`tid`,`tname`) values (1,'卡梅克斯');
	
	insert into students (`id`,`name`,`tid`) values (1,'小红',1);
	insert into students (`id`,`name`,`tid`) values (2,'小黄',1);
	insert into students (`id`,`name`,`tid`) values (3,'小黑',1);
	insert into students (`id`,`name`,`tid`) values (4,'小白',1);
	insert into students (`id`,`name`,`tid`) values (5,'小紫',1);

5.9.2.2 实体类与接口

  • 学生相关

    • 【Students】实体类

      package com.camemax.pojo;
      import org.apache.ibatis.type.Alias;
      
      @Alias("students")
      public class Students {
          private int sid;
          private String sname;
          // 添加【Teachers】类属性
          private Teachers teacher;
      
          public Students() {};
      
          public Students(int sid, String sname, Teachers teacher) {
              this.sid = sid;
              this.sname = sname;
              this.teacher = teacher;
          }
      
          public int getSid() {
              return sid;
          }
      
          public void setSid(int sid) {
              this.sid = sid;
          }
      
          public String getSname() {
              return sname;
          }
      
          public void setSname(String sname) {
              this.sname = sname;
          }
      
          public Teachers getTeacher() {
              return teacher;
          }
      
          public void setTeacher(Teachers teacher) {
              this.teacher = teacher;
          }
      
          @Override
          public String toString() {
              return "Students{" +
                      "sid=" + sid +
                      ", sname='" + sname + '\'' +
                      ", teacher=" + teacher +
                      '}';
          }
      }
      
    • 【StudentsMapper】接口

      package com.camemax.dao;
      
      import com.camemax.pojo.Students;
      import java.util.List;
      
      public interface StudentsMapper {
      	//查询所有学生信息,同时输出教师信息
          List<Students> getStudentsInfo();
      }
      
      
  • 教师相关

    • 【Teachers】实体类

      package com.camemax.pojo;
      
      import org.apache.ibatis.type.Alias;
      
      @Alias("teachers")
      public class Teachers {
          private int tid;
          private String tname;
      
          public Teachers() {};
      
          public Teachers(int tid, String tname) {
              this.tid = tid;
              this.tname = tname;
          }
      
          public int getTid() {
              return tid;
          }
      
          public void setTid(int tid) {
              this.tid = tid;
          }
      
          public String getTname() {
              return tname;
          }
      
          public void setTname(String tname) {
              this.tname = tname;
          }
      
          @Override
          public String toString() {
              return "Teachers{" +
                      "tid=" + tid +
                      ", tname='" + tname + '\'' +
                      '}';
          }
      }
      
    • 【TeachersMapper】接口

      package com.camemax.dao;
      
      public interface TeachersMapper {
      }
      

5.9.2.3 Mapper映射器

  • mybatis-config.xml

    <configuration>
        <properties resource="db.properties"/>
    
        <settings>
            <setting name="logImpl" value="LOG4J"/>
        </settings>
    
        <typeAliases>
            <package name="com.camemax.pojo"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${propDriver}"/>
                    <property name="url" value="${propUrl}"/>
                    <property name="username" value="${propUsername}"/>
                    <property name="password" value="${propPassword}"/>
                </dataSource>
            </environment>
        </environments>
        <!-- 注册Mapper-->
        <mappers>
            <mapper resource="mapper/studentsMapper.xml"/>
            <mapper resource="mapper/teachersMapper.xml"/>
        </mappers>
    </configuration>
    

5.9.2.4. 按查询嵌套处理【子查询】

  • studentsMapper.xml

     <!-- 按查询嵌套处理 -->
    <select resultMap="StudentsInfoMapBySelect" id="getStudentsInfo">
        select * from school.students
    </select>
    <resultMap id="StudentsInfoMapBySelect" type="students">
        <id property="sid" column="id"/>
        <result property="sname" column="name"/>
    
        <!-- 复杂类型: Teachers类
                【association】: 对象
                    - 【property】: 设置获取到的结果集字段 => private Teachers teacher
                    - 【column】: 设置映射对应的数据库字段 => tid
                    - 【javaType】: 设置返回类型 => Teachers
                    - 【select】: 子查询绑定。通过其他<select>标签中的值,指向其他select语句 => <select id="TeachersInfo">
                【collection】: 集合
            -->
        <association property="teacher" column="tid" javaType="Teachers" select="TeachersInfo"/>
    </resultMap>
    
    <!-- 查询指定教师信息 -->
    <select id="TeachersInfo" resultType="teachers">
        select * from school.teachers where tid = #{tid}
    </select>
    
    
    
  • teachersMapper.xml

    <mapper namespace="com.wyl.mybatis.dao.TeachersMapper">
    </mapper>
    

5.9.2.5 按结果嵌套处理【关联】

  • studentsMapper.xml

    <!-- 按结果嵌套处理 -->
    <select id="getStudentsInfo" resultMap="getStudentsInfoByResult">
        select s.id studentId,
        s.name studentName,
        t.tname teacherName
        from students s,teachers t
        where s.tid = t.tid;
    </select>
    <resultMap id="getStudentsInfoByResult" type="students">
        <id property="sid" column="studentId"/>
        <result property="sname" column="studentName"/>
        <association property="teacher" javaType="Teachers">
            <result property="tname" column="teacherName"/>
        </association>
    </resultMap>
    
  • teachersMapper.xml

    <mapper namespace="com.wyl.mybatis.dao.TeachersMapper">
    </mapper>
    

5.9.3. 一对多查询

  • 模拟测试:一名老师有多名学生 => 【面向教师】
  • 本质:使用<collection>标签完成一对多的输出

5.9.3.1. 基于[12.1环境搭建](#12.1 环境搭建)做出的修改

  1. dao层 => 【TeachersDao】

    package com.camemax.dao;
    
    import com.camemax.pojo.Students;
    import com.camemax.pojo.Teachers;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    public interface TeachersMapper {
    	// 传入指定教师编号,返回其下学生信息
        List<Students> getTeacherByIdHasStudents(@Param("tid") int tid);
    }
    
    
  2. 实现类 => 【Teachers】

    package com.camemax.pojo;
    
    import org.apache.ibatis.type.Alias;
    
    import java.util.List;
    
    @Alias("teachers")
    public class Teachers {
        private int tid;
        private String tname;
        // 新增属性 : 教师拥有的学生
        private List<Students> teacherHasStudents;
    
        public List<Students> getTeacherHasStudents() {
            return teacherHasStudents;
        }
    
        public void setTeacherHasStudents(List<Students> teacherHasStudents) {
            this.teacherHasStudents = teacherHasStudents;
        }
    
        public Teachers(int tid, String tname, List<Students> teacherHasStudents) {
            this.tid = tid;
            this.tname = tname;
            this.teacherHasStudents = teacherHasStudents;
        }
    
        public Teachers() {};
    
        public int getTid() {
            return tid;
        }
    
        public void setTid(int tid) {
            this.tid = tid;
        }
    
        public String getTname() {
            return tname;
        }
    
        public void setTname(String tname) {
            this.tname = tname;
        }
    
        @Override
        public String toString() {
            return "Teachers{" +
                    "tid=" + tid +
                    ", tname='" + tname + '\'' +
                    ", teacherHasStudents=" + teacherHasStudents +
                    '}';
        }
    }
    
  3. 实体类 => 【Students】

    package com.camemax.pojo;
    
    import org.apache.ibatis.type.Alias;
    
    @Alias("students")
    public class Students {
        private int sid;
        private String sname;
        private int tid;
    
        public Students(){};
        
        public Students(int sid, String sname, int tid) {
            this.sid = sid;
            this.sname = sname;
            this.tid = tid;
        }
    
        @Override
        public String toString() {
            return "Students{" +
                    "sid=" + sid +
                    ", sname='" + sname + '\'' +
                    ", tid=" + tid +
                    '}';
        }
    
        public int getSid() {
            return sid;
        }
    
        public void setSid(int sid) {
            this.sid = sid;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        public int getTid() {
            return tid;
        }
    
        public void setTid(int tid) {
            this.tid = tid;
        }
    }
    
  4. 测试实现类 => 【DaoTest】

    @Test
    public void getStudentsByTid(){
        MyBatisUtils mybatis = new MyBatisUtils();
        SqlSession sqlSession = mybatis.getSqlSession();
        TeachersMapper mapper = sqlSession.getMapper(TeachersDao.class);
        
        System.out.println(mapper.getStudentsByTid(1));
      	sqlSession.close();
    }
    

5.9.3.2. 按查询嵌套处理 【子查询】

  1. XML映射文件 => teachersMapper.xml

    <select id="getStudentsByTid" resultMap="getStudentsByTidMapUseSelect">
        select * from school.teachers where tid = #{tid}
    </select>
    
    <!-- 创建【getStudentsByTidMapUseSelect】映射结果集,实现一对多结果返回。 
    	注意:Teachers类 使用了 @Alias("teachers") 
    -->
    <resultMap id="getStudentsByTidMapUseSelect" type="teachers">
        <id property="tid" column="tid" />
        <result property="tname" column="name" />
        <!-- Teachers类中新增List<Students> teacherHasStudents属性字段 
    			javaType: 指定在java中的字段类型属性
    			ofType: 指定类型所属类
    			select: 使resultMap绑定指定<select>标签
    			column: 使resultMap传递指定的属性字段
    	-->
        <collection property="teacherHasStudents" javaType="ArrayList" ofType="students" select="getStudentsByTid" column="tid"/> 
    </resultMap>
    
    <!-- 子查询:学生信息 -->
    <select id="getStudentsByTid" resultMap="studentsMap">
    	select * from school.students where tid = #{tid}
    </select>
    
    <!-- 创建【studentsMap】,映射Students类中,与Teachers表字段不一致的属性字段 -->
    <resultMap id="studentsMap" type="students">
        <id property="sid" column="id" />
        <result property="sname" column="name"/>
     	<!-- 不加会导致字段【tid】结果为0 -->   
        <result property="tid" column="tid" />
    </resultMap>
    
  2. 输出结果

    // 按查询嵌套处理 => 子查询 结果:
    [Teachers{tid=1, tname='卡梅克斯', teacherHasStudents=[Students{sid=1, sname='小红', tid=1}, Students{sid=2, sname='小黄', tid=1}, Students{sid=3, sname='小黑', tid=1}, Students{sid=4, sname='小白', tid=1}, Students{sid=5, sname='小紫', tid=1}]}]
    

5.9.3.3. 按结果嵌套处理 【关联查询】

  1. XML映射文件 => teachersMapper.xml

    <select id="getTeacherByIdHasStudents" resultMap="teacherGetStudentsByResult">
        select s.id studentId,s.name studentName,s.tid,t.tname teacherName,t.tid
        from students s,teachers t
        where s.tid = t.tid
        and t.tid = #{tid}
    </select>
    <resultMap id="teacherGetStudentsByResult" type="teachers">
        <id property="tid" column="tid"/>
        <result property="tname" column="teacherName"/>
        <collection property="teacherHasStudents" ofType="students">
            <id property="sid" column="studentId"/>
            <result property="sname" column="studentName"/>
            <result property="tid" column="tid" />
        </collection>
    </resultMap>
    
  2. 测试结果

    // 按结果嵌套处理 => 关联查询 结果:
    [Teachers{tid=1, tname='卡梅克斯', teacherHasStudents=[Students{sid=1, sname='小红', tid=1}, Students{sid=2, sname='小黄', tid=1}, Students{sid=3, sname='小黑', tid=1}, Students{sid=4, sname='小白', tid=1}, Students{sid=5, sname='小紫', tid=1}]}]
    

5.10.缓存

如果每次查询都连接数据库 ,耗资源!一次查询的结果,给他暂存在一个可以直接取到的地方!–> 内存 : 缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了

所以经常查询又不常改变的数据可以使用缓存,减少和数据库的交互次数,减少系统开销,提高系统效率.

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存.

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

5.10.1.一级缓存

也叫本地缓存: SqlSession.

  1. 一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

  2. 一级缓存就是一个Map

  3. 在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;

  4. 不同的 SqlSession 之间的缓存是相互隔离的;

  5. 用一个 SqlSession, 可以通过配置使得在查询前清空缓存;

  6. 任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。

5.10.2.二级缓存

  1. 级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  2. 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  3. 工作机制
    3.1.一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    3.2.如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    3.3.新的会话查询信息,就可以从二级缓存中获取内容;
    3.4.不同的mapper查出的数据会放在自己对应的缓存(map)中;

步骤:

开启全局缓存

<!--显示的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>

在要使用二级缓存的Mapper中开启

<!--在当前Mapper.xml中使用二级缓存-->
<cache/>

也可以自定义参数

<!--在当前Mapper.xml中使用二级缓存-->
<cache  eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中;
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓冲中

6.动态sql

动态 SQL 是 MyBatis 的强大特性之一。

MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

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

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

6.2.choose、when、otherwise

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

6.3.trim、where、set

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

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

6.4.script

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

6.5.bind

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

6.6.trim

<select id="queryBlogsByTrim" parameterType="blogs">
	select * from test.blog
    <trim prefix="WHERE" prefixOverride="AND |OR ">
        <if test="titleMap != null"> AND title = #{titleMap}</if>
        <if test="authorMap != null"> OR author = #{authorMap}</if>
    </trim>
</select>
<update id="updateBlogInfoByTrim" parameterType="map">
	update test.blog
    <trim prefix="SET" suffixOverride=",">
        <if test="titleMap != null"> title = #{titleMap},</if>
        <if test="authorMap != null"> author = #{authorMap},</if>
    </trim>
    where id = #{idMap}
</update>

6.7.多数据库支持

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

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

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

MyBatis 从 3.2 版本开始支持插入脚本语言,这允许你插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。

可以通过实现以下接口来插入一种语言:

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

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

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

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

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

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

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

7.注解

image-20210824160741551

优缺点:

  • 优点:省去复杂的mapper映射器中的sql代码相关配置
  • 缺点:无法执行复杂的SQL,例如:存在字段异常不匹配时,使用注解执行SQL容易出现找不到值的情况(查询结果为'null')
 @Select("select * from school.users where id = #{userId}")
    Users getUserInfoById(@Param("userId") int id);

    @Insert("insert into school.users value(#{id},#{username},#{password},#{email},#{gender})")
    int insertUserInfo(@Param("userId") int id
            ,@Param("userName") String username
            ,@Param("userPassword") String password
            ,@Param("userEmail") String email
            ,@Param("userGender") int gender
    );

    @Delete("delete from school.users where id = #{userId}")
    int deleteUserInfoById(@Param("userId") int id);

    @Update("update school.users set username = #{userName} , password = #{userPassword} , email = #{userEmail} , gender = #{userGender} where id = #{userId}")
    int updateUserInfoById(
            @Param("userId") int id
            ,@Param("userName") String username
            ,@Param("userPassword") String password
            ,@Param("userEmail") String email
            ,@Param("userGender") int gender
    );

8.日志

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

在mybatis-config.xml中我们已经设置了日志的默认值为STDOUT_LOGGING标准日志输出

 <!--配日志-SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

测试输出

image-20210824182641931

8.1.Log4J

  • Log4j是Apache的一个开源项目,通过使用Log4j,可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;
  • 控制每一条日志的输出格式;通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  1. 导入Maven依赖

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  2. 映射器开启日志功能

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

#将等级为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.ConversionPattern=【%c】-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/MyBatis.txt
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

8.2.日志类使用

导入Apache-Log4J包

import org.apache.log4j.Logger;

使用反射当前对象来创建当前Logger对象

// 创建静态变量Logger对象 => logger
// 使用当前类.class反射创建logger对象
static Logger logger = logger.getLogger(UsersDaoTest.class)
@Test
public void log4jTest(){
    logger.info("info: 日志输出等级【Info】");
    logger.debug("debug: 日志输出等级【DEBUG】");
    logger.error("error: 日志输出等级【ERROR】");
}

image-20210824183517453

相关文章: