【21.9.24】Mybatis

【21.9.24】Mybatis

1. 简介

1.1、Mybatis官网

mybatis – MyBatis 3 | 简介

1.2、什么是Mybatis

  • MyBatis 是一款优秀的持久层框架

  • 它支持自定义 SQL、存储过程以及高级映射

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

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

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

  • 2013年11月迁移到Github

1.3、持久层

(1).数据持久化(动作)

  • 持久化就是将程序的数据在持久化状态和瞬时状态转化的过程

  • 内存:数据断电即失(瞬时状态)

  • 将数据存在数据库(jdbc)或io文件使得数据处于持久化状态

(2).为什么需要持久化?

因为有一些数据我们不想让它丢掉,想一直使用它们,还有就是内存太贵了,用来存储数据显得比较浪费!

(3).持久层(是名词,是一个概念)

  • 完成持久化工作的代码块

  • 层的界限十分明显

1.4、为什么需要Mybatis?

  • 帮助程序员将数据快速存入到数据库

  • 方便

  • 传统的jdbc代码太复杂了,需要简化,所以出现了Mybatis框架。框架的作用是:相当于你填写个人信息时,不再是给一张白纸你填写了,而是给份表格你,这份表格的作用就起到了框架的作用,只要你往里面填信息就能达到想要的效果!

  • 特点:

    • 简单易学

    • 灵活

    • sql和代码的分离,提高了可维护性。

    • 提供映射标签,支持对象与数据库的orm(Object Relation Mapping:对象-关系映射)字段关系映射

    • 提供对象关系映射标签,支持对象关系组建维护

    • 提供xml标签,支持编写动态sql

 

2. 第一个Mybatis程序

2.1、搭建环境

(1).搭建数据库

//创建数据库
create database mybatis;
use mybatis;

//创建表
create table user(
id int(20) not null primary key,
name varchar(30) default null,
pwd varchar(30) default null
)engine=INNODB default charset=utf8;

//向user表中插入数据
insert into user(id,name,pwd) values(1,'狂神','123456'),(2,'张三','12345678'),(3,'李四','12345678910')

(2).新建maven项目

  • 删除src(让这个工程变成一个父工程,以后不用重复导包!!)

  • 编写pom.xml核心配置文件

    <!--2.导入依赖-->
          <!--2.1 导入mysql驱动-->
       <dependencies>
           <dependency>
               <groupId>mysql</groupId>
               <artifactId>mysql-connector-java</artifactId>
               <version>5.1.46</version>
           </dependency>

           <!--2.2 导入mybatis包-->
           <dependency>
               <groupId>org.mybatis</groupId>
               <artifactId>mybatis</artifactId>
               <version>3.5.7</version>
           </dependency>

           <!--2.3 导入junit测试包-->
           <dependency>
               <groupId>junit</groupId>
               <artifactId>junit</artifactId>
               <version>4.12</version>
           </dependency>
       </dependencies>
  • 给父工程创建一个子工程(new一个module)

(3).编写mybatis核心配置文件

在子工程的resource文件夹里新创一个“mybatis-config.xml”核心配置文件,代码如下:

<!--3.1 configuration 核心配置文件-->
<configuration>
<!--3.2 environments复数,代表可以通过default配置id实现多套使用环境下的转换-->
   <environments default="development">

       <environment id="development">
           <!--3.3 事务管理,默认使用jdbc的-->
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <!--3.4 和数据库相关的一些信息-->
               <property name="driver" value="com.mysql.jdbc.Driver"/>
               <!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
               <!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
               <!--&amp:表示&。只不过在xml文件中要这样进行转义-->
               <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>
</configuration>

(4).编写mybatis的工具类

定义一个工具类,因为每用到mybatis时都要做这些准备工作,所以将它封装出来

public class MybatisUtils {

   private static SqlSessionFactory sqlSessionFactory;
   //4.1 一加载类就创建sqlsessionFactory对象,这是使用mybatis的第一步
   static{
       try {
           //4.2 先找到Mybatis的核心配置文件
           String resource = "mybatis-config.xml";
           //4.3 将配置文件转成输入流
           InputStream inputStream =Resources.getResourceAsStream(resource);
           //4.4 使用这个流结合Builder来创建sqlSessionFactory对象
           sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// org.apache.ibatis.session.SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
// 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
   public static SqlSession getSqlSession(){
       //4.5 获得sqlSession
       SqlSession sqlSession=sqlSessionFactory.openSession();
       return sqlSession;
  }
}

简单理解一下SqlSessionFactoryBuilder、sqlSessionFactory、SqlSession的关系:

 

(5).编写代码

  • 5.1 编写实体类

    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 + '\'' +
                   '}';
      }
    }
  • 5.2 编写UserDao接口(Mapper)

    public interface UserDao {
       //获取所有的用户
       List<User> getUserList();
    }
  • 5.3 编写接口的实现类(xml文件)

    由原来的UserDaoImpl转变为一个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">
    <!--5.1 namespace命名空间:绑定一个对应的Dao或Mapper接口,相当于之前接口的实现类-->
    <mapper namespace="com.kuang.dao.UserDao">
       <!--5.2 id:对应接口里的方法名,相当于之前重写接口里的方法-->
       <!--5.3 resultType:经sql语句操作后返回的结果类型,这里要用到全限定名-->
       <select id="getUserList" resultType="com.kuang.pojo.User">
      select * from Blog where id = #{id}
     </select>
    </mapper>
  • 测试

    (1).常见报错:org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.(Mapper还没有注册!!)

    解决办法:每一个Mapper.xml都需要在Mybatis核心配置文件(mybatis-config.xml)中进行注册!

    <mappers>
           <mapper resource="com/kuang/dao/UserMapper.xml"></mapper>
       </mappers>

    (2).配置文件缺少报错

    java.lang.ExceptionInInitializerError

    Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/kuang/dao/UserMapper.xml

    解决方法:在父工程和子工程的pom.xml文件(为了保险,所以都加)加上资源导出代码

    <!--在build中配置resources,防止我们资源导出失败的问题-->
       <build>
       <resources>
           <!--设置正常情况的resources目录下的properties文件-->
           <resource>
               <!--配置路径-->
               <directory>src/main/resources</directory>
               <includes>
                   <!--resources这个文件夹可以包含什么文件,下面配置了可以包含.properties和.xml文件-->
                   <include>**/*.properties</include>
                   <include>**/*.xml</include>
               </includes>
           </resource>
           <!--     设置java路径的properties文件-->
           <resource>
               <directory>src/main/java</directory>
               <includes>
                   <!--java这个文件夹可以包含什么文件,下面配置了可以包含.properties和.xml文件。如果不配置,那么java
                   文件夹下只能写java代码,如果你写了其他格式的文件,到时候项目导出时,那些文件是导不出的!!-->
                   <include>**/*.properties</include>
                   <include>**/*.xml</include>
               </includes>
           </resource>
       </resources>
       </build>

    (3).用junit测试

    一定要关闭sqlSession!!

    public class UserDaoTest {
       @Test
       public void test(){
           //6.1 调用工具类方法获取sqlSession
           SqlSession sqlSession= MybatisUtils.getSqlSession();
           //6.2 方式一:getMapper() 先通过sqlSeesion结合接口映射,然后调用其方法执行相应的sql语句(推荐使用)
           UserDao userDao=sqlSession.getMapper(UserDao.class);
           List<User> userList=userDao.getUserList();
           
           //6.3 方式二:具体到某一个方法,有局限性,需要强转很麻烦!!(不建议使用,了解即可)
          // List<User> userList=sqlsession.selectList("com.kuang.UserDao.getUserList");
           
           for (User user : userList) {
               System.out.println(user);
          }

           //6.4 关闭sqlSeesion
           sqlSession.close();
      }
    }

    结果:image-20210924180944877

     

2.2、CRDU(增删改查)

步骤:先写接口,再去到xml文件写sql语句,最后去写测试类!!

最重要一点:增删改最后必须要提交事务,否则数据库的数据不会进行更新!!

  • id表示接口里面的方法名

  • resultType表示要返回的数据类型

  • parameterType表示参数类型

  • #{id}表示接收传过来的参数id

(1).select语句

<select id="getUserById" resultType="com.kuang.pojo.User">
  select * from mybatis.user where id=#{id};
   </select>

(2).insert语句

<insert id="addUser" parameterType="com.kuang.pojo.User">
  insert into mybatis.user values(#{id},#{name},#{pwd})
   </insert>

(3).update语句

<update id="updateUser" parameterType="com.kuang.pojo.User">
      update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
   </update>

(4).delete语句

<delete id="deleteUser" parameterType="int">
      delete from mybatis.user where id=#{id}
   </delete>

(5).万能Map

用Map来传参你不需要知道数据库表里有多少个字段,你只需传你想做修改的字段过去就行了!!

总结:

  • Map传递参数,直接在sql中传key就行了,而且它存在灵活性。parameterType="map"

  • 对象传递参数,要在sql中把这个对象的所有属性都要写出来,有点局限性,当表的属性一多时就很麻烦了。parameterType="Object"

  • 当只传一个基本类型参数的情况下,可以省略parameter不写

  • 传多个参数一定要想起用map!

//接口代码
//用map来根据id查询用户
   User getUserById2(Map<String,Object> map);
<!--xml代码-->
<!--万能的map:这里的#{userId}是定义了map里的key的值,可由你随意定制-->
   <insert id="addUser2" parameterType="map">
  insert into mybatis.user values(#{userId},#{userName},#{userPwd})
   </insert>
//测试类代码
//万能的Map
   public void addUser2(){
       SqlSession sqlSession=MybatisUtils.getSqlSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       Map<String,Object> map=new HashMap<String, Object>();
       map.put("userId",5);
       map.put("userName","李五");
       map.put("userPwd","555555");
       mapper.addUser2(map);

       //一定要提交事务!!
       sqlSession.commit();

       sqlSession.close();
  }

2.3、模糊查询

(1).在java代码中就传递通配符%过去给sql

 List<User> userList=mapper.getUserLike("%李%");

(2).在sql拼接中使用通配符

//java代码
List<User> userList=mapper.getUserLike("李");
select * from mybatis.user where name like "%"#{value}"%"; <!--拼接死了!-->

3. 配置解析

3.1、核心配置文件

  • mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
           PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--3.1 configuration 核心配置文件-->
    <configuration>
    <!--3.2 environments复数,代表可以配置多套环境供使用-->
       <environments default="development">

           <environment id="development">
               <!--3.3 事务管理,默认使用jdbc的-->
               <transactionManager type="JDBC"/>
               <dataSource type="POOLED">
                   <!--3.4 和数据库相关的一些信息-->
                   <property name="driver" value="com.mysql.jdbc.Driver"/>
                   <!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
                   <!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
                   <!--&amp:表示&。只不过在xml文件中要这样进行转义-->
                   <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                   <property name="username" value="root"/>
                   <property name="password" value="123456"/>
               </dataSource>
           </environment>
       </environments>

       <!--每一个Mapper.xml都需要在Mybatis核心配置文件中进行注册!!这个点很容易忘记的!-->
       <mappers>
           <mapper resource="com/kuang/dao/UserMapper.xml"></mapper>
       </mappers>

    </configuration>

     

  • Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息

configuration 配置
properties 属性
settings 设置
typeAliases 类型命名
typeHandlers 类型处理器
objectFactory 对象工厂
plugins 插件
environments 环境
environment 环境变量
transactionManager 事务管理器--有两种:JDBC和MANAGED
dataSource 数据源--dbcp,c3p0,druid--连接数据库
mappers 映射器

3.2、环境配置(environments)

  • Mybatis可以配置适应多套环境,不过要记住,尽管可以配置多套环境,但每个SqlSessionFactory实例只能选择一种环境进行使用!!

  • Mybatis默认的事务管理器就是JDBC,连接池是POOLED

3.3、属性(properties)

我们可以通过properties属性来实现引用配置文件!!

这些属性都是可外观配置且可以动态替换的,既可以在典型的 Java 属性配置文件(db.properties)中配置, 又可以通过 properties 元素的子元素来传递。

(1).编写一个数据库配置文件

db.properties

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

(2).在核心配置文件中引入

image-20210930201819294

有两种引入格式,分别如下:

用$来引入参数值!!

  1. 直接引入外部文件db.properties

    <!--一、自闭合引入外部配置文件-->
       <properties resource="db.properties"/>

    <environments default="development">

           <environment id="development">
               <!--3.3 事务管理,默认使用jdbc的-->
               <transactionManager type="JDBC"/>
               <dataSource type="POOLED">
                   <!--3.4 和数据库相关的一些信息-->
                   <property name="driver" value="${driver}"/>
                   <!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
                   <!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
                   <!--&amp:表示&。只不过在xml文件中要这样进行转义-->
                   <property name="url" value="${url}"/>
                   <property name="username" value="${username}"/>
                   <property name="password" value="${password}"/>
               </dataSource>
           </environment>
       </environments>
  2. 也可以在properties增加一些属性配置

    //db.properties文件,少了两个属性
    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&Unicode=true&characterEncoding=UTF-8
    <!--二、-->
       <properties resource="db.properties">
           <property name="username" value="root"></property>
           <property name="pwd" value="123456"></property>
       </properties>

    <environments default="development">

           <environment id="development">
               <!--3.3 事务管理,默认使用jdbc的-->
               <transactionManager type="JDBC"/>
               <dataSource type="POOLED">
                   <!--3.4 和数据库相关的一些信息-->
                   <property name="driver" value="${driver}"/>
                   <!--useSSL=true:开启了安全连接;useUnicode=true:使用了Unicode字符-->
                   <!--characterEncoding=UTF-8:字符集编码使用了UTF-8-->
                   <!--&amp:表示&。只不过在xml文件中要这样进行转义-->
                   <property name="url" value="${url}"/>
                   <property name="username" value="${username}"/>
                   <property name="password" value="${pwd}"/>
               </dataSource>
           </environment>
       </environments>

注意:如果两个文件有同一字段但值不同,如密码等,它会优先使用外部配置文件的字段其内部文件的字段将不会被使用!!

3.4、映射器(mappers)

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

方式一:完全限定名(推荐使用)

<!--每一个Mapper.xml都需要在Mybatis核心配置文件中进行注册!!这个点很容易忘记的!-->
<mappers>
   <mapper resource="com/kuang/dao/UserMapper.xml"></mapper>
</mappers>

方式二:使用class文件绑定注册

<mappers>
   <mapper class="com.kuang.dao.UserMapper"></mapper>
</mappers>

 

方式三:使用扫描包进行注入绑定

<mappers>
   <package name="com.kuang.dao"></package>
</mappers>

方式二和三的注意点(不注意就会报错!!):

  • 接口和它的Mapper配置文件必须同名!

  • 接口和它的Mapper配置文件必须在同一个包下!

 

3.5、生命周期和作用域

image-20210930211211601

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

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessoionFactory,就不再需要它了

  • 作用域:方法作用域(局部变量)

SqlSessionFactory:

  • 说白了就是可以想象为数据库连接池(等待别人关联,然后关闭)

  • 一旦被创建,SqlSessionFactory 实例应该在你的应用程序执行期间都存在。没有任何理由丢弃它或重新创建另一个实例。

  • 最简单的就是使用单例模式或者静态单例模式

  • 最佳作用域:应用作用域(全局变量

SqlSession:

  • 连接到连接池的一个请求

  • 用完之后需要赶紧关闭,否则资源被占用!

  • SqlSession的实例不是线程安全的,因此是不能被共享的!

  • 最佳作用域:请求或方法作用域

image-20210930212249220

这里面的每个mapper就是一个具体的业务(方法)!!

 

4. ResultMap结果集映射

4.1、应用场景

解决(实体类)属性名和(数据库)字段名不一致的问题

  • 数据库中的字段

image-20210930214645944

  • 新建项目改实体类中的属性(将之前的pwd改成password)---发现最后得出的结果为null;

image-20210930220153792

解决方法:

  • 起别名(将数据库中名字改成实体类中对应的名字)

select id,name,pwd as password from mybatis.user where id=#{id};
  • 结果集映射--resultMap(代替了resultType)

 <!--去掉了resultType改用resultMap-->
<!--type表示要将其结果映射成User类-->
   <resultMap id="UserMap" type="com.kuang.pojo.User">
       <!--column表示数据库中字段名,property表示实体类中属性名
       result标签的作用是将数据库中的字段名映射成实体类中的属性名,实际上只需改有变动的就行,不用动id和name这些没变化的字段-->
      <!-- <result column="id" property="id"></result>
       <result column="name" property="name"></result>-->
       <result column="pwd" property="password"></result>
   </resultMap>
   <select id="getUserById" resultMap="UserMap">
        select * from mybatis.user where id=#{id};
    <!--   select id,name,pwd as password from mybatis.user where id=#{id};-->

   </select>

 

4.2、resultMap的特点

  • resultMap 元素是 MyBatis 中最重要最强大的元素。

  • ResultMap 的设计思想是,对简单的语句做到零配置(属性名和字段名一致的情况下,我们不需要对他们进行配置),对于复杂一点的语句,只需要描述语句之间的关系就行了。

  • ResultMap 的优秀之处——当你熟悉时,完全可以不用显式地配置它们,就是和第二点一样,做到零配置!!

 

5. 日志

5.1、日志工厂

作用:如果一个数据库操作,出现了异常,我们需要快速排错!日志就是最好的助手。以往我们用到的排错手段是sout(控制台输出错误)和debug,现在我们学习日志工厂来排错!

日志工厂的配置代码如下:

<!--一定要注意这些标签的位置顺序!!-->
   <!--注意:name的字母一定要注意大小写;value要注意前后不要多加空格-->
   <settings>
       <setting name="logImpl" value="STDOUT_LOGGING"/>
   </settings>

image-20211027110218133

相应的value值如下:

  • SLF4J

  • LOG4J 【掌握】

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING 【掌握】

  • NO_LOGGING

在Mybatis中具体使用哪一个日志,由设置setting标签(在核心配置文件:mybatis-config.xml文件里)来决定!

 

5.2、STDOUT_LOGGING

STDOUT_LOGGING :标准日志输出,就是我们什么都不用配,直接使用即可!

下面我们来看一下控制台结果输出的区别显示:

  • 使用日志工厂之后控制台输出的结果显示:

image-20211027112706568

  • 没用日志工厂的普通输出结果显示:

image-20211027111822152

 

5.3、LOG4J

(1). 什么是log4j?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件

  • 我们也可以控制每一条日志的输出格式

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程

  • 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

(2). 在(子工程)pom.xml文件导入log4j的包

<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.17</version>
</dependency>

(3). 在resources文件夹下写配置文件(log4j.properties)

#将等级为DEBUG的日志信息输出到console和file这两个目的地。console和file的定义在下面代码。
log4j.rootLogger=DEBUG,console,file

#在控制台输出的相关设置
#输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#以sout这样的方式去输出
log4j.appender.console.Target=System.out
#Threshold:开端,界限
log4j.appender.console.Threshold=DEBUG
#输出的布局
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#布局的格式
log4j.appender.console.layout.ConversiconPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#文件输出的相关设置
#输出到文件
log4j.appender.file=org.apache.log4j.RollingFileAppender
#文件的路径以及命名;./表示当前项目路径
log4j.appender.file.File=./log/yuzhou.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

#日志输出级别
#会输出mybatis、sql、Statement、ResultSet、PreparedStatement这四个DEBUG时的日志信息
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.Statement=DEBUG
log4j.logger.java.ResultSet=DEBUG
log4j.logger.java.PreparedStatement=DEBUG

(4). 配置log4j为日志的实现方式

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

(5). 直接测试log4j的使用

image-20211031095240624

image-20211031095609628

(6). 简单使用log4j

1. 在要使用log4j的中导入“org.apache.log4j.Logger”的包,切记不要导错!

image-20211031100347772

2. 在创建的日志对象中参数为当前类的class

public class UserDaoTest {
//   通过反射来获得当前类的信息创建日志对象
   static Logger logger = Logger.getLogger(UserDaoTest.class);
   @Test
   public void testlog4j(){
       //分别为不同的级别
       logger.info("info:进入testlog4j方法"); //正常的输出
       logger.debug("debug:进入testlog4j方法"); //调试时用的
       logger.error("error:进入testlog4j方法"); //错误时使用,一般在try-catch时使用

  }

}

输出结果显示:

image-20211031101126449

在当前项目中会生成一个log文件(因为前面在配置文件已经设置好了)

image-20211031101334904

  1. 明确日志常使用的级别

//分别为不同的级别
       logger.info("info:进入testlog4j方法"); //正常的输出
       logger.debug("debug:进入testlog4j方法"); //调试时用的
       logger.error("error:进入testlog4j方法"); //错误时使用,一般在try-catch时使用

 

6.实现数据分页

6.1、limit sql语句在mybaits中实现分页(面向sql)

(1). 为什么要分页?

  • 减少数据的处理量

(2). 使用Limit分页

语法:select * from user limit startIndex,pageSize
语句1:select * from user limit 0,2  
  结果显示:会查出前面两条信息显示出来!

语句2:select * from user limit 3
  结果显示:页面显示3条信息,从0开始算起

语句3:select * from user limit 2,-1
  结果显示:现在会报错!但是在早期时可以使用的,表示从下标为2到数据结尾的信息全部显示出来。大家了解一下即可,现在这个已经没用了

startIndex:表示数据开始的下标位置,从0开始的

pageSize:表示页面大小

(3). 使用Mybatis实现分页核心就是sql)

1.写好UserMapper接口

//分页--使用万能的Map来传参
   List<User> getUserByLimit(Map<String,Integer> map);

2.再写UserMapper.xml

<!--分页实现查询-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
  select * from user limit #{startIndex},#{pageSize}
</select>

3.最后写测试

@Test
   public void getUserByLimit(){
       //1.获取session
       SqlSession sqlSession = MybatisUtils.getSqlSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);

       HashMap<String, Integer> map = new HashMap<String, Integer>();
       map.put("startIndex",1);
       map.put("pageSize",2);
       List<User> userList = mapper.getUserByLimit(map);
       for (User user : userList) {
           System.out.println(user);
      }

       sqlSession.close();//关掉
  }

 

6.2、RowBounds(面向对象)----不建议使用

(1).先写接口

//分页2--使用RowBound
   List<User> getUserByRowBounds(Map<String,Integer> map);

(2).写UserMapper.xml

<!--分页2:RowBounds实现查询:具体分页操作由对象来做-->
   <select id="getUserByRowBounds" resultMap="UserMap">
      select * from user
   </select>

(3). 写测试,在对象中实现分页操作

 @Test
   public void getUserByRowBounds(){
       SqlSession sqlSession = MybatisUtils.getSqlSession();

       //RowBounds实现分页
       RowBounds rowBounds = new RowBounds(1, 2);

       //通过java层面来实现分页
       List<User> user = sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
       for (User user1 : user) {
           System.out.println(user1);
      }

       sqlSession.close();
  }

 

6.3、Mybatis分页插件(了解即可)

想了解的去这个官网了解即可,步骤很简单,文档说得很明白!! image-20211031114504635

 

7. 使用注解开发

注意:针对mybatis来说,注解显得不太方便,建议还是使用配置文件来实现!!

image-20211101102140082

本质:运用反射机制实现

底层:动态代理

 

7.1、简单实现步骤

(1). 注解在接口上实现

//在注解里写sql语句,写完之后要去配置文件绑定接口
   @Select("select * from user")
   List<User> getUsers();

(2). 需要在核心配置文件中绑定接口

<!--绑定接口-->
   <mappers>
       <mapper class="com.kuang.dao.UserMapper"/>
   </mappers>

(3). 测试

@Test
public void test(){
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   UserMapper mapper = sqlSession.getMapper(UserMapper.class);
   List<User> users = mapper.getUsers();
   for (User user : users) {
       System.out.println(user);
  }
   sqlSession.close();
}

(4). 结果显示

image-20211101105734945

在mybatis里使用注解会出现一些问题的,虽然说可以解决,但是比较费劲,所以建议在mybatis中还是使用配置文件。

 

7.2、mybatis详细执行过程(debug走源码)

过程如下:

  1. 通过Resources获取加载核心配置文件

  2. 实例化SqlSessionFactoryBuilder构造器

  3. 解释配置文件流XMLConfigBuilder(源码)

  4. 返回Configuration所有的配置详细(源码)

  5. 实例化sqlSessionFactory

  6. 经过事务管理器transaction(debug)

  7. 创建executor执行器(debug)

  8. 创建sqlSession

  9. 实现CURUD,此时有事务了就会回滚到第6步事务管理器

  10. 查看事务是否执行成功,不成功也会回滚到事务管理器再次执行

  11. 提交事务

  12. 关闭

     

7.3、注解CRUD

(1). 设置成自动提交事务

public static SqlSession getSqlSession(){
       //4.5 获得sqlSession
//       SqlSession sqlSession=sqlSessionFactory.openSession(); //需要我们手动提交事务(可以查看源码看这个方法的重载)
       SqlSession sqlSession=sqlSessionFactory.openSession(true); //直接设置成true,可以自动提交事务
       return sqlSession;
  }

(2). 编写各大接口,增加注解

//方法存在多个参数,所有的基本类型或String类型的参数必须
//   加上@Param("")注解
//   下面#里的名字是要Param里的名字要一致的
   @Select("select * from user where id=#{id}")
   User getUserByID(@Param("id") int id);

   //user这里对应数据库表里的字段名,后面#里的对应实体类里的名字
   @Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})")
   int addUser(User user);

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

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

(3). 测试

注意:我们必须要先将接口注册绑定到我们的核心配置文件中!!

(4). 关于@Param( )注解

  • 基本类型或String类型的参数,需要加上

  • 引用类型不需要加

  • 如果只有一个基本类型的话,可以不加,但建议大家加上

  • 我们在SQL中引用的参数名就是我们这里的@Param()中设定的属性名

(5). #{}和${}的区别(了解)

image-20211101173813974

 

8. Lombok

注意:使用注解来写 java的实体类,虽说可以快速帮你用注解写好实体类的get,set,toString等方法,但可读性差,还是不太建议使用,了解即可!!

8.1、使用步骤

(1). 在IDEA中安装Lombok插件

image-20211101180155901

image-20211101180310450

下载安装完之后需要重启一下IDEA才能使用这个插件!!

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

去maven仓库里搜索Lombok的jar依赖

<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.22</version>
</dependency>

(3). 了解一下它常用的注解

@Getter and @Setter:生成get、set方法 @FieldNameConstants:生成字段名常量 @ToString:生成toString方法 @EqualsAndHashCode:生成equals和hashCode方法 @AllArgsConstructor:生成有参构造

@RequiredArgsConstructor :生成特定参数的有参构造

@NoArgsConstructor:生成无参构造 @Data:生成无参构造、get、set、equal、toString、hashCode等方法

(4). 在实体类上加注解即可

image-20211101181307321

(5). 使用Lombok的优缺点

image-20211101182028198

image-20211101182113793

 

9. 复杂查询

9.1、多对一环境搭建

(1). 关联:多对一(多个学生对一个老师)

集合:一对多(一个老师带多个学生)

image-20211101192650112

(2). 搭建环境

  • 导入lombok

  • 新建实体类Teacher,Student以及数据库表

Teacher类:

@Data
public class Teacher {
   private int id;
   private String name;
}

Student类:

@Data
public class Student {
  private int id;
  private String name;
  //学生需要关联一个老师,意味着每个学生要有一个老师属性
  private Teacher teacher;
}

数据库:

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);
  • 建立Mapper接口

  • 建立Maper.xml文件

    TeacherMapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--3.1 configuration 核心配置文件-->
    <mapper namespace="com.kuang.dao.TeacherMapper">

    </mapper>

    StudentMapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--3.1 configuration 核心配置文件-->
    <mapper namespace="com.kuang.dao.StudentMapper">

    </mapper>

     

  • 在核心配置文件中绑定注册我们的Mapper接口或者文件

 <!--绑定接口-->
<mappers>
   <mapper class="com.kuang.dao.TeacherMapper"/>
   <mapper class="com.kuang.dao.StudentMapper"/>
</mappers>
  • 测试查询是否能够成功

(3). 按照查询嵌套处理

<!--方法一:类似子查询-按照查询嵌套处理-->
   <select id="getStudent" resultMap="StudentTeacher">
      select * from student
   </select>
   <resultMap id="StudentTeacher" type="Student">
       <result property="id" column="id"></result>
       <result property="name" column="name"></result>
       <!--复杂的属性我们需要单独处理
           对象:association
           集合:collection
        -->
       <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
   </resultMap>

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

(4). 按照结果嵌套处理

<!--方法二:按照结果嵌套处理-->
   <select id="getStudent2" resultMap="StudentTeacher">
      select s.id sid,s.name sname,t.name tname
      from student s,teacher t
      where s.tid=t.id;
   </select>
   <resultMap id="StudentTeacher" type="Student">
       <result property="id" column="sid"/>
       <result property="name" column="sname"/>
       <association property="teacher" javaType="Teacher">
           <result property="name" column="tname"/>
       </association>
   </resultMap>

(5). 回顾一下Mysql多对一的查询方式:

  • 子查询

    select id name from student where id=(select语句)
  • 联表查询

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

(6). 踩过的bug坑:

就是如此简单的,却困扰了我一个晚上,希望遇到下面这个错的同学就此能快速排错:

image-20211102091010296

image-20211102091034843

原因:这里是因为在它们的.xml文件中写了多个resultMap,并且我给它们起了一样的名字!当我改过来时再去看上面的报错图片,发现它已经说的很明白了,就是我初学Mybatis还不能太明白,但这就是一个循序渐进的学习过程嘛,我相信大家也一定对这个过程爱恨交加,但也会在这个过程学到很多东西!

修改:

下面代码是在同一个.xml文件下的

<select id="getStudent" resultMap="StudentTeacher1">
      select * from student
   </select>
   <resultMap id="StudentTeacher1" type="Student">
       <result property="id" column="id"></result>
       <result property="name" column="name"></result>
       <!--复杂的属性我们需要单独处理
           对象:association
           集合:collection
        -->
       <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
   </resultMap>
<select id="getStudent2" resultMap="StudentTeacher1">
      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>

大家看到上面的两个resultMap了吗?我当时这两个resultMap的名字是一样的,后来我将他们分别改成StudentTeacher1和StudentTeacher2,错误就迎刃而解了!!

 

9.2、一对多环境搭建

和前面步骤基本一样

(1). 导入lombok

(2). 新建实体类Teacher,Student

Teacher类:

@Data
public class Teacher {
   private int id;
   private String name;
   //一个老师拥有多个学生,所以就加进一个学生集合
   private List<Student> students;
}

Student类:

@Data
public class Student {
   private int id;
   private String name;
   private int tid;
   //现在时一个老师对应多个学生的关系,所以学生是一个很单纯的个体
}

(3). 按照结果嵌套处理

 <!--按结果嵌套查询-->
   <select id="getTeacher2" resultMap="TeacherStudent">
      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="TeacherStudent" type="Teacher">
       <result property="id" column="tid"></result>
       <result property="name" column="tname"></result>
<!--复杂的属性,我们需要单独处理。对象:association 集合:collection-->
       <!--javaType是一个指定属性的类型
           oType是指定集合List中泛型约束的类型
        -->
       <collection property="students" ofType="Student">
           <result property="id" column="sid"></result>
           <result property="name" column="sname"></result>
           <result property="tid" column="tid"></result>
       </collection>
   </resultMap>

(4). 按查询嵌套处理

<!--按查询嵌套处理-->
   <select id="getTeacher3" resultMap="TeacherStudent2">
      select * from teacher where id=#{tid}
   </select>
   <resultMap id="TeacherStudent2" type="Teacher">
       <collection property="students" javaType="ArrayList" ofType="
Student" select="getStudentByTeacherID" column="id"></collection>
   </resultMap>
   <select id="getStudentByTeacherID" resultType="Student">
      select * from student where tid=#{tid}
   </select>

(5). 小结

  1. 关联-association

  2. 集合-collection

  3. javaType和ofType

    • javaType:用来指定实体类属性的类型

    • ofType:用来指定映射到List或集合中的pojo类型,即泛型中的约束类型

  4. 注意:

    • 要保证SQL的可读性

    • 注意一对多和多对一中属性名和字段名的问题

    • 如果问题不好排查错误的,可以使用日志,建议使用log4j

  5. 避免慢SQL

    • Mysql引擎

    • InnoDB底层原理

    • 索引

    • 索引优化!

 

10. 动态SQL

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

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

10.1、搭建环境

(1). 数据库表搭建

CREATE TABLE blog(
id varchar(50) NOT NULL COMMENT '博客iD',
title varchar(100) NOT NULL COMMENT '博客标题',
author varchar(30) NOT NULL COMMENT '博客作者',
creat_time datetime NOT NULL COMMENT '创建时间',
 views int(30) NOT NULL COMMENT '浏览量'
) ENGINE=INNODB DEFAULT CHARSET=utf8

(2). 写实体类

@Data
public class Blog {
   private String id;
   private String title;
   private String author;
   private Date createTime; //属性名createTime跟字段名create_time不一致,此时的字段名有一个下划线,在mybatis里有一个专门的设置是处理这个问题的
   private int views;
}

解决特殊的属性名与字段名不一致的问题:在核心配置文件中加入以下设置

<!--是否开启驼峰命名转换规则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>

image-20211102153501859

(3). 增加一个工具类IDutils(用来生成随机、唯一的id)

@SuppressWarnings("all") //抑制警告
public class IDutils {
   //生成随机的唯一的id
   public static String getId(){
       return UUID.randomUUID().toString().replace("-","");
  }
}

(4). 编写接口类

public interface BlogMapper {
   //插入数据
   int addBlog(Blog blog);
}

(5). 编写.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.BlogMapper">
   <insert id="addBlog" parameterType="Blog">
      insert into mybatis.blog (id,title,author,creat_time,views)
      values(#{id},#{title},#{author},#{createTime},#{views});
   </insert>
</mapper>

(5). 运行测试类插入数据

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

       Blog blog=new Blog();
       blog.setId(IDutils.getId());
       blog.setTitle("Mybatis如此简单");
       blog.setAuthor("狂神说");
       blog.setCreateTime(new Date());
       blog.setViews(9999);
       mapper.addBlog(blog);

       blog.setId(IDutils.getId());
       blog.setTitle("java如此简单");
       mapper.addBlog(blog);

       blog.setId(IDutils.getId());
       blog.setTitle("Spring如此简单");
       mapper.addBlog(blog);

       blog.setId(IDutils.getId());
       blog.setTitle("微服务如此简单");
       mapper.addBlog(blog);

       sqlSession.close();
  }
}

 

10.2、if 标签

test:表示一个判断表达式,如果成立,那么就拼接相应的sql语句到主sql语句后面!!

<!--IF查询-->
<select id="queryBlogIF" parameterType="blog" resultType="Blog">
  select * from blog where 1=1
   <if test="title !=null">
      and title=#{title}
   </if>
   <if test="author !=null">
      and author=#{author}
   </if>

</select>

 

10.3、choose (when, otherwise)标签

原理:有一个when条件成立了,后面的就不会再执行了,如果所有的when都不成立,那就执行otherwise条件的sql语句

<select id="queryBlogChoose" parameterType="map" resultType="Blog">
  select * from blog
   <where>
     <choose>
     <!--有一个when条件成立了,后面的就不会再执行了,如果所有的when都不成立,那就执行otherwise条件的sql语句-->
         <when test="title !=null">
                 <!--第一个可以不写and-->
                title=#{title}
         </when>
         <when test="author !=null">
                and author=#{author}
         </when>
         <otherwise>
            and views =#{views}
         </otherwise>
     </choose>
   </where>
</select>

 

10.4、trim (where, set)

(1). where标签:它的作用就是,如果条件成立,可以帮你自动加上where,and,or等关键字;如果不能成立,也可以帮你去掉这些关键字,总之不让你的sql语句因为where,and的拼接而出错!!

image-20211102172304126

<!--IF查询-->
   <select id="queryBlogIF" parameterType="blog" resultType="Blog">
      select * from blog
       <where>
           <if test="title !=null">
               <!--第一个可以不写and-->
                title=#{title}
           </if>
           <if test="author !=null">
              and author=#{author}
           </if>
       </where>
   </select>

上面两个if标签都成立,我们去日志那里看一下完整的失去了语句:

image-20211102173331300

 

(2). set标签:它的作用就是用于update更新语句,如果条件成立,那么它会帮你自动补上set,并且把最后不相关的逗号去除,拼接成完整正确的失去了语句!!

image-20211102193421226

<update id="updateBlog" parameterType="map">
  update blog
   <set>
       <if test="title!=null">
          title=#{title},
       </if>
       <if test="author!=null">
          author=#{author}
       </if>
   </set>
  where id = #{id}
</update>

上面两个if标签都成立,我们去日志看一下sql语句究竟是如何拼接的:

image-20211102195615529

(3). trim标签:它的作用就是自定义where和set标签!!

image-20211102200534041

image-20211102200606649

由上面例子可以知道:

1. prefix="where" 表示在符合条件的拼接语句中自动补上where,因为and,or在拼接sql语句的前面,所以是prefixOverrides,表示删掉拼接语句中不该有的and或or;

2. 而prefix=“set“表示在符合条件的拼接语句中自动补上set,”是在sql语句的后面,所以是suffixOverrides,表示删除掉拼语句中不该有的“ ,”。

3. 注意:**prefix,prefixOverrides,suffixOverrides这些的值可以自己定义的,不固定是where,set等这些,这才是trim标签的作用之处!!但一般而言,where标签和set标签就足够用了,trim标签很少用到!!**

<trim prefix="WHERE" prefixOverrides="AND">
   <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>
</trim>

 

10.5、foreach标签

作用:遍历集合输出数据!!

image-20211102210728501

例子:select * from user where 1=1 and (id=1 or id=2 or id=3)

<!--用foreach改编-->
<foreach item="id" index="index" collection="ids" open="and (" close=")" separator="or">
id = #{id}
</foreach>

(1). 说明:首先我们需要先定义一个集合传参过来给collection,然后id就是这个集合里面的数值,index是下标,一般很少用。最后我们组合语句,open表示以什么开头,close表示以什么结束,separator表示以什么分割;#{id}表示获取集合里面的数据!!

(2). .xml文件代码

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

(3). 测试类代码

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

       HashMap map = new HashMap();

       ArrayList<Integer> ids = new ArrayList<Integer>();
       ids.add(1);
       ids.add(2);

       map.put("ids",ids);

       mapper.queryBlogForeach(map);

       sqlSession.close();
  }

(4). 结果显示

image-20211102213931880

 

10.6、SQL片段

有时候,我们可能会将一些sql功能的部分抽取出来,方便复用!!

通过定义id和refid关联起来!!

(1). 使用SQL标签抽取公共的部分

<sql id="updateSql">
   <if test="title!=null">
      title=#{title},
   </if>
   <if test="author!=null">
      author=#{author}
   </if>
</sql>

(2). 使用include标签引用的部分

 <update id="updateBlog" parameterType="map">
    update blog
    <set>
        <include refid="updateSql"></include>
    </set>
    where id = #{id}
</update>

(3). 注意事项

  • 最好基于单表来定义SQL片段!(因为如果是多表的话,会降低SQL片段的复用性就没有那种效果了)

  • 不要存在where标签,尽量存在if标签就好了!

 

11. 缓存(了解)

11.1、简介

  1. 什么是缓存【Cache】?

    • 存在内存中的临时数据

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

  2. 为什么使用缓存?

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

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

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

 

11.2、Mybatis缓存

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

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

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

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

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

读写分离,主从复制

11.3、一级缓存

  • 一级缓存也叫本地缓存

    • 与数据库同一次会话期间查询的数据会放在本地缓存中

    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

(2). 测试步骤:

  1. 开启日志

  2. 测试类代码

    @Test
       public void test(){
           SqlSession sqlSession = MybatisUtils.getSqlSession();
           UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           User user = mapper.queryUserById(1);
           System.out.println(user);
           System.out.println("==============================");
           User user1 = mapper.queryUserById(1);
           System.out.println(user1);

           System.out.println(user==user1);

           sqlSession.close();
      }
  3. 查看日志输出

    image-20211103091715922

  4. 结果分析

    上面代码分别查询两个相同的数据,第一次查询明显可以从日志中看出,它走了数据库执行了sql语句然后查了出来,而第二次查询没有执行sql是语句,更没有走数据库,而是从一级缓存中取出数据来,因为前后查的数据都是一样的,并且它们俩在缓存中的地址是一样的,因为是同一个东西嘛,所以最后比较时为true。

(3). 缓存失效的情况

image-20211103091306387

  1. 增删改操作可能会改变原来的数据,所以必定刷新缓存,故缓存失效

    @Test
       public void test(){
           SqlSession sqlSession = MybatisUtils.getSqlSession();
           UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           User user = mapper.queryUserById(1);
           System.out.println(user);

           mapper.updateUser(new User(2,"aaa","bbbb"));

           System.out.println("=====================");
           User user2 = mapper.queryUserById(1);
           System.out.println(user2);
           System.out.println(user==user2);

           sqlSession.close();
      }

    结果显示:

    image-20211103093346500

    因为期间夹杂了增删改其中的操作,而系统不知道你有没有改变先前查出来的东西,所以它就会刷新缓存从头来过,以保证结果的正确性!!

  2. 手动清理缓存

    @Test
       public void test(){
           SqlSession sqlSession = MybatisUtils.getSqlSession();
           UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           User user = mapper.queryUserById(1);
           System.out.println(user);

           sqlSession.clearCache();

           System.out.println("=====================");
           User user2 = mapper.queryUserById(1);
           System.out.println(user2);
           System.out.println(user==user2);


           sqlSession.close();
      }

    结果显示:

    image-20211103093818958

    使用了手动清理缓存sqlSession.clearCache();,故缓存失效了!!

  3. 不同Mapper之间获取相同的内容缓存失效,因为已经超出一级缓存有效的作用域了

  4. 小结

    一级缓存默认时开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区段有效。一级缓存就相当于是一个Map。

11.4、二级缓存

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

  • 基于namespace级别的缓存,一个名称空间对应一个二级缓存

  • 工作机制

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

    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据保存到二级缓存中

    • 新的会话查询信息,就可以从二级缓存中获取内容

    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

(2). 测试步骤

  1. 去核心配置文件设置开启缓存

image-20211103102312008

<setting name="cacheEnabled" value="true"/>
  1. 去当前mapper开启二级缓存

    a. 直接引入<cache/>就开启了(不建议使用

<mapper namespace="com.kuang.dao.UserMapper">
   
   <!--开启二级缓存-->
   <!--需要去实体类序列化-->
   <cache/>
   
   <select id="queryUserById" parameterType="_int" resultType="user">
      select * from user where id = #{id}
   </select>

   <update id="updateUser" parameterType="user">
      update user set name=#{name},pwd=#{pwd} where id=#{id};
   </update>

</mapper>

实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
   private int id;
   private String name;
   private String pwd;
}

 

image-20211103105917298

 

b. 自定义缓存开启

<mapper namespace="com.kuang.dao.UserMapper">

   <!--开启二级缓存-->
   <!--自定义cache-->
   <cache
           eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>

   <select id="queryUserById" parameterType="_int" resultType="user">
      select * from user where id = #{id}
   </select>

   <update id="updateUser" parameterType="user">
      update user set name=#{name},pwd=#{pwd} where id=#{id};
   </update>

</mapper>

image-20211103103104670

 

注意:上面圈出来的那个部分的意思是,使用cache需要设置读写或显示定义实体类序列化,二者必选一个。当你直接用<cache/>标签引用缓存时,这时就需要去到实体类设置序列化,否则会报错;而当你自定义cache时里面包含了readOnly这个属性,它的内部也是将这个实体类序列化了,所以就不用序列化了!!

  1. 测试类

@Test
   public void test(){
       //创建了两个SqlSession会话
       SqlSession sqlSession = MybatisUtils.getSqlSession();
       SqlSession sqlSession1 = MybatisUtils.getSqlSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

       User user = mapper.queryUserById(1);
       System.out.println(user);

       sqlSession.close();  //在这里就把第一次会话给关闭了
       
       System.out.println("=====================");

       User user1 = mapper1.queryUserById(1);

       System.out.println(user1);

       System.out.println("==============================");
       System.out.println(user==user1);

       sqlSession1.close();
  }

image-20211103104228050

分析:上面代码开启了两个SqlSession,当第一个会话执行完操作查询出来结果后,关闭会话,用第二个SqlSession查询相同内容的时候,这时不需要再执行sql语句,直接从二级缓存那里获取相同的数据就行了!!

  1. 小结

    • 只要开启了二级缓存,在同一个Mapper下就有效

    • 所有的数据都会先放在一级缓存中

    • 只有当会话提交或者关闭的时候,才会提交到二级缓冲中!

 

11.5、Mybatis缓存原理

image-20211103112116313

 

11.6、自定义缓存--ehcache(了解即可)

我们现在做缓存,都基本用到redis这种非关系型数据库来做了,很少用到这些缓存。

Ehcache是一种广泛使用的开源java分布式缓存。主要面向通用缓存

要在程序中使用ehcache,先要导包

<dependency>
   <groupId>org.mybatis.caches</groupId>
   <artifactId>mybatis-ehcache</artifactId>
   <version>1.1.0</version>
</dependency>
<!--自定义缓存:在当前Mapper文件使用ehcache实现缓存-->
   <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

要写一个ehcache.xml配置文件

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
   
   <diskStore path="java.io.tmpdir"/>

   <!--
   Mandatory Default Cache configuration. These settings will be applied to caches
   created programmtically using CacheManager.add(String cacheName)
   -->
   <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      maxElementsOnDisk:硬盘最大缓存个数。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
   -->
   <defaultCache
           maxElementsInMemory="10000"
           eternal="false"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           overflowToDisk="true"
           maxElementsOnDisk="10000000"
           diskPersistent="false"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU"
   />
</ehcache>

了解即可!!

 

posted @ 2021-12-24 11:19  周游码世界  阅读(265)  评论(0)    收藏  举报