SSM-MyBatis

MyBatis

https://mybatis.org/mybatis-3/zh/index.html

文章目录

  • MyBatis
    • 1、简介
      • 1.1 什么是 MyBatis
      • 1.2 MyBatis 发展
      • 1.3 如何获取 MyBatis
      • 1.4 持久化
      • 1.5 持久层
      • 1.6 为什么使用 MyBatis
      • 1.7 MyBatis 特点
    • 2、我的第一个 MyBatis 程序
      • 2.1 搭建环境
      • 2.2 创建一个不同模块
      • 2.3 编写代码
      • 2.4 测试
      • 2.5 测试中遇到的问题
    • 3、CRUD(增删改查)
      • 3.1 namespace
      • 3.2 select
      • 3.3 insert
      • 3.4 update
      • 3.5 delete
      • 3.6Map
      • 3.7 模糊查询
    • 4、配置解析
      • 4.1 核心配置文件
      • 4.2 环境配置(environments)
      • 4.3 属性(properties)
      • 4.4 类型别名(typeAliases)
      • 4.5 设置(settings)
      • 4.6 其他配置
      • 4.7 映射器(mappers)
      • 4.8 作用域(Scope)和生命周期
    • 5、解决属性名和字段名不一致的问题
      • 5.1 问题
      • 5.2 解决办法
        • 5.2.1 给服务器字段起别名
        • 5.2.2 resultMap(结果集映射)
    • 6、日志
      • 6.1 日志工厂
      • 6.2 Log4j
    • 7、分页
      • 7.1 使用 Limit 分页
      • 7.2 使用 MyBatis 实现分页,核心 sql
      • 7.3 RowBounds 分页
      • 7.4 分页插件
    • 8、使用注解开发
      • 8.1 面向接口编程
        • 8.1.1 接口的理解
        • 8.1.2 三个面向区别
      • 8.2 注解开发
      • 8.3 CRUD(增删改查)
      • 8.4 关于 @Param() 注解
      • 8.5 #{} 和 ${} 的区别
    • 9、MyBatis 的详细执行流程
    • 10、Lombok
      • 10.1 使用步骤
      • 10.2 优缺点
    • 11、多对一处理
      • 11.1 SQL :
      • 11.2 测试环境搭建
      • 11.3 按照查询嵌套处理
      • 11.4 按照结果嵌套处理
    • 12、一对多
      • 12.1 按结果嵌套处理
      • 12.2 按查询嵌套处理
    • 13、多对一、一对多 小结
    • 14、动态 SQL
      • 14.1 什么是动态 SQL?
      • 14.2 搭建环境
      • 14.3 IF
      • 14.4 choose(when、otherwise)
      • 14.5 trim(where、set)
      • 14.6 SQL 片段
      • 14.7 foreach
    • 15、缓存
      • 15.1 MyBatis 缓存
      • 15.2 一级缓存
      • 15.3 缓存失效情况
      • 15.4 二级缓存
      • 15.5 缓存原理
      • 15.6 自定义缓存 ehcache

1、简介

1.1 什么是 MyBatis

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

1.2 MyBatis 发展

  • MyBatis 本是 apache 的一个开源项目开源项目 iBatis
  • 2010年这个项目由 apache software foundation 迁移到了google code,并且改名为 MyBatis
  • 2013年11月迁移到 Github

1.3 如何获取 MyBatis

  • Maven 仓库:https://mvnrepository.com/search?q=mybatis

    
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.7version>
    dependency>
    
  • Github:https://github.com/mybatis/mybatis-3

  • 中文文档:https://mybatis.org/mybatis-3/zh/index.html

1.4 持久化

数据持久化:

  • 持久化就是将程序的数据在持久状态和瞬时状态的转化过程
    • 持久状态:数据库(jdbc)、IO 文件
    • 瞬时状态:内存
  • 内存:断电即失

为什么需要持久化?

  • 不能失去一些对象
  • 内存不适合没钱人!

1.5 持久层

  • 完成持久化工作的代码块
  • 层界限非常明显

1.6 为什么使用 MyBatis

  • 更方便将数据存入数据库中
  • 用的人多
  • 传统的 JDBC 代码太复杂。
  • 简化,更容易上手
  • 框架
  • 自动化

1.7 MyBatis 特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个 jar 文件以及配置几个 sql 映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis 不会对应用程序或者数据库的现有设计强加任何影响。sql 写在 xml 里,便于统一管理和优化。通过 sql 语句可以满足操作数据库的所有需求。
  • 解除 sql 与程序代码的耦合:通过提供 DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql 和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的 orm 字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

2、我的第一个 MyBatis 程序

MyBatis 程序思路:

  • 搭建环境 —> 导入 jar 包 —> 编写代码 —> 测试

2.1 搭建环境

搭建一个数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user`(
    `id` INT(20) NOT NULL,
    `name` VARCHAR(20) DEFAULT NULL,
    `pwd` VARCHAR(20) DEFAULT NULL,
    PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) 
VALUE (1,'张三','123456'),
(2,'李四','123456'),
(3,'王五','123456')

新建一个项目

  1. 新建一个普通的 maven 项目

  2. 核对 maven 配置

  3. 删除 src (这是一个父工程,干净最好)

  4. 导入依赖包

    • mysql 驱动
    • mybatis
    • 单元测试 junit
    
    <dependencies>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.25version>
        dependency>
        
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.7version>
        dependency>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.13version>
        dependency>
    dependencies>
    

2.2 创建一个不同模块

编写 mybatis 的核心配置文件(mybatis-config.xml)


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="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>
    
    
    
configuration>

编写 mybatis 工具类(MybatisUtils)

SSM-MyBatis_第1张图片

// SqlSessionFactory ---> SqlSession
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        // 获取 sqlSessionFactory 对象
        try {
            String resource = "org/mybatis/example/mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例
    // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
    // 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

2.3 编写代码

  1. 实体类

    package com.aze.pojo;
    
    public class User {
        private int id;
        private String name;
        private String pwd;
    
        public User() {
        }
    
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
    
  2. Dao 接口(Mapper 接口)

    package com.aze.dao;
    
    import com.aze.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询所用用户的所有信息
        List<User> getUserList();
    }
    
  3. 接口实现类(UserMapper.xml)

    • JavaWeb 中是编写 UserDaoImpl.java 来实现
    • 如今使用 UserMapper.xml 来实现(简洁方便)
    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    
    <mapper namespace="com.aze.dao.UserMapper">
        
        <select id="getUserList" resultType="com.aze.pojo.User">
            select * from mybatis.user;
        select>
    mapper>
    

2.4 测试

  • 单元测试 junit
package dao;

import com.aze.dao.UserMapper;
import com.aze.pojo.User;
import com.aze.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

    @Test
    public void test1(){
        // 1. 获得 sqlSession 对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // 2. 获取 UserMapper 接口中的方法
        // 方式一
        // 方式一方法有很多优势,首先它不依赖于字符串字面值,会更安全一点
        // 其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择到映射好的 SQL 语句
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();

        // 方式二
        // 这种方式和用全限定名调用 Java 对象的方法类似
        // 这样,该命名就可以直接映射到在命名空间中同名的映射器类,并将已映射的 select 语句匹配到对应名称、参数和返回类型的方法
        //List userList1 = sqlSession.selectList("com.aze.dao.UserMapper.getUserList");

        // 遍历查询结果
        for (User user : userList) {
            System.out.println(user);
        }

        // 3. 关闭 sqlSession
        sqlSession.close();
    }
}

2.5 测试中遇到的问题

  • 编写的 Mapper.xml 配置文件没有注册(很多人都会忘记的事!)

    • 解决办法:在 mybatis-config.xml 中注册 配置文件
    <mappers>
        <mapper resource="com/aze/dao/UsetMapper.xml"/>
    mappers>
    
  • 文件过滤,在 java 文件夹下的 xml 文件是会被过滤的(Maven 导出资源问题)

    • 解决办法:在父工程的 pom.xml 中配置文件过滤的 build 即可
    
    <build>
        <resources>
            <resource>
                <directory>src/main/resourcesdirectory>
                <includes>
                    <include>**/*.propertiesinclude>
                    <include>**/*.xmlinclude>
                includes>
                <filtering>truefiltering>
            resource>
            <resource>
                <directory>src/main/javadirectory>
                <includes>
                    <include>**/*.propertiesinclude>
                    <include>**/*.xmlinclude>
                includes>
                <filtering>falsefiltering>
            resource>
        resources>
    build>
    
  • java.io.IOException: Could not find resource org/mybatis/example/mybatis-config.xml

    • 问题所在:在 mybatis 工具类,获取 sqlSessionFactory 对象的 配置文件路径写错了
    • 解决:修改为正确路径
  • Loading class ‘com.mysql.jdbc.Driver’. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver’. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.

    • 问题所在:com.mysql.jdbc.Driver 数据库驱动已经被弃用了
    • 解决:将 com.mysql.jdbc.Driver 改为 com.mysql.cj.jdbc.Driver
  • Cause: java.sql.SQLSyntaxErrorException: Unknown database ‘mybatis/’

    • 问题所在:mybatis-config.xml 配置文件中,url 的地址写错了

    在这里插入图片描述

    • 解决办法:将上图的斜杠去掉(/)
  • 以上是本人遇到的,可能还有其他的,给我上网查!!!

  • 还有可能遇到的问题:

    • 判定接口错误
    • 方法名不对
    • 返回类型不对

3、CRUD(增删改查)

3.1 namespace

namespace 中的 包名 要和 Dao/mapper 接口的包名一致

  • select
    • id:namespace 中对应的方法名
    • resultType:sql 语句执行的返回值
    • parameterType:参数类型
  • insert
  • update
  • delete

注意:增删改 需要提交事务,否则数据库中的数据不会改变!!!

注意:增删改 需要提交事务,否则数据库中的数据不会改变!!!

注意:增删改 需要提交事务,否则数据库中的数据不会改变!!!

3.2 select

选择,查询语句:

  1. 编写Mapper接口方法(UserMapper.java)

    // 查询所用用户的所有信息
    List<User> getUserList();
    
    // 根据 id 查询用户
    User getUserById(int id);
    
  2. 编写 Mapper接口方法的配置文件(UserMapper.xml)

    
    <mapper namespace="com.aze.dao.UserMapper">
        
        <select id="getUserList" resultType="com.aze.pojo.User">
            select * from mybatis.user;
        select>
        <select id="getUserById" resultType="com.aze.pojo.User" parameterType="int">
            select * from mybatis.user where id=#{id}
        select>
    mapper>
    
  3. 编写测试方法

    // 获取所有用户所有信息
    @Test
    public void test1(){
        // 1. 获得 sqlSession 对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        // 2. 获取 UserMapper 接口中的方法
        // 方式一
        // 方式一方法有很多优势,首先它不依赖于字符串字面值,会更安全一点
        // 其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择到映射好的 SQL 语句
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
    
        // 方式二
        // 这种方式和用全限定名调用 Java 对象的方法类似
        // 这样,该命名就可以直接映射到在命名空间中同名的映射器类,并将已映射的 select 语句匹配到对应名称、参数和返回类型的方法
        //List userList1 = sqlSession.selectList("com.aze.dao.UserMapper.getUserList");
    
        // 遍历查询结果
        for (User user : userList) {
            System.out.println(user);
        }
    
        // 3. 关闭 sqlSession
        sqlSession.close();
    }
    
    // 根据 id 查询用户
    @Test
    public void test2(){
        // 1. 获取 SqlSession 对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2. 获取 UserMapper 接口中的方法
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userById = mapper.getUserById(2);
        System.out.println(userById);
        // 3.关闭 sqlSession
        sqlSession.close();
    }
    
  4. 测试

3.3 insert

增添语句:

  1. 编写Mapper接口方法(UserMapper.java)

    // 新增一个用户
    int addUser(User user);
    
  2. 编写 Mapper接口方法的配置文件(UserMapper.xml)

    <insert id="addUser" parameterType="com.aze.pojo.User">
        insert into mybatis.user(id, name, pwd) value (#{id},#{name},#{pwd})
    insert>
    
  3. 编写测试方法

    // 新增一个用户,增删改必须要提交事务
    @Test
    public void addTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.addUser(new User(4,"老六","123456"));
        if (i > 0){
            System.out.println("成功!");
        }
        // 提交事务(增删改都必须要有)
        sqlSession.commit();
        sqlSession.close();
    }
    
  4. 测试

3.4 update

修改语句:

  1. 编写Mapper接口方法(UserMapper.java)

    // 修改一个用户
    int updateUser(User user);
    
  2. 编写 Mapper接口方法的配置文件(UserMapper.xml)

    <update id="updateUser" parameterType="com.aze.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
    update>
    
  3. 编写测试方法

    // 修改用户信息
    @Test
    public void updateUserTest(){
    
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.updateUser(new User(4, "火箭", "666666"));
        if (i > 0){
            System.out.println("修改成功!");
        }
    
        sqlSession.commit();
        sqlSession.close();
    }
    
  4. 测试

3.5 delete

删除语句:

  1. 编写Mapper接口方法(UserMapper.java)

    // 根据 id 删除用户
    int deleteUserById(int id);
    
  2. 编写 Mapper接口方法的配置文件(UserMapper.xml)

    <delete id="deleteUserById" parameterType="int">
        delete from mybatis.user where id=#{id}
    delete>
    
  3. 编写测试方法

    // 根据 id 删除用户
    @Test
    public void deleteUserByIdTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.deleteUserById(4);
        if (i > 0){
            System.out.println("删除成功!");
        }
    
        sqlSession.commit();
        sqlSession.close();
    }
    
  4. 测试

3.6Map

如果实体类或者数据库中的表,字段或者参数很多,我们就可以使用 Map

  • Map 传递参数,直接在 sql 取出 key (parameterType=“Map”)
  • 对象传递参数,直接在 sql 中取出对象的属性(parameterType=“Object”)
  • 只有一个基本类型参数的情况下,可以直接在 sql 中取得
  • 多个参数使用 Map 或者 注解
// 使用 map 新增用户
int addUserByMap(Map<String,Object> map);
<insert id="addUserByMap" parameterType="Map">
    insert into mybatis.user(id, name, pwd) value (#{id4},#{name4},#{password4})
insert>
// 使用 Map 新增用户
@Test
public void addUserByMapTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("id4",4);
    map.put("name4","老六");
    map.put("password4","123456");
    int i = mapper.addUserByMap(map);
    if (i > 0){
        System.out.println("新增成功!");
    }
    sqlSession.commit();
    sqlSession.close();
}

3.7 模糊查询

  • java 代码执行的时候,传递通配符 %

    List<User> userList = mapper.getUserLike("%李%");
    
  • 在 sql 拼接中使用通配符

    select * from mybatis.user where name like "%"#{value}"%"
    

4、配置解析

4.1 核心配置文件

mybatis-config.xml

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
    • configuration(配置)
      • properties(属性)
      • settings(设置)
      • typeAliases(类型别名)
      • typeHandlers(类型处理器)
      • objectFactory(对象工厂)
      • plugins(插件)
      • environments(环境配置)
        • environment(环境变量)
          • transactionManager(事务管理器)
          • dataSource(数据源)
      • databaseIdProvider(数据库厂商标识)
      • mappers(映射器)

4.2 环境配置(environments)

MyBatis 可以配置成适应多种环境

  • 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
  • 要学会配置多套 运行环境

MyBatis 默认的事务管理器是 JDBC,默认连接池 POOLED

4.3 属性(properties)

通过 properties 属性来实现引用配置文件

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

在这里插入图片描述

编写一个配置文件:

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=root

在核心配置文件中引入

  • 可以直接引入外部文件
<properties resource="db.properties"/>
  • 可以在这里增加一些属性配置
<properties resource="db.properties">
    <property name="" value="">
properties>
  • 如果两个文件有同一个字段,优先会使用外部配置文件

4.4 类型别名(typeAliases)

类型别名是为 Java 类型设置一个短的名字

存在的意义仅用于减少类完全限定名的冗余

  1. 可以给实体类起别名:
<typeAliases>
    <typeAlias type="com.aze.pojo.User" alias="User"/>
typeAliases>
  1. 也可以指定一个包名,MyBatis 会在包名下搜索需要的 JavaBean:
  • 扫描实体类的包,它的默认别名就为这个类首字母小写的类名
<typeAliases>
    <package name="com.aze.pojo"/>
typeAliases>
  • 区别:

    • 第一种起别名的方法,在实体类比较少的情况下使用

    • 第二种方法,在实体类非常多的时候使用

    • 第一种起的别名可以DIY(自定义)

    • 第二种起的别名在核心配置文件中不能够自定义,只能在注解中修改

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

4.5 设置(settings)

这是 MyBatis 中纪委重要的调整设置,他们会改变 MyBatis 的运行时行为

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

SSM-MyBatis_第2张图片

4.6 其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
    • mybatis-generator-core
    • mybatis-plus
    • 通用 mapper

4.7 映射器(mappers)

MapperRegistry:注册绑定我们的 Mapper 文件

每一个 Mapper.xml 都需要在 MyBatis 核心配置文件中注册!

  • 方式一:使用相对于类路径的资源引用(推荐使用这个)

    <mappers>
        <mapper resource="com/aze/dao/UserMapper.xml"/>
    mappers>
    
  • 方式二:使用映射器接口实现类的完全限定类名

    • 注意:
      • 接口和它的 Mapper 配置文件必须同名
      • 接口和它的 Mapper 配置文件必须在同一个包下
    <mappers>
        <mapper class="com.aze.dao.UserMapper"/>
    mappers>
    
  • 方式三:将包内的映射器接口实现全部注册为映射器

    • 注意
      • 接口和它的 Mapper 配置文件必须同名
      • 接口和它的 Mapper 配置文件必须在同一个包下
    <mappers>
        <mapper name="com.aze.dao"/>
    mappers>
    

4.8 作用域(Scope)和生命周期

作用域和生命周期是至关重要的,因为错误的使用会导致非常严重的并发问题。

SSM-MyBatis_第3张图片

SqlSessionFactoryBuilder:

  • 一旦创建了 SqlSessionFactory,就不需要它了
  • 局部变量

SqlSessionFactory:

  • 可以比作为:数据库连接池
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一致存在,没有任何理由抛弃他或重新创建另一个实例
  • SqlSessionFactory 的最佳作用域是应用作用域
  • 最简单的就是使用 单例模式 或者 静态单例模式

SqlSession:

  • 可以比作为:连接数据库的一个请求
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法域
  • 用完之后需要直接关闭,否则资源被占用,资源一直被浪费就会形成宕机(并发)

SSM-MyBatis_第4张图片

每一个 Mapper 就代表着一个具体的业务。

5、解决属性名和字段名不一致的问题

5.1 问题

数据库中的字段:

SSM-MyBatis_第5张图片

如果实体类字段与数据库字段不一致怎么办?

public class User{
    private int id;
    private String name;
    private String password;
}

这样就会出现这种情况,而这种情况 MyBatis 会自动创建一个 ResultMap,在基于属性名来映射列到 JavaBean 的属性上

5.2 解决办法

5.2.1 给服务器字段起别名

在这里插入图片描述

5.2.2 resultMap(结果集映射)

数据库字段 实体类字段

​ id id

​ name name

​ pwd password

  • 就是通过 resultMap,将数据库中的一列映射到这里变成一个字段

<resultMap id="UserMap" type="User">
    
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
resultMap>


<select id="getUserList" resultMap="UserMap">
    select * from mybatis.user;
select>
  • resultMap 元素是 MyBatis 中最重要的也是最强大的元素
  • ResultMap 的设计思想是,对于简单的语句根本不需要配置显示的结果集,而对于复杂一点的语句只需要描述他们之间的关系就行
  • ResultMap 最优秀的地方在于,虽然你已经对他相当了解了,但是根本就不需要显示的定义他们
  • 然而困难的还在后边(多对一、一对多)

6、日志

6.1 日志工厂

如果一个数据库操作出现了异常,这样就需要排错,而日志就是最好的帮手!

在这里插入图片描述

  • SLF4J
  • LOG4J(*)
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING(*)
  • NO_LOGGING

在 MyBatis 中具体使用哪一个日志实现

  • STDOUT_LOGGING 标准日志输出

  • 在 mybatis 核心配置文件中配置我们的日志

    <settings>
        
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>
    

SSM-MyBatis_第6张图片

6.2 Log4j

什么是 Log4j?

  • Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是 控制台、文件、GUI 组件
  • 可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  1. 这个日志类型需要导包:

    
    
    <dependency>
        <groupId>log4jgroupId>
        <artifactId>log4jartifactId>
        <version>1.2.17version>
    dependency>
    
  2. 配置 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/aze.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. 配置 log4j 为日志的实现

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    settings>
    
  4. 测试 log4j


Log4j 的简单使用:

  1. 在要使用 Log4j 的类中导入包

    import org.apache.log4j.Logger;
    
  2. 日志对象,参数为当前类的 class

    static Logger logger = Logger.getLogger(UserMapperTest.class);
    
  3. 日志级别

    logger.info("info:666666666");
    logger.debug("debug:666666666");
    logger.error("error:666666666");
    

7、分页

7.1 使用 Limit 分页

  • 语法

    select * from `` limit startIndex,pageSize;
    select * from `` limit n;     #[0,n]
    

7.2 使用 MyBatis 实现分页,核心 sql

  1. 配置接口

    // 分页
    List<User> getUserListByLimit(Map<String,Integer> map);
    
  2. 接口配置文件

    
    <mapper namespace="com.aze.dao.UserMapper">
        
        <resultMap id="UserMap" type="User">
            
            <result column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="pwd" property="password"/>
        resultMap>
        
        <select id="getUserList" resultMap="UserMap">
            select * from mybatis.user;
        select>
        
        <select id="getUserListByLimit" resultMap="UserMap" parameterType="User">
            select * from mybatis.user limit #{startIndex},#{pageSize}
        select>
    mapper>
    
  3. 测试

    // 分页
    @Test
    public void getUserListByLimitTest(){
        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> userListByLimit = mapper.getUserListByLimit(map);
        for (User user : userListByLimit) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }
    

7.3 RowBounds 分页

  1. 配置接口

    // 分页
    List<User> getUserListByRowBounds();
    
  2. 接口配置文件

    
    <select id="getUserListByRowBounds" resultMap="UserMap">
        select * from mybatis.use
    select>
    
  3. 测试

    // 分页
    @Test
    public void getUserListByRowBoundsTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        RowBounds rowBounds = new RowBounds(0,2);
    
        List<User> userListByLimit = mapper.getUserListByLimit("com.aze.dao.UserMapper.getUserListByRowBounds",null,rowBounds);
        for (User user : userListByLimit) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }
    

7.4 分页插件

https://pagehelper.github.io/

8、使用注解开发

8.1 面向接口编程

在真实开发中,很多时候就是选择面向接口编程

  • 根本原因:
    • 解耦
    • 可拓展
    • 提高复用
    • 分层开发,上层不管具体实现,大家都遵守共同的标准
    • 规范性好

而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

8.1.1 接口的理解
  • 接口从更深层次的理解,应是定义(规范、约束)与实现(名实分离的原则)的分离
  • 接口的本身反映了系统设计人员对系统的抽象理解
  • 接口应该有两类:
    • 第一类是对一个个体的抽象,他可对应为一个抽象体(abstract class)
    • 第二类是一个个体某一方面的抽象,即形成一个抽象面(interface)
    • 一个个体可能有多个抽象面,抽象体和抽象面是有区别的
8.1.2 三个面向区别
  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性以及方法
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构

8.2 注解开发

本质:反射机制实现

底层:动态代理

SSM-MyBatis_第7张图片

  1. 编写接口

    @Select("select * from user")
    List<User> getUser();
    
  2. 绑定接口

    <mappers>
        <mapper class="com.aze.dao.UserMapper"/>
    mappers>
    
  3. 测试

8.3 CRUD(增删改查)

可以在工具类创建的时候实现自动提交事务!

  • openSession 设置为 true
public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);
}

编写接口

@Select("select * from user")
List<User> getUser();

@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
User addUser(User user);

@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);

@Delete("delete from user where id=#{id}")
int deleteUserById(@Param("id") int id);

8.4 关于 @Param() 注解

  • 基本类型的参数或者 String 类型,需要加 @Param() 注解
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以不加,但建议加上
  • SQL 中引用的就是 @Param() 注解中设置的属性名

8.5 #{} 和 ${} 的区别

#{}

  • 能够很好的防止 SQL 注入

${}

  • 无法防止 SQL 注入
  • 一般传入数据库名的时候才用 ${}

能用 #{} 就用 #{}

9、MyBatis 的详细执行流程

SSM-MyBatis_第8张图片

10、Lombok

偷懒专用的!

  • Lombok是一款Java代码功能增强库
  • 它会自动集成到你的编辑器和构建工具中,从而使你的Java代码更加生动有趣
  • 通过Lombok的注解,你可以不用再写getter、setter、equals等方法,Lombok将在编译时为你自动生成。

10.1 使用步骤

  1. 安转 Lombok

SSM-MyBatis_第9张图片

SSM-MyBatis_第10张图片

  1. 安转完成后,重启 IDEA

  2. 在项目中导入 Lombok 的 jar 包

    
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.18.20version>
    dependency>
    
  3. 在实体类中加注解

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String password;
    
    }
    

Lombok 中的注解:

@Getter and @Setter // get/set
@FieldNameConstants
@ToString // toString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor // 有参构造,无参构造
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data //无参,get,set,equals,canEqual,hashCod,toString
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows

10.2 优缺点

SSM-MyBatis_第11张图片

在这里插入图片描述

11、多对一处理

什么是多对一?

  • 例如多个学生,对应一个老师
  • 对于学生而言,多个学生关联一个老师[关联](多对一)
  • 对于老师而言,一个老师有很多学生[集合](一对多)

SSM-MyBatis_第12张图片

  • association:一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection: 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用

11.1 SQL :

CREATE TABLE `teacher` (
    `id` INT(10) NOT NULL,
    `name` VARCHAR(30) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`,`name`) VALUES (1, '秦老师'); 

CREATE TABLE `student` (
    `id` INT(10) NOT NULL,
    `name` VARCHAR(30) DEFAULT NULL,
    `tid` INT(10) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `fktid` (`tid`),
    CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, '小明', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, '小红', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, '小张', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, '小李', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, '小王', 1);

11.2 测试环境搭建

  1. 新建项目

  2. 导入响应的 jar 包

  3. 新建实体类

  4. 新建 Mapper接口

  5. 新建 Mapper.xml 文件

    SSM-MyBatis_第13张图片

  6. 核心配置文件中绑定注册 Mapper接口

  7. 测试

11.3 按照查询嵌套处理

StudentMapper.xml(像子查询)

select s.id,s.name,t.name from student s,teacher t where s.tid=t.id;

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.aze.dao.StudentMapper">

    
    <select id="getStudent" resultMap="StudentTeacher">
        select * from mybatis.student
    select>

    <resultMap id="StudentTeacher" type="Student">
        
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    resultMap>

    <select id="getTeacher" resultType="Teacher">
        select * from mybatis.teacher where id=#{id}
    select>

mapper>

11.4 按照结果嵌套处理

StudentMapper.xml(像联表查询)

select s.id,s.name,t.name from student s left join teacher t on s.tid = t.id

<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid=t.id
select>
<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    association>
resultMap>

12、一对多

例如:一个老师拥有多个学生(集合)

对于老师而言,就是一对多的关系

  • 集合:collection
    • javaType:指定属性的类型
    • ofType:集合中的泛型信息,使用这个
  1. 环境搭建

    @Data
    public class Student {
    
        private int id;
        private String name;
        private int tid;
    
    }
    
    @Data
    public class Teacher {
    
        private int id;
        private String name;
        // 一个老师有多有个学生
        private List<Student> students;
    
    }
    

12.1 按结果嵌套处理

  • 推荐用这个(像联表查询),自我理解认为这个好理解一点
Teacher getTeacherById(@Param("tid") int id);
<select id="getTeacherById" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id and t.id=#{tid}
select>
<resultMap id="StudentTeacher2" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    collection>
resultMap>

12.2 按查询嵌套处理

Teacher getTeacherById2(@Param("tid") int id);
<select id="getTeacherById2" resultMap="StudentTeacher3">
    select id,name from teacher where id=#{tid}
select>
<resultMap id="StudentTeacher3" type="Teacher">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentGetTeacherById"/>
resultMap>
<select id="getStudentGetTeacherById" resultType="Student">
    select * from student where tid=#{tid}
select>

13、多对一、一对多 小结

关联(多对一):association

集合(一对多):collection

  • javaType 与 ofType
    • javaType :用来指定实体类中属性的类型
    • ofType:用来指定映射到 List 或者集合中的 pojo 类型,泛型中的约束类型

注意点:

  • 保证 sql 的可读性,尽量保证通俗易懂
  • 注意一对多和多对一中的属性名和字段的问题
  • 如果问题不好排查,可以使用日志,log4j

14、动态 SQL

本质还是 sql 语句,只是我们可以在 SQL 层面,去执行一个逻辑代码

动态 SQL 就是在拼接 SQL 语句,只要保证 SQL 的正确性,按照 SQL 的格式,去排列组合即可

14.1 什么是动态 SQL?

动态 SQL 就是指根据不同的条件生成不同的 SQL 语句

利用动态 SQL 这一特性可以彻底摆脱根据不同条件拼接 SQL 语句的痛苦

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

14.2 搭建环境

MySQL:

CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8

创建一个基础工程:

  1. 新建项目并导包

  2. 编写配置文件

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
    username=root
    password=root
    
    
    DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <properties resource="db.properties"/>
    
        <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
            
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        settings>
    
        <typeAliases>
            <typeAlias type="com.aze.pojo.Blog" alias="blog"/>
        typeAliases>
    
        <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 class="com.aze.dao.BlogMapper"/>
        mappers>
    
    configuration>
    
  3. 编写 MyBatis工具类

    // SqlSessionFactory ---> SqlSession
    public class MybatisUtils {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            // 获取 sqlSessionFactory 对象
            try {
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        // 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例
        // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
        // 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession(true);
        }
    }
    
  4. 编写实体类

    @Data
    public class Blog {
        private String id;
        private String title;
        private String author;
        private Date createTime;
        private int views;
    }
    
  5. 编写实体类对应 Mapper 接口 和 Mapper.xml 文件

    public interface BlogMapper {
    
        int addBlog(Blog blog);
    
    }
    
    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.aze.dao.BlogMapper">
    
        <insert id="addBlog" parameterType="blog">
            insert into mybatis.blog (id, title, author, create_time, views) values (#{id}, #{title}, #{author}, #{createTime}, #{views})
        insert>
    
    mapper>
    

14.3 IF

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1=1
    <if test="title != null">
        and title=#{title}
    if>
    <if test="author != null">
        and author=#{author}
    if>
select>

14.4 choose(when、otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用,这怎么办呢?

  • 针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <choose>
            <when test="title != null">
                title=#{title}
            when>
            <when test="author != null">
                and author=#{author}
            when>
            <otherwise>
                and views=#{views}
            otherwise>
        choose>
    where>
select>
  • 传入了 “title” 就按 “title” 查找
  • 传入了 “author” 就按 “author” 查找的情形
  • 如果两个都传入了,就先看谁先传入,只走先传入的那个
  • 若两者都没有传入,就会走 otherwise,这里按 views 查询

14.5 trim(where、set)

where

  • where 元素只会在至少有一个子元素的条件返回 sql 子句的情况下才会去插入 where 子句
  • 当语句开头为 and、or 的时候,where 元素标签会自动删除这些
<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <if test="title != null">
            title=#{title}
        if>
        <if test="author != null">
            and author=#{author}
        if>
    where>
select>

set

  • 用于动态更新语句的类似解决方案叫做 set
  • set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
  • set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <if test="title != null">
            title=#{title},
        if>
        <if test="author != null">
            author=#{author}
        if>
    set>
    where id=#{id}
update>

trim:

  • 可以通过自定义 trim 元素来定制元素的功能。

    • prefix:需要定义的元素

    • prefixOverrides:前缀

    • suffixOverrides:后缀

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

14.6 SQL 片段

有时候可能有一些 SQL 的逻辑代码需要复用,我们就会提出来

  • 使用 sql 标签抽取公共部分
  • 在需要的使用的地方,用 include 标签引用
<sql id="if-title-author">
    <if test="title != null">
        and title=#{title}
    if>
    <if test="author != null">
        and author=#{author}
    if>
sql>
<select id="getBlogBySql" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <include refid="if-title-author">include>
    where>
select>

注意:

  • 最好基于单表来定义 SQL 片段
  • 在 SQL 片段中不要使用 where 标签

14.7 foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历

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

提示:

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

  • 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素

  • 当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

<select id="getBlogByForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id=#{id}
        foreach>
    where>
select>

测试

@Test
public void test01(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    Map map = new HashMap();
    List<String> idList = new ArrayList<String>();
    idList.add("0906933ce1e74c869ed18591950c8e65");
    idList.add("42a45373b0534353af51581521502dc4");
    idList.add("74a6f72b805945d980561ffaac80d3a4");

    map.put("ids",idList);
    List<Blog> blogList = mapper.getBlogByForeach(map);
    for (Blog blog : blogList) {
        System.out.println(blog);
    }

    sqlSession.close();
}

15、缓存

什么是缓存[Cache]?

  • 存在内存中的临时数据
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题

为什么用缓存?

  • 减少和数据库的交互次数,减少系统开销,提高系统效率

什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据

15.1 MyBatis 缓存

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

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

  • 默认情况下,只有一级缓存开启。(SqlSession 级别的缓存,也称为本地缓存,只存在于 sqlSession 存在的时候)
  • 二级缓存需要手动开启和配置,他是基于 namespace级别的缓存(Mapper.xml)
  • 为了提高扩展性,MyBatis 定义了缓存接口 Cache,我们可以通过实现 Cache 接口来定义二级缓存

15.2 一级缓存

一级缓存也加本地缓存:SqlSession

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

测试一级缓存:

  1. 先创建一个新项目
  2. 配置文件
  3. 开启日志
  4. 测试在一个 Session 中查询两次相同记录
  5. 查看日志的输出

SSM-MyBatis_第14张图片

注意:

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

15.3 缓存失效情况

  1. 查询不同数据

  2. 增删改操作,这回改变原来的数据,所以会刷新缓存

  3. 查询不同的 Mapper.xml

  4. 手动清理缓存

    sqlSession.clearCache();
    

15.4 二级缓存

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于 namespace 级别的缓存一个名称空间,对应一个二级缓存
  • 工作机制:
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了
    • 但是,我们想要的是,会话关闭了,一级缓存的数据会被保存到二级缓存中
    • 开启了全局缓存且一级缓存被关闭,新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的 mapper 查出的数据会放在自己对应的缓存(map)中

SSM-MyBatis_第15张图片

开启全局缓存步骤:

  1. 在核心配置文件中设置 settings,显示开启缓存

    
    <setting name="cacheEnabled" value="true"/>
    
  2. 在要开启全局缓存的 Mapper.xml 配置文件中开启二级缓存

    • 可以在其中设置一些高级的配置,例如:缓存的模式,缓存刷新的时间,最多可以存储结果对象多少个,等等
    
    <cache/>
    
  3. 测试

    @Test
    public void test01(){
        SqlSession sqlSession1 = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = mapper1.getUser(1);
        System.out.println("user1:" + user1);
        sqlSession1.close();
    
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.getUser(1);
        System.out.println("user2:" + user2);
        System.out.println("user1==user2" + (user1 == user2));
        sqlSession2.close();
    }
    

    SSM-MyBatis_第16张图片

注意:

  • 实体类需要序列化或者全局缓存中要设置只读
    • 开启序列化,第二次从缓存中取得的对象就不会和第一次的相等
  • 所有的数据局都会先放在一级缓存中
  • 只有当会话提交或者关闭的时候,才会提交到二级缓存中

15.5 缓存原理

SSM-MyBatis_第17张图片

二级缓存 —> 一级缓存 —> 连接数据库查询

15.6 自定义缓存 ehcache

EhCache 是一种广泛使用的开源 Java 分布式缓存,主要面向通用缓存

使用步骤:

  1. 导入 ehcache 的 jar 包

    
    <dependency>
        <groupId>org.mybatis.cachesgroupId>
        <artifactId>mybatis-ehcacheartifactId>
        <version>1.2.1version>
    dependency>
    
  2. 配置 ehcache.xml

    
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
    
        <diskStore path="./tmpdir/Tmp_EhCache"/>
    
        <defaultCache
                eternal="false"
                maxElementsInMemory="10000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="259200"
                memoryStoreEvictionPolicy="LRU"/>
    
        <cache
                name="cloud_user"
                eternal="false"
                maxElementsInMemory="5000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="1800"
                memoryStoreEvictionPolicy="LRU"/>
    ehcache>
    
  3. 在 Mapper.xml 中指定使用 ehcache 缓存

    
    
    

注意:如今都是用 Redis 数据库来做缓存(K-V 键值对)

你可能感兴趣的:(SSM,框架,java,数据库,mysql)