# MyBatis

# 简介

MyBatis 本是 apache 的一个开源项目 <font style="color:blue">iBatis</font>,2010 年这个项目由 apache software foundation 迁移到了 google code , 并且改名为 MyBatis

  • 2013 年 11 月迁移到 Github

iBatis 一词来源于 "internet" 和 "abatis" 的组合,是一个基于 java 的持久层框架,iBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects (DAOs)

# 如何获取 MyBatis

  1. maven
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.11</version>
</dependency>
  1. github

image_2023-02-12-20-37-09

github 地址: 点击查看

  1. 中文文档地址: 点击查看

# 什么是 MyBatis

image_2023-02-12-17-24-01

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

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

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

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

关键问题:什么是持久层

# MyBatis 特性

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

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

  3. MyBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 java 的 POJO (Plain Old Java Objects, 普通的 java 对象) 映射成数据库中的记录

  4. MyBatis 是一个半自动的 ORM (Object Relation Mapping) 框架

查看 orm 详解

# 和其它持久化层技术对比

  • JDBC

    • SQL 夹杂在 java 代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA

    • 操作简单,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动产生的 SQL, 不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射比较困难
    • 反射操作太多,导致数据库性能下降
  • MyBatis

    • 轻量级,性能出色
    • SQL 和 java 编码分开,功能边界清晰,java 代码专注业务,SQL 语句专注数据
    • 开发效率稍逊于 HIbernate, 但是完全能够接受

# 持久层

数据持久化

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

  • 什么是持久状态:将数据持久性存储,比如:数据库 (Jdbc),io 文件持久化

  • 什么是瞬时状态:比如 内存:<font style="color:red"> 断电即失 </font>

  • 生活中的持久化:冷藏,罐头 吃的时候取出来不吃的时候冻上不坏吃的时候在解冻吃

持久层

Dao 层,Service 层,Controller 层 ...

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

  • 层界限十分明显

为什么需要 MyBatis

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

  • 方便

  • 传统的 JDBC 代码太复杂了,简化,框架,自动化

  • 优点:

    • 简单易学
    • 灵活
    • sql 和代码的分离,提高了可维护性
    • 提供映射标签,支持对象与数据库的 orm 字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供 xml 标签,支持编写动态 sql

# 第一个 MyBatis 程序

思路:搭建环境 --> 导入 MyBatis --> 编写代码 --> 测试!

# 搭建环境

搭建数据库

#MyBatis
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 ar(id)
) engine=innodb;
insert into user values(1,'张三','123'),(2,'李四','321'),(3,'刘桑','456');

新建 maven 项目

创建父子工程

执行命令创建父工程

  • mvn archetype:generate

将该工程改为父工程,该配置文件中的 packaging 为 pom

  • 父工程
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.dkx.mybatis</groupId>
  <artifactId>maven-mybatis-fu</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>maven-mybatis-fu</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 <dependencyManagement>
  <dependencies>
    <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.28</version>
    </dependency>
      <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.6</version>
      </dependency>
  </dependencies>
 </dependencyManagement>
  
  <modules>
    <module>maven-mybatis-zi</module>
  </modules>
</project>

创建子工程,在父工程的 pom 目录下,执行命令

  • mvn archetype:generate

创建好后父子工程的配置文件会自动配置好父子的配置标签

  • 子工程
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.dkx.mybatis</groupId>
    <artifactId>maven-mybatis-fu</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <groupId>com.dkx.mybatis01</groupId>
  <artifactId>maven-mybatis-zi</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>maven-mybatis-zi</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
  </dependencies>
</project>
  • 在 main 目录同级的 resources 目录下创建配置文件: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">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="dkx"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

在 Idea 上连接上数据库

image_2023-02-14-11-58-18

  • 测试连接成功后

image_2023-02-14-12-02-54

  • 展示全部的数据库,这个也可以连接好后 OK 然后在设置

image_2023-02-14-12-04-14

  • 或者只显示 mybatis 数据库 (自动创建好的)

image_2023-02-14-12-05-49

如果因为时区连接不上则设置

serverTimezone:Hongkong

image_2023-02-14-11-56-51

将配置文件的信息写全

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration 核心配置文件 -->
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="dkx"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

# 编写 MyBatis 工具类

package com.dkx.mybatis.util;
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.InputStream;
/**
 * @author Dkx
 * @version 1.0
 * @2023/2/1414:20
 * @function
 * @comment
 * sqlsessionfactory --> sqlsession
 */
@SuppressWarnings("all")
public class MybatisUtils {
    private static SqlSessionFactory sqlsessionfactory;
    static{
        try{
//            使用 MyBatis 第一步,获取 SqlSessionFactory 对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlsessionfactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch(Exception e){}
    }
//    既然有了 sqlSessionFactory, 顾名思义,我们就可以从中获得 SqlSession 的实例了
//    SqlSession 完全包含了面向数据库执行,SQL 命令所需的所有方法
    public static SqlSession getSqlSession(){
        return sqlsessionfactory.openSession();
    }
}

# 编写代码

  • 实体类
package com.dkx.mybatis.pojo;
/**
 * @author Dkx
 * @version 1.0
 * @2023/2/1414:39
 * @function
 * @comment
 */
@SuppressWarnings("all")
public class User {
    private Integer id;
    private String name;
    private String pwd;
    public User() {
    }
    public User(Integer id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public 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 接口
package com.dkx.mybatis.dao;
import com.dkx.mybatis.pojo.User;
import java.util.List;
/**
 * @author Dkx
 * @version 1.0
 * @2023/2/1414:46
 * @function
 * @comment
 */
@SuppressWarnings("all")
public interface UserDao {
    List<User> getUserList();
}
  • 接口实现类
package com.dkx.mybatis.dao.impl;
import com.dkx.mybatis.dao.UserDao;
import com.dkx.mybatis.pojo.User;
import java.util.List;
/**
 * @author Dkx
 * @version 1.0
 * @2023/2/1414:48
 * @function
 * @comment
 */
@SuppressWarnings("all")
public class UserDaoImpl implements UserDao {
    public List<User> getUserList(){
        return null;
    }
}

这是之前我们常写的写法,但是用 MyBatis 就不需要了,因为:MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

我们需要创建一个配置文件

image_2023-02-14-15-10-02

  • 使用 Mapper 配置文件来实现 UserDaoImpl 类的编写
<?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">
<!--namespace 绑定一个对应的 Dao (mapper) 接口 -->
<mapper namespace="com.dkx.mybatis.dao.UserDao">
<!-- id: 重写 UserDao 接口的抽象方法 resultType: 返回类型通过反射找到 -->
    <select id="getUserList" resultType="com.dkx.mybatis.pojo.User">
        <!-- 查询 sql 语句 -->
        select * from mybatis.user;
    </select>
</mapper>

# 测试

测试的项目目录和正常工程目录要一致,规范

  • 注意点:org.apache.ibatis.binding.BindingException: Type interface com.dkx.mybatis01.dao.UserDao is not known to the MapperRegistry.

翻译:

org.apache.ibatis.binding.BindingException: 接口类型 com.dkx.mybatis01.dao.UserDao 不被 MapperRegistry 所知。

解决方案:

  • main 同级目录下的 resources 目录中的配置文件:mybatis-config.xml
    • 原因:缺少了 mappers 配置标签
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="dkx"/>
            </dataSource>
        </environment>
    </environments>
    <!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
        <mapper resource="com/dkx/mybatis/dao/UserDaoMapper.xml"/>
    </mappers>
</configuration>

报错:

java.lang.ExceptionInInitializerError
	at com.dkx.mybatis01.util.MybatisUtilsTest.test(MybatisUtilsTest.java:21)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.lang.RuntimeException: org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in com/dkx/mybatis01/dao/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/dkx/mybatis01/dao/UserMapper.xml
	at com.dkx.mybatis01.util.MybatisUtils.<clinit>(MybatisUtils.java:26)
	... 23 more
Caused by: org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in com/dkx/mybatis01/dao/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/dkx/mybatis01/dao/UserMapper.xml
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:80)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:64)
	at com.dkx.mybatis01.util.MybatisUtils.<clinit>(MybatisUtils.java:24)
	... 23 more
Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/dkx/mybatis01/dao/UserMapper.xml
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:122)
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(XMLConfigBuilder.java:99)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:78)
	... 25 more
Caused by: java.io.IOException: Could not find resource com/dkx/mybatis01/dao/UserMapper.xml
	at org.apache.ibatis.io.Resources.getResourceAsStream(Resources.java:114)
	at org.apache.ibatis.io.Resources.getResourceAsStream(Resources.java:100)
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement(XMLConfigBuilder.java:375)
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:120)
	... 27 more

解决方案:

maven 由于它的约定大于配置,我们之后可能遇到我们写的配置文件,无法被导出或者生效的问题,解决方案

<!-- 在 build 中配置 resources, 来防止我们资源导出失败的问题 -->
<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>

# MapperRegistry 是什么

核心配置文件中注册 mappers

# 队命名空间的一点补充

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

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

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

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

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

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

理解我们之前讨论的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常 <font style="color:red"> 严重 </font > 的并发问题

image_2023-02-16-12-39-35

提示 对象生命周期和依赖注入框架

依赖注入框架可以创建线程安全的,基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期,如果对如何通过依赖注入框架使用 MyBatis 感兴趣,可以研究一个 MyBatis-Spring 或 MyBatis-Guice 两个子项目

SqlSessionFactoryBuilder

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

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

  • 局部变量

SqlSessionFactory

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

  • 说白了就是可以想象为:数据库连接池

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

  • 因此 SqlSessionFactory 的最佳作用域是应用作用域

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

SqlSession

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

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

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

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

image-20230216130724127

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

下面的实例就是一个确保 SqlSession 关闭的标准模式:

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

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

# 增删改查 (CRUD) 实现

# namespace

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

# select

选择,查询语句:

  • id: 就是对应的 namespace 中的方法名

  • resultType:Sql 语句执行的返回值

  • parameterType: 参数类型

  • 查询表的 count 的时候需要返回值 Integer 否则会报错

工具类

public class MybatisUtils {
    private static SqlSessionFactory sqlsessionfactory;
    static{
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlsessionfactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
        return sqlsessionfactory.openSession();
    }
}

<font style="color:red"> 主要提示:增删改的操作需要 commit 提交事务否则不会发生变化 </font>

SqlSession.commit();

  1. 编写接口
public interface UserDao {
//    查询所有用户
    List<User> getUserList();
//    根据 id 查询用户
    User getUserById(int id);
//    添加数据
    int add(User u);
//    修改数据
    int update(User u);
//    删除数据
    int delete(int id);
}
  1. 编写对应的 mapper 中 sql 语句
<?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">
<!--namespace 绑定一个对应的 Dao (mapper) 接口 -->
<mapper namespace="com.dkx.mybatis.dao.UserDao">
    <!-- id: 重写 UserDao 接口的抽象方法 resultType: 返回类型通过反射找到 -->
    <select id="getUserList" resultType="com.dkx.mybatis.pojo.User">
        <!-- 查询 sql 语句 -->
        select * from mybatis.user;
    </select>
    <select id="getUserById" parameterType="int" resultType="com.dkx.mybatis.pojo.User">
        select * from mybatis.user where id = #{id};
    </select>
<!--    对象中的属性可以直接取出来 -->
    <insert id="add" parameterType="com.dkx.mybatis.pojo.User">
        insert into mybatis.user (id,name,pwd) values(#{id},#{name},#{pwd});
    </insert>
    <update id="update" parameterType="com.dkx.mybatis.pojo.User">
        update mybatis.user set pwd = #{pwd},name = #{name} where id = #{id};
    </update>
    <delete id="delete" parameterType="int">
        delete from mybatis.user where id = #{id};
    </delete>
</mapper>
  1. 测试
public class UserDaoTest {
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
//        更加简洁,清晰,类型安全的方式
//        推荐方式
        UserDao userdao = sqlSession.getMapper(UserDao.class);
        List<User> list = userdao.getUserList();
        for(User i:list){
            System.out.println(i);
        }
//        旧方式
         List<User> list1 = sqlSession.selectList("com.dkx.mybatis.dao.UserDao.getUserList");
         for(User i:list1){
             System.out.println(i);
         }
    }
    @Test
    public void test1(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserDao userdao = sqlsession.getMapper(UserDao.class);
        User list = userdao.getUserById(1);
        System.out.println(list);
        sqlsession.close();
    }
    @Test
    public void test2(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserDao userdao = sqlsession.getMapper(UserDao.class);
        int i = userdao.add(new User(4,"李四","321"));
        if(i > 0){
            System.out.println("提交成功");
        }
        sqlsession.commit();
        sqlsession.close();
    }
    @Test
    public void test3(){
         SqlSession sqlsession = MybatisUtils.getSqlSession();
         UserDao u = sqlsession.getMapper(UserDao.class);
         int i = u.update(new User(1,"张四","321"));
        if(i > 0){
            System.out.println("修改成功!!");
        }
        sqlsession.commit();
        sqlsession.close();
    }
    @Test
    public void test4(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserDao u = sqlsession.getMapper(UserDao.class);
        int i = u.delete(1);
        if(i > 0){
            System.out.println("删除成功!!!");
        }
        sqlsession.commit();
        sqlsession.close();
    }
}

mapper 中的 sql 语句

select 查询所有用户

<select id="getUserList" resultType="com.dkx.mybatis.pojo.User">
        <!-- 查询 sql 语句 -->
        select * from mybatis.user;
    </select>

select id 根据 id 查询用户

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

insert 添加数据

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

update 更改数据

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

delete 删除数据

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

<font style="color:red"> 注意点:再次提醒 </font>

  • 增删改 需要提交事务,调用 SqlSession 对象点 commit ();

# Map

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

  • 接口
public interface UserDao {
//    map 添加数据
    int addUsermap(Map<String,Object> map);
}
  • mapper.xml
<!--    对象中的属性,可以直接取出来  传递 map 的 key-->
    <insert id="addUsermap" parameterType="map">
#         map可以只列出我们想要操作的字段出来进行操作
        insert into mybatis.user (id,pwd) values (#{userId},#{userPwd});
    </insert>
  • 测试类
@Test
    public void test5(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserDao userdao = sqlsession.getMapper(UserDao.class);
        Map<String,Object> m = new HashMap<>();
        m.put("userId",1);
        m.put("userPwd",333);
        userdao.addUsermap(m);
        sqlsession.commit();
    }

Map 传递参数,直接在 sql 中取出 key 即可 [parameterType="map"]

对象传递参数,直接在 sql 中取对象的属性即可 [parameterType="map"]

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

传送门点击

多个参数用 Map, 或者注解

传送门点击

# Like (模糊查询)

  1. java 代码执行的时候,传递通配符 % %
  • 接口
//    模糊查询
    List<User> getUserLike(String value);
  • UserMapper.xml
<!--    模糊查询 -->
    <select id="getUserLike" resultType="com.dkx.mybatis.pojo.User">
        select * from mybatis.user where name like #{name};
    </select>
  • 测试类
public void test7(){
    SqlSession sqlsession = MybatisUtils.getSqlSession();
    UserDao u = sqlsession.getMapper(UserDao.class);
    List<User> list = u.getUserLike("李%");
    for(User i : list){
        System.out.println(i);
    }
}

# 配置解析

# 1. 核心配置文件

  • mybatis-config.xml (名字可随意起,官方建议为其名)

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

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

# 环境变量 (environments)

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

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

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个,而如果十三个数据库,就需要三个实例,以此类推,记起来很简单;

  • 每个数据库对应一个 SqlSessionFactory 实例

为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可

具体看中文官方文档

学会使用配置多套运行环境

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

# 属性 (properties)

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

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--    引用外部配置文件资源 -->
    <properties resource="druid.properties">
        <property name="username" value="root"/>
<!-- 可以在 properties 中不写密码然后在 mapper.xml 中写也是可以的但是不能都不写 -->
        <property name="password" value="dkx"/>
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
        <mapper resource="com/dkx/mybatis/dao/UserDaoMapper.xml"/>
    </mappers>
</configuration>

这些属性都是可外部配置且可动态替换的,即可以在典型的 java 属性文件中配置,亦可通过 properties 元素的子元素来传递.[db.properties]

编写一个 (properties) 配置文件

druid.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/day14?characterEncoding=utf-8&serverTimezone=UTC
username=root
password=dkx
initialSize=10
minIdle=5
maxActive=20
maxWait=5000
  • 报错解析

image_2023-02-16-09-47-50

在 xml 中,所有的标签都是可以规定其顺序的

报错信息:

The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".

翻译:

元素类型“configuration”的内容必须匹配“(属性?,设置?,类型别名?,类型处理程序?,对象工厂?,对象包装器工厂?,反射器工厂?,插件?,环境?,databaseIdProvider?,映射器?)”。
  • 细节

问题:properties 和 mapper.xml 里面都配置密码会优先走谁

properties

driver=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=UTC
password=dkx

mapper.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">
<configuration>
    <!--    引用外部配置文件资源 -->
    <properties resource="druid.properties">
        <property name="username" value="root"/>
<!--        错误密码 -->
        <property name="password" value="123dkx"/>
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
        <mapper resource="com/dkx/mybatis/dao/UserDaoMapper.xml"/>
    </mappers>
</configuration>

运行结果

image_2023-02-16-10-01-08

得出结论:优先走 properties 配置文件

测试另一种结果

将 properties 中 的密码改为错误的将 mapper.xml 中的密码改为正确的测试

  • properties
driver=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=UTC
password=123dkx
  • mapper.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">
<configuration>
    <!--    引用外部配置文件资源 -->
    <properties resource="druid.properties">
        <property name="username" value="root"/>
<!--        错误密码 -->
        <property name="password" value="dkx"/>
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
        <mapper resource="com/dkx/mybatis/dao/UserDaoMapper.xml"/>
    </mappers>
</configuration>

运行结果

image_2023-02-16-10-04-09

将 properties 中的密码删除就只能走 mapper.xml 运行就会成功

  • 可以直接引入外部文件

  • 可以在其中增加一些属性配置

  • 如果两个文件有同一个字段,优先使用外部配置文件

# 类型别名 (typeAliases)

类型别名可为 java 类型设置一个缩写名称,它仅用于 xml 配置,意在降低冗余的全限定类名写

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

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

例如:

  • 实体类起别名
<!--    给实体类起别名 -->
    <typeAliases>
        <typeAlias type="com.dkx.mybatis.pojo.User" alias="User"/>
    </typeAliases>
  • 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean

  • 扫描实体类的包,它的默认别名就为这个类的 类名,首字母小写

  • 通过包起类别名

mybatis-config.xml

<!--    通过包给实体类起别名,类名首字母小写 -->
    <typeAliases>
        <package name="com.dkx.mybatis.pojo"/>
    </typeAliases>

mapper.xml

<!--    mybatis-config.xml 中配置了 com.dkx.mybatis.pojo 的包我们可以使用类名首字母小写来使用 -->
    <select id="getUserList" resultType="user">
        <!-- 查询 sql 语句 -->
        select * from mybatis.user;
    </select>

测试

image_2023-02-16-10-57-23

演示

  • mybatis-config.xml 配置文件

  • typeAliases 必须写在第二行中

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--    引用外部配置文件资源 -->
    <properties resource="druid.properties">
        <property name="username" value="root"/>
<!--        错误密码 -->
        <property name="password" value="dkx"/>
    </properties>
<!--    给实体类起别名 -->
    <typeAliases>
        <typeAlias type="com.dkx.mybatis.pojo.User" alias="User"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
        <mapper resource="com/dkx/mybatis/dao/UserDaoMapper.xml"/>
    </mappers>
</configuration>
  • mapper.xml
<?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">
<!--namespace 绑定一个对应的 Dao (mapper) 接口 -->
<mapper namespace="com.dkx.mybatis.dao.UserDaoMapper">
    <!-- id: 重写 UserDao 接口的抽象方法 resultType: 返回类型通过反射找到 -->
<!--   mybatis-config.xml 中已经配置了 com.dkx.mybatis.pojo.User 的别名我们可以直接使用别名 User-->
    <select id="getUserList" resultType="User">
        <!-- 查询 sql 语句 -->
        select * from mybatis.user;
    </select>
</mapper>
  • 测试类
public class UserDaoTest {
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
//        更加简洁,清晰,类型安全的方式
//        推荐方式
        UserDaoMapper userdao = sqlSession.getMapper(UserDaoMapper.class);
        List<User> list = userdao.getUserList();
        for(User i:list){
            System.out.println(i);
        }
    }
}

运行结果

image_2023-02-16-10-45-31

在实体类比较少的时候,使用第一种方式 <br> 如果实体类比较多,建议使用第二种

区别:

第一种可以 DIY (自定义)

# 使用注解方式

在实体类上加注解:@Alias ("别名")

image_2023-02-16-11-02-59

优先大于配置文件运行测试

配置文件

<?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">
<!--namespace 绑定一个对应的 Dao (mapper) 接口 -->
<mapper namespace="com.dkx.mybatis.dao.UserDaoMapper">
    <!-- id: 重写 UserDao 接口的抽象方法 resultType: 返回类型通过反射找到 -->
<!--   mybatis-config.xml 中已经配置了 com.dkx.mybatis.pojo.User 的别名我们可以直接使用别名 User-->
<!--    mybatis-config.xml 中配置了 com.dkx.mybatis.pojo 的包我们可以使用类名首字母小写来使用 -->
    <select id="getUserList" resultType="user">
        <!-- 查询 sql 语句 -->
        select * from mybatis.user;
    </select>
</mapper>

image_2023-02-16-11-03-44

将配置文件中改为实体类注解的别名

<select id="getUserList" resultType="niubi">
        <!-- 查询 sql 语句 -->
        select * from mybatis.user;
    </select>

运行测试

image_2023-02-16-11-06-10

下面是一些为常见的 java 类型内建的类型别名,它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格

只介绍部分,具体看文档

地址: 点击

别名映射的类型
_intint
intInteger
dateDate
mapMap

# 设置

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

具体看文档中的设置标题

点击查看

# 其它配置

  • typeHandlers (类型处理器)

  • objectFactory (对象工厂)

  • plugins 插件

    • mybatis-generator-core
    • mybatis-plus
    • 通用 mapper

# 映射器 (mappers)

MapperRegistry: 注册绑定 Mapper 文件;

方式一:使用相对于类路径的资源引用 <font style="color:red"> 推荐使用方式 </font>

<!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
<!--        使用相对于类路径的资源引用 -->
        <mapper resource="com/dkx/mybatis/dao/UserDaoMapper.xml"/>
    </mappers>

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

<!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
        <!--        使用映射器接口实现类的完全限定类名 -->
        <mapper class="com.dkx.mybatis.dao.UserDaoMapper"/>
    </mappers>

注意点

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

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

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

<!--    每一个 Mapper.XML 都需要在 MyBatis 核心配置文件中注册!-->
    <mappers>
<!--        将包内的映射器接口全部注册为映射器 -->
        <package name="com.dkx.mybatis.dao"/>
    </mappers>

注意点

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

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

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

数据库中的字段

image_2023-02-16-13-11-50

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

image_2023-02-16-14-11-11

运行结果

image_2023-02-16-14-11-41

其运行找不到 pwd 的原理

image_2023-02-16-14-18-02

  • 解决方式:使用别名查询

image_2023-02-16-14-29-13

  • Mapper.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.dkx.mybatis.dao.UserMapper">
    <!--SQL 语句头标签 -->
    <!--select: 查询语句,id: 对应接口的方法名,parameterType: 参数类型,resultType: 返回值类型 -->
    <select id="getUserList" resultType="user">
        select id,name,pwd as password from mybatis.user;
    </select>
</mapper>

# 使用 resultMap 解决字段不一致

  • 实体类
public class User {
    private Integer id;
    private String name;
    private String password;
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

结果集映射

column:     id  name    pwd
property:   id  name    password
  • Mapper.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.dkx.mybatis.dao.UserMapper">
    <!--SQL 语句头标签 -->
    <!--select: 查询语句,id: 对应接口的方法名,parameterType: 参数类型,resultType: 返回值类型 -->
<!--    结果集映射 -->
<!--    引入 id 为 resultMap 的 resultMap 返回类型为 User-->
    <resultMap id="resultMap" type="User">
<!--        column:mysql 中字段,property: 实体类的属性 -->
<!--        将 mysql 中的 id 字段映射为 id-->
        <result column="id" property="id"/>
<!--        将 mysql 的 name 字段映射为 name-->
        <result column="name" property="name"/>
<!--        将 mysql 的 pwd 映射为 password-->
        <result column="pwd" property="password"/>
    </resultMap>
    <select id="getUserList" resultMap="resultMap">
        select * from mybatis.user;
    </select>
</mapper>

测试结果

image_2023-02-16-14-47-05

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

  • resultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了

  • resultMap 最优秀的地方在于,虽然你已经对它相当了解了,但是根据就不需要显式地用到它们

比方说:

<?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.dkx.mybatis.dao.UserMapper">
    <!--SQL 语句头标签 -->
    <!--select: 查询语句,id: 对应接口的方法名,parameterType: 参数类型,resultType: 返回值类型 -->
<!--    结果集映射 -->
<!--    引入 id 为 resultMap 的 resultMap 返回类型为 User-->
    <resultMap id="resultMap" type="User">
<!--        column:mysql 中字段,property: 实体类的属性 -->
<!--        将 mysql 的 pwd 映射为 password-->
        <result column="pwd" property="password"/>
    </resultMap>
    <select id="getUserList" resultMap="resultMap">
        select * from mybatis.user;
    </select>
</mapper>
  • 多对一

image_2023-02-16-15-13-48

# 日志

如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手

曾经:sout,debug

现在:日志工厂

image_2023-02-16-16-06-23

  • SLF4J

  • LOG4J(3.5.9 起废弃)

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING [掌握]

  • NO_LOGGING

在 Mybatis 中具体使用那个日志实现,在设置中设定

STDOUT_LOGGING 标准日志输出

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

  • mybatis-config.xml
<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

mage_2023-02-16-16-33-59

# log4j

什么是 log4j?

  • Log4j 是 <font style="color:blue">Apache</font > 的一个开源项目,通过使用 Log4j, 我们可以控制日志信息输送的目的地是 < font style="color:blue"> 控制台 </font>, 文件,<font style="color:blue">GUI</font > 组件

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

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

  • 通过一个 <font style="color:blue"> 配置文件 </font > 来灵活地进行配置,而不需要修改应用的代码

  1. 先导入 Log4j 的包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. log4j.properties
#将等级为 DEBUG 的日志信息输出到 console 和 file 这两个目的地,console 和 file 的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 配置 log4j 为日志实现

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>
    <properties resource="druid.properties"/>
    <settings>
        <setting name="log4j" value="STDOUT_LOGGING"/>
    </settings>
    <typeAliases>
        <typeAlias type="com.dkx.mybatis.pojo.User" alias="H"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/dkx/mybatis/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 如果这里报错可以将 mybatis-config.xml 中的 settings 配置给注释掉,运行时也会导出日志文件的

测试结果

image_2023-02-17-10-47-19

# 简单使用:log4j

  1. 在要使用 Log4j 的类中,导入包 import org.apache.log4.Logger;

  2. 生成日志对象,加载参数为当前类的 class

private static Logger logger = Logger.getLogger(UserMapper.class);
  • 日志级别
logger.info("进入了:testlog4j方法");
logger.debug("Debug:进入了testlog4j方法");
logger.error("error:进入了testlog4j方法");
  1. 输出日志信息方法
public class UserMapperTest {
    private static Logger logger = Logger.getLogger(UserMapper.class);
    @Test
    public void testlog4j(){
//        信息
        logger.info("进入了:testlog4j方法");
        logger.debug("Debug:进入了testlog4j方法");
        logger.error("error:进入了testlog4j方法");
    }
}

运行后查看运行结果

image_2023-02-17-11-01-35

查看生产出的 log 目录的日志文件

image_2023-02-17-11-02-34

# 分页

思考: 为什么要分页?

  • 减少数据的处理量

# Limit 分页

案例操作步骤:

  1. 接口
public interface UserMapper {
//    分页查询
    List<User> getUserMyLimit(Map<String,Integer> map);
}
  1. UserMapper.xmlx
<?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">
<mapper namespace="com.dkx.mybatis.dao.UserMapper">
    <select id="getUserMyLimit" parameterType="map" resultType="H">
        select * from mybatis.user limit #{startIndex},#{pageSize};
    </select>
</mapper>
  1. 测试
public void test4(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        Map<String,Integer> map = new HashMap<String,Integer>();
        map.put("startIndex",0);
        map.put("pageSize",2);
        List<User> u = mapper.getUserMyLimit(map);
        for(User i : u){
            System.out.println(i);
        }
        sqlsession.close();
    }

Run 结果

image_2023-02-17-23-36-33

# RowBounds 分页

不再使用 SQL 实现分页

通过 <font style="color:blue">RowBounds</font > 实现分页查询 < font style="color:red">(重要提醒:不推荐使用,一般公司不会使用)</font>

演示操作步骤:

  • 接口
public interface UserMapper {
//    RowBounds 分页查询
    List<User> getUserMyRowBounds(Map<String,Integer> map);
}
  • UserMapper.xml
<select id="getUserMyRowBounds" resultType="H">
        select * from mybatis.user;
    </select>
  • 测试代码
public void test5RoBounds(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
//        RowBounds 实现分页查询
        RowBounds rowbounds = new RowBounds(1,2);
//        通过 java 代码层面实现分页
        List<User> list = sqlsession.selectList("com.dkx.mybatis.dao.UserMapper.getUserMyRowBounds",null,rowbounds);
        for(User i:list){
            System.out.println(i);
        }
        sqlsession.close();
    }

Run 结果

image_2023-02-18-00-13-56

# 分页插件

# MyBatisHelper

image_2023-02-18-00-27-12

使用方式具体看文档介绍

了解即可,万一以后公司的架构师,说要使用,你需要知道它是什么东西!

# 使用注解开发

# 面向接口编程

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

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

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

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

# 关于接口的理解

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

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

  • 接口应有两类:

    • 第一类是对一个个体的抽象,它可对应为一个抽象体 (abstract class)
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面 (interface)
  • 一个体有可能多个抽象面,抽象体与抽象面是有区别的

本质:反射机制实现

底层:动态代理

mage_2023-02-18-16-49-52

MyBatis 执行流程

image_2023-02-18-17-32-08

# 代码演示

  • 项目目录

image_2023-02-18-16-07-06

去掉了 UserMapper.xml 文件

  • 接口
public interface UserMapper {
    @Select("select * from user")
    List<User> getUserList();
}
  • 实体类
public class User {
    private Integer id;
    private String name;
    private String password;
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
  • 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>
    <properties resource="druid.properties"/>
    <typeAliases>
        <package name="com.dkx.mybatis.pojo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
<!--    绑定配置文件 -->
<!--    <mappers>-->
<!--        <mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<!--    </mappers>-->
<!--    绑定接口 -->
    <mappers>
        <mapper class="com.dkx.mybatis.dao.UserMapper"/>
    </mappers>
</configuration>

Run 结果

image_2023-02-18-16-08-50

由于实体类的字段和 mysql 的字段不一致导致获取不到 password 的值,这个问题在之前使用 UserMapper.xml 的时候只需要添加 typeMap 映射字段来解决,那使用注解方式并没有 UserMapper.xml 配置文件怎么解决呢?

  • 官方回答:

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪,因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于你和你的团队,换句话说,永远不要拘泥于一种方式,你可以很轻松地在基于注解和 XML 的语句映射方式间自由移植和切换

  • 总结:建议使用 XML 来解决这种字段不一致映射的问题

# 注解 CRUD

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

  • MybatisUtils 工具类
public static SqlSession getSqlSession(){
//        在构造器中设置 true 自动提交事务
        return sqlsession.openSession(true);
    }

# @Param ("") 参数注解

多个参数需要使用 @Param ("") 注解,参数为字段名,一个参数可以不写该注解,多个参数必须写否则报错

引用类型不需要使用 @param 比如,对象,Map 集合

错误示范

  • 接口
//    方法存在多个参数所有的参数前面必须使用 @Param (”“) 注解
    @Select("select * from user where id = #{id} and name = #{name}")
    User getUserById(@Param("id") int id,String name);
  • Run 结果

image_2023-02-18-18-49-06

正确代码演示

  • 接口
//    方法存在多个参数所有的参数前面必须使用 @Param (”“) 注解
    @Select("select * from user where id = #{id}")
    User getUserById(@Param("id") int id);
  • 测试代码
public void test1(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        User u = mapper.getUserById(1);
        System.out.println(u);
        sqlsession.close();
    }
  • Run 结果

image_2023-02-18-18-30-14

# 让 @Param 注解参数与查询语句 #{} 中参数不一致得出的以下情况

  • Run 结果:报错!!!

将 @Param 中的参数名更改

//    方法存在多个参数所有的参数前面必须使用 @Param (”“) 注解
    而查询语句中#{}中的参数名为id
    @Select("select * from user where id = #{id}")
    更改@Param中的参数名
    User getUserById(@Param("qid") int id);
  • 两个参数名不一致查看下 Run 结果

image_2023-02-18-18-33-06

同样的使用 UserMapper.xml 配置来实现 CRUD 在接口中的参数列表中使用 @Param 注解来定义参数名如果与 xml 中的 #{} 参数名不一致也会导致报错

# 使用 UserMapper.xml 的情况

  • 接口
User getUserById(@Param("id") int id);
  • UserMapper.xml
<select id="getUserById" resultType="H">
        select * from user where id = #{id};
    </select>
  • Run 结果

mage_2023-02-18-18-41-13

更改接口中 @Param 注解中的参数名让其与 UserMapper.xml 中的参数不一致

  • 接口
User getUserById(@Param("qid") int id);
  • Run 结果

image_2023-02-18-18-42-51

报错!

# Insert

<font style="color:red"> 使用注解来执行 insert 注解的写法为 @Insert, 其中 i 为大写 </font>

  • 接口
@Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
    int addUser(User u);
  • 测试代码

<font style="color:red"> 因为在工具类 (MybatisUtils) 类中将 openSession 参数列表中赋值 true 为自动提交事务所以再次不需要手动的去提交事务了 </font>

public void test2(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        int i = mapper.addUser(new User(7,"小小明","7755"));
        if(i > 0){
            System.out.println("执行成功!!!");
        }
        sqlsession.close();
    }
  • Run 结果

image_2023-02-18-19-06-25

查看 mysql 中 table 的数据

image_2023-02-18-19-06-50

# Update

<font style="color:red"> 使用注解来执行 update 注解的写法为 @Update, 其中 u 为大写 </font>

  • 接口
@Update("update user set name = #{name} where id = #{id}")
    int updateUser(User u);
  • 测试代码
public void test3(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        int i = mapper.updateUser(new User(7,"大大明","7755"));
        if(i > 0){
            System.out.println("执行成功!!!");
        }
        sqlsession.close();
    }
  • Run 结果

image_2023-02-18-19-20-24

mysql 中的结果

!mage_2023-02-18-19-20-55

# 关于 @Param ("") 注解

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

  • 引用类型 比如:对象,Map 集合 ... 不需要加

  • 如果只有一个基本类型的话可以忽略,但是建议加上

  • 在 SQL 中引用的就是在 @Param ("id") 中设定的属性名

# #{} 与 ${} 的区别

具体查看点击

# Lombok

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

Lombok 是一款 Java 开发插件,使得 Java 开发者可以通过其定义的一些注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的 Java 模型对象 (POJO), 在开发环境中使用 Lombok 插件后,Java 开发人员可以节省出重复构建,诸如 hashCode 和 equals 这样的方法以及各种业务对象模型的 accessor 和 toString 等方法的大量时间,对于这些方法,它能够编译源代码期间自动帮我们生成这些方法,并没有如反射那样降低程序的性能

<font style="color:red"> 偷懒专用插件 </font>

# Lombok 的优缺点

优点

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

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

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

缺点:

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

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

总结

知乎上有位大神发表对 Lombok 的一些看法:<br> 这是一种低级趣味的插件,不建议使用,Java 发展到今天,各种插件层出不穷,如何甄别各种插件的优劣?能从架构上优化你的设计的,能提高应用程序性能的,实现高度封装可扩展的..., 像 lombo 这种,像这种插件,已经不仅仅是插件了,改变了你如何编写源码,事实上,少去了的代码,你写上去又任何?如果 Java 家族到处充斥这样的东西,那只不过是一坨披着金属颜色的屎,今早会被其它的语言取代

  • java library

  • plugs

  • build tools

  • with one annotation your class

使用步骤:

  1. <font style="color:red"> 在 IDEA 中安装 Lombok 插件 </font>

image_2023-02-19-00-01-58

  1. <font style="color:red"> 在项目中导入 Lombok 的 jar 包 </font>
<dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.10</version>
    </dependency>
  1. <font style="color:red"> 在实体类上加注解即可 </font>
  • Lombok 中的注解
@NoArgsConstructor(access = AccessLevel.PRIVATE) 无参构造,指定访问级别为私有(private)
@AllArgsConstructor(access = AccessLevel.PRIVATE) 有参构造,指定访问级别为私有(private)
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@ToString(of = {"",""}) 生成toString方法,of指定重写要参与的字段
@EqualsAndHashCode(of = {"",""}) 生成equals与hashCode重写方法,of指定要参与的字段

@Data:无参构造 (自带),get,set,toString,equals,hashcode

@Value:

  1. 所有的成员变量都是 private final 修饰的。
  2. 只有 getter 方法,没有 setter 方法,因为普通方法无法修改 final 修饰的成员变量。
  3. 适合加在值不希望被改变的类上,像是某个类的值当创建后就不希望被更改,只希望读它。
  4. 提供带参数的构造器,根据传入的参数来初始化类里的成员变量。
  5. 重写 hashCode 和 equals 方法。
@Data()
public class User {
    private Integer id;
    private String name;
    private String pwd;
}

效果

image_2023-02-19-00-19-06

# 多对一处理

image_2023-02-19-01-05-37

  • 多个学生,对应一个老师:多对一

  • 对于学生这边而言, 关联 ... 多个学生,关联一个老师

  • 对于老师而言: 集合。一个老师,有很多学生 [一对多]

  • association 一个复杂类型的关联,许多结果将包装成这种类型

    • 嵌套结果映射 - 关联本身可以是一个 resultMap 元素,或者从别处引用一个
  • collection 一个复杂类型的集合

    • 嵌套结果映射 - 集合本身可以是一个 resultMap 元素,或者从别处引用一个

代码演示

  • 实体类
public class Teacher {
    private Integer id;
    private String name;
    private Student student;
}
public class Student {
    private Integer id;
    private String name;
}
  • 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>
    <properties resource="druid.properties"/>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
<!--    错误写法 -->
<!--    <mappers>-->
<!--        <mapper class="com.dkx.mybatis.dao.*Mapper"/>-->
<!--    </mappers>-->
<!--    正确写法 -->
    <mappers>
        <mapper class="com.dkx.mybatis.dao.StudentMapper"/>
        <mapper class="com.dkx.mybatis.dao.TeacherMapper"/>
    </mappers>
    <!--    正确写法 -->
<!--    <mappers>-->
<!--        <package name="com.dkx.mybatis.dao"/>-->
<!--    </mappers>-->
<!--    错误写法 -->
<!--    <mappers>-->
<!--        <mapper class="com.dkx.mybatis.dao.StudentMapper"/>-->
<!--    </mappers>-->
</configuration>
  • 接口

  • StudentMapper

public interface StudentMapper {
    @Select("select * from student where id = #{id}")
    List<Student> getUser(@Param("id") int id);
}
  • TeacherMapper
public interface TeacherMapper {
    @Select("select * from teacher where id = #{id}")
    List<Teacher> getUser(@Param("id") int id);
}

测试代码 Run 结果

  • TeacherMapper

image_2023-02-20-08-58-25

  • StudentMapper

image_2023-02-20-08-58-59

# 按照查询嵌套处理

  • TeacherMapper.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.dkx.mybatis.dao.TeacherMapper">
<!--    思路:
        1. 查询所有老师的信息
        2. 根据查询出来的老师的 tid,寻找对应的学生
    -->
    <resultMap id="studentTeacher" type="T">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
<!--        复杂的属性:我们需要单独处理
            对象:association
            集合:collection
-->
        <association property="student" column="tid" javaType="S" select="getStudent"/>
    </resultMap>
    <select id="getTeacher" resultMap="studentTeacher">
        select * from teacher;
    </select>
    <select id="getStudent" resultType="S">
        select * from student where id = #{id};
    </select>
</mapper>

查询 TeacherMapper Run 结果

image_2023-02-20-11-24-53

# 按照结果嵌套处理

  • TeacherMapper.xml
<!--    按照结果嵌套处理 -->
    <select id="getTeacher1" resultMap="teacherstudent">
        select t.id as tid,t.name as tname,s.name as sname,s.id as sid from student s,teacher t where t.tid = s.id;
    </select>
    <resultMap id="tweacherstudent" type="T">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <association property="student" javaType="S">
            <result property="name" column="sname"/>
            <result property="id" column="sid"/>
        </association>
    </resultMap>

Run 结果

image_2023-02-20-11-54-39

# 一对多处理

比如:一个老师拥有多个学生

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

实体类

  • Student
public class Student {
    private Integer id;
    private String name;
//    多个学生对应一个老师
    private List<Teacher> teachers;
}
  • Teacher
public class Teacher {
    private Integer id;
    private String name;
    private Integer tid;
}
  • 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>
    <properties resource="druid.properties"/>
    <typeAliases>
        <typeAlias type="com.dkx.mybatis.pojo.Student" alias="S"/>
        <typeAlias type="com.dkx.mybatis.pojo.Teacher" alias="T"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="com.dkx.mybatis.dao.StudentMapper"/>
        <mapper class="com.dkx.mybatis.dao.TeacherMapper"/>
    </mappers>
</configuration>
  • 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.dkx.mybatis.dao.StudentMapper">
    <select id="getStudent" resultMap="teacherstudent">
        select s.id as sid,s.name as sname,t.id as rid,t.name as tname,t.tid from student s,teacher t
        where s.id = t.tid and s.id = #{id};
    </select>
    <resultMap id="teacherstudent" type="S">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
<!--         集合中的泛型信息,使用 ofType 获取 -->
        <collection property="teachers" ofType="T">
            <result property="id" column="rid"/>
            <result property="name" column="tname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
<!--==========================================================================================-->
    <select id="getStudent1" resultMap="teacherstudent1">
        select * from student where id = #{id};
    </select>
    <resultMap id="teacherstudent1" type="S">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="teachers" javaType="ArrayList" ofType="T" select="getTeacher" column="id">
        </collection>
    </resultMap>
    <select id="getTeacher" resultType="T">
        select * from teacher where tid = #{tid};
    </select>
</mapper>
  • 接口
public interface StudentMapper {
    List<Student> getStudent(@Param("id") int id);
    List<Student> getStudent1(@Param("id") int id);
}

Run 结果

image_2023-02-20-15-50-21

按照结果嵌套处理

<select id="getStudent" resultMap="teacherstudent">
        select s.id as sid,s.name as sname,t.id as rid,t.name as tname,t.tid from student s,teacher t
        where s.id = t.tid and s.id = #{id};
    </select>
    <resultMap id="teacherstudent" type="S">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
<!--         集合中的泛型信息,使用 ofType 获取 -->
        <collection property="teachers" ofType="T">
            <result property="id" column="rid"/>
            <result property="name" column="tname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

按照查询嵌套处理

<select id="getStudent1" resultMap="teacherstudent1">
        select * from student where id = #{id};
    </select>
    <resultMap id="teacherstudent1" type="S">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="teachers" javaType="ArrayList" ofType="T" select="getTeacher" column="id">
        </collection>
    </resultMap>
    <select id="getTeacher" resultType="T">
        select * from teacher where tid = #{tid};
    </select>

总结:

  • 对象使用 - (关联):association [多对一]

  • 集合使用 - (集合):collection [一对多]

  • javaType & ofType

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

注意点:

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

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

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

# 动态 SQL

什么是动态 SQL:

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

利用动态 SQL 这一特性可以彻底摆脱这种痛苦

动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似,在 MyBatis 之前的版本中,有很多元素需要花时间了解,MyBatis3 大大精简了元素种类,现在只需要学习原来一半的元素便可,MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素

# if

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

如果希望通过 "title" 和 "author" 两个参数进行可选搜索该怎么办呢?首先,我想先将名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可

点击查看代码

# choose(when,otherwise)

# trim(where,set)

# foreach

# 搭建环境

create table blog(
    id int,
    title varchar(30),
    author varchar(30),
    create_time datetime,
    views int
)character set utf8 collate utf8mb3_bin;

创建一个基础工程

  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>
    <properties resource="druid.properties"/>
    <typeAliases>
        <typeAlias type="com.dkx.mybatis.pojo.Blog" alias="B"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/dkx/mybatis/dao/BlogMapper.xml"/>
    </mappers>
</configuration>
  1. 编写实体类
public class Blog {
    private Integer id;
    private String title;
    private String author;
    private Date create_time;
    private Integer views;
    public Blog() {
    }
    public Blog(Integer id, String title, String author, Date create_time, Integer views) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.create_time = create_time;
        this.views = views;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public Date getCreate_time() {
        return create_time;
    }
    public void setCreate_time(Date create_time) {
        this.create_time = create_time;
    }
    public Integer getViews() {
        return views;
    }
    public void setViews(Integer views) {
        this.views = views;
    }
    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", create_time=" + create_time +
                ", views=" + views +
                '}';
    }
}
  1. 编写实体类对应的 Mapper 接口和 Mapper.xml 文件
  • Blogmapper
public interface BlogMapper {
//    插入数据
    int addblog(Blog blog);
//    查询博客
    List<Blog> queryBlogIF(Map map);
}
  • BlogMapper.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.dkx.mybatis.dao.BlogMapper">
    <insert id="addblog" parameterType="B">
insert into blog
    (id,title,author,create_time,views)
    values
(#{id},#{title},#{author},#{createTime},#{views})
    </insert>
<!--    动态 SQL-->
    <select id="queryBlogIF" parameterType="map" resultType="B">
        select * from blog where 1=1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>
</mapper>
  • 启用驼峰命名法解决 mysql 中 _ 为大写的问题

image_2023-02-21-09-16-42

具体看官方文档: 点击查看

<settings>
<!--        开启驼峰命名法 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

Run 结果

查询条件:

map.put("title","微服务如此简单");

image_2023-02-21-10-37-21

# where 元素标签

where 元素只会在子元素返回任何内容的情况下才插入 "WHERE" 子句,而且,若子句的开头为 "AND" 或 "OR",where 元素会将它们去除

  • BlogMapper.xml
<!--    动态 SQL-->
    <select id="queryBlogIF" parameterType="map" resultType="B">
        select * from blog
        <where>
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
        </where>
    </select>

Run 结果

image_2023-02-21-11-33-13

正常情况下不适用 where 元素标签的话执行语句为:

select * from blog and title = #{title}

这个语句会报错,使用 where 元素标签后会将用不到的 and 去除掉

查询条件有两个时为以下 sql:

select * from blog and title = #{title} and author = #{author}

如果什么条件都没有则为如下 sql:

select * from blog

# choose when

类似于 java 中的 swithc 语句

  • BlogMapper.xml
<select id="queryBlogChoose" parameterType="map" resultType="B">
        select * from blog
        <where>
            <choose>
<!--                当满足第一个之后不会执行第二个 -->
<!--                类似于 java 中的 if else-->
                <when test="title != null">
                    and title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    and views = #{views}
                </otherwise>
            </choose>
        </where>
    </select>

# Set

Set 元素会动态前置 Set 关键字,同时也会删掉无关的逗号

  • BlogMapper.xml
<update id="updateBlog" parameterType="map">
        update blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author},
            </if>
            <if test="views != null">
                views = #{views}
            </if>
        </set>
        where id = #{id};
    </update>

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

# Foreach

image_2023-02-21-16-37-49

  • BlogMapper.xml
<!--    我们传递一个万能的 Map, 这个 Map 中可以存在一个集合 -->
    <select id="getListBlog" parameterType="map" resultType="B">
        select * from blog
        <where>
<!--                     集合            id      开始          结尾          分隔 -->
            <foreach collection="ids" item="id" open="and (" close = ")" separator="or">
                id = #{id}
            </foreach>
        </where>
    </select>
  • BlogMapper 接口
List<Blog> getListBlog(Map map);
  • 测试代码
public void test6(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlsession.getMapper(BlogMapper.class);
        Map map = new HashMap();
        List<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        map.put("ids",ids);
        List<Blog> list = mapper.getListBlog(map);
        for(Blog i:list){
            System.out.println(i);
        }
        sqlsession.close();
    }

Run 结果

image_2023-02-21-17-13-34

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

建议:

  • 先在 MySQL 中写出完整的 SQL, 在对应的去修改成为我们的动态 SQL 实现通用即可

# SQL 片段

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

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

<!--    SQL 代码片段 -->
    <sql id="if-title-author">
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </sql>

2. 在需要使用的地方使用 include 标签引用即可

<select id="getList" parameterType="map" resultType="B">
        select * from blog
        <where>
<!--            在查询语句中引用 SQL 代码片段来进行语句拼接查询 -->
            <include refid="if-title-author"></include>
        </where>
    </select>

注意事项:

  • 最好基于单表来定义 SQL 片段!

  • 不要存在 where 标签

# 缓存

# 简介

  1. 什么是缓存 [Cache]?

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

    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据能使用缓存?

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

# MyBatis 缓存

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

  • MyBatis 系统中默认定义了两级缓存:<font style="color:red"> 一级缓存 </font> 和 < font style="color:red"> 二级缓存 </font>

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

# 一级缓存

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

测试步骤:

  1. 开启日志!

  2. 测试在一个 Session 中查询两次相同记录

  3. 查看日志

image_2023-02-21-20-41-22

缓存失效的情况:

1. 查询不同的东西

image_2023-02-21-20-58-11

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

3. 查询不同的 Mapper.xml

4. 手动清理缓存!

image_2023-02-21-21-00-02

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

# 二级缓存

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

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

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了,但我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的 mapper 查出的数据会放在自己对应的缓存 (map) 中

步骤:

  1. 在 mybatis-config.xml 中开启全局缓存
<settings>
<!--        显示的开启全局缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
  1. 在要使用二级缓存的 Mapper 中开启
<!--    在当前 Mapper.xml 中,开启二级缓存 -->
    <cache/>

也可以自定义参数

<!--    在当前 Mapper.xml 中,开启二级缓存 -->
    <cache eviction="FIFO"
            flushInterval="6000"
            size="512"
            readOnly="true"/>
  1. 测试

  2. 问题:我们需要将实体类序列化!否则就会报错

Caused by: java.io.NotSerializableException: com.dkx.mybatis.pojo.User

将实体类序列化

public class User implements Serializable {
    private static final long SerialVersionUID = 1L;
}

创建两个对象来进行测试

  • UserMapperTest
public void test1(){
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        User u = mapper.queryUserById(1);
        System.out.println(u);
        sqlsession.close();
        SqlSession sqlsession1 = MybatisUtils.getSqlSession();
        UserMapper mapper1 = sqlsession1.getMapper(UserMapper.class);
        User u1 = mapper1.queryUserById(1);
        System.out.println(u1);
        System.out.println(u==u1);
        sqlsession1.close();
    }

Run 结果

image_2023-02-21-21-50-14

创建了两次对象查询结果两次两个对象肯定是不同的

使用二级缓存,因为二级缓存在一级缓存死亡后会保存到二级缓存中

  • UserMapperTest

    • 还是那个测试类
  • UserMapper.xml

<mapper namespace="com.dkx.mybatis.dao.UserMapper">
    <!--    在当前 Mapper.xml 中,开启二级缓存 -->
<!--    只对一个 namespace.xml 文件生效 -->
    <cache eviction="FIFO"
           flushInterval="6000"
           size="512"
           readOnly="true"/>
<!--                                                             手动的设置是否开启缓存 -->
    <select id="queryUserById" parameterType="_int" resultType="U" useCache="true">
        select * from user where id = #{id}
    </select>

Run 结果

image_2023-02-21-21-53-53

查询为同一个

小结:

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

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

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

# 缓存原理

image_2023-02-22-09-15-03

<!--                                            不刷新缓存 -->
    <update id="updateUser" parameterType="U" flushCache="false">
        update user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>

# EhCache 自定义缓存

EhCache 是一个纯 Java 的进程内缓存框架,具有快速,精干等特点,是 Hibernate 中默认的 CacheProvider<br>Ehcache 是一种广泛使用的开源 Java 分布式缓存,主要面向通用缓存

在程序中导包

<dependency>
          <groupId>org.mybatis.caches</groupId>
          <artifactId>mybatis-ehcache</artifactId>
          <version>1.1.0</version>
      </dependency>

在 mapper 中指定使用我们的 ehcache 缓存实现

<!--    只对一个 namespace.xml 文件生效 -->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

使用自定义缓存

除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为

# 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 path="./tmpdir/Tmp_EhCache"/>
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>