狂神说-Mybatis笔记(总)

环境

  • JDK1.8

  • MySQL 8.0.23

  • maven 3.6.1

  • IDEA2020.3

框架:需要配置文件

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

一、简介

1.什么是Mybatis

  • MyBatis 是一款优秀的持久层框架,是一个半自动化的 ORM 框架

  • 不同于其他的对象关系映射框架,MyBatis 并未将 Java 对象和数据库表关联,而是将 Java 方法与 SQL 语句关联。

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

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

    • jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

    • Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
  • MyBatis 的真正强大在于它的(SQL)语句映射

1.1 Mybatis发展史

  • MyBatis本是apache的一个开源项目**iBatis**。

  • 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。

  • 2013年11月迁移到Github。

1.2 如何获得Mybatis

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>

2.数据持久化

什么是持久化?_51CTO博客_持久化

  • ***持久化(Persistence) :***将程序的数据的在持久状态和瞬时状态间转化的机制。即将数据(如内存中的对象)存储在可持久保存的存储介质上(如数据库、磁盘、文件系统等)

    • “持久化”这个概念是和“暂时”等概念相对的,数据在计算机中有一般有两个存储地,内存为暂存,因为电源关机就会数据丢失,如果需要反复使用,就要持久保存,实现持久化了。

    • 内存:断电即失

    • 数据持久化 : 将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称.

      • 数据模型可以是任何数据结构或对象模型;存储模型可以是关系模型、XML、二进制流等。

      • cmp和Hibernate只是对象模型到关系模型之间转换的不同实现。只不过对象模型和关系模型应用广泛,所以就会误认为数据持久化就是对象模型到关系型数据库的转换罢了。

    • 持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。

    • JDBC就是一种持久化机制。文件IO也是一种持久化机制。

    • 生活:冷藏、罐头

  • **持久化对象 :是指在程序中表示的对象,可以存储在持久化存储介质(如数据库、文件系统等)中并能够长期保留。简单来说,持久化对象是指在内存中的对象通过某种方式保存到外部存储介质中,并且在需要时可以重新加载到内存中进行使用。

  • ***持久层(Persistence Layer):***专注于实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联。(就是用于完成持久化工作的代码块(dao 层【DAO(Data Access Object)】))

2.1 为什么需要持久化?

  • 保存数据对象:有一些对象,不能让它丢失

  • 内存太昂贵

之所以需要持久化,是由于内存自身缺陷导致。我们知道,内存在遇到某些外界因素影响后会丢失,但是我们的一些数据是绝对不能丢失的,但我们又无法保证不收外界因素影响。

同时内存成本较高,比起硬盘、光盘等外存,其价格要高上几个数量级,而且维持成本也较高。在这种情况下,我们不得不寻求另一种方案来存储数据对象,而持久化就是其中的一种选择,我们能够通过持久化将数据缓存到外存,从而降低成本。

2.2 持久化的技术——ORM

ORM-Object/Relational Mapper,即“对象-关系型数据映射组件”。对于O/R,即 Object(对象)和Relational(关系型数据),表示必须同时使用面向对象和关系型数据进行开发。

备注:建模领域中的 ORM 为Object/Role Modeling(对象角色建模)。另外这里是“O/R Mapper”而非“O/R Mapping”。相对来讲,O/R Mapping 描述的是一种设计思想或者实现机制,而 O/R Mapper指以O/R原理设计的持久化框架(Framework),包括 O/R机制还有 SQL自生成,事务处理,Cache管理等。

除了ORM 技术,还有以下持久其它持久化技术

2.3 为什么要做持久化和ORM设计

数据持久化 - petercao - 博客园

在目前的企业应用系统设计中,MVC,即 Model(模型)- View(视图)-Control(控制)为主要的系统架构模式。MVC 中的Model 包含了复杂的业务逻辑和数据逻辑,以及数据存取机制(如 JDBC的连接、SQL生成和Statement创建、还有ResultSet结果集的读取等)等。将这些复杂的业务逻辑和数据逻辑分离,以将系统的紧耦合关系转化为松耦合关系(即解耦合),是降低系统耦合度迫切要做的,也是持久化要做的工作。MVC 模式实现了架构上将表现层(即View)和数据处理层(即Model)分离的解耦合,而持久化的设计则实现了数据处理层内部的业务逻辑和数据逻辑分离的解耦合。而 ORM 作为持久化设计中的最重要也最复杂的技术,也是目前业界热点技术。

简单来说,按通常的系统设计,使用 JDBC 操作数据库,业务处理逻辑和数据存取逻辑是混杂在一起的。

一般基本都是如下几个步骤:
1、建立数据库连接,获得 Connection 对象。
2、根据用户的输入组装查询 SQL 语句。
3、根据 SQL 语句建立 Statement 对象 或者 PreparedStatement 对象。
4、用 Connection 对象执行 SQL语句,获得结果集 ResultSet 对象。
5、然后一条一条读取结果集 ResultSet 对象中的数据。
6、根据读取到的数据,按特定的业务逻辑进行计算。
7、根据计算得到的结果再组装更新 SQL 语句。
8、再使用 Connection 对象执行更新 SQL 语句,以更新数据库中的数据。
7、最后依次关闭各个 Statement 对象和Connection 对象。

由上可看出代码逻辑非常复杂,这还不包括某条语句执行失败的处理逻辑。其中的业务处理逻辑和数据存取逻辑完全混杂在一块。而一个完整的系统要包含成千上万个这样重复的而又混杂的处理过程,假如要对其中某些业务逻辑或者一些相关联的业务流程做修改,要改动的代码量将不可想象。另一方面,假如要换数据库产品或者运行环境也可能是个不可能完成的任务。而用户的运行环境和要求却千差万别,我们不可能为每一个用户每一种运行环境设计一套一样的系统。
所以就要将一样的处理代码即业务逻辑和可能不一样的处理即数据存取逻辑分离开来,另一方面,关系型数据库中的数据基本都是以一行行的数据进行存取的,而程序运行却是一个个对象进行处理,而目前大部分数据库驱动技术(如ADO.NET、JDBC、ODBC等等)均是以行集的结果集一条条进行处理的。所以为解决这一困难,就出现ORM 这一个对象和数据之间映射技术。

3.为什么需要Mybatis

针对数据库操作,Java 其实已经提供了相应的解决方案 – JDBC。那既然已经有了现成的工具,那为什么还会有 MyBatis 的出现呢?

思考:在开始之前,思考下如何通过JDBC查询Emp表中的所有记录,并封装到一个List集合中返回。(演示:准备数据、导包、导入JDBC程序)

  1. 使用传统方式JDBC访问数据库:

    1. 使用JDBC访问数据库有大量重复代码(比如注册驱动、获取连接、获取传输器、释放资源等);

    2. JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;

    3. 如果使用数据库连接池就能在一定程度上缓解该问题;

    4. SQL 语句在代码中出现,会造成代码不易维护;

    5. SQL是写死在程序中,一旦修改SQL,需要对类重新编译;

    6. 使用 preparedStatement 向占位符传递参数时存在硬编码,也会进一步加大系统维护的难度;

    7. 对结果集进行解析时存在硬编码,SQL 变化将导致解析代码改变,系统难以维护,但如果能将数据库记录进行封装成 POJO 对象,解析起来就会方便很多。

  2. 使用mybatis框架访问数据库:

    1. Mybatis对JDBC对了封装,可以简化JDBC代码;

    2. Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率;

    3. Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译。

    4. 对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象。

总之,JDBC中所有的问题(代码繁琐、有太多重复代码、需要操作太多对象、释放资源、对结果的处理太麻烦等),在Mybatis框架中几乎都得到了解决!!

4.Mybatis的优点

  • 简单易学:自身小且简单,无任何第三方依赖(最简单安装只要两个jar文件+配置几个sql映射文件);

  • 灵活:MyBatis 不会对应用程序或数据库的现有设计强加任何影响,写在 XML 中,便于统一管理和优化;

  • 解除 SQL 与代码程序的耦合:通过提供 DAO 层,将业务逻辑与数据访问逻辑分离,使系统设计更加清晰、易维护、易于单元测试。sql和代码的分离,提高了程序的可维护性;

  • 提供 XML 标签,支持编写动态 SQL

  • 提供映射标签,支持对象与数据库的ORM字段关系映射。

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

二、第一个Mybatis程序

  • 要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。

  • 如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
  1. 搭建数据库
create database mybatis;

use mybatis;

create table user(
	id int(20) not null,
	name varchar(30) default null,
	pwd varchar(30) default null,
	primary key(id)
)ENGINE=INNODB default CHARSET=utf8;
	
insert into user values
	(1,"狂神","123456"),
	(2,"张三","892457"),
	(3,"李四","784728");

select * from user;
  1. 创建maven项目,删除src目录,导入maven依赖
      <!--mysql驱动,连接数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>
        <!--mybatis-->
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.13</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
  1. 创建一个module

    1. 核心配置文件mybatis-config.xml中进行配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "https://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.cj.jdbc.Driver"/>
              <!--&amp;表示转义后的&-->
              <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
              <property name="username" value="root"/>
              <property name="password" value="niit1234"/>
          </dataSource>
      </environment>
  </environments>

</configuration>

MySQL版本号是8.0以上,设置driver为com.mysql.cj.jdbc.Driver;还需要设置serverTimezoneuseSSL等参数

概念:serverTimezone连接mysql数据库时指定了时差

serverTimezone重要性
(1)时差会导致插入的date数据发生变化(自动更换时差)
(2)UTC是全球标准时间,北京地区早标准时间8小时
(3)注意使用useSSL=false

(4)&amp;serverTimezone=Asia/Shanghai" //注意Shanghai是开头大写

       `&amp;serverTimezone=GMT%2B8`

2. 编写mybatis**工具类**——固定语句,以后可以直接使用
package com.kuang.utils;

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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {
  private static SqlSessionFactory sqlSessionFactory;

  static {

      try {
          //读取配置文件
          //下面三句话是固定的
          //使用Mybatis第一步:获取sqlSessionFactory对象
          String resource = "mybatis-config.xml";
          InputStream inputStream = Resources.getResourceAsStream(resource);
          //SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
          sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

  //既然有了SqlSessionFactory,我们可以从中获得 SqlSession 的实例
  //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
  public static SqlSession getSqlSession(){
      return sqlSessionFactory.openSession();
  }
}

  1. 编写代码
  • 实体类
package com.kuang.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 + '\'' +
                '}';
    }
}
  • Dao接口,即Mapper接口
package com.kuang.dao;

import com.kuang.pojo.User;

import java.util.List;

public interface UserDao {//Dao=Mapper
    public List<User> getUserList();
}
  • 接口实现类,由原来的UserDaoImpl转变为一个Mapper配置文件UserDaoMapper.xml

namespace很重要

在之前版本的 MyBatis 中,**命名空间(Namespaces)**的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

**命名解析:**为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

  • 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。

  • 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.kuang.dao.UserDao">
    <select id="getUserList" resultType="com.kuang.pojo.User">
        select * from mybatis.user
    </select>
</mapper>
  • 在核心配置文件mybatis-config.xml中注册mapper,如不添加报错——org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.
   <mappers>
        <mapper resource="com/kuang/dao/UserDaoMapper.xml"/>
    </mappers>
  1. 测试(135条消息) mybatis常见错误_mybatis =报错_不断前进的皮卡丘的博客-CSDN博客
@Test
public void test(){
  //第一步:获取SqlSession对象
  SqlSession sqlSession= MybatisUtils.getSqlSession();
  //执行SQL
  UserDao userDao=sqlSession.getMapper(UserDao.class);
  List<User> userList = userDao.getUserList();
  for(User user:userList){
    System.out.println(user);
  }
}
  • 报错1:Caused by: java.io.IOException: Could not find resource com/kuang/dao/UserDaoMapper.xml

即target/classes/com/kuang/dao下没有产生UserDaoMapper.xml

在这里插入图片描述

解决:在pom.xml中添加如下代码

<!--在build中配置resource,防止资源导出失败问题-->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
  • 报错2:Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 1 字节的 UTF-8 序列的字节 1 无效。

解决:Maven静态资源过滤问题 ,需要在pom.xml中加入resources

https://img-blog.csdnimg.cn/41bc5d3c3dcd428a9510d18a09501ef7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YeeahOWtpuS5oOS5i-i3rw==,size_20,color_FFFFFF,t_70,g_se,x_16

注意:我们修改完以后,要mvn clean一下,再编译,否则还会报错

  1. 注意问题

    1. 配置文件没有注册

    2. 绑定接口错误

    3. 方法名不对

    4. 返回类型不对

    5. maven导出资源问题

三、相关API

SqlSessionFactoryBuilder

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

SqlSessionFactory

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

SqlSession

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

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

映射器实例

映射器 是一些绑定映射语句的接口。

映射器接口的实例是从 SqlSession 中获得的。

虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。

映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的应用逻辑代码
}

getMapper()后的返回值为什么不同

https://blog.csdn.net/Doraemon_Nobita/article/details/116044160

通过SqlSession.getMapper(XXXMapper.class)方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,我们使用Mapper 接口的某一个方法时,MyBatis 会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select(“statementId”,parameterObject);或者SqlSession.update(“statementId”,parameterObject); 等等来实现对数据库的操作, MyBatis 引用Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置SQL语句,这样就可以脱离XML配置文件,实现“0配置”)。


四、CRUD

1.namespace

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

1个Dao接口类对应1个mapper,也对应1个namespace,

1个Dao接口中的方法对应1个namespace中一个SQL语句

2.CRUD

  • id:对应的namespace接口中的方法名

  • resultType:SQL语句执行的返回值——承载数据的实体类

  • parameterType:参数类型

2.1 编写接口UserMapper

public interface UserMapper {
    // 查询全部用户
    List<User> getUserList();

    // 根据id查询用户
    User getUserById(int id);

    // 插入一个用户
    int addUser(User user);

    // 修改一个用户
    int updateUser(User user);

    // 删除一个用户
    void deleteUser(int id);
 }

2.2 编写对应的mapper中的sql语句UserDaoMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.kuang.dao.UserMapper">
    <select id="getUserList" resultType="com.kuang.pojo.User">
        select * from mybatis.user
    </select>

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

    <!--对象中的属性,可以直接取出来-->
    <insert id="addUser" parameterType="com.kuang.pojo.User">
        insert into user(id,name,pwd) values(#{id},#{name},#{pwd})
    </insert>

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

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

2.3 将mapper注册到核心配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://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.cj.jdbc.Driver"/>
                <!--&amp;表示转义后的&-->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="niit1234"/>
            </dataSource>
        </environment>
    </environments>

    <!--注册mapper-->
    <mappers>
        <mapper resource="com/kuang/mapper/UserDaoMapper.xml"/>
    </mappers>
</configuration>

2.4 测试

package com.kuang.dao;

import com.kuang.Utils.MybatisUtils;
import com.kuang.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {

    //对于@Test的方法,光标聚焦在对应方法上右键
    @Test
    public void test(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession= MybatisUtils.getSqlSession();;
        try{
            //方式1:getMapper()
            UserMapper userMapper =sqlSession.getMapper(UserMapper.class);
            List<User> userList = userMapper.getUserList();
            for(User user:userList){
                System.out.println(user);
            }

            System.out.println();

            //方式2:selectList()  不推荐
//            List<User> userList2=sqlSession.selectList("com.kuang.dao.UserMapper.getUserList");
//            for(User user : userList2){
//                System.out.println(user);
//            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //关闭sqlSession
            sqlSession.close();
        }
    }

    @Test
    public void getUserById(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        try{
            UserMapper mapper=sqlSession.getMapper(UserMapper.class);
            User user = mapper.getUserById(1);

            System.out.println(user);

        }catch(Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    //增删改必须要提交事务
    @Test
    public void addUser(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        try{
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);

            //修改Userdao中的该方法返回值为void则没有返回值
            int res=mapper.addUser(new User(4,"赵六","123489"));
            if(res>0){
                System.out.println("插入成功");
            }
            //提交事务
            sqlSession.commit();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

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

        UserMapper mapper=sqlSession.getMapper(UserMapper.class);

        int res=mapper.updateUser(new User(4,"赵六六","124376"));
        if(res>0){
            System.out.println("修改成功");
        }
        sqlSession.commit();
        sqlSession.close();

    }

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

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res=mapper.deleteUser(4);
        if(res>0) {
			sqlsession.commit();
			System.out.println("SUCCESS!");
		}else {
			sqlsession.rollback();
			System.out.println("FAILED!");
		}
        sqlSession.commit();
        sqlSession.close();
    }
}
  • 增删改必需要提交事务sqlSession.commit()

在这里插入图片描述

2.5 常见错误

  • 标签不要匹配错

  • resource绑定mapper,需要使用路径

  • 程序配置文件必须符合规范

  • 空指针异常,没有注册到资源

  • target输出xml文件中存在中文乱码问题

  • maven资源没有导出问题

3. 万能Map

假设我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map!

  1. UserMapper
 //万能的map
    int  addUser2(Map<String,Object> map);
  1. UserDaoMapper
<!--传递map的key-->
<insert id="addUser2" parameterType="map" >
    insert into mybatis.user(id,name,pwd) values (#{userid},#{userName},#{passWord});
</insert>
  1. 测试
@Test
public  void addUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    HashMap<String, Object> map = new HashMap<>();
    map.put("userid",5);
    map.put("userName","hello");
    map.put("passWord","976543");

    mapper.addUser2(map);

    sqlSession.commit();
    sqlSession.close();
}
  • Map 传递参数,直接在sql中取出key 【parameterType=“Map”

  • 对象传递参数,直接在sql中取对象的属性即可 【parameterType=“com.kuang.pojo.User”

  • 只有一个基本类型参数的情况下,可以直接在sql中取到

  • 多个参数用Map或注解

4.模糊查询

like+通配符

通配符:%——0或多个 ;_——1个

两种方法:

  • 在sql拼接中使用通配符(不推荐,有sql注入问题)
<select id="getUserLike">
    select * from user where name like "%#{value}%"
</select>
  • Java代码执行的时候,传递通配符 %%。如下步骤
  1. UserMapper
//模糊查询
List<User> getUserLike(String value);
  1. UserDaoMapper.xml
   <select id="getUserLike">
        select * from user where name like #{value}
    </select>
  1. 测试
@Test
public void getUserLike(){
  SqlSession sqlSession = MybatisUtils.getSqlSession();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);

  List<User> userlist = mapper.getUserLike("李%");
  for(User user:userlist){
    System.out.println(user);
  }
}

五、配置解析

Mybatis-03

1. 核心配置文件 mybatis-config.xml

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

新建一个mybatis-02模块:

1. 在src/main/resources路径下建立mybatis-config.xml文件

2. 在src/main/java/com/qjd/utils路径下编写工具类MybatisUtils.java读取配置文件获取sqlsessionfactory

3. 在src/main/java/com/qjd/pojo路径下编写实体类User.java

4. 在src/main/java/com/qjd/dao路径下编写接口UserMapper.java和UserMapper.xml

5. 编写测试类

2.环境配置(environments)

  • MyBatis 可以配置成适应多种环境,根据id识别不同环境

  • 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

  • 学会配置多套运行环境-----更改id

<environments default="id">
  • Mybatis默认的事务管理器就是JDBC,连接池:POOLED

  • 连接池:使并发 Web 应用快速响应请求

<!--默认使用的环境 ID(比如:default="development")-->
  <environments default="development">
      <!--每个 environment 元素定义的环境 ID(比如:id="development")-->
      <environment id="development">
          <!--事务管理器的配置 type="JDBC(默认)|MANAGED")-->
          <transactionManager type="JDBC"/>
          <!--数据源的配置 type="POOLED(默认)|UNPOOLED|JDN")连接池:使并发 Web 应用快速响应请求-->
          <dataSource type="POOLED">
              <!--driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)-->
              <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
              <!--url – 这是数据库的 JDBC URL 地址。 &amp;表示转义后的&-->
              <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
              <!--username – 登录数据库的用户名-->
              <property name="username" value="root"/>
              <!--password – 登录数据库的密码-->
              <property name="password" value="niit1234"/>
          </dataSource>
      </environment>
  </environments>
  • 事务管理器<transactionManager type="[ JDBC | MANAGED ]"/>

  • 有三种内建的数据源类型 <dataSource type="[UNPOOLED|POOLED|JNDI]">

    • unpooled: 这个数据源的实现只是每次被请求时打开和关闭连接。

    • pooled: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得
      并发 Web 应用快速响应请求的流行处理方式。

    • jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

    • 数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等…

3.属性(properties)

  • 数据库这些属性都是可外部配置且可动态替换的

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

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

    我们来优化我们的配置文件

  1. 编写一个配置文件db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=root
password=niit1234
  1. db.properties文件导入核心 配置文件mybatis-config.xml
  • properties元素要卸载environmens元素前面,否则报错
<!--引入外部属性文件 由于db.properties和mabatis-config.xml都在resource目录下不必考虑路径名-->
  <!--方法1:所有属性都是用db.properties中的-->
<!--    <properties resource="db.properties"/>-->
  <!--方法2:既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。在mybatis-config.xml中设置property元素不会覆盖db.properties中的值-->
  <properties resource="db.properties">
      <property name="username" value="root"/>
      <property name="password" value="niit12"/>
  </properties>

也可以在 SqlSessionFactoryBuilder.build() 方法中传入属性值。例如:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);

// ... 或者 ...

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);

注意事项:

  1. 可以直接引入外部文件

  2. 可以在<properties>中通过<property>增加一些属性配置

  3. 如果两个文件有同一个字段,优先使用外部配置文件(db.proerpties)的

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

    • 首先读取在 properties 元素体内指定的属性。

    • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件(如com/kuang/dao/UserDaoMapper.xml),或根据url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。

    • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

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

4.类型别名(typeAliases)

  • 类型别名可为 Java 类型设置一个缩写名字。

  • 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

方法1:<typeAlias>

   <!--给实体类起别名-->
    <typeAliases>
        <typeAlias type="com.kuang.pojo.User" alias="User"/>
    </typeAliases>

方法2:<package>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

即扫描实体类所在的包,包下类的类名就是该类的别名(首字母小写,大写也行)

    <!--给实体类起别名-->
    <typeAliases>
        <!--方法1:-->
        <typeAlias type="com.kuang.pojo.User" alias="User"/>
        <!--方法2:-->
        <package name="com.kuang.pojo"/>
    </typeAliases>

每一个在包 com.kuang.pojo.User 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.kuang.pojo.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

两种方法的区别

  • 实体类比较少的时候,使用第一种方式

  • 如果实体类十分多,建议使用第二种

  • 第一种可以DIY别名,第二种则不行,如果非要改,需要在实体类上增加注解

@Alias("Hello")
public class User 
   <select id="getUserList" resultType="Hello">
        select * from mybatis.user
    </select>

Mybatis自带别名

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。(八大基本数据类型前加下划线,对应的包装类小写即可)

别名映射的类型
_bytebyte
_char (since 3.5.10)char
_character (since 3.5.10)char
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
char (since 3.5.10)Character
character (since 3.5.10)Character
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
bigintegerBigInteger
objectObject
date[]Date[]
decimal[]BigDecimal[]
bigdecimal[]BigDecimal[]
biginteger[]BigInteger[]
object[]Object[]
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

5.设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。(部分重要设置,官网有完整版)

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。truefalse
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。truefalse
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。truefalse
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4JLOG4J(3.5.9 起废弃)

一个配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="aggressiveLazyLoading" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="safeResultHandlerEnabled" value="true"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
  <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
  <setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/>
  <setting name="callSettersOnNulls" value="false"/>
  <setting name="returnInstanceForEmptyRow" value="false"/>
  <setting name="logPrefix" value="exampleLogPreFix_"/>
  <setting name="logImpl" value="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING"/>
  <setting name="proxyFactory" value="CGLIB | JAVASSIST"/>
  <setting name="vfsImpl" value="org.mybatis.example.YourselfVfsImpl"/>
  <setting name="useActualParamName" value="true"/>
  <setting name="configurationFactory" value="org.mybatis.example.ConfigurationFactory"/>
</settings>

6.其它配置

7.映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件,每写一个dao接口就要写一个Mapper文件

  • 映射器 : 定义映射SQL语句文件

  • 既然 MyBatis 的行为其他元素已经配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。映射器是MyBatis中最核心的组件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁。

  • 1张表——1个Entity类——1个EntityDao/EntityMapper接口——1个Mapper.xml——核心配置文件中的1个

  • Dao/Maapper接口中的1个方法——Mapper.xml中的1个sql语句

方式1:使用resource属性

使用相对于类路径的资源引用(推荐)

<!--    每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->    
<mappers>
    <mapper resource="com/kuang/dao/UserDaoMapper.xml"/>
</mappers>

方式2:使用class属性

使用映射器接口实现类的完全限定类名
需要配置文件名称和接口名称一致,并且位于同一目录下

<mappers>
    <!--<mapper class="com.kuang.dao.UserDaoMapper"/>--><!--报错-->
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>

注意点:

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

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

方式3:<package>元素

将包内的映射器接口实现全部注册为映射器,但是需要配置文件名称和接口名称一致,并且位于同一目录下

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

注意点同方法2(同名同包)

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

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

方式4:url属性(不常用)

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

六、生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l9lr11MK-1680880094413)(null)]

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

  • SqlSessionFactoryBuilder

    • 作用:创建 SqlSessionFactory

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

    • SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

  • SqlSessionFactory

    • 可以理解为数据库连接池

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

    • 一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。

    • SqlSessionFactory 的最佳作用域是应用作用域(Application Scope)

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

  • SqlSession(相当于从连接池中获取一个连接)

    • 连接到连接池的一个请求,相当于一个数据库连接(Connnection对象)

    • SqlSession 的实例不是线程安全的,因此是不能被共享的,它的最佳的作用域是请求或方法作用域

    • 用完之后需要赶紧关闭,否则资源被占用。用 try…catch…finally… 语句来保证其正确关闭。

    • 可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。

    • 一个 SqlSession 可以多次使用它的 getMapper 方法,获取多个 mapper 接口实例

  • 打个比方:

    • SqlSessionFactoryBuilder 是造车公司

    • 造了 100 台车,然后卖给租车公司(SQLSessionFactory),然后倒闭

    • SQLSession 用户 租车,使用车

    • mapper 用户的使用,用户租到车之后可以开去这,开去那,任凭使用

    • 用户(SQLSession) 执行完想做的事之后,必须归还 “汽车” 给租车公司(SQLSessionFactory)

      https://img-blog.csdnimg.cn/7e37d23b2f2948f79987c04e454320ca.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pm05pum,size_20,color_FFFFFF,t_70,g_se,x_16

      这里面的每一个 mapper 就代表一个具体的业务。

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

1.问题

数据库中的字段

新建一个项目Mybatis-04,拷贝之前,测试实体类字段不一致的情况

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

测试结果:password字段为空

在这里插入图片描述

问题原因:实体类的属性名和数据库的字段名不同

select * from mybatis.user where id =#{id}
-- 等效于   类型映射器
select id,name,pwd from mybatis.user where id =#{id}
//此时已经没有pwd

解决方法:起别名

<select id="getUserById" parameterType="int" resultType="com.qjd.pojo.User">
    select id,name,pwd as password from mybatis.user where id =#{id}
</select>

2.ResultMap ——结果(集)映射(xml映射文件中配置)

在UserMapper.xml的元素中添加

    <!--结果集映射-->
    <resultMap id="UserMap" type="com.kuang.pojo.User">
        <!--column:数据库中的字段;property:实体类中的属性-->
<!--        <result column="id" property="id"/>-->
<!--        <result column="name" property="name"/>-->
        <result column="pwd" property="password"/>
    </resultMap>

<!--修改resultType为resultMap(两者不能同时使用)-->
<select id="getUserById" parameterType="int" resultMap="UserMap">
    select * from user where id=#{id}
</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素

  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了

  • MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。

  • 在之前的学习中resultMap都是被隐式创建的,因此需保证实体类的属性名和数据库的列名或列别名对应相同

  • 中的type为全限定名或类型别名

  • select、update、insert、delete中的属性可以使用 resultType 或 resultMap,但不能同时使用。

回顾步骤

  1. 新建一个mybatis-04模块:

  2. 在src/main/resources路径下建立mybatis-config.xml文件建立核心配置文件

  3. 在src/main/java/com/qjd/utils路径下编写工具类MybatisUtils.java读取配置文件获取sqlsessionfactory

  4. 在src/main/java/com/qjd/pojo路径下编写实体类User.java

  5. 在src/main/java/com/qjd/dao路径下编写接口UserMapper.java和UserMapper.xml

  6. 编写测试类

八、日志——<setting name="logImpl" value=""/>(核心配置文件的<settings>中配置)

1.日志工厂

如果一个数据库操作出现了异常,我们需要排错。日志就是最好的助手!
曾经:debug、sout
现在:日志工厂

在这里插入图片描述

value属性只能为以下值:

  • SLF4J

  • LOG4J 【掌握】

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING【掌握】

  • NO_LOGGING

1.STDOUT_LOGGING——标准的日志工厂实现

在核心配置文件中配置日志实现

<settings>
    <!--标准的日志工厂实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

2.Log4j

什么是LOG4J

  • Log4j是Apache的一个开源项目

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

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

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

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

  1. 导入log4j的maven依赖
  <!-- https://mvnrepository.com/artifact/log4j/log4j -->
  <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
  </dependency>
  1. 在CLAASSPATH下新建log4j.properties文件(resource目录下),编写log4j.properties文件
### 配置根 ###
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 =  %d{ABSOLUTE} %5p %c{1}:%L - %m%n

### 配置输出到文件 ###
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.File = ./log/qjd.log

log4j.appender.file.Append = true
log4j.appender.file.Threshold = debug

log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 配置输出到文件,并且每天都创建一个文件 ###
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.File = logs/log.log
log4j.appender.dailyRollingFile.Append = true
log4j.appender.dailyRollingFile.Threshold = debug
log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 设置输出sql的级别,其中logger后面的内容全部为jar包中所包含的包名 ###
log4j.logger.org.mybatis=debug
log4j.logger.java.sql=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug
  1. 在核心配置文件中配置日志实现
 <settings>
     <setting name="logImpl" value="LOG4J"/>
 </settings>
  1. 运行刚才的测试

在这里插入图片描述
在这里插入图片描述

2.1 Log4j的简单使用

  1. 在要使用log4j的类中导入Apache的包
import org.apache.log4j.Logger;
  1. 日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserDaoTest.class);
  1. 使用日志级别
    @Test
    public void testLog4j(){
        //相当与sout,但输出的日志级别不同
        logger.info("info:进入testLog4j");
        logger.debug("debug:进入了testLog4j");
        logger.error("error:进入了testLog4j");
    }

控制台输出:(日志文件中也会添加以下输出)
21:07:25,697  INFO UserDaoTest:63 - info:进入testLog4j
21:07:25,702 DEBUG UserDaoTest:64 - debug:进入了testLog4j
21:07:25,702 ERROR UserDaoTest:65 - error:进入了testLog4j

九、分页查询

为什么要分页?减少数据的处理量

1.使用Limit分页——通过sql实现

SELECT * FROM user limit startIndex,pageSize;
SELECT * FROM user limit 0,2;--从第1行开始(包括第1行)显示2条记录,即(1,2]或[2,2]
SELECT * FROM user limit 3; --[0,n]显示前3条记录
  1. UserMapper接口(方法的参数为Map)
/**
 * 分页查询用户
 * @param map
 * @return
 */
List<User> getUserByLimit(Map<String,Object> map);
  1. xml映射文件
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
    select * from users limit #{startIndex},#{pageSize}
</select>
  1. 测试
    @Test
    public void getUserByLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        HashMap<String,Integer> hashMap=new HashMap<>();
        hashMap.put("startIndex",1);
        hashMap.put("pageSize",2);

        List<User> userList = mapper.getUserByLimit(hashMap);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();

    }

输出:
User{id=2, name='张三', password='892457'}
User{id=3, name='李四', password='784728'}

2.RowBounds实现分页——通过java代码实现【了解】

  1. UserMapper接口
    //RowBounds分页
    List<User> getUserByRowBounds();
  1. UserMapper.xml映射文件
    <!--RowBounds分页查询-->
    <select id="getUserByRowBounds"  resultMap="UserMap">
        select * from mybatis.user
    </select>
  1. 测试
@Test
public void getUserByRowRounds(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

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

    List<User> userList=sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

输出:
User{id=2, name='张三', password='892457'}
User{id=3, name='李四', password='784728'}

3. 分页插件【了解】

MyBatis 分页插件 PageHelper

如何使用----如何使用分页插件

十、面向接口编程

1.面向接口编程

我们之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程

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

  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;

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

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类:

    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)

    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)

    • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .

  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .

  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

2.利用注解开发

  1. UserMapper接口
    @Select("select * from user")
    List<User> getUsers();
  1. 核心配置文件中绑定接口
 <!--绑定接口-->
    <mappers>
        <mapper class="com.kuang.dao.UserMapper"/>
    </mappers>
  1. 测试
    @Test
    public void getUsers(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> userList = mapper.getUsers();

        for (User user : userList) {
            System.out.println(user);
        }

        sqlSession.close();
    }

输出:
Opening JDBC Connection
Created connection 2125238280.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7eac9008]
==>  Preparing: select * from user
==> Parameters: 
<==    Columns: id, name, pwd
<==        Row: 1, 狂神, 123456
<==        Row: 2, 张三, 892457
<==        Row: 3, 李四, 784728
<==        Row: 5, 李六, 982374
<==      Total: 4
User{id=1, name='狂神', password='null'}
User{id=2, name='张三', password='null'}
User{id=3, name='李四', password='null'}
User{id=5, name='李六', password='null'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7eac9008]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7eac9008]
Returned connection 2125238280 to pool.

原因:数据库中的pwd字段和User类中的属性不同名,将User类中的password属性名改为pwd后输出正常

本质:反射机制实现

底层:动态代理

在这里插入图片描述

3.*Mybatis详细的执行流程(分析源码)

https://img-blog.csdnimg.cn/ecd24030215140ec9879bc05910ce443.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pm05pum,size_18,color_FFFFFF,t_70,g_se,x_16

4.注解实现CRUD

  • 我们可以在工具类MybatisUtil中创建sqlSession对象的时候实现自动提交事务!
public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession(true); //事务自动提交
}

 public static SqlSession getSession(boolean flag){
      return sqlSessionFactory.openSession(flag);
  }
  1. 接口
package com.kuang.dao;

import com.kuang.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;
import java.util.Map;

public interface UserMapper {

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

    //方法存在多个参数,所有的参数前面都要加上@Param("id")注
    @Select("select * from user where id=#{id}")
    User getUserByID(@Param("id")int id);


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

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

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

  1. 在配置文件绑定Mapper接口
<mappers>
    <mapper class="com.sunny.dao.UserMapper"/>
</mappers>
  1. 测试代码同上

@Param注解

MyBatis(十五):@Param()注解 - 谁知道水烫不烫 - 博客园

数据库中user表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-76nA7u9u-1686379802821)(null)]

  1. 当在UserMapper中使用如下@param
    @Delete("delete from user where id=#{id}")
    int deleteUser(@Param("uid")int id);

测试时报错:`org.apache.ibatis.exceptions.PersistenceException:

Error updating database. Cause: org.apache.ibatis.binding.BindingException: Parameter ‘id’ not found. Available parameters are [uid, param1]`

报错了,它说“id”没有找到,可用参数为“uid”

  1. 将SQL语句中的#{id}改为#{uid},也就是注解中的名字,正常运行:
    @Delete("delete from user where id=#{uid}")
    int deleteUser(@Param("uid")int id);

单个基本类型的形参时不使用@Param也能执行成功。

那么其他更加复杂的情况呢,比如说,多个基本类型?引用类型?

下面我们就来深入探讨一下@Param()究竟该怎么用。

  1. UserMapper接口
    @Select("select * from user where id=#{id} and name=#{username}")
    User getUserByID2(int id, String username);
  1. 测试
   @Test
    public void getUserById2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserByID2(2, "张三");
        System.out.println(user);
        sqlSession.close();
    }

报错:org.apache.ibatis.exceptions.PersistenceException: ###Error querying database.Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]

添加@Param后:

    @Select("select * from user where id=#{id} and name=#{username}")
    User getUserByID2(@Param("id")int id, @Param("username")String username);

测试正常运行。

以上都是举得直接在接口中注解SQL语句的例子,在Mapper.xml文件中@Param()同样使用,只需要注意的是,使用@Param()注解,parameterType属性就不用再设置了。

三、总结

简单总结一下:

  1. sql的#{}中的参数就是@Param(“”)中设置的参数,而不是方法的形参

  2. 传入单个基本类型或String类型的时候,使用不使用@Param()注解都可以。

  3. 如果参数是 JavaBean , 则不能使用@Param。

  4. 需要传入多个参数时,有三种方法:JavaBean、@Param()注解、Map。个人认为优先使用JavaBean,当需要传入的数据较多但又不能完全将JavaBean利用起来的时候使用@Param()注解或Map。

  5. 单个基本类型,SQL对应的就是形参的名字;使用JavaBean,SQL中对应的就是JavaBean在结果集映射的属性(没有显性映射就是默认的属性);使用@Param(),SQL中对应的就是@Param()注解中的名字;使用Map,SQL中对应的就是Map的键名。

  6. 使用@Param()注解的时候,Mapper.xml文件无需再设置parameterType属性。

#{}${}区别

{}中的参数即为Mapper接口中对应方法的形参。

#{}

  1. #{} 的作用主要是替换**预编译**语句(PrepareStatement)中的占位符? 【推荐使用】

  2. 底层是preparedStatement

  3. 预编译

  4. 能防止sql注入,自动添加了‘ ’ 引号

例如:select * from user where username = #{name} //输入的参数lisa,就会自动加上引号,变成:select * from user where username = ‘lisa’

${}

  1. 不防注入,就是**字符串拼接**

  2. 只能${}的情况:order by、like 语句只能用${}了,用#{}会多个' '导致sql语句失效;此外动态拼接sql也要用${}

  3. #{} 这种取值是编译好SQL语句再取值

${} 这种是取值以后再去编译SQL语句

详细讲解:

1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:
order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111",
 如果传入的值是id,则解析成的sql为order by "id".
2. $将传入的数据直接显示生成在sql中。如:order by $user_id$,
如果传入的值是111,那么解析成sql时的值为order by user_id, 
 如果传入的值是id,则解析成的sql为order by id.
3. #方式能够很大程度防止sql注入。
4.$方式无法防止Sql注入。
5.$方式一般用于传入数据库对象,例如传入表名.
6.一般能用#的就别用$.
MyBatis排序时使用order by 动态参数时需要注意,用$而不是#
字符串替换
默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置
安全的值(比如?)。这样做很安全,很迅速也是首选做法
有时你只是想直接在sql语句中插入一个不改变的
字符串。比如,像ORDER BY,你可以这样来使用:ORDER BY ${columnName}
这里MyBatis不会修改或转义字符串。重要:接受从用户输出的内容并提供给语句中
不变的字符串,这样做是不安全的。这会导致潜在的sql注入攻击,因此你不应该
允许用户输入这些字段,或者通常自行转义并检查。

使用注解和配置文件协同开发,才是MyBatis的最佳实践!

十一、Lombok

Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能。不需要再写getter、setter或equals方法,只要有一个注解,你的类就有一个功能齐全的构建器、自动记录变量等等。

是一个在Java开发过程中用注解的方式,简化了 JavaBean(实体类) 的编写,避免了冗余和样板式代码而出现的插件,让编写的类更加简洁。

1. 使用步骤

  1. 在IDEA中安装lombok插件:File —> Settings —> Plugins —> Browse repositories —> 搜索lombok

  2. 在pom.xml中导入lombok的maven依赖(包)

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.26</version>
    <scope>provided</scope>
</dependency>
  1. 在实体类上加注解即可

2. lombok常用注解

  • @Data : 自动生成set/get方法,toString方法,equals方法,hashCode方法,不带参数的构造方法

  • @NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor
    自动生成无参/有参构造方法

  • @Setter/@Getter : 自动生成set和get方法

  • @ToString : 自动生成toString方法

  • @EqualsAndHashcode : 从对象的字段中生成hashCode和equals方法

以下为不常用注解:

@NonNull : 用在成员方法或者构造方法的参数前面,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常

@CleanUp : 自动资源管理:不用再在finally中添加资源的close方法

@Value : 用于注解final类

@Builder : 产生复杂的构建器api类

@SneakyThrows : 异常处理(谨慎使用)

@Synchronized : 同步方法安全的转化

@Getter(lazy=true) :

@Log: 支持各种logger对象,使用时用对应的注解,如:@Log4j

3.优缺点

优点:

  1. 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、 toString等方法,提高了一定的开发效率

  2. 让代码变得简洁,不用过多的去关注相应的方法

  3. 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等

缺点:

  1. 不支持多种参数构造器的重载

  2. 虽然省去了手动创建getter/seter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

4.总结

   Lombok虽然有很多优点,但Lombok更类似于一种IDE插件,项目也需要依赖相应的jar包。Lombok依赖jar包是因为编译时要用它的注解,为什么说它又类似插件?因为在使用时,eclipse或Intelli] DEA都需要安装相应的插件,在编译器编译时通过操作AST(抽象语法树)改变字节码生成,变向的就是说它在改变java语法。

   它不像spring的依赖注入或者mybatis的ORM一样是运行时的特性,而是编译时的特性,这里我个人最感觉不爽的地方就是对插件的依赖!因为Lombok只是省去了一些人丁生成代码的麻烦,但IDE都有快捷键来协助生成etter/setter等方法,也非常方便
    知乎上有位大神发表过对Lombok的一些看法:这是一种低级趣味的插件,不建议使用。JAva发展到今天,各种插件层出不穷,如何甄别各种插件的优劣?能从架构上优化你的设计的,能提高应用程序性能的,实现高度封装可扩展的..., 像1mbok这种,像这种插件,已经不仅仅是插件了,改变了你如何编写源码,事实上,少去了的代码你写上去又如何?如果JAVA家族到外充斥这样的东西,那只不过是一垃披着金属颜色的屎,迟早会被其它的语言取代。
   虽然话糙但理确实不糙,试想一个项目有非常多类似Lombok这样的插件,个人觉得真的会极大的降低阅读源代码的舒适度,虽然非常不建议在属性的getter/seter写一些业务代码,但在多年项目的实战中,有时通过给getter/setter加一点点业务代码,能极大的简化某些业务场景的代码。所谓取舍,也许就是这时的舍弃一定的规范,取得极大的方便。
   我现在非常坚信一条理念,任何编程语言或插件,都仅仅只是工具而已,即使工具再强大也在于用的人,就如同小米加步枪照样能前飞机大炮的道理一样。结合具体业务场景和项目实际情况,无需一味追求高大上的技术,适合的才是王道。
    Lombok有它的得天独厚的优点,也有它避之不及的缺点,熟知其优缺点,在实战中灵活运用才是王道

十二、多对一和一对多

1.多对一

多对一的理解:

  • 多个学生对应一个老师

  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师!

结果映射(resultMap):

  • association

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

    • 一个复杂类型的集合

    • 嵌套结果映射 —— 集合可以是 resultMap 元素,或是对其它结果映射的引用

以下使用两种方式实现以下sql语句:

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

1.1 按照查询嵌套处理

类似子查询

  1. 数据库设计

https://mmbiz.qpic.cn/mmbiz_png/uJDAUKrGC7LPbib5To6slfFhMArq5QvCjofjccx37cuQgKsWEHibax0bDiaicU6ojNfEzWrj3TibFsX3MJju4sAp5Pg/640?wx_fmt=png&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1

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');
  1. 实体类STudent和Teacher
public class Teacher {
    private int id;
    private String name;

    public Teacher(){

    }

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

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

public class Student {
    private int id;

    public String getName() {
        return name;
    }

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

    private String name;
    private Teacher teacher;//学生需要关联一个老师

    public Student(){

    }

    public int getId() {
        return id;
    }

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

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}
  1. StudentMapper接口和TeacherMapper
package com.kuang.dao;
import com.kuang.pojo.Student;
import java.util.List;

public interface StudentMapper {

    //查询所有的学生信息,以及对应的老师的信息
    public List<Student> getStudent();
}

public interface TeacherMapper {

}
  1. StudentMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace绑定一个对应的Dao/Mapper接口的全限定名-->
<mapper namespace="com.kuang.dao.StudentMapper">
    <resultMap id="StudentTeacher" type="Student">
        <!--主键可使用<id>-->
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--复杂的属性,需要单独处理——引用类型:<association>  集合:<collection>-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>


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

    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id=#{id}
    </select>
</mapper>
  1. 核心配置文件
    <mappers>
        <mapper class="com.kuang.dao.TeacherMapper"/>
        <mapper class="com.kuang.dao.StudentMapper"/>
    </mappers>

  1. 测试
    @Test
    public void testStudent(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudent();//动态代理产生一个实现Mapper接口的对象并赋值个,将该对象赋值给接口的引用
        for (Student student : studentList) {
            System.out.println(student);
        }
        sqlSession.close();
    }

在这里插入图片描述

1.2 按结果嵌套处理

  1. StudentMapper接口
public List<Student> getStudent2();
  1. StudentMapper.xml
    <!--===========按照结果嵌套查询==============-->

    <!--
        按查询结果嵌套处理
        思路:
        1. 直接查询出结果,进行结果集的映射
    -->
    <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>
  1. 测试
    @Test
    public void testStudent(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudent2();//动态代理产生一个实现Mapper接口的对象并赋值个,将该对象赋值给接口的引用
        for (Student student : studentList) {
            System.out.println(student);
        }
        sqlSession.close();
    }

在这里插入图片描述

teacher的id和数据库中不一样????数据库中id=1

小结

  • 按照查询进行嵌套处理就像SQL中的子查询

  • 按照结果进行嵌套处理就像SQL中的联表查询

2.一对多

  1. 实体类
package com.kuang.pojo;

public class Student {
    private int id;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    private String name;

    public int getTid() {
        return tid;
    }

    public void setTid(int tid) {
        this.tid = tid;
    }

    private int tid;

    public Student(){

    }

    public int getId() {
        return id;
    }

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

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

package com.kuang.pojo;

import java.util.List;

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


    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    public Teacher(){

    }

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

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", students=" + students +
                '}';
    }
}
  1. TeacherMapper接口
List<Teacher> getTeacher();
  1. TeacherMapper.xml
    <select id="getTeacher" resultType="Teacher">
        select * from teacher;
    </select>
  1. 核心配置文件中注册Mapper

  2. 测试环境正常

    @Test
    public void test(){

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher> teacherList = mapper.getTeacher();
        for (Teacher teacher : teacherList) {
            System.out.println(teacher);
        }
        sqlSession.close();
    }

在这里插入图片描述

1.1 按结果嵌套查询

  1. TeacherMapper接口
Teacher getTeacherById(@Param("tid")int id);
  1. TeaccherMapper.xml——按结果嵌套查询
    <!--==================按结果嵌套查询================-->
    <select id="getTeacherById" resultMap="TeacherStudent">
        select t.name tname,t.id tid,s.id sid,s.name sname
        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 property="name" column="tname"/>
        <!--复杂的属性,需要单独处理——
            引用类型:<association>  javaType:指定的属性的类型
            集合:<collection>   集合中的泛型信息,我们使用ofType获取
          -->
        <!--Teacher实体类中有一个名为students的引用类型的List集合,将List中Student对象的各属性与sql语句返回的字段进行映射-->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

  1. 测试
    @Test
    public void getTeacherById(){

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacherById(1);
        System.out.println(teacher);
        sqlSession.close();
    }

输出:
Opening JDBC Connection
Created connection 1278254413.
==>  Preparing: 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=?
==> Parameters: 1(Integer)
<==    Columns: sid, sname, tname, tid
<==        Row: 1, 小明, 秦老师, 1
<==        Row: 2, 小红, 秦老师, 1
<==        Row: 3, 小张, 秦老师, 1
<==        Row: 4, 小李, 秦老师, 1
<==        Row: 5, 小王, 秦老师, 1
<==      Total: 5
Teacher{id=1, name='秦老师', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小红', tid=1}, Student{id=3, name='小张', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小王', tid=1}]}
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4c309d4d]
Returned connection 1278254413 to pool.

1.2 按照查询嵌套处理

  1. TeacherMapper
Teacher getTeacherById2(@Param("tid")int id);
  1. TeacherMapper.xml
    <!--===========按照查询嵌套处理================-->
    <select id="getTeacherById2" resultMap="TeacherStudent2">
        select * from mybatis.teacher where id=#{tid}
    </select>

    <resultMap id="TeacherStudent2" type="Teacher">
        <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/><!--这里的column="id"是teacher表中的id-->
    </resultMap>

    <select id="getStudentByTeacherId" resultType="Student">
        <!--这里的id是上文中的id-->
        select * from student where tid=#{id}
    </select>
  1. 测试
    @Test
    public void getTeacherById2() {

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacherById2(1);
        System.out.println(teacher);
        sqlSession.close();
    }


输出:
Opening JDBC Connection
Created connection 931675031.
==>  Preparing: select * from mybatis.teacher where id=?
==> Parameters: 1(Integer)
<==    Columns: id, name
<==        Row: 1, 秦老师
====>  Preparing: select * from student where tid=?
====> Parameters: 1(Integer)
<====    Columns: id, name, tid
<====        Row: 1, 小明, 1
<====        Row: 2, 小红, 1
<====        Row: 3, 小张, 1
<====        Row: 4, 小李, 1
<====        Row: 5, 小王, 1
<====      Total: 5
<==      Total: 1
Teacher{id=0, name='秦老师', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小红', tid=1}, Student{id=3, name='小张', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小王', tid=1}]}
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@37883b97]
Returned connection 931675031 to pool.

小结

  1. 关联:association【多对一】

  2. 集合:collection 【一对多】

  3. association是用于一对一和多对一,而collection是用于一对多的关系

  4. javaType & ofType

    • JavaType是用来指定pojo中属性的类型

    • ofType指定的是映射到list集合属性中pojo的类型。

注意说明:

1、保证SQL的可读性,尽量通俗易懂

2、根据实际要求,尽量编写性能更高的SQL语句

3、注意属性名和字段不一致的问题

4、注意一对多和多对一 中:字段和属性对应的问题

5、尽量使用Log4j,通过日志来查看自己的错误

面试高频

  • Mysql引擎

  • InnoDB底层原理

  • 索引

  • 索引优化

十三、动态SQL

1.什么是动态SQL

什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句.

类似JSTL标签

官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。


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

我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

2.环境搭建

  1. 新建一个数据库表:blog

字段:id,title,author,create_time,views

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. 创建Mybatis基础工程

https://mmbiz.qpic.cn/mmbiz_png/uJDAUKrGC7JISvrLfLvE3e9Wv1kpFL9qzPPOq4EuoibKKLvGve4vEicLpXeEHfz1flqX3ribyzpbjDlOGzziapTsIw/640?wx_fmt=png&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1

  1. IDutil工具类
public class IDUtil {

   public static String genId(){
       return UUID.randomUUID().toString().replaceAll("-","");
  }
}
  1. 实体类编写 【注意set方法作用】

注意:Date类为java.util.Date,不是java.sql.Date

import java.util.Date;

public class Blog {

   private String id;
   private String title;
   private String author;
   private Date createTime;
   private int views;
   //set,get....
}
  1. 编写Mapper接口及xml文件
public interface BlogMapper {
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.BlogMapper">

</mapper>
  1. mybatis核心配置文件,下划线驼峰自动转换
<settings>
   <setting name="mapUnderscoreToCamelCase" value="true"/>
   <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
 <mapper resource="com/kuang/dao/BlogMapper.xml"/>
</mappers>
  1. 插入初始数据

编写接口

//新增一个博客
int addBlog(Blog blog);

映射文件

<insert id="addBlog" parameterType="blog">
  insert into blog (id, title, author, create_time, views)
  values (#{id},#{title},#{author},#{createTime},#{views});
</insert>

初始化博客方法

import com.kuang.dao.BlogMapper;
import com.kuang.pojo.Blog;
import com.kuang.utils.IDUtil;
import com.kuang.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.Date;

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

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

        mapper.addBlog(blog);

        blog.setId(IDUtil.genId());
        blog.setTitle("Java如此简单");
        mapper.addBlog(blog);

        blog.setId(IDUtil.genId());
        blog.setTitle("Spring如此简单");
        mapper.addBlog(blog);

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

        session.close();
    }
}

初始化数据完毕!

if标签

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。

可以实现按不同列搜索的功能(类似方法重载实现的效果)

  1. BlogMapper
List<Blog> queryBlogIF(Map map);
  1. BlogMapper.xml
    <select id="queryBlogIF" parameterType="map">
        select * from mybatis.blog where 1=1
        <if test="title != null">
            and title=#{title}
        </if>
        <if test="author != null">
            and author=#{author}
        </if>

    </select>

这条语句提供了可选的查找文本功能。如果不传入 “title”和“author”,那么所有BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符);如果传入了 “author” 参数,那么就会对 “author” 一列进行查找并返回对应的 BLOG 结果;如果都传,也返回相应的结果

  1. 测试
    @Test
    public void queryBlog(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);

        Map map=new HashMap();
        map.put("title","Mybatis如此简单");
//        map.put("author","狂神说");

        List<Blog> blogList = mapper.queryBlogIF(map);
        for (Blog blog : blogList) {
            System.out.println(blog);
        }

        sqlSession.close();
    }

在这里插入图片描述

在这里插入图片描述

trim,where,set

where

  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 关键字。

  • 若子句的开头为 “AND” 或 “OR”,where 元素也会视情况将它们去除或保留。

  1. BlogMapper.xml
    <select id="queryBlogIF" parameterType="map">
<!--        select * from mybatis.blog where 1=1-->
<!--        <if test="title != null">-->
<!--            and title=#{title}-->
<!--        </if>-->
<!--        <if test="author != null">-->
<!--            and author=#{author}-->
<!--        </if>-->

        <!--为什么需要1=1-->
        <!--如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:SELECT * FROM BLOG WHERE 这会导致查询失败-->
        <!--如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:SELECT * FROM BLOG WHERE AND title = ‘someTitle’ 这个查询也会失败。-->
        select * from mybatis.blog where
<!--        <if test="title != null">-->
<!--            title=#{title}-->
<!--        </if>-->
<!--        <if test="author != null">-->
<!--            and author=#{author}-->
<!--        </if>-->

        <!--如何不使用where 1=1-->
        select * from mybatis.blog
        <where>
            <if test="title != null">
                title=#{title}
            </if>
            <if test="author != null">
                and author=#{author}
            </if>
        </where>
    </select>
  1. 测试代码同上

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

set

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

  1. BlogMapper
int updateBlog(Map map);
  1. BlogMapper.xml
    <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>
  1. 测试
    @Test
    public void updateBlog(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);

        Map map=new HashMap();
        map.put("title","Mybatis如此简单2");
        map.put("author","狂神说");
        map.put("id","5482dbf65d264012833e78b74e9fd95b");

        mapper.updateBlog(map);

        sqlSession.close();
    }

只传title,运行正常

只传author,运行正常

传title和author,运行正常

不传titl和author,报错

trim

<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>

参数说明

  1. prefix:给 trim 标签内 sql 语句加上前缀

  2. suffix:给 trim 标签内 sql 语句加上后缀

  3. prefixOverrides:去除多余的前缀内容,如:prefixOverrides=“OR”,去除 trim 标签内 sql 语句多余的前缀 “OR”

  4. suffixOverrides:去除多余的后缀内容,如:suffixOverrides=“,”,去除 trim 标签内 sql 语句多余的后缀 “,”

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

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

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

你可以通过使用trim元素来达到中同样的效果:

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

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

choose、when、otherwise

类似与switch…case…default

  1. BlogMapper
List<Blog> queryBlogChoose(Map map);
  1. BlogMapper.xml
    <select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <choose>
            <when test="title != null">
                title=#{title}
            </when>
            <when test="author != null">
                author=#{author}
            </when>
            <otherwise>
                and views=#{views}
            </otherwise>
        </choose>
    </select>
  1. 测试
    @Test
    public void queryBlogChoose(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);

        Map map=new HashMap();
//        map.put("title","Mybatis如此简单");
//        map.put("author","狂神说");
        map.put("views",9999);

        List<Blog> blogList = mapper.queryBlogChoose(map);
        for (Blog blog : blogList) {
            System.out.println(blog);
        }

        sqlSession.close();
    }

不传参数时,正常运行

传title和author时,正常运行

传author时,正常运行

foreach

将blog表中的id改为1,2,3,4

  1. BlogMapper
    //查询1,2,3号记录的博客
    List<Blog> queryBlogForeach(Map map);
  1. BlogMapper.xml
    <!-- select * from blog where 1=1 and (id=1 or id=2 or id=3)-->
    <select id="queryBlogForeach" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <!--我们现在传递一个万能的map,map中存在一个集合-->
            <foreach collection="ids" item="id" open="and (" close=")" separator="or">
                id=#{id}
            </foreach>
        </where>
    </select>
  1. 测试
    @Test
    public void queryBlogForeach(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);

        Map map=new HashMap();
        ArrayList<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        map.put("ids",ids);

        mapper.queryBlogForeach(map);

        sqlSession.close();
    }

集合为空时:

在这里插入图片描述

集合中添加1个id时(where标签智能去除了开头的and)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yitsSEcO-1686381160300)(null)]

集合中添加3个id时

在这里插入图片描述

SQL片段

将需要重复编写的sql片段提取出来方便复用

  1. 使用sql标签抽取出重复sql片段:<sql id="sql片段id名">
    <sql id="if-title-author">
        <if test="title != null">
            title=#{title},
        </if>
        <if test="author != null">
            author=#{author}
        </if>
    </sql>
  1. 在需要使用的地方使用include标签引用即可:<include refid="sql片段id名">
    <select id="queryBlogIF" parameterType="map" resultType="com.kuang.pojo.Blog">
        select * from mybatis.blog
        <where>
            <include refid="if-title-author"/>
        </where>
    </select>

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

建议:先在MYSQL中写出完整的SQL再对应的去修改成动态SQL实现

十四、缓存

1.简介

1、什么是缓存 [ Cache ]?

  • 存在内存中的临时数据。

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

2、为什么使用缓存?

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

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

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

2.Mybatis缓存

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

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

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

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

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

一级缓存

一级缓存也叫本地(会话)缓存

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

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

测试

1、在mybatis中加入日志,方便测试结果

    <settings>
        <!--标准的日志工厂实现-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

2、编写接口方法

//根据id查询用户
User queryUserById(@Param("id") int id);

3、接口对应的Mapper文件

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

4、测试

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSqlSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   //查询同一个用户
   User user2 = mapper.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
}

5、结果分析:可以看到只运行了一次sql

在这里插入图片描述

一级缓存失效的四种情况

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

1.查询不同的记录
2,增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
3.查询不同的Mapper.xml
4.手动清理缓存!

1、sqlSession不同

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSqlSession();
   SqlSession session2 = MybatisUtils.getSqlSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

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

   session.close();
   session2.close();
}

观察结果:发现发送了两条SQL语句!

结论:每个sqlSession中的缓存相互独立

2、sqlSession相同,查询条件不同

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSqlSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session.getMapper(UserMapper.class);

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

   session.close();
}

观察结果:发现发送了两条SQL语句!很正常的理解

结论:当前缓存中,不存在这个数据

3、sqlSession相同,两次查询之间执行了增删改操作!

增加方法

//修改用户
int updateUser(Map map);

编写SQL

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

测试

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSqlSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   //第一次查询
   User user = mapper.queryUserById(1);
   System.out.println(user);

   //更新另一条记录
   HashMap map = new HashMap();
   map.put("name","kuangshen");
   map.put("id",4);
   mapper.updateUser(map);

   //第二次查询同一id记录
   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

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

   session.close();
}

观察结果:查询在中间执行了增删改操作后,重新执行了

在这里插入图片描述

结论:因为增删改操作可能会对当前数据产生影响

4、sqlSession相同,手动清除一级缓存

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSqlSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

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

   System.out.println("===================");
   session.clearCache();//手动清除缓存

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

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

   session.close();
}

在这里插入图片描述

一级缓存就是一个map

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段(相当于一个用户不断查询相同的数据,比如不断刷新),一级缓存就是一个map

二级缓存

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

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

  • 工作机制

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

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

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

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

使用步骤

1、开启全局缓存 【mybatis-config.xml】

<!--cacheEnabled的默认值为true-->
<setting name="cacheEnabled" value="true"/>

2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单【xxxMapper.xml】

可以不加属性,也可以自定义属性

<!--在当前mapper.xml文件中开启二级缓存-->
<cache/>

官方示例=====>查看官方文档
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

3、代码测试

  • 实体类要先实现序列化接口,否则报错:Cause: java.io.NotSerializableException: com.kuang.pojo.User

  • 测试代码

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();

   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

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

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

   session2.close();
}

开启二级缓存前

在这里插入图片描述

开启二级缓存后

为什么还是false?视频中是true

使用以下配置开启对应映射文件的二级缓存

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

在这里插入图片描述

结论
  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据

  • 查出的数据都会被默认先放在一级缓存中

  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

缓存原理图

缓存顺序:

1 先看二级缓存中有没有

  1. 再看一级缓存中有没有

  2. 查询数据库

注:一二级缓存都没有,查询数据库,查询后将数据放入一级缓存

在这里插入图片描述

EhCache(第三方二级缓存)

在这里插入图片描述

第三方缓存实现–EhCache: 查看百度百科

介绍:

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

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

  • Enchache可以使用配置文件ehcache.xml

使用方法

  1. 要在应用程序中使用Ehcache,需要引入依赖的jar包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.3</version>
</dependency>
  1. 配置mapper中cache type
<mapper namespace = "com.kuang.dao.UserMapper" >
    <!--开启ehcache缓存-->
    
    <!-- 以下两个<cache>标签二选一,第一个可以输出日志,第二个不输出日志 -->
    <cache type="org.mybatis.caches.ehcache.LoggingEhcache" />

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>
  1. 加入Ehcache的配置文件

在src/main/resources/下创建编写ehcache.xml文件,如果在加载时未找到ehcache.xml资源或出现问题,则将使用默认配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
        updateCheck="false">
   <!--
      diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
      user.home – 用户主目录
      user.dir – 用户当前工作目录
      java.io.tmpdir – 默认临时文件路径
    -->
   <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"/>
   <!--
      defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
    -->
   <!--
     name:缓存名称。
     maxElementsInMemory:缓存最大数目
     maxElementsOnDisk:硬盘最大缓存个数。
     eternal:对象是否永久有效,一但设置了,timeout将不起作用。
     overflowToDisk:是否保存到磁盘,当系统当机时
     timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
     timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
     diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
     diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
     memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
     clearOnFlush:内存数量最大时是否清除。
     memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
     FIFO,first in first out,这个是大家最熟的,先进先出。
     LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
     LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
  -->

</ehcache>

合理的使用缓存,可以让我们程序的性能大大提升!

自定义二级缓存
  1. 实现Cache接口
package com.kuang.pojo;

import org.apache.ibatis.cache.Cache;

import java.util.concurrent.locks.ReadWriteLock;

public class MyCache implements Cache {
    @Override
    public String getId() {
        return null;
    }

    @Override
    public void putObject(Object key, Object value) {

    }

    @Override
    public Object getObject(Object key) {
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        return null;
    }

    @Override
    public void clear() {

    }

    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }
}
  1. 配置mapper中cache type
<mapper namespace = "com.kuang.pojo.UserMapper" >
      <cache type="com.kuang.pojo.MyCache"/>
</mapper>

这部分关于缓存的内容了解就可以,以后开发我们会用Redis数据库来做缓存!K-V

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值