Mybatis(千峰学习笔记)

什么是MyBatis

1.MyBatis是一款优秀的持久层框架,它支持定制化SQL,存储过程及高级映射。

2.MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的工作。

3.Mybatis通过XML或注解的方式配置和映射原生信息。

Mybatis特点:

1.MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJO映射成数据库中的记录。

2.MyBatis提供了很多高级映射的特性,如自动映射、联合映射和结果集映射等。

3.Mybatis支持动态SQL,可以根据不同的条件来动态生成SQL语句。

总结:

1.MyBatis是一款优秀的持久成框架,支持定制化SQL、存储过程以及高级映射。

2.MyBatis通过XML或注解的方式配置和映射原生信息,提供了很多高级映射的特性。

3.MyBatis支持动态SQL,可以根据不同的条件来动态生成SQL语句。

4.Mybatis提供了插件机制,可以在MyBatis执行的某些关键点动态地修改它的行为。​​​​​​​

MyBatis简介

概念:

● MyBatis原是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation迁移到了Google Code,随着开发团队转投Goolgle Code旗下,iBatis3.x正式 更名为Mybatis。是一个基于Java的持久层框架。

● MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。

● MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

● MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

为什么使用MyBatis

● MyBatis框架可以简化数据库编程!

● MyBatis底层封装的是JDBC,使用起来比JDBC大大简化。

● 在使用MyBatis实现数据库编程时,SQL与Java编码分开,功能边界清晰。Java代码专注业 务、SQL语句专注数据

● MyBatis框架还实现了数据库编程的其它细节,例如对查询结果的缓存等等。

● MyBatis对比其他的持久层框架,它的性能较为出色,而且是轻量级的框架。

MyBatis环境搭建

MyBatis一般我们会配合Spring与SpringMVC一起使用,它们在一起统称SSM,也是学习Java必 须的“三大框架”,当然我们现在先来搞定MyBatis,也就是持久层的开发,所以我们一般会通过 Maven来导入对应的Jar来进行使用。

开发环境

需要准备Maven项目

MySQL版本:8.0.31

准备Maven项目mybatisDemo

在项目中引入Maven依赖坐标

我们会通过单元测试来对持久层进行测试

    
        
        
            junit
            junit
            4.11
            test
        
        
        
            mysql
            mysql-connector-java
            8.0.31
        
        
        
            org.mybatis
            mybatis
            3.5.11
        
    

Jar包下载方式

当然如果不使用Maven,也可以从官网上去下载对应jar包,导入进行使用,具体下载地址如下:

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

Mybatis(千峰学习笔记)_第1张图片

框架的学习,我们一般都必要要查看官方提供的文档,从官方提供的文档开始入手学习,当然以上下载的jar包中包含官方英文文档,但是MyBatis提供了专门的中文文档,具体网址如下:

MyBatis中文网

Mybatis(千峰学习笔记)_第2张图片

Mybatis核心原理

MyBatis作为Java最常用的持久层框架,它使用了ORM实现了结果集的封装。

什么叫ORM

● ORM是Object Relational Mapping 对象关系映射。简单来说,就是把数据库表和实体类及 实体类的属性对应起来,让开发者操作实体类就实现操作数据库表,它封装了JDBC操作的 很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等复杂过 程。

● ORM:也就是对象关系映射,是一种程序设计思想,MyBatis就是ORM的一种实现方式, 简单来说就是将数据库中查询出的数据映射到对应的实体中。

程序分层

如果各位学习过我讲的 JavaWeb,那么在最后的项目里面我给大家分析了JavaWeb项目的分层, 一般分为:

1. 控制层 controller

2. 业务逻辑层 service

3. 持久层(数据库访问层)dao

其实在这些层中,每一层在实际研发中都会使用到框架,比如控制层就会使用到SpringMVC,而 我们重点要关注的是持久层也就是Dao层,我们一般会用到框架有: Hibernate、MyBatis、Jpa ,但是其中目前使用最为广泛的也就是MyBatis。

但是在MyBatis之前最常用的是Hibernate。

MyBatis与Hibernate的区别

Hibernate:是一个全表映射的框架。通常开发者只需定义好持久化对象到数据库表的映射关 系,就可以通过Hibernate提供的方法完成持久层操作。开发者并不需要熟悉地掌握SQL语句的 编写,Hibernate会根据制定的存储逻辑,自动的生成对应的SQL,并调用JDBC接口来执行,所 以其开发效率会高于Mybatis。然而Hibernate自身也存在着一些缺点,例如它在多表关联时,对 SQL查询的支持较差;更新数据时,需要发送所有字段;不支持存储过程;不能通过优化SQL来 优化性能等。这些问题导致其只适合在场景不太复杂且对性能要求不高的项目中使用。

Mybatis:是一个半自动映射的框架。这里所谓的”半自动"是相对于Hibernate全表映射而言, Mybatis需要手动匹配提供POJO(实体类)、SQL和映射关系,而Hibernate只需要POJO和映射 关系即可。与Hibernate相比,虽然使用Mybatis手动编写SQL要比使用Hibernate的工作量大, 但Mybatis可以配置动态SQL并优化SQL,可以通过配置决定SQL的映射规则,它还支持存储过程 等。对于一些复杂的和需要优化性能的项目来说,显然使用Mybatis更加合适。

Mybatis是当前主流的Java持久层框架之一,它与Hibernate一样,也是一种ORM框架。因其性 能优异,且具有高度的灵活性、可优化性和易于维护等特点,所以受到了广大互联网企业的青 睐,是目前大型互联网项目的首选框架。

MyBatis操作数据库步骤

1.读取Mybatis配置文件mybatis-config.xml。mybatis-config.xml作为Mybatis的全局配置文件,配置Mybatis的运行环境等信息,其中主要内容是获取数据库连接。

2.加载映射文件Mapper.xml。Mapper.xml文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在mybatis-config.xml中加载才能执行。mybatis-config.xml可以加载多个配置文件,每个配置文件对应数据库中的一张表。

3.构建会话工厂。通过Mybatis的环境等配置信息构建会话工厂SqlSessionFactory。

4.创建SqlSession对象。由会话工厂创建SqlSession对象,该对象中包含执行SQL的所有方法。

MyBatis核心配置文件

每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的。SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得。而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先配置的Configuration实例来构建出SqlSessionFactory实例。

● 此配置文件为XML文件,一般名称为:mybatis-config.xml,当然也可以自定义

● mybatis-confog.xml作为Mybatis的全局配置文件,配置Mybatis的运行环境等信息,其中主要内容是获取数据库连接。

在Maven项目中创建mybatis-config.xml

直接在IDEA中进行创建此文件,同时文件内容,可以直接从官方进行粘贴。

Mybatis(千峰学习笔记)_第3张图片

Mybatis(千峰学习笔记)_第4张图片

粘贴之后,修改对应的数据库连接配置,要注意,引入映射文件的位置先不要动,后期我们创建 了映射文件以后,还会再来修改




    
        
            
            
                
                
                
                
            
        
    
    
        
    

创建MyBatis的Mapper映射

数据准备

当然在创建之前,我们需要准备一个数据库,以及对应的表用于我们测试,以下是具体脚本:

create database db_mybatis character set utf8;

use db_mybatis;

create table tb_user(
  id int primary key auto_increment,
  username varchar(20),
  password varchar(20)
);

insert into tb_user(username,password) values('tom','123'),('jerry','456');

在创建脚本后,我们需要此表对应的一个实体类,虽然我们现在使用的是MyBatis框架,但是它也是遵循ORM的

package entity;

public class User {
  Integer id;
  String username;
  String password;
  
  public User(){
  }
  
  public User(Integer id, String id; String password) {
    this.id = id;
    this.username = username;
    this.password = password;
  }
  
  public Integer getId() {
    return id;
  }
  
  public void setId(Integer id) {
    this.id = id;
  }
  
  public String getUsername() {
    return username;
  }
  
  public void setUsername(String username) {
    this.username = iusername;
  }
  
  public String getPassword() {
    return password;
  }
  
  public void setPassword(String password) {
    this.password = password;
  }
  
  @Override
  public String toString() {
    return "User{" + 
           "id=" + id +
           ",username='" + username + '\'' +
           ",password='" + password + '\'' +
           '}';
  }
}

创建Mapper接口

此时当我们创建好表以及对应的实体类以后,我们就需要编写持久层了,也就是程序中的”数据访 问层Dao“,按照以往的套路此时我们应该创建一个UserDao类型,但是因为我们采用了MyBatis 框架,所以MyBatis框架对应替代的是一个叫做Mapper的接口,同时此接口还要配置一个 Mapper.xml映射文件。

Mapper接口:在此接口中编写要执行的SQL方法,也可以编写简单的SQL语句,命名方式为相 关的实体类(User --- > UserMapper)

Mapper.xml文件:此文件要配合Mapper接口使用,其中编写的就是Mapper接口中对应方法要 执行的SQL语句,命名方式与对应的Mapper接口保持一致(UserMapper --- > UserMapper.xml),位置一般在resource目录下创建mapper目录,将所有Mapper保存到这里

接口:

Mybatis(千峰学习笔记)_第5张图片

映射xml:

Mybatis(千峰学习笔记)_第6张图片

创建Mapper.xml映射文件

此时我们已经创建好了MyBatis最基本的接口和映射文件了,如果我们想对user表做一个新增用 户数据的操作,我们应该怎么做,首先需要编写接口中的方法

package mapper;

/**
 * Description:
 */
public interface UserMapper {
    /**
     * 新增用户数据方法
     * @return 新增结果
     */
    Integer insert();
}

对应的xml映射编写,我们需要查看官网提供的案例,直接粘贴并且修改

Mybatis(千峰学习笔记)_第7张图片

Mybatis(千峰学习笔记)_第8张图片

namespace属性:此属性的作用就是绑定当前映射xml文件与对应接口的关系,也就是我们需 要把UserMapper的全限定名填写到namespace属性中,建立接口与xml的关系

CURD标签:在官方提供的案例中,mapper中出现了select标签,其实我们要编写sql语句的位置就是在这里,也就是mapper标签下包含了CURD所用到的所有标签,select、update、insert、delete,我们需要写对应的sql语句只需要写到对应的标签中即可,标签中的id就是对应当前接口中要执行的方法名称

package mapper;

/**
 * Description:
 */
public interface UserMapper {
    /**
     * 新增用户数据方法
     * @return 新增结果
     */
    Integer insert();
}

注意:其实使用MyBatis的核心就在于梳理好对应关系

数据表——>实体类——>Mapper接口——>Mapper.xml文件

此时我们回到mybatis-config.xml文件中,在此文件最后引入映射文件位置,要填写上我们编写 好的映射文件。




    
        
            
            
                
                
                
                
            
        
    
    
    
        
    

构建会话工厂

在设置好MyBatis核心配置文件,Mapper接口与Mapper.xml映射文件后,我们现在要通过MyBatis提供的API来执行具体SQL语句了。

Junit单元测试

● Junit是Java语言的单元测试框架。

● Junit测试也是程序员测试,即所谓的白盒测。

主要作用:通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方 法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程 序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以 后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。

例子:比如我们想要开发一个web工程,它可以帮助我们单独测试某一层的代码是否能够正确完 成业务以及执行是否成功。

具体使用:一般在项目的 test>java 目录中编写测试类型,通过@test注解来声明测试方法

Mybatis(千峰学习笔记)_第9张图片

MyBatisAPI调用

MyBatis中的核心类型为SqlSessionFactory,也就是说我们现在需要将当前所配置的核心配置文件以及Mapper映射配置都加载到此类型中,那么此类型可以通过SqlSessionFactoryBuilder进行获取

SqlSessionFactory:"生产"SqlSession的"工厂"

工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关 代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象

SqlSession:通过SqlSessionFactory生产而来,代表Java程序和数据库之间的会话,通过此对 象来完成SQL语句的执行和获取结果等操作。

import mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

/**
 * Description:
 */
public class MyBatisDemo {
    @Test
    public void test() throws IOException {
        //读取MyBatis的核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //创建SqlSessionFactory对象(通过创建SqlSessionFactoryBuilder来进行获取)
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        //创建sqlSession对象(Mybatis操作数据库的会话对象,通过此对象可以获取SQL语句,并执行)
        //注意:openSession方法默认是不会进行自动事物提交的,所以我们如果想做DML操作并且自动提交事物,需要加上true参数,默认为false
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        //获取Mapper对象(代理模式——>可以帮助我们返回当前接口的实例化对象)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //测试功能
        Integer num = userMapper.insert();
        //手动提交
        //sqlSession.commit()
        System.out.println(num);
    }
}

执行效果:

Mybatis(千峰学习笔记)_第10张图片

注解方式映射

各位以上就是MyBatis框架使用的最基本的使用方式,那么对于简单的sql操作其实我们可以不使 用Mapper映射文件,MyBatis其实还提供了注解的映射方式,我们可以一起来试验一下

将UserMapper.xml文件中的insert标签内容删除,然后直接在UserMapper接口insert方法上添 加@insert("SQL语句")注解,再来看执行结果




    



package mapper;

import org.apache.ibatis.annotations.Insert;

/**
 * Description:
 */
public interface UserMapper {
    /**
     * 新增用户数据方法
     * @return 新增结果
     */
    @Insert("insert into tb_user(username,password) values('张三','123')")
    Integer insert();
}

执行单元测试(内容不变)

Mybatis(千峰学习笔记)_第11张图片

其实不仅仅是@Insert注解,针对增删改查都有对应的注解。

mybatis-config配置文件-properties

properties:该标签可以引入外部配置的属性,也可以自己配置。该配置标签所在的同一个配置文件中的其他配置均可引用此配置中的属性。

目前我们的配置文件中数据库连接相关参数其实是写死的,但是我们清楚一般情况下这种参数的配置我们不会写死,而是通过properties这种配置文件进行读取,所以我们完全可以利用properties配置文件配合properties标签来进行读取引入

1.首先我们需要创建一个jdbc.properties的配置文件

注意:因为在一个项目中可能存在多个properties配置文件,所以可能出现重名的key,所以起 名时我们可以加上对应的前缀来避免此种情况发生

db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3307/db_mybatis?serverTimezone=GMT%2B8
db.username=root
db.password=1234

2.在mybatis-config配置文件中通过properties标签引入以上配置




    
    
    
        
            
            
                
                
                
                
            
        
    
    
    
        
    

3. 测试,新增数据,查看结果

Mybatis(千峰学习笔记)_第12张图片

Mybatis(千峰学习笔记)_第13张图片

mybatis-config配置文件-其他配置

在WEB工程中,对于MyBatis最核心的全局配置文件是mybatis-config.xml文件,其中包含了数据库的连接配置信息,Mapper映射文件的加载路径、全局参数、类型别名等

具体配置信息大致如下:

配置项 解析
configuration 包裹所有配置标签,是整个配置文件的顶级标签。
properties 属性,该标签可以引入外部配置的属性,也可以自己配置。该配置标签所在的同一个配置文件中的其他配置均可引用此配置中的属性。
settings 全局配置参数,用来配置一些改变运动时行为的信息,例如是否使用缓存机制,是否使用延迟加载,是否使用错误处理机制等。并且可以设置最大并发请求数量、最大并发事物数量,以及是否启用命令空间等。
typeAliases 类型别名,用来设置一些别名来代替Java的长类型声明,如java.lang.int 变为 int,减少配置编码的冗余。
typeHandlers 类型处理器,将sql中返回的数据库类型转换为相应Java类型的处理器配置。
objectFactory 对象工厂,实例化目标类的工厂类配置。
plugins 插件,可以通过插件修改MyBatis的核心行为,例如对语句执行的某一点进行拦截调用。
environments 设置多个连接数据库的环境
environment 设置具体的连接数据库的环境信息
transactionManager 事物管理,指定MyBatis的事物管理器。
dataSource 数据源,使其中的type指定数据源的连接类型,在标签对中可以使用properties属性指定数据库连接池的其他信息。
mappers 映射器,配置sql映射文件的位置,告知MyBatis去哪里加载sql映射配置

以上是mybatis-config配置文件的基本全部配置内容,当然其中很多我们是可以省略的,这些内 容其实我们也是主要作为了解,并且其中很多内容可以省略,但是要注意配置顺序不可以混乱。

注意:后期SSM框架整合之后,其实mybatis-config文件可以被省略,可以统一交给Spring进 行管理。

详细配置详解可以查看官网:https://mybatis.net.cn/configuration.html

简单演示案例:




    
    
    
    
        
    
    
        
        
            
            
            
                
                
                
                
                
                
                
                
            
        
    
    
    
        
        
        
    

MyBatis完成增删改查

MyBatis最主要的功能就是完成业务的增删改查,所以我们必须要搞定基本的这种需求

插入功能

接口写法

    /**
     * 新增用户数据方法
     * @return 新增结果
     */
    @Insert("insert into tb_user(username,password) values('小李','789')")
    Integer insert();

xml写法

    
    
        insert into tb_user(username,password) values('小明','123')
    

删除功能

接口写法

/**
 * 删除数据方法
* @return 影响行数
*/
 @Delete("delete from tb_user where id=3")
 Integer delete();

xml写法



delete from tb_user where id=3

更改功能

接口写法

/**
 * 修改方法
* @return 影响行数
*/
@Update("update tb_user set username = '赵四' where id = 4")
Integer update();

xml写法



update tb_user set username = '赵四' where id = 4

查询功能

接口写法

/**
* 简单查询
* @return
*/
@Select("select * from tb_user where id = 2")
User select();

xml写法

注意:如果利用XML写法进行查询,就必须指定以下映射关系才能使用

  resultType:设置默认映射关系

  resultMap:设置自定义映射关系(多用于复杂查询-多表查询)


多数据查询

多数据查询其实就是接受多条User的实例,所以这里我们可以通过List集合来进行接受

接口写法

/**
* 查询全部数据
* @return 返回所有数据集合
*/
@Select("select * from tb_user")
List selectAll();

xml写法,同样的道理必须设置resultType属性


具体测试方法

@Test
public void selectAll(){
    try {
        SqlSession sqlsession = MybatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List users = userMapper.selectAll();
        // lambda表达式遍历
        users.forEach(user ——> System.out.println(user));
        //System.out.println(users.toString());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

MyBatis中传递参数

之前我们所编写的SQL语句都是写死的,但是实际上我们在编写SQL语句的时候应该是可以传递 参数的,比如用户传递的id来进行查询数据,那么对于这种业务,我们其实可以通过MyBatis提 供的获取用户传递的参数的方法来进行解决,最常见的有两种:

● ${}: 此种方式本质为字符串拼接

● #{}: 此种方式为占位字符串

SELECT * FROM ${tableName} WHERE username=#{username}

获取单个参数

现在有一个需求,查询tb_user表中id为3的用户信息,具体编写如下

UserMapper接口

此时的#{}可以替换为${}

@Select("select * from tb_user where id = #{id}")
User selectById(Integer id);

测试

    @Test
    public void selectById() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectById(3);
        System.out.println(user.toString());
    }

获取多个参数

如果此时需求为,查询tb_user表中用户id为1的数据,此时我们就要把id和表名都作为参数。

注意:此时表名不能使用#{}需要使用${},因为表名位置要进行字符串拼接,而#{}占位符会出现语法问题。

@Select("select * from ${table} where id = #{id}")
User selectByIdAndTable(String table,Integer id);

测试

    @Test
    public void selectByIdTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectByIdAndTable("tb_user", 1);
        System.out.println(user.toString());
    }

此时会报错:

在调用抽象方法时,给出了2个参数的值,分别是table的值和id的值,这2个值在MyBatis处理时,参数名称其实分别是arg0和arg1,或者,使用param1和param2也可以访问到这2个参数!

@Select("select * from ${arg0} where id = #{arg1}")
User selectByIdAndTable(String table,Integer id);

       关于以上问题,其根本原因在于java语言源代码.java文件在执行之前,需要被编译为.class字节码文件,在这个编译过程中,如果没有特殊的干预的情况下,所有局部变量的变量名都是不会被保存的,所以,即使在设计抽象方法时,使用了table、id这样的参数名,其实,在最终运行的.class文件中,根本就没有这样的名称!

       当抽象方法的参数只有1个时,MyBatis会自动的使用唯一的那一个参数,所以,在配置SQL映 射时,使用的#{}占位符中的名称根本就不重要!在开发时,推荐使用规范的名称,但是,从代 码是否可以运行的角度来说,这个占位符中写什么都不重要!

       在MyBatis的处理过程中,可以根据arg或param作为前缀,按照顺序来确定参数的值,所以, 使用第1个参数时,可以使用arg0或param1作为名称,第2个可以使用arg1或param2 作为名 称,如果有更多参数,顺序编号即可,序号可参考抽象方法的声明!

      当然,在配置SQL映射时,使用arg0、arg1或param1、param2这样的名称,并不利于代码的阅读和理解,所以Mybatis框架提供了@Param注解,可以在抽象方法的参数列表中,在每个参数之前添加该注解,并在注解中配置参数的名称:

    @Select("select * from ${table} where id = #{id}")
    User selectByIdAndTable(
            @Param("table") String table,
            @Param("id") Integer id
    );

测试

Mybatis(千峰学习笔记)_第14张图片

总结:在使用MyBatis框架,设计抽象方法时,如果参数的数量超过1个(有2个或更多个),就 为每一个参数都添加@Param注解

Map类型参数

若mapper接口中的方法需要的参数为多个时,此时也可以手动创建Map集合,将数据放入到 Map中通过#{}或者${}访问Map集合键就可以获取对应的值

@Select("select * from tb_user where username = #{username} and password = #{password}")
User selectByUsernameAndPassword(Map map);

测试

    @Test
    public void selectByUsernameAndPasswordTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //手动将输入放入都Map中
        Map map = new HashMap();
        map.put("username","jerry");
        map.put("password","456");
        User user = userMapper.selectByUsernameAndPassword(map);
        System.out.println(user);
    }

实体类参数

此时如果有插入数据的需求,我们可以通过实体类参数的方式来进行插入数据

    @Insert("insert into tb_user(username,password) values(#{username},#{password})")
    Integer insertToUser(User user);

测试

    @Test
    public void insertToUserTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null,"赵六","258");
        int num = userMapper.insertToUser(user);
        System.out.println(num);
    }

Mybatis(千峰学习笔记)_第15张图片

总结:MyBatis执行时候就会自动读取参数参数User的属性填充到对应占位符号#{},比如会读取User的username属性填充到#{username}的位置,按照这个规则执行SQL就可以将User对象中 的数据存储到数据库中。

MyBatis获取参数方式详解

使用#{}格式的占位符时,只能表示某个参数值,不可以表示SQL语句中的某个片段!使用这种占 位符时,MyBatis在处理过程中,是使用预编译的做法,即:使用占位符来填充整个SQL语句,此时,并不关心占位符对应的值是多少,就直接将这样的SQL语句交给数据库进行词法分析、语 义分析、编译,编译之后,再将占位符对应的值代进编译好的SQL语句中并执行!

使用${}格式的占位符时,可以表示SQL语句中的任何部分,可以是某个值,也可以是SQL语句中 的某个片段!使用这种占位符时,MyBatis在处理过程中,是先将占位符对应的值拼接到SQL语 句中,然后,将整个SQL语句交给数据进行词法分析、语义分析、编译,如果无误,就可以直接 执行SQL语句,如果出现语法错误,就会导致程序运行过程中出现异常!

Mybatis(千峰学习笔记)_第16张图片

使用#{}格式的占位符时,由于使用了预编译的做法,所以,这种处理方式是安全的,而${}占位 符是先拼接SQL语句再执行的过程,并没有预编译的处理,所以,存在SQL注入的风险的!

所以,虽然使用${}的占位符可以实现的效果看似更多,但是,需要考虑数据类型的问题(例如字 符串类型的值需要自行添加一对单引号),同时还存在SQL注入的风险,一般不推荐使用,应该 优先使用#{}格式的占位符!

MyBatis查询数据为Map

很多时候,我们需要将查询出的数据转换为Map来方便业务层的访问,那么数据表中的数据可以 映射为实体类数据,也就是属性:值,所以其实我们的查询结果也可以转换为Map

查询一条数据

比如当前业务为查询id为5的用户信息为一个Map

    @Select("select * from tb_user where id = #{id}")
    Map findUserToMap(int id);

测试

    @Test
    public void findUserToMapTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Map users = userMapper.findUserToMap(5);
        System.out.println(users);
    }

Mybatis(千峰学习笔记)_第17张图片

当然此时为注解的写法,但是如果改为xml,要注意resultType的格式

注意:在MyBatis中,对于Java中常用的类型都设置了类型别名

● java.lang.Integer对应别名:int | Integer

● int对应别名:int | integer

● Map对应别名:map

● List对应别名: list

    
    

查询多条数据

其实查询多条数据为Map有两种方式:

1、 一条数据对应一个Map,我们可以将多个Map放入到List集合中

    //查询所有用户数据
    @Select("select * from tb_user")
    List> findUserAllToMap();

测试

    @Test
    public void findUserAllToMapTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List> users = userMapper.findUserAllToMap();
        users.forEach(user -> {
            System.out.println(user);
        });
    }

Mybatis(千峰学习笔记)_第18张图片

2、一条数据对应一个Map,将多个Map放入到一个Map集合中,但是要配合 注解设置Map集 合的键,@MapKey注解会把对应查询出的数据中的某一个字段作为建,所以必须要保证此字段 不能重复,所以一般为id。

注意:此种方式只能用于xml

接口:

    @MapKey("id")
    Map findUserAllToMap();

xml:

    
    

测试结果:

    @Test
    public void findUserAllToMapTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Map users = userMapper.findUserAllToMap();
        System.out.println(users);
    }

MyBatis获取自增的主键

当我们执行新增操作的时候,因为一般情况下id字段都是主键自增的,所以对应映射的实体类对 象,id的值始终是为null的,那么如果出现获取新增数据的id这样的业务,我们就无法解决了, 所以为了能够解决这种业务问题,我们可以在mapper.xml文件中加上两个属性

● useGeneratedKeys: 为true时,设置使用自增的主键

● keyProperties: 指定具体主键字段名(将获取的自增的主键放在传输的参数user对象的某个属性中)

如果采用注解的写法,添加注解:

    @Options(useGeneratedKeys = true, keyProperty = "id")

注解写法:

@Insert("insert into tb_user(username,password) values(#{username},#{password})")
@Options(useGeneratedKeys = true,keyProperty = "id")
Integer insertToOne(User user);

xml写法:

    
    
        insert into tb_user(username,password) values(#{username},#{password})
    

测试:

    @Test
    public void insertToOne() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null, "王三麻子", "789");
        Integer num = userMapper.insertToOne(user);
        System.out.println(num);
        System.out.println(user);
    }

MyBatis分页

分页的作用:MyBatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对 数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理 小部分数据,这样对数据库压力就在可控范围内。

执行分页的SQL语句为:

#语法   stratIndex:当前页的起始索引  pageSize每页显示的条数
select * from table limit startIndex, pageSize

select * from tb_user limit 0,2;

具体用法

UserMapper接口

    /**
     * 用户查询分页
     * @param map 分页参数
     * @return 分页的数据
     */
    @Select("select * from tb_user limit #{startIndex},#{pageSize}")
    List selectUserLimit(Map map);

测试

    @Test
    public void selectUserLimitTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        int currentPage = 1;  //第几页
        int pageSize = 2;  //每页显示几个
        Map map = new HashMap();
        //起始位置 =  (当前页面 - 1 ) * 页面大小
        map.put("startIndex",(currentPage-1)*pageSize);
        map.put("pageSize",pageSize);
        List users = userMapper.selectUserLimit(map);
        users.forEach(user -> System.out.println(user));
    }

Mybatis(千峰学习笔记)_第19张图片

MyBatis关联查询

当我们碰到需要使用关联查询的业务时如何使用MyBatis帮我们完成那?

数据准备

用户表tb_user对应tb_dept部门表

SQL脚本:

CREATE TABLE tb_dept (
    id INT(11) AUTO_INCREMENT COMMENT '部门ID',
    name VARCHAR(20) NOT NULL COMMENT '部门名',
    PRIMARY KEY (id)
);

-- 在tb_dept表中插入数据
INSERT INTO tb_dept (name) VALUES ('Java'), ('C++'), ('Linux');

-- tb_user 表中增加外键列,dept_id
ALTER TABLE tb_user ADD COLUMN dept_id INT;

-- 根据 tb_dept 中id更新tb_user表中dept_id列的值,供测试使用
UPDATE tb_user u SET dept_id = (SELECT d.id FROM tb_dept d WHERE 
name='Java') WHERE u.id >5;

UPDATE tb_user u SET dept_id = (SELECT d.id FROM tb_dept d WHERE 
name='C++') WHERE u.id <=5;

实际案例

一般我们这种联合查询的时候,都不会查出两张表全部的信息,只会查询出有用的信息,比如: 查询员工id,姓名对应的部门名称,此时我们就需要查询tb_user表的id,name和tb_dept表的 name

注意:此时两张表有重名的字段名称我们可以通过别名来进行区分

select u.id,u.username,d.name
from tb_user u
left join tb_dept d
on u.dept_id=d.id;

此时我们的查询结果只有3条数据,按照正常的映射逻辑来说,如果我们想通过MyBatis完成以上 的查询,就需要有一个对应的实体类来进行接受查询的数据,但是此时明显这种联合查询的数据 不属于任何实体类,此时我们可以采取使用值对象(Values Object)的方式进行封装查询结果, 也就是查询结果有几列,在vo这个类型中就定义几个属性

VO类型:

package vo;

/**
 * Description: 用于当前业务查询到的数据进行封装
 * 是实体类的一种,查询结果有几列
 * 就封装几个对应的属性
 */
public class UserVO {
    Integer id;
    String username;
    String name;

    public UserVO() {
    }

    public UserVO(Integer id, String username, String name) {
        this.id = id;
        this.username = username;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getName() {
        return name;
    }

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

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

编写UserMapper查询方法

接口:

 @Select("select u.id,u.username,d.name\n" +
        "from tb_user u\n" +
        "left join tb_dept d\n" +
        "on u.dept_id=d.id;")
 List findUserDept();

xml写法

注意:虽然目前这种查询还可以通过注解的方式编写,但是一般联合查询sql语句比较复杂一般 会采取xml的形式进行编写

 
 

测试

    @Test
    public void findUserDeptTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List list = userMapper.findUserDept();
        list.forEach(user -> System.out.println(user));
    }

Mybatis(千峰学习笔记)_第20张图片

resultMap应用

假设现在要查询某个部门的信息,并且查询该部门有哪些用户

执行的SQL

select
  d.*,u.id,u.username
from
  tb_dept d
left join
  tb_user u
on
  d.id = u.dept_id
where
  d.id = 1;

结果如下:

Mybatis(千峰学习笔记)_第21张图片

从这个查询结果中不难看出,此时我们还需要使用vo类型来封装本次查询的查询结果,但是要注 意一点,此时查询出的员工信息不是一条数据而是多条。所以vo类型设计如下:

package vo;

import entity.User;

import java.util.List;

/**
 * Description:
 */
public class DeptVo {
    Integer id;
    String name;
    List users;

    public DeptVo() {
    }
    public DeptVo(Integer id, String name, List users) {
        this.id = id;
        this.name = name;
        this.users = users;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List getUsers() {
        return users;
    }
    public void setUsers(List users) {
        this.users = users;
    }

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

接下来编写接口

注意:此前使用的UserMapper.java都是配置处理用户数据的相关功能,此次,查询的主体是部 门的数据,抽象方法应该另外创建接口来存放,以便于管理数据!

创建DeptMapper接口

/**
* 查询当前部门信息,包括此部门对应的用户信息
* @param id 部门编号
* @return 部门信息+部门员工信息
*/
DeptVo findDept(Integer id);

创建DeptMapper.xml文件

注意:此次的查询时,某个部门可能有多个用户,所以结果可能有多条,但是,抽象方法的返回 值是1个对象,那么,就存在MyBatis不知道如何将若干个查询结果封装到1个对象中去!所以, 在配置时,就需要结合需要执行的SQL语句并使用resultMap来完成配置:

● resultMap属性:设置自定义映射id名称

● resultMap标签:

      ○ id:表示自定义映射的唯一标识,不能重复

      ○ type:查询的数据要映射的实体类的类型

      ○ id标签:设置主键字段映射

      ○ result标签: 设置普通字段映射

            ■ column:映射具体的列

            ■ property:具体映射的属性

            ■ collection : 通过它来解析查询到的多个员工信息

                   ■ property:具体映射的属性

                   ■ ofType:映射的类型






    
        
        
        
            
            
            
        
    
    
    

测试:

    @Test
    public void findDeptTest() throws Exception {
        SqlSession sqlSession = MyBatisDemo.getSqlSession();
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        DeptVo vo = deptMapper.findDept(1);
        System.out.println(vo);
    }

附加:若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类 中的属性名符合Java的规则(使用驼峰)。此时也可通过以下两种方式处理字段名和实体类中的 属性的映射关系

select d_name dName(别名) from dept

当然也可以开启全局设置在核心配置文件中的setting标签中,设置一个全局配置信息 mapUnderscoreToCamelCase,可以在查询表中数据时,自动将_类型的字段名转换为驼峰。

例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为 userName。


    

MyBatis加入Log4j查看日志

我们在执行的过程中能否监控执行的SQL语句那?其实可以通过Log4j来进行实现

添加依赖

        
        
            log4j
            log4j
            1.2.17
        

添加配置,在resource目录中添加即可

日志的级别:FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

注意:从左到右打印的内容越来越详细

log4j.xml




    
        
        
            
        
    
    
        
    
    
        
    
    
        
        
    

MyBatis动态SQL

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了 解决拼接SQL语句字符串时的问题

简单理解:根据调用方法时给出的参数不同,最终生成的SQL语句会不相同!

动态SQL-if

IF一般实现的效果就是根据不同的参数,查询出不同的结果,比如现在我们来写一条查询语句

select * from tb_user;

上面的这条SQL语句本身会查出tb_user这张表中全部的数据,但是如果我们现在加上id条件,或 者加上多个条件,比如id and username,动态SQL IF就会根据不同的参数查询出不同的结果

创建一个新的DynamicMapper

    List selectDynamicIf(Map map);




    
    

不加任何条件测试 (新建测试类:MyBatisDemo1):

import entity.User;
import mapper.DynamicMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Description:
 */
public class MyBatisDemo1 {
    private static SqlSession getSqlSession() throws IOException {
        //读取MyBaits的核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
                //创建SqlSessionFactory对象(通过创建SqlSessionFactoryBuilder来进行获取)
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        //创建sqlSession对象(MyBatis的操作数据库的会话对象,通过此对象可以获取SQL语句,并执行)
        //注意:openSession对象默认是不会进行自动的事务提交的,所以我们如果想做DML操作并且自动事务提交,需要加上true参数,默认为false
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        return sqlSession;
    }
    @Test
    public void selectDynamicIfTest(){
        try {
            SqlSession sqlSession = getSqlSession();
            DynamicMapper mapper =
                    sqlSession.getMapper(DynamicMapper.class);
            Map map = new HashMap();
            List users = mapper.selectDynamicIf(map);
            users.forEach(user -> System.out.println(user));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

结果查询出全部数据:

Mybatis(千峰学习笔记)_第22张图片

添加条件测试(不同条件结果不同)

    @Test
    public void selectDynamicIfTest01(){
        try {
            SqlSession sqlSession = getSqlSession();
            DynamicMapper mapper = sqlSession.getMapper(DynamicMapper.class);
            Map map = new HashMap();
            //添加条件
            //map.put("id","1");
            map.put("username","jerry");
            List users = mapper.selectDynamicIf(map);
            users.forEach(user -> System.out.println(user));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Mybatis(千峰学习笔记)_第23张图片

动态SQL-where

在上一个IF的案例中,我们明显发现执行的SQL语句中Where 1=1有些多余,有的时候我们就不 需要使用Where那么Where可以不可以变成动态的拿?答案是可以的

我们只需要将刚才的案例where 1=1改成动态标签where即可


    
    
 

而且如果没有没有任何IF的条件,Where标签不会生成Where子句

Mybatis(千峰学习笔记)_第24张图片

但是如果此时我们修改语句为and或者or放在后面

    

此时只有第一个条件的测试就会报错,因为后面会多于一个and关键字

Mybatis(千峰学习笔记)_第25张图片

为了避免这种情况我们可以使用另外一种trim的标签

动态SQL-trim

● trim用于去掉或添加标签中的内容

● 常用属性:

       ○ prefix:在trim标签中的内容的前面添加某些内容

       ○ suffix:在trim标签中的内容的后面添加某些内容

       ○ prefixOverrides:在trim标签中的内容的前面去掉某些内容

       ○ suffixOverrides:在trim标签中的内容的后面去掉某些内容

通过此标签,我们甚至可以代替使用where,通过trim标签解决以上案例问题

    
    

测试结果:

Mybatis(千峰学习笔记)_第26张图片

当然如果没有任何参数的情况下,trim标签中任何内容都不会出现,变为正常查询

动态SQL-choose、when、otherwise

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

choose:表示此套标签的最外层

when:表示判断各种条件,类似于case

otherwise:所有条件都不符合时走这里


 

测试

 @Test
 public void selectDynamicIfTest(){
    try {
        SqlSession sqlSession = getSqlSession();
        DynamicMapper mapper = sqlSession.getMapper(DynamicMapper.class);
        Map map = new HashMap();
        //添加条件
        //map.put("id","1");
        map.put("username","tom");
        List users = mapper.selectDynamicIf(map);
        users.forEach(user -> System.out.println(user));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

结果:

Mybatis(千峰学习笔记)_第27张图片

如果没有任何条件走otherwise

Mybatis(千峰学习笔记)_第28张图片

动态SQL-foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取 到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

假设需要实现“删除id=?, id=?, id=?的用户数据”,即“一次性删除若干条数据”,具体需要删除几 条、哪几条,对于开发人员来说,都是不确定的。实现这样的操作,需要执行的SQL语句大致是

DELETE FROM tb_user WHERE id IN (?,?,?)

以上SQL语句中,问号的数量与值都是开发人员无法确定的,但是,可以肯定的是“这里的问号表 示的都是id值”。实现这个功能,在设计抽象方法时,可以将若干个问号的值,也就是若干个id的 值,使用集合或数组来表示,例如:

Integer deleteByIds(List ids);

或者

Integer deleteByIds(Integer[] ids);

甚至是可变参数

Integer deleteByIds(Integer... ids);

完成了抽象方法的声明之后,就需要配置以上抽象方法的映射!配置映射时,需要使用节点实现 动态SQL中的遍历,即遍历以上方法中的参数值,以生成?,?,?这样的SQL语句片段!

Integer deleteByIds(@Param("ids") Integer[] ids);

当然如果你不加当前我们传递的是数字格式就需要在collection属性上添加 array 如果是List集合 就需要添加 list 属性值

属性解释:

collection:表示被遍历的对象,当抽象方法的参数只有1个且没有添加@Param注解时,如果参 数的类型是List集合类型的,则取值为list,如果参数的类型是数组类型的,则取值为array;否 则,取值为@Param注解中配置的名称;

item:在遍历过程中,被遍历到的数据的名称,将根据这个名称来使用被遍历到的数据,所以, 在的子级,将根据这个属性配置的值来使用数据;

separator:分隔符,用于分隔遍历时生成的多个占位符(也可以理解为遍历产生的多个值);

open与close:遍历生成的SQL语句片段的起始字符串和结束字符串。比如:open="(" close=")"

动态删除案例

Dynamicmapper.xml

    
    
        DELETE FROM tb_user WHERE id IN
        
            #{id}
        
    

测试:

    @Test
    public void deleteByIdsTest() throws IOException {
        SqlSession sqlSession = getSqlSession();
        DynamicMapper mapper = sqlSession.getMapper(DynamicMapper.class);
        Integer n = mapper.deleteByIds(new Integer[]{1, 3});
        System.out.println(n);
    }

Mybatis(千峰学习笔记)_第29张图片

MyBatis缓存

什么是缓存?

● 存在内存中的临时数据

● 将用户经常查询的数据放入缓存(内存)中,用户去查询数据就不需要每次都从磁盘上查 询,从缓存(内存)中查询,从而提高查询效率,解决了高并发系统的性能问题。

为什么使用缓存?

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

什么样的数据需要缓存?

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

MyBatis提供缓存

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

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

      ○ 默认情况下,MyBatis默认开启一级缓存(SqlSession级别的缓存,也称之为本地缓 存,SqlSession对象使用到close)

      ○ 二级缓存需要手动开启和配置,它是基于SqlSessionFactory级别的缓存(简单理解就 是通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存)

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

一级缓存

一级缓存也称为本地缓存:只在SqlSession这个区间有效

一级缓存相当于一个Map

● 与数据库同一会话期间查询到的数据会放入到本地缓存中。

● 以后需要获取相同的数据,直接从缓存中拿,不需要再次查找数据库。

测试案例

首先我们新建一个CaCheMapper

package mapper;

import entity.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

/**
 * Description:
 */
public interface CacheMapper {
    @Select("select * from tb_user where id = #{id}")
    User selectById(Integer id);

    @Update("update tb_user set username = #{username} where id = #{id}")
    Integer UpdateById(
            @Param("id") Integer id,
            @Param("username") String username);
}

一定要创建对应的Mapper.xml映射





新建测试

    @Test
    public void selectByIdTest() throws IOException {
        SqlSession sqlSession = getSqlSession();
        //一级缓存
        CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);
        User user = cacheMapper.selectById(2);
        System.out.println(user);
        System.out.println("---------------------------");
        User user1 = cacheMapper.selectById(2);
        System.out.println(user1);
        //结果为true,证明user与user1引用地址都相同,从缓存中读取的数据
        System.out.println(user==user1);

        //一级缓存结束
        sqlSession.close();
    }

查看验证一级缓存结果:

注意:此时我们要查看日志,首先sql语句只执行了一次,并且查询结果相同,而且user与user1 的对象引用地址都完全相同,证明user1是数据是读取的上一次查询的缓存数据。

Mybatis(千峰学习笔记)_第30张图片

一级缓存失效的四种情况

1. 不同的SqlSession对应不同的一级缓存

2. 同一个SqlSession但是查询条件不同

    @Test
    public void selectByIdTest() throws IOException {
        SqlSession sqlSession = getSqlSession();
        //一级缓存
        CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);
        User user = cacheMapper.selectById(2);
        System.out.println(user);
        System.out.println("---------------------------");
        User user1 = cacheMapper.selectById(5);
        System.out.println(user1);
        //结果为true,证明user与user1引用地址都相同,从缓存中读取的数据
        System.out.println(user==user1);

        //一级缓存结束
        sqlSession.close();
    }

Mybatis(千峰学习笔记)_第31张图片

3. 同一个SqlSession两次查询期间执行了任何一次增删改操作

    @Update("update tb_user set username = #{username} where id = #{id}")
    Integer UpdateById(
            @Param("id") Integer id,
            @Param("username") String username);

测试

    @Test
    public void selectByIdTest() throws IOException {
        SqlSession sqlSession = getSqlSession();
        //一级缓存
        CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);
        User user = cacheMapper.selectById(2);
        System.out.println(user);

        Integer num = cacheMapper.UpdateById(2, "tom");
        System.out.println(num);

        System.out.println("---------------------------");
        User user1 = cacheMapper.selectById(2);
        System.out.println(user1);
        //结果为true,证明user与user1引用地址都相同,从缓存中读取的数据
        System.out.println(user==user1);

        //一级缓存结束
        sqlSession.close();
    }

结果:

Mybatis(千峰学习笔记)_第32张图片

4. 同一个SqlSession两次查询期间手动清空了缓存

    @Test
    public void selectByIdTest() throws IOException {
        SqlSession sqlSession = getSqlSession();
        //一级缓存
        CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);
        User user = cacheMapper.selectById(2);
        System.out.println(user);

        //手动调用清理缓存方法
        sqlSession.clearCache();
        
        System.out.println("---------------------------");
        User user1 = cacheMapper.selectById(2);
        System.out.println(user1);
        //结果为true,证明user与user1引用地址都相同,从缓存中读取的数据
        System.out.println(user==user1);

        //一级缓存结束
        sqlSession.close();
    }

二级缓存

二级缓存也称之为全局缓存,因为一级的作用域太低了,所以才有了二级缓存

二级缓存基于SqlSessionFactory级别的缓存,通过同一个SqlSessionFactory创建的SqlSession 查询的结果会被缓存

工作机制:

● 一个SqlSession查询一条数据,这个数据就会被放在当前SqlSession的一级缓存中

● 如果当前SqlSession关闭了,这个SqlSession对应的一级缓存就没了,但我们的需求是 SqlSession关闭,一级缓存中的数据被保存在二级缓存中

● 新的SqlSession查询相同信息,就可以从二级缓存中获取内容

开启二级缓存

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

但是在这之前你需要在核心配置文件中开启

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

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

可用的清除策略有:

● LRU – 最近最少使用:移除最长时间不被使用的对象。

● FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

● SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。

● WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

在Mybatis-config.xml中开启全局缓存

Mybatis(千峰学习笔记)_第33张图片

使用二级缓存

注意:使用二级缓存要注意以下几点:

1. 实体类必须实例化

2. 必须使用mapper.xml映射

3. 因为二级缓存是基于SqlSessionFactory的,SqlSession对象要在同一个缓存范围内

在CacheMapper.xml中添加

这里我们可以使用官方提供的FIFO缓存:FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或 列表的 512 个引用

Mybatis(千峰学习笔记)_第34张图片

CacheMapper.xml




    
    
    

此时二级缓存已经开启,我们现在的实验结果是在一个sqlSession会话关闭以后,另外一个 sqlSession可以从二级缓存中读取数据

import entity.User;
import mapper.CacheMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

/**
 * Description:
 */
public class MyBatisDemo2 {
    //因为二级缓存是基于SqlSessionFactory的,所以这里我们更改封装方法
    private static SqlSessionFactory getSqlSessionFactory() throws IOException {
        //读取MyBaits的核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //创建SqlSessionFactory对象(通过创建SqlSessionFactoryBuilder来进行获取)
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        return sqlSessionFactory;
    }

    @Test
    public void selectByIdTest() throws IOException {
        //获取同一个SqlSessionFactory下的SqlSession会话对象
        SqlSessionFactory sqlSessionFactory = MyBatisDemo2.getSqlSessionFactory();
        //开启2个会话 sqlSession1开启
        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);

        //一级缓存
        CacheMapper cacheMapper = sqlSession1.getMapper(CacheMapper.class);
        User user = cacheMapper.selectById(2);
        System.out.println(user);
        //一级缓存结束 sqlSession1会话结束
        sqlSession1.close();

        System.out.println("------------------");

        CacheMapper cacheMapper1 = sqlSession2.getMapper(CacheMapper.class);
        User user1 = cacheMapper1.selectById(2);
        System.out.println(user1);
        //结果为true,证明user与user1引用地址都相同,从缓存中读取的数据
        System.out.println(user==user1);

        //一级缓存结束 sqlSession2会话结束
        sqlSession2.close();
    }
}

测试结果:

Mybatis(千峰学习笔记)_第35张图片

小结:

1. 一级缓存默认开启:用户访问进来先查看一级缓存是否有查询的数据,没有在走数据库查询

2. 当二级缓存开启以后:用户访问进来先查看二级缓存是否有查询的数据,没有在走一级缓 存,如果都没有在走数据库查询

使用第三方EHCache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的 CacheProvider(缓存提供者)。

使用此框架的主要目的是为了替代MyBatis原生的二级缓存,EHCache了解即可。

学习EHCache主要是学习使用第三方组件的套路

而且对于这个级别的缓存,一般在实际工作中作用不大,目前常用的缓存为redis

具体使用步骤

1、导入依赖

        
        
            org.mybatis.caches
            mybatis-ehcache
            1.2.1
        

2、创建核心配置文件

ehcache.xml



    
    
    
    

配置详解:

以下属性是必须的:
maxElementsInMemory - 在内存中缓存的element的最大数目 
maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,
如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:
timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过
timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷
大
timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无
穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.
每个Cache都应该有自己的一个缓冲区.
 diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每
个120s,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除
缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO
(先进先出)

3、设置二级缓存类型

在xxxMapper.xml文件中设置二级缓存类型,让其应用为Ehcache










    

    
    

4、简单测试

Mybatis(千峰学习笔记)_第36张图片

你可能感兴趣的:(mybatis)