MyBatis学习笔记

54 阅读40分钟

在这里插入图片描述

正文开始

前段时间动力节点杜老师发布了最新的MyBatis视频,相比大家也是等了好久,前些时间一直没有时间,正好国庆放假几天时间跟着杜老师一起把MyBatis的知识巩固了一遍!!!为了以后更好的复习,于是简单写了一篇博客,为了更好的知识储备,也希望可以帮助到有需要的同学哈!!!

第一章 框架概述

1.1 框架

●在⽂献中看到的framework被翻译为框架

● Java常⽤框架:

○ SSM三⼤框架:Spring + SpringMVC + MyBatis

○ SpringBoot

○ SpringCloud

○ 等。。

● 框架其实就是对通⽤代码的封装,提前写好了⼀堆接⼝和类,我们可以在做项⽬的时候直接引⼊这些

接⼝和类(引⼊框架),基于这些现有的接⼝和类进⾏开发,可以⼤⼤提⾼开发效率。

● 框架⼀般都以jar包的形式存在。(jar包中有class⽂件以及各种配置⽂件等。)

● SSM三⼤框架的学习顺序:

○ ⽅式⼀:MyBatis、Spring、SpringMVC(建议)

○ ⽅式⼆:Spring、MyBatis、SpringMVC

软件开发常用结构

三层架构

三层架构包含的三层:

界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)

三层的职责

  1. 界面层(表示层,视图层) : 主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。
  2. 业务逻辑层:接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
  3. 数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库.

三层的处理请求的交互:

客户端<—>界面层<—>业务逻辑层<—>数据访问层<—>数据库。

如图 :

为什么要使用三层架构 ?

1、 结构清晰、耦合度低、各层分工明确、

2、 可维护度高,可扩展性高。

3、有利用标准化。

4、 开发人员可以只关注整个结构中的其中某一层的功能实现

5、有利于各层逻辑的复用

常用框架

常见的 J2EE 中开发框架

image-20220710211042246

框架是什么

框架定义

​ 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种认为,框架是可被应用开发者定制的应用骨架、模板。

简单的说,框架其实是半成品软件,就是一组组件,供你使用完成你自己的系统。从另一个角度来说框架一个舞台,你在舞台上做表演。在框架基础上加入你要完成的功能。

框架安全的,可复用的,不断升级的软件。

框架解决的问题

​ 框架要解决的最重要的一个问题是技术整合,在 J2EE 的 框架中,有着各种各样的技术,不同的应用,系统使用不同的技术解决问题。需要从 J2EE 中选择不同的技术,而技术自身的复杂性,有导致更大的风险。企业在开发软件项目时,主要目的是解决业务问题。 即要求企业负责技术本身,又要求解决业务问题。这是大多数企业不能完成的。框架把相关的技术融合在一起,企业开发可以集中在业务领域方面。

另一个方面可以提供开发的效率。

JDBC编程

使用JDBC的编程回顾
public void findStudent() {
Connection conn = null; 
Statement stmt = null;
ResultSet rs = null;
try {
//注册 mysql 驱动 
  Class.forName("com.mysql.jdbc.Driver");
//连接数据的基本信息 url ,username,password
String url = "jdbc:mysql://localhost:3306/springdb"; 
String username = "root";
String password = "123456";
//创建连接对象 
conn = DriverManager.getConnection(url, username, password);
//保存查询结果 
List<Student> stuList = new ArrayList<>();
//创建 Statement, 用来执行 sql 语句 
stmt = conn.createStatement();
//执行查询,创建记录集, 
rs = stmt.executeQuery("select * from student");
while (rs.next()) {
Student stu = new Student(); 
stu.setId(rs.getInt("id"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
//从数据库取出数据转为 Student 对象,封装到 List 集合 
    PreparedStatement ps = conn.prepareStatement(sql);
// 繁琐的赋值:思考⼀下,这种有规律的代码能不能通过反射机制来做⾃动化。
ps.setString(1, "1");
ps.setString(2, "123456789");
ps.setString(3, "zhangsan");
ps.setString(4, "123456");
ps.setString(5, "1980-10-11");
ps.setString(6, "男");
ps.setString(7, "zhangsan@126.com");
ps.setString(8, "北京");
ps.setString(9, "⼤兴区凉⽔河⼆街");
ps.setString(10, "1000000");
ps.setString(11, "16398574152");
ps.setString(12, "A");
// 执⾏SQL
int count = ps.executeUpdate();
// ......
stuList.add(stu);}
  }catch(Exception e){
      e.printStackTrace();
}finally{
   try{
if(rs != null)
      rs.lose();
if(pstm != null)
      pstm.close();
   if(con != null)
      con.close();
   
}catch(Exception e){
  e.printStackTrace();
	}
}
使用JDBC的缺陷
  1. 代码比较多,开发效率比较低。
  2. SQL语句写死在Java程序中,不灵活。改SQL的话就要改Java代码。违背开闭原则OCP。
  3. 需要关注Connection,Statement,ResultSet对象的创建和销毁。
  4. 对ResultSet的查询结果,需要自己去封装为List。
  5. 重复冗余的代码比较多。
  6. 业务代码和数据库的操作混在一起。

了解MyBatis

MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD。

● MyBatis在三层架构中负责持久层的,属于持久层框架。

● MyBatis的发展历程:【引⽤百度百科】

○ MyBatis本是apache的⼀个开源项⽬iBatis,2010年这个项⽬由apache software foundation迁

移到了google code,并且改名为MyBatis。2013年11⽉迁移到Github。

○ iBATIS⼀词来源于“internet”和“abatis”的组合,是⼀个基于Java的持久层框架。iBATIS提供的

持久层框架包括SQL Maps和Data Access Objects(DAOs)。

● 打开mybatis代码可以看到它的包结构中包含:ibatis

image-20220916231211933

  • ORM : 对象关系映射

    ○ O(Object):Java虚拟机中的Java对象

    ○ R(Relational):关系型数据库

    ○ M(Mapping):将Java虚拟机中的Java对象映射到数据库表中⼀⾏记录,或是将数据库表中

    ⼀⾏记录映射成Java虚拟机中的⼀个Java对象。

    ○ ORM图示

image-20220916231612458

下面一张图很清晰的介绍什么是ORM

  • MyBatis属于半自动化ORM框架
  • Hibernate全自动化ORM框架

  • MyBatis框架的优点 :

    ○ ⽀持定制化 SQL、存储过程、基本映射以及⾼级映射

    ○ 避免了⼏乎所有的 JDBC 代码中⼿动设置参数以及获取结果集

    ○ ⽀持XML开发,也⽀持注解式开发。【为了保证sql语句的灵活,所以mybatis⼤部分是采⽤

    XML⽅式开发。】

    ○ 将接⼝和 Java 的 POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的

    记录

    ○ 体积⼩好学:两个jar包,两个XML配置⽂件。

    ○ 完全做到sql解耦合。

    ○ 提供了基本映射标签。

    ○ 提供了⾼级映射标签。

    ○ 提供了XML标签,⽀持动态SQL的编写。

    ○ …

MyBatis框架解决的主要问题

减轻使用 JDBC 的复杂性,不用编写重复的创建 Connetion , Statement ; 不用编写关闭资源代码。直接使用 java 对象,表示结果数据。让开发者专注 SQL的处理。 其他分心的工作由 MyBatis 代劳。

MyBatis 可以完成:

注册数据库的驱动,例如 Class.forName(“com.mysql.jdbc.Driver”))

创建 JDBC 中必须使用的 Connection , Statement, ResultSet 对象

从 xml 中获取 sql,并执行 sql 语句,把 ResultSet 结果转换 java 对象

List<Student> list = new ArrayLsit<>();
ResultSet rs = state.executeQuery(“select * from student”); 
while(rs.next){
Student student = new Student();
student.setName(rs.getString(“name”)); student.setAge(rs.getInt(“age”));
list.add(student);
}

4.关闭资源
ResultSet.close() , Statement.close() , Conenection.close()
MyBatis框架的概述

image-20220710211122577

1、 mybatis配置

mybatis-config.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

5、 Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

6、 Mapped Statement对sql执行输入参数进行定义,包括HashMap基本类型pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

7、 Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojoExecutor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

第二章 MyBatis框架快速入门

2.1 内容列表

快速开始一个MyBatis

基本CURD的操作

MyBatis内部对象分析

使用DaoImpl.xml

2. 2 入门案例

2.2.1 开发准备

搭建 MyBatis 开发环境,实现第一个案例

下载 mybatis

MyBatis官网

下载后的目录结构

image-20220710211134605

其中:

mybatis-3.5.1.jar ---->mybatis的核心包

lib ---->mybatis的依赖包

mybatis-3.5.1.pdf ---->mybatis使用手册

2.2 MyBatis下载

image-20220917150902782image-20220917150917323

image-20220917150932329

将框架以及框架的源码都下载下来,下载框架后解压,打开mybatis⽬录

image-20220917151007894

○ 通过以上解压可以看到,框架⼀般都是以jar包的形式存在。我们的mybatis课程使⽤maven,所

以这个jar我们不需要。

○ 官⽅⼿册需要。

2.3 MyBatis⼊⻔程序开发步骤

● 写代码前准备:

○ 准备数据库表:汽⻋表t_car,字段包括:

id:主键(⾃增)【bigint】

car_num:汽⻋编号【

varchar】

brand:品牌【

varchar】

guide_price:⼚家指导价【

decimal类型,专⻔为财务数据准备的类型】

produce_time:⽣产时间【

char,年⽉⽇即可,10个⻓度,‘2022-10-11’】

car_type:汽⻋类型(燃油⻋、电⻋、氢能源)【

varchar】

  • 使⽤navicat for mysql⼯具建表

    image-20220917151115623

  • 使⽤navicat for mysql⼯具向t_car表中插⼊两条数据,如下:

    image-20220917151128346

  • 创建Project:建议创建Empty Project,设置Java版本以及编译版本等。

image-20220917151150338

  • 步骤1:打包⽅式:jar(不需要war,因为mybatis封装的是jdbc。)

    <groupId>com.powernode</groupId> 
    <artifactId>mybatis-001-introduction</artifactId>
    <version>1.0-SNAPSHOT</version> 
    <packaging>jar</packaging> 
    
  • 步骤2:引⼊依赖(mybatis依赖 + mysql驱动依赖)

    <!--mybatis核⼼依赖-->
    <dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId>
     <version>3.5.10</version>
    </dependency>
    <!--mysql驱动依赖-->
    <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.30</version>
    </dependency>
    
  • 步骤3:在resources根⽬录下新建mybatis-config.xml配置⽂件(可以参考mybatis⼿册拷⻉)

    <?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/po
    wernode"/>
     <property name="username" value="root"/>
     <property name="password" value="root"/>
     </dataSource>
     </environment>
     </environments>
     <mappers>
     <!--sql映射⽂件创建好之后,需要将该⽂件路径配置到这⾥-->
     <mapper resource=""/>
     </mappers>
    </configuration>
    
  • 注意1:mybatis核⼼配置⽂件的⽂件名不⼀定是mybatis-config.xml,可以是其它名字。

  • 注意2:mybatis核⼼配置⽂件存放的位置也可以随意。这⾥选择放在resources根下,相当于放到了类的 根路径下。

  • 步骤4:在resources根⽬录下新建CarMapper.xml配置⽂件(可以参考mybatis⼿册拷⻉)

    <?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先随意写⼀个-->
    <mapper namespace="car">
     <!--insert sql:保存⼀个汽⻋信息-->
     <insert id="insertCar">
     insert into t_car
     (id,car_num,brand,guide_price,produce_time,car_type)
     values
     (null,'102','丰⽥mirai',40.30,'2014-10-05','氢能源')
     </insert>
    </mapper>
    
  • 注意1:sql语句最后结尾可以不写“;”

  • 注意2:CarMapper.xml⽂件的名字不是固定的。可以使⽤其它名字。

  • 注意3:CarMapper.xml⽂件的位置也是随意的。这⾥选择放在resources根下,相当于放到了类的根路 径下。

  • 注意4:将CarMapper.xml⽂件路径配置到mybatis-config.xml:

    <mapper resource="CarMapper.xml"/> 1
    
  • 步骤5:编写MyBatisIntroductionTest代码

    package com.manman.test;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    
    /**
     * @author Gaoziman
     * @version 1.0
     * description:
     * @date 2022/9/15 22:16
     */
    public class MyBatisTest {
        
        @Test
        public void testDelete() throws IOException {
        //    创建SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            //    创建流 加载mybatis主配置文件
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            //    通过流创建SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
            //    通过SqlSessionFactory创建SqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();
        //    使用SQL
        //    int i = sqlSession.delete("deleteCar");
            int i = sqlSession.insert("insertCar");
            //    手动提交事务
            sqlSession.commit();
            System.out.println("受影响的行数为 : " +i);
        }
    }
    

    注意1:默认采⽤的事务管理器是:JDBC。JDBC事务默认是不提交的,需要⼿动提交。

    ● 步骤6:运⾏程序,查看运⾏结果,以及数据库表中的数据

    image-20220917151607889

image-20220917151628888

2.4 关于MyBatis核⼼配置⽂件的名字和路径详解

  • 核⼼配置⽂件的名字是随意的,因为以下的代码:
// ⽂件名是出现在程序中的,⽂件名如果修改了,对应这⾥的java程序也改⼀下就⾏了。
InputStream is = Thread.currentThread().getContextClassLoader().getResource
AsStream("mybatis-config.xml");
  • 虽然mybatis核⼼配置⽂件的名字不是固定的,但通常该⽂件的名字叫做:mybatis-config.xml
  • 虽然mybatis核⼼配置⽂件的路径不是固定的,但通常该⽂件会存放到类路径当中,这样让项⽬的移植更加健壮。
  • 在mybatis中提供了⼀个类:Resources【org.apache.ibatis.io.Resources】,该类可以从类路径当中获取资源,我们通常使⽤它来获取输⼊流InputStream,代码如下
// 这种⽅式只能从类路径当中获取资源,也就是说mybatis-config.xml⽂件需要在类路径下。
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");

2.5 MyBatis第⼀个⽐较完整的代码写法

package com.powernode.mybatis;
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;
/**
 * ⽐较完整的第⼀个mybatis程序写法
 * @author gaoziman
 * @since 1.0
 * @version 1.0
 */
public class MyBatisCompleteCodeTest {
 public static void main(String[] args) {
 SqlSession sqlSession = null;
 try {
     // 1.创建SqlSessionFactoryBuilder对象
     SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSes
    sionFactoryBuilder();
     // 2.创建SqlSessionFactory对象
     SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
     // 3.创建SqlSession对象
     sqlSession = sqlSessionFactory.openSession();
     // 4.执⾏SQL
     int count = sqlSession.insert("insertCar");
     System.out.println("更新了⼏条记录:" + count);
     // 5.提交
     sqlSession.commit();
 	} catch (Exception e) {
 // 回滚
 if (sqlSession != null) {
     sqlSession.rollback();
 	}
 	e.printStackTrace();
 	} finally {
 // 6.关闭
 if (sqlSession != null) {
 	sqlSession.close();
	 	}
 	}
 }

2.6 引入单元测试

  • JUnit是专⻔做单元测试的组件。

    • 在实际开发中,单元测试⼀般是由我们Java程序员来完成的。

    • 我们要对我们⾃⼰写的每⼀个业务⽅法负责任,要保证每个业务⽅法在进⾏测试的时候都能通过。

    • 测试的过程中涉及到两个概念:

      • 期望值
      • 实际值
    • 期望值和实际值相同表示测试通过,期望值和实际值不同则单元测试执⾏时会报错。

    • 这⾥引⼊JUnit是为了代替main⽅法。

  • 使⽤JUnit步骤:

    • 第⼀步:引⼊依赖
<!-- junit依赖 -->
<dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.13.2</version>
 <scope>test</scope>
</dependency>
  • 第⼆步:编写单元测试类【测试⽤例】,测试⽤例中每⼀个测试⽅法上使⽤@Test注解进⾏标注。

    • 测试⽤例的名字以及每个测试⽅法的定义都是有规范的:

      测试⽤例的名字:XxxTest

      测试⽅法声明格式:public void test业务⽅法名(){}

// 测试⽤例
public class CarMapperTest{
 
 // 测试⽅法
 @Test
 public void testInsert(){}
 
 @Test
 public void testUpdate(){}
 
}
  • 第三步:可以在类上执⾏,也可以在⽅法上执⾏

    • 在类上执⾏时,该类中所有的测试⽅法都会执⾏。
    • 在⽅法上执⾏时,只执⾏当前的测试⽅法。
  • 编写⼀个测试⽤例,来测试insertCar业务

package com.powernode.mybatis;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
public class CarMapperTest {
 
 @Test
 public void testInsertCar(){
 SqlSession sqlSession = null;
 try {
 // 1.创建SqlSessionFactoryBuilder对象
 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSes
sionFactoryBuilder();
 // 2.创建SqlSessionFactory对象
 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder
.build(Resources.getResourceAsStream("mybatis-config.xml"));
 // 3.创建SqlSession对象
 sqlSession = sqlSessionFactory.openSession();
 // 4.执⾏SQL
 int count = sqlSession.insert("insertCar");
 System.out.println("更新了⼏条记录:" + count);
 // 5.提交
 sqlSession.commit();
 } catch (Exception e) {
 // 回滚
 if (sqlSession != null) {
 sqlSession.rollback();
 }
 e.printStackTrace();
 } finally {
 // 6.关闭
 if (sqlSession != null) {
 sqlSession.close();
 	}
 	}
 	} 
}

执⾏单元测试,查看数据库表的变化:

image-20220917233826281

2.7 引入日志框架

  • 引⼊⽇志框架的⽬的是为了看清楚mybatis执⾏的具体sql。
  • 启⽤标准⽇志组件,只需要在mybatis-config.xml⽂件中添加以下配置:【可参考mybatis⼿册】
<settings>
 <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

标准⽇志也可以⽤,但是配置不够灵活,可以集成其他的⽇志组件,例如:log4j,logback等。

  • logback是⽬前⽇志框架中性能较好的,较流⾏的,所以我们选它。

  • 引⼊logback的步骤:

    • 第⼀步:引⼊logback相关依赖

      <dependency>
       <groupId>ch.qos.logback</groupId>
       <artifactId>logback-classic</artifactId>
       <version>1.2.11</version>
       <scope>test</scope>
      </dependency>
      
    • 第⼆步:引⼊logback相关配置⽂件(⽂件名叫做logback.xml或logback-test.xml,放到类路径

      当中)

      <?xml version="1.0" encoding="UTF-8"?>
      <configuration debug="false">
       <!--定义⽇志⽂件的存储地址-->
       <property name="LOG_HOME" value="/home"/>
       <!-- 控制台输出 -->
       <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
       <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncode
      r">
       <!--格式化输出:%d表示⽇期,%thread表示线程名,%-5level:级别从左显示5 个字符宽度%msg:⽇志消息,%n是换⾏符-->
       <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logge
      r{50} - %msg%n</pattern>
       </encoder>
       </appender>
       <!-- 按照每天⽣成⽇志⽂件 -->
       <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAp
      pender">
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRolling
      Policy">
       <!--⽇志⽂件输出的⽂件名-->
       <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</F
      ileNamePattern>
       <!--⽇志⽂件保留天数-->
       <MaxHistory>30</MaxHistory>
       </rollingPolicy>
       <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncode
      r">
       <!--格式化输出:%d表示⽇期,%thread表示线程名,%-5level:级别从左显示5 个字符宽度%msg:⽇志消息,%n是换⾏符-->
       <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logge
      r{50} - %msg%n</pattern>
       </encoder>
       <!--⽇志⽂件最⼤的⼤⼩-->
       <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTrig
      geringPolicy">
       <MaxFileSize>100MB</MaxFileSize>
       </triggeringPolicy>
       </appender>
       <!--mybatis log configure-->
       <logger name="com.apache.ibatis" level="TRACE"/>
       <logger name="java.sql.Connection" level="DEBUG"/>
       <logger name="java.sql.Statement" level="DEBUG"/>
       <logger name="java.sql.PreparedStatement" level="DEBUG"/>
      
      再次执⾏单元测试⽅法testInsertCar,查看控制台是否有sql语句输出 每⼀次获取SqlSession对象代码太繁琐,封装⼀个⼯具类 ● ○ 2.8 MyBatis⼯具类SqlSessionUtil的封装 ●
       <!-- ⽇志输出级别,logback⽇志级别包括五个:TRACE < DEBUG < INFO < WARN < ER
      ROR -->
       <root level="DEBUG">
       <appender-ref ref="STDOUT"/>
       <appender-ref ref="FILE"/>
       </root>
      </configuration>		
      
  • 再次执⾏单元测试⽅法testInsertCar,查看控制台是否有sql语句输出

image-20220917234236235

2.8 MyBatis工具类SessionUtils的封装

大家应该可以发现,每当我们进行CEUD的时候,会发现我们每次获取SqlSession都需要很繁杂的代码,所以此时就需要一个工具类来帮我们完成,以后获取SqlSession时直接调用即可。

package com.manman.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;

/**
 * @author Gaoziman
 * @version 1.0
 * description:
 * @date 2022/9/17 16:44
 */
public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory;
    private static  SqlSession sqlSession;

    /**
     * 类加载时初始化sqlSessionFactory对象
     */
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 每调⽤⼀次openSession()可获取⼀个新的会话,该会话⽀持⾃动提交。
     *
     * @return 新的会话对象
     */
    public static SqlSession  getSqlSession(){
        if (sqlSessionFactory != null){
             sqlSession = sqlSessionFactory.openSession();
        }
        return sqlSession;
    }
}

测试⼯具类,将testInsertCar()改造

/**
     * description : 测试工具类
     */
    @Test
    public void testInsert() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        int count = sqlSession.insert("insertCar");
        System.out.println( "受影响的行数为 : " + count);
        sqlSession.commit();
        sqlSession.close();
    }

第三章 使用MyBatis完成CRUD

  • 准备⼯作

    • 创建module(Maven的普通Java模块):mybatis-002-crud

    • pom.xml

      • 打包⽅式jar

      • 依赖:

        • mybatis依赖
        • mysql驱动依赖
        • junit依赖
        • logback依赖
    • mybatis-config.xml放在类的根路径下

    • CarMapper.xml放在类的根路径下

    • logback.xml放在类的根路径下

    • 提供com.powernode.mybatis.utils.SqlSessionUtil⼯具类

    • 创建测试⽤例:com.powernode.mybatis.CarMapperTest

3.1 Inser(Create)

分析以下SQL映射⽂件中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先随便写-->
<mapper namespace="car">
 <insert id="insertCar">
 insert into t_car(car_num,brand,guide_price,produce_time,car_typ
e) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油⻋')
 </insert>	
</mapper>

存在的问题是:SQL语句中的值不应该写死,值应该是⽤户提供的。之前的JDBC代码是这样写的:

// JDBC中使⽤ ? 作为占位符。那么MyBatis中会使⽤什么作为占位符呢?
String sql = "insert into t_car(car_num,brand,guide_price,produce_time,car_
type) values(?,?,?,?,?)";
// ......
// 给 ? 传值。那么MyBatis中应该怎么传值呢?
ps.setString(1,"103");
ps.setString(2,"奔驰E300L");
ps.setDouble(3,50.3);
ps.setString(4,"2022-01-01");
ps.setString(5,"燃油⻋");

在MyBatis中可以这样做:

在Java程序中,将数据放到Map集合中

在sql语句中使⽤ #{map集合的key} 来完成传值,#{} 等同于JDBC中的 ? ,#{}就是占位符

Java程序这样写:

package com.powernode.mybatis;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
 * 测试MyBatis的CRUD
 * @author manman
 * @version 1.0
 * @since 1.0
 */
public class CarMapperTest {
 @Test
 public void testInsertCar(){
 // 准备数据
 Map<String, Object> map = new HashMap<>();
 map.put("carNum", "103");
 map.put("brand", "奔驰E300L");
 map.put("guidePrice", 50.3);
 map.put("produceTime", "2020-10-01");
 map.put("carType", "燃油⻋");
 // 获取SqlSession对象
 SqlSession sqlSession = SqlSessionUtil.openSession();
 // 执⾏SQL语句(使⽤map集合给sql语句传递数据)
 int count = sqlSession.insert("insertCar", map);
 System.out.println("插⼊了⼏条记录:" + count);
 } }

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先随便写-->
<mapper namespace="car">
 <insert id="insertCar">
 insert into t_car(car_num,brand,guide_price,produce_time,car_typ
e) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
 </insert>
</mapper>

注意 : 我们在给key值起名的时候,一定要见名知意,不要使用k1.k2.k3.k4…类似这样的名字

#{} 的⾥⾯必须填写map集合的key,不能随便写。运⾏测试程序,查看数据库:

image-20220918143607020

如果#{}⾥写的是map集合中不存在的key会有什么问题?

<?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先随便写-->
<mapper namespace="car">
 <insert id="insertCar">
 insert into t_car(car_num,brand,guide_price,produce_time,car_typ
e) values(#{carNum1},#{brand},#{guidePrice},#{produceTime},#{carType})
 </insert>
</mapper>

运行程序 :

image-20220918143744411 image-20220918143812382

通过测试,看到程序并没有报错。正常执⾏。不过 #{kk} 的写法导致⽆法获取到map集合中的数据,最终

导致数据库表car_num插⼊了NULL。

使⽤Map集合可以传参,那使⽤ pojo(简单普通的java对象)可以完成传参吗?测试⼀下:

● 第⼀步:定义⼀个pojo类Car,提供相关属性。

package com.manman.pojo;

/**
 * @author Gaoziman
 * @version 1.0
 * description:
 * @date 2022/9/18 0:54
 */
public class Car {
    private Long id;
    private String carNum;
    private String brand;
    private Double guidePrice;
    private String produceTime;
    private String carType;

    public Car() {
    }

    @Override
    public String toString() {
        return "Car{" +
                "id=" + id +
                ", carNum='" + carNum + '\'' +
                ", brand='" + brand + '\'' +
                ", guidePrice=" + guidePrice +
                ", produceTime='" + produceTime + '\'' +
                ", carType='" + carType + '\'' +
                '}';
    }

    public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {
        this.id = id;
        this.carNum = carNum;
        this.brand = brand;
        this.guidePrice = guidePrice;
        this.produceTime = produceTime;
        this.carType = carType;
    }

    public Long getId() {
        return id;
    }

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

    public String getCarNum() {
        return carNum;
    }

    public void setCarNum(String carNum) {
        this.carNum = carNum;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Double getGuidePrice() {
        return guidePrice;
    }

    public void setGuidePrice(Double guidePrice) {
        this.guidePrice = guidePrice;
    }

    public String getProduceTime() {
        return produceTime;
    }

    public void setProduceTime(String produceTime) {
        this.produceTime = produceTime;
    }

    public String getCarType() {
        return carType;
    }

    public void setCarType(String carType) {
        this.carType = carType;
    }
}
  • 第二步 :Java小程序

    @Test
    public void testInsertCarByPOJO(){
     // 创建POJO,封装数据
     Car car = new Car();
     car.setCarNum("103");
     car.setBrand("奔驰C200");
     car.setGuidePrice(33.23);
     car.setProduceTime("2020-10-11");
     car.setCarType("燃油⻋");
     // 获取SqlSession对象
     SqlSession sqlSession = SqlSessionUtil.openSession();
     // 执⾏SQL,传数据
     int count = sqlSession.insert("insertCarByPOJO", car);
     System.out.println("插⼊了⼏条记录" + count); }
    
  • 第三步 :SQL语句

    <insert id="insertCarByPOJO">
     <!--#{} ⾥写的是POJO的属性名-->
     insert into t_car(car_num,brand,guide_price,produce_time,car_type) values
    (#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>
    
  • 运行程序 ,查看数据库表 :

    image-20220918144142493

#{} ⾥写的是POJO的属性名,如果写成其他的会有问题吗?

<insert id="insertCarByPOJO">
 insert into t_car(car_num,brand,guide_price,produce_time,car_type) values
(#{a},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>

image-20220918144219130

错误信息中描述:在Car类中没有找到a属性的getter⽅法。

修改POJO类Car的代码,只将getCarNum()⽅法名修改为getA(),其他代码不变,如下:

image-20220918144316193

  • 再运行程序,查看数据表 :

经过测试得出结论:

如果采⽤map集合传参,#{} ⾥写的是map集合的key,如果key不存在不会报错,数据库表中会插 ⼊NULL。

如果采⽤POJO传参,#{} ⾥写的是get⽅法的⽅法名去掉get之后将剩下的单词⾸字⺟变⼩写(例 如:getAge对应的是#{age},getUserName对应的是#{userName}),如果这样的get⽅法不存在会报错。

注意:其实传参数的时候有⼀个属性parameterType,这个属性⽤来指定传参的数据类型,不过这个属

性是可以省略的

<insert id="insertCar" parameterType="java.util.Map">
 insert into t_car(car_num,brand,guide_price,produce_time,car_type) values
(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert> <insert id="insertCarByPOJO" parameterType="com.manman.pojo.Car"
>
 insert into t_car(car_num,brand,guide_price,produce_time,car_type) values
(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>

3.2 delete(Delete)

需求:根据car_num进⾏删除。

SQL语句这样写:

<delete id="deleteByCarNum">
 delete from t_car where car_num = #{SuiBianXie}
</delete>

Java程序实在这样写的 :

@Test
public void testDeleteByCarNum(){
 // 获取SqlSession对象
 SqlSession sqlSession = SqlSessionUtil.openSession();
 // 执⾏SQL语句
 int count = sqlSession.delete("deleteByCarNum", "102");
 System.out.println("删除了⼏条记录:" + count); }

image-20220918144913436

注意:当占位符只有⼀个的时候,${} ⾥⾯的内容可以随便写。

3.3 update(Update)

需求:修改id=34的Car信息,car_num为102,brand为⽐亚迪汉,guide_price为30.23,produce_time

为2018-09-10,car_type为电⻋

修改前:

image-20220918145015975

SQL语句如下 :

<update id="updateCarByPOJO">
 update t_car set
 car_num = #{carNum}, brand = #{brand},
 guide_price = #{guidePrice}, produce_time = #{produceTime},
 car_type = #{carType}
 where id = #{id}
</update>

Java代码如下 :

@Test
 public void testUpdateCarByPOJO(){
 // 准备数据
 Car car = new Car();
 car.setId(34L);
 car.setCarNum("102");
 car.setBrand("⽐亚迪汉");
 car.setGuidePrice(30.23);
 car.setProduceTime("2018-09-10");
 car.setCarType("电⻋");
 // 获取SqlSession对象
 SqlSession sqlSession = SqlSessionUtil.openSession();
 // 执⾏SQL语句
 int count = sqlSession.update("updateCarByPOJO", car);
 System.out.println("更新了⼏条记录:" + count);
 }

image-20220918145118660

image-20220918145128215

当然了,如果使⽤map传数据也是可以的。

3.4 select(Retrieve)

select语句和其它语句不同的是:查询会有⼀个结果集。来看mybatis是怎么处理结果集的!!!

查询一条语句

需求:查询id为1的Car信息

SQL语句如下:

<select id="selectCarById">
 select * from t_car where id = #{id}
</select>

Java程序如下 :

@Test
public void testSelectCarById(){
 // 获取SqlSession对象
 SqlSession sqlSession = SqlSessionUtil.openSession();
 // 执⾏SQL语句
 Object car = sqlSession.selectOne("selectCarById", 1);
 System.out.println(car); }

运行结果如下 :

### Error querying database. Cause: org.apache.ibatis.executor.ExecutorExc
eption:
 A query was run and no Result Maps were found for the Mapped Statement
'car.selectCarById'. 【翻译】:对于⼀个查询语句来说,没有找到查询的结果映射。
 It's likely that neither a Result Type nor a Result Map was specified. 【翻译】:很可能既没有指定结果类型,也没有指定结果映射。

以上的异常⼤致的意思是:对于⼀个查询语句来说,你需要指定它的“结果类型”或者“结果映射”。

所以说,你想让mybatis查询之后返回⼀个Java对象的话,⾄少你要告诉mybatis返回⼀个什么类型的

Java对象,可以在标签中添加resultType属性,⽤来指定查询要转换的类型:

<select id="selectCarById" resultType="com.manman.pojo.Car">
 select * from t_car where id = #{id}
</select>

image-20220918145418402

运⾏后之前的异常不再出现了,这说明添加了resultType属性之后,解决了之前的异常,可以看出

resultType是不能省略的。

仔细观察控制台的⽇志信息,不难看出,结果查询出了⼀条。并且每个字段都查询到值了:Row: 1, 100, 宝⻢520Li, 41.00, 2022-09-01, 燃油⻋

但是奇怪的是返回的Car对象,只有id和brand两个属性有值,其它属性的值都是null,这是为什么呢?我 们来观察⼀下查询结果列名和Car类的属性名是否能⼀⼀对应:

查询结果集的列名:id, car_num, brand, guide_price, produce_time, car_type

Car类的属性名:id, carNum, brand, guidePrice, produceTime, carType

通过观察发现:只有id和brand是⼀致的,其他字段名和属性名对应不上,这是不是导致null的原因呢?

我们尝试在sql语句中使⽤as关键字来给查询结果列名起别名试试 :

<select id="selectCarById" resultType="com.powernode.mybatis.pojo.Car">
 select
 id, car_num as carNum, brand, guide_price as guidePrice, produce_time a
s produceTime, car_type as carType
 from
 t_car
 where
 id = #{id}
</select>

运行结果如下 :

image-20220918145512215

通过测试得知,如果当查询结果的字段名和java类的属性名对应不上的话,可以采⽤as关键字起别名,

当然还有其它解决⽅案,我们后⾯再看。

查询所有数据

需求:查询所有的Car信息。

SQL语句如下:

<!--虽然结果是List集合,但是resultType属性需要指定的是List集合中元素的类型。-->
<select id="selectCarAll" resultType="com.powernode.mybatis.pojo.Car">
 <!--记得使⽤as起别名,让查询结果的字段名和java类的属性名对应上。-->
 select
 id, car_num as carNum, brand, guide_price as guidePrice, produce_time a
s produceTime, car_type as carType
 from
 t_car
</select>

Java代码如下 :

@Test
public void testSelectCarAll(){
 // 获取SqlSession对象
 SqlSession sqlSession = SqlSessionUtil.openSession();
 // 执⾏SQL语句
 List<Object> cars = sqlSession.selectList("selectCarAll");
 // 输出结果
 cars.forEach(car -> System.out.println(car)); 
}

image-20220918145621950

3.5 关于SQL中的namespace

在SQL Mapper配置⽂件中标签的namespace属性可以翻译为命名空间,这个命名空间主要是

为了防⽌sqlId冲突的。

创建CarMapper2.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">
<mapper namespace="car2">
 <select id="selectCarAll" resultType="com.powernode.manman.pojo.Car">
 select
 id, car_num as carNum, brand, guide_price as guidePrice, produ
ce_time as produceTime, car_type as carType
 from
 t_car
 </select>
</mapper>

不难看出,CarMapper.xml和CarMapper2.xml⽂件中都有 id=“selectCarAll”

将CarMapper2.xml配置到mybatis-config.xml⽂件中。

<mappers>
 <mapper resource="CarMapper.xml"/>
 <mapper resource="CarMapper2.xml"/>
</mappers>

编写Java代码如下 :

@Test
public void testNamespace(){
 // 获取SqlSession对象
 SqlSession sqlSession = SqlSessionUtil.openSession();
 // 执⾏SQL语句
 List<Object> cars = sqlSession.selectList("selectCarAll");
 // 输出结果
 cars.forEach(car -> System.out.println(car)); }

运行结果如下:

org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalArgumentException:
 selectCarAll is ambiguous in Mapped Statements collection (try using the
full name including the namespace, or rename one of the entries)
 【翻译】selectCarAll在Mapped Statements集合中不明确(请尝试使⽤包含名称空间的全 名,或重命名其中⼀个条⽬)
 【⼤致意思是】selectCarAll重名了,你要么在selectCarAll前添加⼀个名称空间,要有你改 个其它名字。

Java代码修改如下 :

@Test
public void testNamespace(){
 // 获取SqlSession对象
 SqlSession sqlSession = SqlSessionUtil.openSession();
 // 执⾏SQL语句
 //List<Object> cars = sqlSession.selectList("car.selectCarAll");
 List<Object> cars = sqlSession.selectList("car2.selectCarAll");
 // 输出结果
 cars.forEach(car -> System.out.println(car)); }

运行结果如下 :

image-20220918150026469

第四章 MyBatis核心配置文件详解

<?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"/>
 <property name="username" value="root"/>
 <property name="password" value="root"/>
 </dataSource>
 </environment>
 </environments>
 <mappers>
 <mapper resource="CarMapper.xml"/>
 <mapper resource="CarMapper2.xml"/>
 </mappers>
</configuration>
  • configuration:根标签,表示配置信息。

  • environments:环境(多个),以“s”结尾表示复数,也就是说mybatis的环境可以配置多个数据源。

  • default属性:表示默认使⽤的是哪个环境,default后⾯填写的是environment的id。default的 值只需要和environment的id值⼀致即可。

  • environment:具体的环境配置(主要包括:事务管理器的配置 + 数据源的配置)

    • id:给当前环境⼀个唯⼀标识,该标识⽤在environments的default后⾯,⽤来指定默认环境的 选择。
  • transactionManager:配置事务管理器

  • type属性:指定事务管理器具体使⽤什么⽅式,可选值包括两个

    ■ JDBC:使⽤JDBC原⽣的事务管理机制。底层原理:事务开启

    ​ conn.setAutoCommit(false); …处理业务…事务提交conn.commit();

    ■ MANAGED:交给其它容器来管理事务,⽐如WebLogic、JBOSS等。如果没有管理事务的 容器,则没有事务。没有事务的含义:只要执⾏⼀条DML语句,则提交⼀次。

  • dataSource:指定数据源

    • type属性:⽤来指定具体使⽤的数据库连接池的策略,可选值包括三个

      • UNPOOLED:采⽤传统的获取连接的⽅式,虽然也实现Javax.sql.DataSource接⼝,但是并没有使⽤池的思想。
    • property可以是:

      ○ driver 这是 JDBC 驱动的 Java 类全限定名。

      ○ url 这是数据库的 JDBC URL 地址。

      ○ username 登录数据库的⽤户名。

      ○ password 登录数据库的密码。

      ○ defaultTransactionIsolationLevel 默认的连接事务隔离级别。

      ○ defaultNetworkTimeout 等待数据库操作完成的默认⽹络超时时间(单位:毫秒)

■ POOLED:采⽤传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实

现。

  • property可以是(除了包含UNPOOLED中之外):

    • poolMaximumActiveConnections 在任意时间可存在的活动(正在使⽤)连接数量,默认值:10
    • poolMaximumIdleConnections 任意时间可能存在的空闲连接数。
    • 其它…

■ JNDI:采⽤服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到

​ DataSource是不⼀样。如果不是web或者maven的war⼯程,JNDI是不能使⽤的。

  • property可以是(最多只包含以下两个属性):

    • initial_context 这个属性⽤来在 InitialContext 中寻找上下⽂(即,
    • initialContext.lookup(initial_context))这是个可选属性,如果忽略,那么将会直接从
    • InitialContext 中寻找 data_source 属性。

○ data_source 这是引⽤数据源实例位置的上下⽂路径。提供了 initial_context 配置时会 在其返回的上下⽂中进⾏查找,没有提供时则直接在 InitialContext 中查找。

  • mappers:在mappers标签中可以配置多个sql映射⽂件的路径。
  • mapper:配置某个sql映射⽂件的路径
  • resource属性:使⽤相对于类路径的资源引⽤⽅式
  • url属性:使⽤完全限定资源定位符(URL)⽅式

4.1 environment

mybatis-003-configuration

<?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="dev">-->
 <!--默认使⽤⽣产环境-->
 <environments default="production">
 <!--开发环境-->
 <environment id="dev">
 <transactionManager type="JDBC"/>
 <dataSource type="POOLED">
 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
 <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
 <property name="username" value="root"/>
 <property name="password" value="root"/>
 </dataSource>
 </environment>
 <!--⽣产环境-->
 <environment id="production">
 <transactionManager type="JDBC" />
 <dataSource type="POOLED">
 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
 <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
 <property name="username" value="root"/>
 <property name="password" value="root"/>
 </dataSource>
 </environment>
 </environments>
 <mappers>
 <mapper resource="CarMapper.xml"/>
 </mappers>
</configuration>
<?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="car">
 <insert id="insertCar">
 insert into t_car(id,car_num,brand,guide_price,produce_time,car_ty
pe) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carTyp
e})
 </insert>
</mapper>
package com.powernode.mybatis;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
public class ConfigurationTest {
 @Test
 public void testEnvironment() throws Exception{
 // 准备数据
 Car car = new Car();
 car.setCarNum("133");
 car.setBrand("丰⽥霸道");
 car.setGuidePrice(50.3);
 car.setProduceTime("2020-01-10");
 car.setCarType("燃油⻋");
 // ⼀个数据库对应⼀个SqlSessionFactory对象
 // 两个数据库对应两个SqlSessionFactory对象,以此类推
 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSession
FactoryBuilder();
 // 使⽤默认数据库
 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.bui
ld(Resources.getResourceAsStream("mybatis-config.xml"));
 SqlSession sqlSession = sqlSessionFactory.openSession(true);
 int count = sqlSession.insert("insertCar", car);
 System.out.println("插⼊了⼏条记录:" + count);
 // 使⽤指定数据库
 SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.bu
ild(Resources.getResourceAsStream("mybatis-config.xml"), "dev");
 SqlSession sqlSession1 = sqlSessionFactory1.openSession(true);
 int count1 = sqlSession1.insert("insertCar", car);
 System.out.println("插⼊了⼏条记录:" + count1);
 } 
}

执行结果 :

image-20220918150846696

4.2 transactionManager

<?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="dev">
 <environment id="dev">
 <transactionManager type="MANAGED"/>
 <dataSource type="POOLED">
 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
 <property name="url" value="jdbc:mysql://localhost:3306/po
wernode"/>
 <property name="username" value="root"/>
 <property name="password" value="root"/>
 </dataSource>
 </environment>
 </environments>
 <mappers>
 <mapper resource="CarMapper.xml"/>
 </mappers>
</configuration>
@Test
public void testTransactionManager() throws Exception{
 // 准备数据
 Car car = new Car();
 car.setCarNum("133");
 car.setBrand("丰⽥霸道");
 car.setGuidePrice(50.3);
 car.setProduceTime("2020-01-10");
 car.setCarType("燃油⻋");
 // 获取SqlSessionFactory对象
 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFact
oryBuilder();
 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(R
esources.getResourceAsStream("mybatis-config2.xml"));
 // 获取SqlSession对象
 SqlSession sqlSession = sqlSessionFactory.openSession();
 // 执⾏SQL
 int count = sqlSession.insert("insertCar", car);
 System.out.println("插⼊了⼏条记录:" + count); }
  • 当事务管理器是:JDBC

    • 采⽤JDBC的原⽣事务机制:
  • 开启事务:conn.setAutoCommit(false);

  • 处理业务…

  • 提交事务:conn.commit();

  • 当事务管理器是:MANAGED

    • 交给容器去管理事务,但⽬前使⽤的是本地程序,没有容器的⽀持,当mybatis找不到容器的⽀持 时:没有事务。也就是说只要执⾏⼀条DML语句,则提交⼀次。

4.3 dataSource

<?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="dev">
 <environment id="dev">
 <transactionManager type="JDBC"/>
 <dataSource type="UNPOOLED">
 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
 <property name="url" value="jdbc:mysql://localhost:3306/po
wernode"/>
 <property name="username" value="root"/>
 <property name="password" value="root"/>
 </dataSource>
 </environment>
 </environments>
 <mappers>
 <mapper resource="CarMapper.xml"/>
 </mappers>
</configuration>
@Test
public void testDataSource() throws Exception{
 // 准备数据
 Car car = new Car();
 car.setCarNum("133");
 car.setBrand("丰⽥霸道");
 car.setGuidePrice(50.3);
 car.setProduceTime("2020-01-10");
 car.setCarType("燃油⻋");
 // 获取SqlSessionFactory对象
 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFact
oryBuilder();
 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(R
esources.getResourceAsStream("mybatis-config3.xml"));
 // 获取SqlSession对象
 SqlSession sqlSession = sqlSessionFactory.openSession(true);
 // 执⾏SQL
 int count = sqlSession.insert("insertCar", car);
 System.out.println("插⼊了⼏条记录:" + count);
 // 关闭会话
 sqlSession.close(); }

当type是UNPOOLED,控制台输出:

image-20220918151114061

修改配置⽂件mybatis-config3.xml中的配置:

<dataSource type="POOLED"> 

Java测试程序不需要修改,直接执⾏,看控制台输出:

image-20220918151216668

通过测试得出:UNPOOLED不会使⽤连接池,每⼀次都会新建JDBC连接对象。POOLED会使⽤数据库

连接池。【这个连接池是mybatis⾃⼰实现的。】

<dataSource type="JNDI">

JNDI的⽅式:表示对接JNDI服务器中的连接池。这种⽅式给了我们可以使⽤第三⽅连接池的接⼝。如果

想使⽤dbcp、c3p0、druid(德鲁伊)等,需要使⽤这种⽅式。

这种再重点说⼀下type="POOLED"的时候,它的属性有哪些?

<?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="dev">
 <environment id="dev">
 <transactionManager type="JDBC"/>
 <dataSource type="POOLED">
 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
 <property name="url" value="jdbc:mysql://localhost:3306/po
wernode"/>
 <property name="username" value="root"/>
 <property name="password" value="root"/>
 <!--最⼤连接数-->
 <property name="poolMaximumActiveConnections" value="3"/>
 <!--最多空闲数量-->
 <property name="poolMaximumIdleConnections" value="1"/>
 <!--强⾏回归池的时间-->
 <property name="poolMaximumCheckoutTime" value="20000"/>
 <!--这是⼀个底层设置,如果获取连接花费了相当⻓的时间,连接池会打印状 态⽇志并重新尝试获取⼀个连接(避免在误配置的情况下⼀直失败且不打印⽇志),默认值:20000
毫秒(即 20 秒)。-->
 <property name="poolTimeToWait" value="20000"/>
 </dataSource>
 </environment>
 </environments>
 <mappers>
 <mapper resource="CarMapper.xml"/>
 </mappers>
</configuration>

poolMaximumActiveConnections:最⼤的活动的连接数量。默认值10

poolMaximumIdleConnections:最⼤的空闲连接数量。默认值5

poolMaximumCheckoutTime:强⾏回归池的时间。默认值20秒。

poolTimeToWait:当⽆法获取到空闲连接时,每隔20秒打印⼀次⽇志,避免因代码配置有误,导致傻

等。(时⻓是可以配置的)

当然,还有其他属性。对于连接池来说,以上⼏个属性⽐较重要。

最⼤的活动的连接数量就是连接池连接数量的上限。默认值10,如果有10个请求正在使⽤这10个连接,

第11个请求只能等待空闲连接。

最⼤的空闲连接数量。默认值5,如何已经有了5个空闲连接,当第6个连接要空闲下来的时候,连接池会

选择关闭该连接对象。来减少数据库的开销。

需要根据系统的并发情况,来合理调整连接池最⼤连接数以及最多空闲数量。充分发挥数据库连接池的

性能。【可以根据实际情况进⾏测试,然后调整⼀个合理的数量。】

下图是默认配置:

image-20220918151354804

在以上配置的基础之上,可以编写java程序测试:

@Test
public void testPool() throws Exception{
 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config3.xml"));
 for (int i = 0; i < 4; i++) {
 SqlSession sqlSession = sqlSessionFactory.openSession();
 Object selectCarByCarNum = sqlSession.selectOne("selectCarByCarNum"
);
 } 
}
<select id="selectCarByCarNum" resultType="com.powernode.mybatis.pojo.Car">
 select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where car_num = '100'
</select>

image-20220918151438112

4.4 Properties

mybatis提供了更加灵活的配置,连接数据库的信息可以单独写到⼀个属性资源⽂件中,假设在类的根路

径下创建jdbc.properties⽂件,配置如下:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode

在mybatis核⼼配置⽂件中引⼊并使⽤:

<?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="jdbc.properties">
 <property name="jdbc.username" value="root"/>
 <property name="jdbc.password" value="root"/>
 </properties>
 <environments default="dev">
 <environment id="dev">
 <transactionManager type="JDBC"/>
 <dataSource type="POOLED">
 <!--${key}使⽤-->
 <property name="driver" value="${jdbc.driver}"/>
 <property name="url" value="${jdbc.url}"/>
 <property name="username" value="${jdbc.username}"/>
 <property name="password" value="${jdbc.password}"/>
 </dataSource>
 </environment>
 </environments>
 <mappers>
 <mapper resource="CarMapper.xml"/>
 </mappers>
</configuration>

编写Java程序测试:

@Test
public void testProperties() throws Exception{
 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFacto
ryBuilder();
 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Re
sources.getResourceAsStream("mybatis-config4.xml"));
 SqlSession sqlSession = sqlSessionFactory.openSession();
 Object car = sqlSession.selectOne("selectCarByCarNum");
 System.out.println(car); }

properties两个属性:

resource:这个属性从类的根路径下开始加载。【常⽤的。】

url:从指定的url加载,假设⽂件放在d:/jdbc.properties,这个url可以写成:

file:///d:/jdbc.properties。注意是三个斜杠哦。

注意:如果不知道mybatis-config.xml⽂件中标签的编写顺序的话,可以有两种⽅式知道它的顺序:

第⼀种⽅式:查看dtd约束⽂件。

第⼆种⽅式:通过idea的报错提示信息。【⼀般采⽤这种⽅式】

4.5 mapper

mapper标签⽤来指定SQL映射⽂件的路径,包含多种指定⽅式,这⾥先主要看其中两种:

第⼀种:resource,从类的根路径下开始加载【⽐url常⽤】

<mappers>
 <mapper resource="CarMapper.xml"/>
</mappers>

如果是这样写的话,必须保证类的根下有CarMapper.xml⽂件。

如果类的根路径下有⼀个包叫做test,CarMapper.xml如果放在test包下的话,这个配置应该是这样写:

<mappers>
 <mapper resource="test/CarMapper.xml"/>
</mappers>

第⼆种:url,从指定的url位置加载

假设CarMapper.xml⽂件放在d盘的根下,这个配置就需要这样写:

<mappers>
 <mapper url="file:///d:/CarMapper.xml"/>
</mappers>

第五章 在WEB中应⽤MyBatis(使⽤MVC架构模式)

⽬标

● 掌握mybatis在web应⽤中怎么⽤

● mybatis三⼤对象的作⽤域和⽣命周期

● ThreadLocal原理及使⽤

● 巩固MVC架构模式

● 为学习MyBatis的接⼝代理机制做准备

实现功能

● 银⾏账户转账

使⽤技术

● HTML + Servlet + MyBatis

WEB应⽤的名称

● bank

5.1 需求分析

image-20221008095435078

5.2 数据库表的设计和准备数据

image-20221008095457316

image-20221008095851305

5.3 实现步骤

第⼀步:环境搭建

● IDEA中创建Maven WEB应⽤(mybatis-04-web)

image-20221008100030527

  • IDEA配置Tomcat,这⾥Tomcat使8+版本。并部署应⽤到tomcat

  • 默认创建的maven web应⽤没有java和resources⽬录,包括两种解决⽅案

    ○ 第⼀种:⾃⼰⼿动加上。

image-20221008100129507

○ 第⼆种:修改maven-archetype-webapp-1.4.jar中的配置⽂件

image-20221008100150591

image-20221008100207543

image-20221008100227814

● 引⼊相关依赖

○ 编译器版本修改为17

○ 引⼊的依赖包括:mybatis,mysql驱动,junit,logback,servlet。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w
3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://mave
n.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.powernode</groupId>
 <artifactId>mybatis-004-web</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>war</packaging>
 <name>mybatis-004-web</name>
 <url>http://localhost:8080/bank</url>
 <properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <maven.compiler.source>17</maven.compiler.source>
 <maven.compiler.target>17</maven.compiler.target>
 </properties>
 <dependencies>
 <!--mybatis依赖-->
 <dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis</artifactId>
 <version>3.5.10</version>
 </dependency>
 <!--mysql驱动依赖-->
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.30</version>
 </dependency>
 <!--junit依赖-->
 <dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.13.2</version>
 <scope>test</scope>
 </dependency>
 <!--logback依赖-->
 <dependency>
 <groupId>ch.qos.logback</groupId> 123456789

 <artifactId>logback-classic</artifactId>
 <version>1.2.11</version>
 </dependency>
 <!--servlet依赖-->
 <dependency>
 <groupId>jakarta.servlet</groupId>
 <artifactId>jakarta.servlet-api</artifactId>
 <version>5.0.0</version>
 <scope>provided</scope>
 </dependency>
 </dependencies>
 <build>
 <finalName>mybatis-004-web</finalName>
 <pluginManagement>
 <plugins>
 <plugin>
 <artifactId>maven-clean-plugin</artifactId>
 <version>3.1.0</version>
 </plugin>
 <plugin>
 <artifactId>maven-resources-plugin</artifactId>
 <version>3.0.2</version>
 </plugin>
 <plugin>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>3.8.0</version>
 </plugin>
 <plugin>
 <artifactId>maven-surefire-plugin</artifactId>
 <version>2.22.1</version>
 </plugin>
 <plugin>
 <artifactId>maven-war-plugin</artifactId>
 <version>3.2.2</version>
 </plugin>
 <plugin>
 <artifactId>maven-install-plugin</artifactId>
 <version>2.5.2</version>
 </plugin>
 <plugin>
 <artifactId>maven-deploy-plugin</artifactId>
 <version>2.8.2</version>
 </plugin>
 </plugins>
 </pluginManagement>
 </build>
    </project>
  • 引⼊相关配置⽂件,放到resources⽬录下(全部放到类的根路径下)

    • mybatis-config.xml
    • AccountMapper.xml
    • logback.xml
    • jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=12123
<?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="jdbc.properties"/>
 <environments default="dev">
 <environment id="dev">
 <transactionManager type="JDBC"/>
 <dataSource type="POOLED">
 <property name="driver" value="${jdbc.driver}"/>
 <property name="url" value="${jdbc.url}"/>
 <property name="username" value="${jdbc.username}"/>
 <property name="password" value="${jdbc.password}"/>
 </dataSource>
 </environment>
 </environments>
 <mappers>
 <!--⼀定要注意这⾥的路径哦!!!-->
 <mapper resource="AccountMapper.xml"/>
 </mappers>
</configuration>
<?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="account">
</mapper>
第⼆步:前端⻚⾯index.html
<!DOCTYPE html>
<html lang="en"> <head>
 <meta charset="UTF-8">
 <title>银⾏账户转账</title>
</head> <body>
<!--/bank是应⽤的根,部署web应⽤到tomcat的时候⼀定要注意这个名字-->
<form action="/bank/transfer" method="post">
 转出账户:<input type="text" name="fromActno"/><br>
 转⼊账户:<input type="text" name="toActno"/><br>
 转账⾦额:<input type="text" name="money"/><br>
 <input type="submit" value="转账"/>
</form>
</body>
</html>
第三步:创建pojo包、service包、dao包、web包、utils包
  • com.manman.pojo
  • com.manman.sevice
  • com.manman.dao
  • com.manman.web
  • com.manman.utils
第四步:定义pojo类:Account
public class Account {
 private Long id;
 private String actno;
 private Double balance;
}
第五步:编写AccountDao接⼝,以及AccountDaoImpl实现类
  • 分析dao中⾄少要提供⼏个⽅法,才能完成转账:

    转账前需要查询余额是否充⾜:selectByActno

    转账时要更新账户:update

public interface AccountDao {
 /**
 * 根据账号获取账户信息
 * @param actno 账号
 * @return 账户信息
 */
 Account selectByActno(String actno);
 /**
 * 更新账户信息
 * @param act 账户信息
 * @return 1表示更新成功,其他值表示失败
 */
 int update(Account act); }
package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
 @Override
 public Account selectByActno(String actno) {
 SqlSession sqlSession = SqlSessionUtil.openSession();
 Account act = (Account)sqlSession.selectOne("selectByActno", actno
);
 sqlSession.close();
 return act;
 }
 @Override
 public int update(Account act) {
 SqlSession sqlSession = SqlSessionUtil.openSession();
 int count = sqlSession.update("update", act);
 sqlSession.commit();
 sqlSession.close();
 return count;
 } }
第六步:AccountDaoImpl中编写了mybatis代码,需要编写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">
<mapper namespace="account">
 <select id="selectByActno" resultType="com.powernode.bank.pojo.Accoun
t">
 select * from t_act where actno = #{actno}
 </select>
 <update id="update">
 update t_act set balance = #{balance} where actno = #{actno}
 </update>
</mapper>
第七步:编写AccountService接⼝以及AccountServiceImpl
public class MoneyNotEnoughException extends Exception{
 public MoneyNotEnoughException(){
     
 }
 public MoneyNotEnoughException(String msg){ super(msg);
                 } 
}
public class AppException extends Exception{
 public AppException(){}
 public AppException(String msg){ super(msg); 
                                } 
}
package com.powernode.bank.service;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
/**
 * 账户业务类。
 * @author 
 * @version 1.0
 * @since 1.0
 */
public interface AccountService {
 /**
 * 银⾏账户转正
 * @param fromActno 转出账户
 * @param toActno 转⼊账户
 * @param money 转账⾦额
 * @throws MoneyNotEnoughException 余额不⾜异常
 * @throws AppException App发⽣异常
 */
 void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException; 
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
public class AccountServiceImpl implements AccountService {
 private AccountDao accountDao = new AccountDaoImpl();
 @Override
 public void transfer(String fromActno, String toActno, double money) t
hrows MoneyNotEnoughException, AppException {
 // 查询转出账户的余额
 Account fromAct = accountDao.selectByActno(fromActno);
 if (fromAct.getBalance() < money) {
 throw new MoneyNotEnoughException("对不起,您的余额不⾜。");
 }
 try {
 // 程序如果执⾏到这⾥说明余额充⾜
 // 修改账户余额
 Account toAct = accountDao.selectByActno(toActno);
 fromAct.setBalance(fromAct.getBalance() - money);
 toAct.setBalance(toAct.getBalance() + money);
 // 更新数据库
 accountDao.update(fromAct);
 accountDao.update(toAct);
 } catch (Exception e) {
 throw new AppException("转账失败,未知原因!");
 }
 } 
}
第⼋步:编写AccountController
@WebServlet("/transfer")
public class AccountController extends HttpServlet {
 private AccountService accountService = new AccountServiceImpl();
 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response)
 throws ServletException, IOException {
 // 获取响应流
 response.setContentType("text/html;charset=UTF-8");
 PrintWriter out = response.getWriter();
 // 获取账户信息
 String fromActno = request.getParameter("fromActno");
 String toActno = request.getParameter("toActno");
 double money = Integer.parseInt(request.getParameter("money"));
 // 调⽤业务⽅法完成转账
 try {
 accountService.transfer(fromActno, toActno, money);
 out.print("<h1>转账成功!!!</h1>");
 } catch (MoneyNotEnoughException e) {
 out.print(e.getMessage());
 } catch (AppException e) {
 out.print(e.getMessage());
 }
 } 
}

启动服务器,打开浏览器,输⼊地址:http://localhost:8080/bank,测试:

image-20221008101320538

image-20221008101330993

image-20221008101338548

5.4 MyBatis对象作⽤域以及事务问题

**MyBatis核⼼对象的作⽤域 **

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()) {
 // 你的应⽤逻辑代码 }

事务问题

在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使⽤事务机

制,在transfer⽅法开始执⾏时开启事务,直到两个更新都成功之后,再提交事务,我们尝试将transfer⽅法进

⾏如下修改:

public class AccountServiceImpl implements AccountService {
 private AccountDao accountDao = new AccountDaoImpl();
 @Override
 public void transfer(String fromActno, String toActno, double money) t
hrows MoneyNotEnoughException, AppException {
 // 查询转出账户的余额
 Account fromAct = accountDao.selectByActno(fromActno);
 if (fromAct.getBalance() < money) {
 throw new MoneyNotEnoughException("对不起,您的余额不⾜。");
 }
 try {
 // 程序如果执⾏到这⾥说明余额充⾜
 // 修改账户余额
 Account toAct = accountDao.selectByActno(toActno);
 fromAct.setBalance(fromAct.getBalance() - money);
 toAct.setBalance(toAct.getBalance() + money);
 // 更新数据库(添加事务)
 SqlSession sqlSession = SqlSessionUtil.openSession();
 accountDao.update(fromAct);
 // 模拟异常
 String s = null;
 s.toString();
 accountDao.update(toAct);
 sqlSession.commit();
 sqlSession.close();
 } catch (Exception e) {
 throw new AppException("转账失败,未知原因!");
 }
 }
}

运⾏前注意看数据库表中当前的数据:

image-20221008101730204

执行程序 :

image-20221008101749730

image-20221008101756552

再次查看数据库表中的数据:

image-20221008101806224

傻眼了吧!!!事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service 和dao中使⽤的SqlSession对象不是同⼀个。

怎么办?为了保证service和dao中使⽤的SqlSession对象是同⼀个,可以将SqlSession对象存放到

ThreadLocal当中。修改SqlSessionUtil⼯具类:

package com.manman.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;

/**
 * @author Gaoziman
 * @version 1.0
 * description:
 * @date 2022/9/17 16:44
 */
public class SqlSessionUtil {
    private SqlSessionUtil(){}

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 全局的,服务器级别的,一个服务器当中定义一个即可。
    // 为什么把SqlSession对象放到ThreadLocal当中呢?为了保证一个线程对应一个SqlSession。
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    /**
     * 获取会话对象。
     * @return 会话对象
     */
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            // 将sqlSession对象绑定到当前线程上。
            local.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭SqlSession对象(从当前线程中移除SqlSession对象。)
     * @param sqlSession
     */
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
            // 注意移除SqlSession对象和当前线程的绑定关系。
            // 因为Tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程。
            local.remove();
        }
    }
}

修改dao中的⽅法:AccountDaoImpl中所有⽅法中的提交commit和关闭close代码全部删除。

当前数据库表中的数据:

image-20221008101954262

再次运⾏程序:

image-20221008102007426

image-20221008102018846

查看数据库表:没有问题

image-20221008102032972

再测试转账成功:

image-20221008102049219

image-20221008102101081

image-20221008102108593

如果余额不⾜呢:

image-20221008102123748

image-20221008102134755

账户的余额依然正常:

image-20221008102146548

第六章 MyBatis小技巧

6.1 #{}和${}

#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防⽌sql注⼊,⽐较常 ⽤。

${}:先进⾏sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注⼊现象。只有在需要 进⾏sql语句关键字拼接的情况下才会⽤到。

需求:根据car_type查询汽⻋

模块名:mybatis-005-antic

使用#{}

<select id="selectAllById" resultType="com.manman.pojo.Car">
        select
            id, car_num carNum ,brand,guide_price guidePrice ,produce_time produceTime,car_type carType
        from t_car
        where id =#{id}
    </select>

使用${}

 <select id="selectFuzzy" resultType="com.manman.pojo.Car">
        select
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from t_car
        where
       brand like '%${brand}%' 
    </select>

CarMapper.xml,放在类的根路径下:注意namespace必须和接⼝名⼀致。id必须和接⼝中⽅法名⼀致。

6.2 什么情况下必须使⽤${}

当需要进⾏sql语句关键字拼接的时候。必须使⽤${}

需求:通过向sql语句中注⼊asc或desc关键字,来完成数据的升序或降序排列。

先使⽤#{}尝试:

CarMapper接⼝:

/**
 * 查询所有的Car
 * @param ascOrDesc asc或desc
 * @return
 */
List<Car> selectAll(String ascOrDesc);

CarMapper.xml⽂件:

<select id="selectAll" resultType="com.mybatis.pojo.Car">
 select
 id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
 from
 t_car
 order by carNum #{key}
</select>

测试程序

@Test
public void testSelectAll(){
 CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(C
arMapper.class);
 List<Car> cars = mapper.selectAll("desc");
 cars.forEach(car -> System.out.println(car)); }

运行结果 :

image-20221008104300247

报错的原因是sql语句不合法,因为采⽤这种⽅式传值,最终sql语句会是这样:

select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car order by carNum ‘desc’

desc是⼀个关键字,不能带单引号的,所以在进⾏sql语句关键字拼接的时候,必须使⽤${}

6.3 拼接表名

业务背景:实际开发中,有的表数据量⾮常庞⼤,可能会采⽤分表⽅式进⾏存储,⽐如每天⽣成⼀张表,表的名字与⽇期挂钩,例如:2022年8⽉1⽇⽣成的表:t_user20220108。2000年1⽉1⽇⽣成的

表:t_user20000101。此时前端在进⾏查询的时候会提交⼀个具体的⽇期,⽐如前端提交的⽇期为:2000年1⽉1⽇,那么后端就会根据这个⽇期动态拼接表名为:t_user20000101。有了这个表名之后,将

表名拼接到sql语句当中,返回查询结果。那么⼤家思考⼀下,拼接表名到sql语句当中应该使⽤#{} 还是${} 呢?

使⽤#{}会是这样:select * from ‘t_car’

使⽤${}会是这样:select * from t_car

<select id="selectAllByTableName" resultType="car">
 select
 id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
 from
 ${tableName}
</select>
/**
 * 根据表名查询所有的Car
 * @param tableName
 * @return
 */
List<Car> selectAllByTableName(String tableName);
@Test
public void testSelectAllByTableName(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 List<Car> cars = mapper.selectAllByTableName("t_car");
 cars.forEach(car -> System.out.println(car)); }

执行结果:

image-20221008104616616

6.4 批量删除

业务背景:⼀次删除多条记录

image-20221008104645849

对应的sql语句:

delete from t_user where id = 1 or id = 2 or id = 3;

delete from t_user where id in(1, 2, 3);

假设现在使⽤in的⽅式处理,前端传过来的字符串:1, 2, 3如果使⽤mybatis处理,应该使⽤#{} 还是 ${}

使⽤#{} :delete from t_user where id in(‘1,2,3’) 执⾏错误:1292 - Truncated incorrect DOUBLE value: ‘1,2,3

使⽤${} :delete from t_user where id in(1, 2, 3)

/**
 * 根据id批量删除
 * @param ids
 * @return
 */
int deleteBatch(String ids);
<delete id="deleteBatch">
 delete from t_car where id in(${ids})
</delete>
@Test
public void testDeleteBatch(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 int count = mapper.deleteBatch("1,2,3");
 System.out.println("删除了⼏条记录:" + count);
 SqlSessionUtil.openSession().commit();
}

执行结果 :

image-20221008104933398

6.5 模糊查询

需求:查询奔驰系列的汽⻋。【只要品牌brand中含有奔驰两个字的都查询出来。】

使⽤${}
/**
 * 根据品牌进⾏模糊查询
 * @param likeBrank
 * @return
 */
List<Car> selectLikeByBrand(String likeBrank);
<select id="selectLikeByBrand" resultType="Car">
 select
 id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
 from
 t_car
 where
 brand like '%${brand}%'
</select>
@Test
public void testSelectLikeByBrand(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 List<Car> cars = mapper.selectLikeByBrand("奔驰");
 cars.forEach(car -> System.out.println(car)); }

执行结果 :

image-20221008105119361

使用#{}

第⼀种:concat函数

<select id="selectLikeByBrand" resultType="Car">
 select
 id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
 from
 t_car
 where
 brand like concat('%',#{brand},'%')
</select>

执行结果 :

image-20221008105207058

第⼆种:双引号⽅式

<select id="selectLikeByBrand" resultType="Car">
 select
 id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
 from
 t_car
 where
 brand like "%"#{brand}"%"
</select>

image-20221008105236005

6.6 插⼊数据时获取⾃动⽣成的主键

前提是:主键是⾃动⽣成的。

业务背景:⼀个⽤户有多个⻆⾊。

image-20221008105345403

插⼊⼀条新的记录之后,⾃动⽣成了主键,⽽这个主键需要在其他表中使⽤时。

插⼊⼀个⽤户数据的同时需要给该⽤户分配⻆⾊:需要将⽣成的⽤户的id插⼊到⻆⾊表的user_id字段 上。

第⼀种⽅式:可以先插⼊⽤户数据,再写⼀条查询语句获取id,然后再插⼊user_id字段。【⽐较麻烦】

第⼆种⽅式:mybatis提供了⼀种⽅式更加便捷。

/**
 * 获取⾃动⽣成的主键
 * @param car
 */
void insertUseGeneratedKeys(Car car);
<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="i
d">
 insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) val
ues(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
@Test
public void testInsertUseGeneratedKeys(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cl
ass);
 Car car = new Car();
 car.setCarNum("5262");
 car.setBrand("BYD汉");
 car.setGuidePrice(30.3);
 car.setProduceTime("2020-10-11");
 car.setCarType("新能源");
 mapper.insertUseGeneratedKeys(car);
 SqlSessionUtil.openSession().commit();
 System.out.println(car.getId()); }

第七章 MyBatis查询语句

7.1 返回Car

package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
/**
 * Car SQL映射器 
 * @version 1.0
 * @since 1.0
 */
public interface CarMapper {
 /**
 * 根据id主键查询:结果最多只有⼀条
 * @param id
 * @return
 */
 Car selectById(Long id); }
<?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.powernode.mybatis.mapper.CarMapper">
 <select id="selectById" resultType="Car">
 select id,car_num carNum,brand,guide_price guidePrice,produce_tim
e produceTime,car_type carType from t_car where id = #{id}
 </select>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
public class CarMapperTest {
 @Test
 public void testSelectById(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
 Car car = mapper.selectById(35L);
 System.out.println(car);
 } 
}

执行结果 :

image-20221008105744526

7.2 返回List<Car>

当查询的记录条数是多条的时候,必须使⽤集合接收。如果使⽤单个实体类接收会出现异常。

/**
* 查询所有的Car
* @return
*/
List<Car> selectAll();	
<select id="selectAll" resultType="Car">
 select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAll(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 List<Car> cars = mapper.selectAll();
 cars.forEach(car -> System.out.println(car)); }

执行结果 :

image-20221008110306602

如果返回多条记录,采⽤单个实体类接收会怎样?

image-20221008110351179

7.3 返回Map

当返回的数据,没有合适的实体类对应的话,可以采⽤Map集合接收。字段名做key,字段值做value。 查询如果可以保证只有⼀条数据,则返回⼀个Map集合即可。

image-20221008110441776

/**
 * 通过id查询⼀条记录,返回Map集合
 * @param id
 * @return
 */
Map<String, Object> selectByIdRetMap(Long id);
<select id="selectByIdRetMap" resultType="map">
 select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
</select>

resultMap=“map”,这是因为mybatis内置了很多别名。

@Test
public void testSelectByIdRetMap(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 Map<String,Object> car = mapper.selectByIdRetMap(35L);
 System.out.println(car); }

执行结果 :

image-20221008110542962

当然,如果返回⼀个Map集合,可以将Map集合放到List集合中吗?当然可以

反过来,如果返回的不是⼀条记录,是多条记录的话,只采⽤单个Map集合接收,这样同样会出现之前 的异常:TooManyResultsException

7.4 返回List<Map>

查询结果条数⼤于等于1条数据,则可以返回⼀个存储Map集合的List集合。List<Map>等同于List<Car>

/**
 * 查询所有的Car,返回⼀个List集合。List集合中存储的是Map集合。
 * @return
 */
List<Map<String,Object>> selectAllRetListMap();
<select id="selectAllRetListMap" resultType="map">
 select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAllRetListMap(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
 List<Map<String,Object>> cars = mapper.selectAllRetListMap();
 System.out.println(cars); }

执⾏结果:

[
 {carType=燃油⻋, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=
33, brand=奔驰E300L},
 {carType=电⻋, carNum=102, guidePrice=30.23, produceTime=2018-09-10, id=34
, brand=⽐亚迪汉},
 {carType=燃油⻋, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=
35, brand=奔驰E300L},
 {carType=燃油⻋, carNum=103, guidePrice=33.23, produceTime=2020-10-11, id=
36, brand=奔驰C200},
 ......
]

7.5 resultMap结果映射

  • 查询结果的列名和java对象的属性名对应不上怎么办?

    • 第⼀种⽅式:as 给列起别名
    • 第⼆种⽅式:使⽤resultMap进⾏结果映射
    • 第三种⽅式:是否开启驼峰命名⾃动映射(配置settings)
resultMap结果映射
/**
 * 查询所有Car,使⽤resultMap进⾏结果映射
 * @return
 */
List<Car> selectAllByResultMap();
<!--
 resultMap:
 id:这个结果映射的标识,作为select标签的resultMap属性的值。
 type:结果集要映射的类。可以使⽤别名。
-->
<resultMap id="carResultMap" type="car">
 <!--对象的唯⼀标识,官⽅解释是:为了提⾼mybatis的性能。建议写上。-->
 <id property="id" column="id"/>
 <result property="carNum" column="car_num"/>
 <!--当属性名和数据库列名⼀致时,可以省略。但建议都写上。-->
 <!--javaType⽤来指定属性类型。jdbcType⽤来指定列类型。⼀般可以省略。-->
 <result property="brand" column="brand" />
 <result property="guidePrice" column="guide_price"/>
 <result property="produceTime" column="produce_time"/>
 <result property="carType" column="car_type"/>
</resultMap>
<!--resultMap属性的值必须和resultMap标签中id属性值⼀致。-->
<select id="selectAllByResultMap" resultMap="carResultMap">
 select * from t_car
</select>
@Test
public void testSelectAllByResultMap(){
 CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.
class);
 List<Car> cars = carMapper.selectAllByResultMap();
 System.out.println(cars); }

执⾏结果正常

是否开启驼峰命名⾃动映射

使⽤这种⽅式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。

Java命名规范:⾸字⺟⼩写,后⾯每个单词⾸字⺟⼤写,遵循驼峰命名⽅式。

SQL命名规范:全部⼩写,单词之间采⽤下划线分割。

⽐如以下的对应关系:

image-20221008111042849

如何启⽤该功能,在mybatis-config.xml⽂件中进⾏配置:

<!--放在properties标签后⾯-->
<settings>
 <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
/**
* 查询所有Car,启⽤驼峰命名⾃动映射
* @return
*/
List<Car> selectAllByMapUnderscoreToCamelCase();
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
 select * from t_car
</select>
@Test
public void testSelectAllByMapUnderscoreToCamelCase(){
 CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
 List<Car> cars = carMapper.selectAllByMapUnderscoreToCamelCase();
 System.out.println(cars); }

执⾏结果正常。

7.6 返回总记录条数

需求:查询总记录条数

/**
 * 获取总记录条数
 * @return
 */
Long selectTotal();
<!--long是别名,可参考mybatis开发⼿册。-->
<select id="selectTotal" resultType="long">
 select count(*) from t_car
</select>
@Test
public void testSelectTotal(){
 CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.
class);
 Long total = carMapper.selectTotal();
 System.out.println(total); }

image-20221008111310143

第八章 动态SQL

有的业务场景,也需要SQL语句进⾏动态拼接,例如:

批量删除

image-20221008111342233

delete from t_car where id in(1,2,3,4,5,6,......这⾥的值是动态的,根据⽤户选择的id不同,值是不同的);
  • 多条件查询

image-20221008111451951

select * from t_car where brand like '丰⽥%' and guide_price > 30 and .....;

8.1 if标签

需求:多条件查询。

可能的条件包括:品牌(brand)、指导价格(guide_price)、汽⻋类型(car_type)

package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface CarMapper {
 /**
 * 根据多条件查询Car
 * @param brand
 * @param guidePrice
 * @param carType
 * @return
 */
 List<Car> selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType); }
<?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.powernode.mybatis.mapper.CarMapper">
 <select id="selectByMultiCondition" resultType="car">
 select * from t_car where
 <if test="brand != null and brand != ''">
 brand like #{brand}"%"
 </if>
 <if test="guidePrice != null and guidePrice != ''">
 and guide_price >= #{guidePrice}
 </if>
 <if test="carType != null and carType != ''">
 and car_type = #{carType}
 </if>
 </select>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.util.List;
public class CarMapperTest {
 @Test
 public void testSelectByMultiCondition(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
 List<Car> cars = mapper.selectByMultiCondition("丰⽥", 20.0, "燃油 ⻋");
 System.out.println(cars);
 } 
}

执⾏结果:

image-20221008111816462

如果第⼀个条件为空,剩下两个条件不为空,会是怎样呢?

List<Car> cars = mapper.selectByMultiCondition("", 20.0, "燃油⻋");

执⾏结果:

image-20221008111953840

报错了,SQL语法有问题,where后⾯出现了and。这该怎么解决呢?

● 可以where后⾯添加⼀个恒成⽴的条件。

image-20221008112034507

执⾏结果:

image-20221008112055280

如果三个条件都是空,有影响吗?

List<Car> cars = mapper.selectByMultiCondition("", null, "");

执⾏结果:

image-20221008112145303

三个条件都不为空呢?

List<Car> cars = mapper.selectByMultiCondition("丰⽥", 20.0, "燃油⻋");

执⾏结果:

image-20221008112230405

8.2 where标签

where标签的作⽤:让where⼦句更加动态智能。 所有条件都为空时,where标签保证不会⽣成where⼦句。 ⾃动去除某些条件前⾯多余的and或or。

继续使⽤if标签中的需求。

/**
* 根据多条件查询Car,使⽤where标签
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiConditionWithWhere(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<select id="selectByMultiConditionWithWhere" resultType="car">
 select * from t_car
 <where>
 <if test="brand != null and brand != ''">
 and brand like #{brand}"%"
 </if>
 <if test="guidePrice != null and guidePrice != ''">
 and guide_price >= #{guidePrice}
 </if>
 <if test="carType != null and carType != ''">
 and car_type = #{carType}
 </if>
 </where>
</select>
@Test
public void testSelectByMultiConditionWithWhere(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 List<Car> cars = mapper.selectByMultiConditionWithWhere("丰⽥", 20.0, "燃油⻋");
 System.out.println(cars); }

运⾏结果:

image-20221008112423041

如果所有条件都是空呢?

List<Car> cars = mapper.selectByMultiConditionWithWhere("", null, "");

执行结果 :

image-20221008112502545

它可以⾃动去掉前⾯多余的and,那可以⾃动去掉前⾯多余的or吗?

List<Car> cars = mapper.selectByMultiConditionWithWhere("丰⽥", 20.0, "燃油 ⻋");
<select id="selectByMultiConditionWithWhere" resultType="car">
 select * from t_car
 <where>
 <if test="brand != null and brand != ''">
 or brand like #{brand}"%"
 </if>
 <if test="guidePrice != null and guidePrice != ''">
 and guide_price >= #{guidePrice}
 </if>
 <if test="carType != null and carType != ''">
 and car_type = #{carType}
 </if>
 </where>
</select>

执⾏结果:

image-20221008112541446

8.3 trim标签

  • trim标签的属性:

prefix:在trim标签中的语句前添加内容

suffix:在trim标签中的语句后添加内容

prefixOverrides:前缀覆盖掉(去掉)

suffixOverrides:后缀覆盖掉(去掉)

/**
* 根据多条件查询Car,使⽤trim标签
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiConditionWithTrim(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<select id="selectByMultiConditionWithTrim" resultType="car">
 select * from t_car
 <trim prefix="where" suffixOverrides="and|or">
 <if test="brand != null and brand != ''">
 brand like #{brand}"%" and
 </if>
 <if test="guidePrice != null and guidePrice != ''">
 guide_price >= #{guidePrice} and
 </if>
 <if test="carType != null and carType != ''">
 car_type = #{carType}
 </if>
 </trim>
</select>
@Test
public void testSelectByMultiConditionWithTrim(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
 List<Car> cars = mapper.selectByMultiConditionWithTrim("丰⽥", 20.0, "") ;
 System.out.println(cars); }

image-20221008113527108

如果所有条件为空,where会被加上吗?

List<Car> cars = mapper.selectByMultiConditionWithTrim("", null, "");

执⾏结果:

image-20221008113554418

8.4 set标签

主要使⽤在update语句当中,⽤来⽣成set关键字,同时去掉最后多余的“,”

⽐如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新

/**
* 更新信息,使⽤set标签
* @param car
* @return
*/
int updateWithSet(Car car);
<update id="updateWithSet">
 update t_car
 <set>
 <if test="carNum != null and carNum != ''">car_num = #{carNum},</if>
 <if test="brand != null and brand != ''">brand = #{brand},</if>
 <if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if>
 <if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
 <if test="carType != null and carType != ''">car_type = #{carType},</i f>
 </set>
 where id = #{id}
</update>
@Test
public void testUpdateWithSet(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 Car car = new Car(38L,"1001","丰⽥霸道2",10.0,"",null);
 int count = mapper.updateWithSet(car);
 System.out.println(count);
 SqlSessionUtil.openSession().commit(); }

执⾏结果:

image-20221008113756964

8.5 choose when otherwise

这三个标签是在⼀起使⽤的:

<choose>
 <when></when>
 <when></when>
 <when></when>
 <otherwise></otherwise>
</choose>

等同于:

if(){
 
}else if(){
 
}else if(){
 
}else if(){
 
}else{ }

只有⼀个分⽀会被选择!!!!

需求:先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据⽣产⽇期查询。

/**
* 使⽤choose when otherwise标签查询
* @param brand
* @param guidePrice
* @param produceTime
* @return
*/
List<Car> selectWithChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("produceTime") String produceTime);
<select id="selectWithChoose" resultType="car">
 select * from t_car
 <where>
 <choose>
 <when test="brand != null and brand != ''">
 brand like #{brand}"%"
 </when>
 <when test="guidePrice != null and guidePrice != ''">
 guide_price >= #{guidePrice}
 </when>
 <otherwise>
 produce_time >= #{produceTime}
 </otherwise>
 </choose>
 </where>
</select>
@Test
public void testSelectWithChoose(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 //List<Car> cars = mapper.selectWithChoose("丰⽥霸道", 20.0, "2000-10-1
0");
 //List<Car> cars = mapper.selectWithChoose("", 20.0, "2000-10-10");
 //List<Car> cars = mapper.selectWithChoose("", null, "2000-10-10");
 List<Car> cars = mapper.selectWithChoose("", null, "");
 System.out.println(cars); }

image-20221008114003604

8.6 foreach标签

循环数组或集合,动态⽣成sql,⽐如这样的SQL:

delete from t_car where id in(1,2,3);
delete from t_car where id = 1 or id = 2 or id = 3; 12
insert into t_car values
 (null,'1001','凯美瑞',35.0,'2010-10-11','燃油⻋'),
 (null,'1002','⽐亚迪唐',31.0,'2020-11-11','新能源'),
 (null,'1003','⽐亚迪宋',32.0,'2020-10-11','新能源')
批量删除
  • ⽤in来删除
/**
* 通过foreach完成批量删除
* @param ids
* @return
*/
int deleteBatchByForeach(@Param("ids") Long[] ids);
<!--
collection:集合或数组
item:集合或数组中的元素
separator:分隔符
open:foreach标签中所有内容的开始
close:foreach标签中所有内容的结束
-->
<delete id="deleteBatchByForeach">
 delete from t_car where id in
 <foreach collection="ids" item="id" separator="," open="(" close=")">
 #{id}
 </foreach>
</delete>
@Test
public void testDeleteBatchByForeach(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 int count = mapper.deleteBatchByForeach(new Long[]{40L, 41L, 42L});
 System.out.println("删除了⼏条记录:" + count);
 SqlSessionUtil.openSession().commit(); }

执⾏结果:

image-20221008114449025

  • ⽤or来删除
/**
* 通过foreach完成批量删除
* @param ids
* @return
*/
int deleteBatchByForeach2(@Param("ids") Long[] ids);
<delete id="deleteBatchByForeach2">
 delete from t_car where
 <foreach collection="ids" item="id" separator="or">
 id = #{id}
 </foreach>
</delete>
@Test
public void testDeleteBatchByForeach2(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
 int count = mapper.deleteBatchByForeach2(new Long[]{40L, 41L, 42L});
 System.out.println("删除了⼏条记录:" + count);
 SqlSessionUtil.openSession().commit(); }

执行结果 :

image-20221008114551486

批量添加
/**
* 批量添加,使⽤foreach标签
* @param cars
* @return
*/
int insertBatchByForeach(@Param("cars") List<Car> cars);
<insert id="insertBatchByForeach">
 insert into t_car values
 <foreach collection="cars" item="car" separator=",">
 (null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#
{car.carType})
 </foreach>
</insert>
@Test
public void testInsertBatchByForeach(){
 CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cl
ass);
 Car car1 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油 ⻋");
 Car car2 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油 ⻋");
 Car car3 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油 ⻋");
 List<Car> cars = Arrays.asList(car1, car2, car3);
 int count = mapper.insertBatchByForeach(cars);
 System.out.println("插⼊了⼏条记录" + count);
 SqlSessionUtil.openSession().commit(); }

执⾏结果:

image-20221008114715205

8.7 sql标签与include标签

sql标签⽤来声明sql⽚段

include标签⽤来将声明的sql⽚段包含到某个sql语句当中

作⽤:代码复⽤。易维护

<sql id="carCols">id,car_num carNum,brand,guide_price guidePrice,produce_t
ime produceTime,car_type carType</sql> <select id="selectAllRetMap" resultType="map">
 select <include refid="carCols"/> from t_car
</select> <select id="selectAllRetListMap" resultType="map">
 select <include refid="carCols"/> carType from t_car
</select> <select id="selectByIdRetMap" resultType="map">
 select <include refid="carCols"/> from t_car where id = #{id}
</select>

第九章 MyBatis的⾼级映射及延迟加载

模块名:mybatis-009-advanced-mapping

打包⽅式:jar

依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖

配置⽂件:mybatis-config.xml、logback.xml、jdbc.properties

拷⻉⼯具类:SqlSessionUtil

准备数据库表:⼀个班级对应多个学⽣。班级表:t_clazz。学⽣表:t_student

image-20221008170404883

image-20221008170431627

创建pojo:Student、Clazz

package com.powernode.mybatis.pojo;
/**
 * 学⽣类
 * @version 1.0
 * @since 1.0
 */
public class Student {
 private Integer sid;
 private String sname;
 //......
}
package com.powernode.mybatis.pojo;
/**
 * 班级类
 * @version 1.0
 * @since 1.0
 */
public class Clazz {
 private Integer cid;
 private String cname;
 //......
}

创建mapper接⼝:StudentMapper、ClazzMapper

创建mapper映射⽂件:StudentMapper.xml、ClazzMapper.xml

9.1 多对一

多种⽅式,常⻅的包括三种:

● 第⼀种⽅式:⼀条SQL语句,级联属性映射。

● 第⼆种⽅式:⼀条SQL语句,association。

● 第三种⽅式:两条SQL语句,分步查询。(这种⽅式常⽤:优点⼀是可复⽤。优点⼆是⽀持懒加载。)

第一种方式: 级联属性映射

pojo类Student中添加⼀个属性:Clazz clazz; 表示学⽣关联的班级对象。

package com.powernode.mybatis.pojo;
/**
 * 学⽣类
 * @version 1.0
 * @since 1.0
 */
public class Student {
 private Integer sid;
 private String sname;
 //......
}
<?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.powernode.mybatis.mapper.StudentMapper">
 <resultMap id="studentResultMap" type="Student">
 <id property="sid" column="sid"/>
 <result property="sname" column="sname"/>
 <result property="clazz.cid" column="cid"/>
 <result property="clazz.cname" column="cname"/>
 </resultMap>
 <select id="selectBySid" resultMap="studentResultMap">
 select s.*, c.* from t_student s join t_clazz c on s.cid = c.cid where sid = #{sid}
 </select>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.StudentMapper;
import com.powernode.mybatis.pojo.Student;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
public class StudentMapperTest {
 @Test
 public void testSelectBySid(){
 StudentMapper mapper = SqlSessionUtil.openSession().getMapper(Stud
entMapper.class);
 Student student = mapper.selectBySid(1);
 System.out.println(student);
 } 
}

执行结果 :

image-20221008171014889

第⼆种⽅式:association
<resultMap id="studentResultMap" type="Student">
 <id property="sid" column="sid"/>
 <result property="sname" column="sname"/>
 <association property="clazz" javaType="Clazz">
 <id property="cid" column="cid"/>
 <result property="cname" column="cname"/>
 </association>
</resultMap>
第三种⽅式:分步查询

其他位置不需要修改,只需要修改以及添加以下三处:

第⼀处:association中select位置填写sqlId。sqlId=namespace+id。其中column属性作为这条⼦sql语句的条件。

<resultMap id="studentResultMap" type="Student">
 <id property="sid" column="sid"/>
 <result property="sname" column="sname"/>
 <association property="clazz"
 select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
 column="cid"/>
</resultMap> 
<select id="selectBySid" resultMap="studentResultMap">
 select s.* from t_student s where sid = #{sid}
</select>

第⼆处:在ClazzMapper接⼝中添加⽅法

package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Clazz;
/**
 * Clazz映射器接⼝
 * @version 1.0
 * @since 1.0
 */
public interface ClazzMapper {
 /**
 * 根据cid获取Clazz信息
 * @param cid
 * @return
 */
 Clazz selectByCid(Integer cid); }

第三处:在ClazzMapper.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">
<mapper namespace="com.powernode.mybatis.mapper.ClazzMapper">
 <select id="selectByCid" resultType="Clazz">
 select * from t_clazz where cid = #{cid}
 </select>
</mapper>

执⾏结果,可以很明显看到先后有两条sql语句执⾏:

image-20221008172213485

分步优点:

第⼀个优点:代码复⽤性增强。

第⼆个优点:⽀持延迟加载。【暂时访问不到的数据可以先不查询。提⾼程序的执⾏效率。】

9.2 多对⼀延迟加载

在mybatis中如何开启全局的延迟加载呢?需要setting配置,如下:

image-20221008172318065

<settings>
 <setting name="lazyLoadingEnabled" value="true"/>
</settings>

把fetchType="lazy"去掉。

执⾏以下程序:

public class StudentMapperTest {
 @Test
 public void testSelectBySid(){
 StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
 Student student = mapper.selectBySid(1);
 //System.out.println(student);
 // 只获取学⽣姓名
 String sname = student.getSname();
 System.out.println("学⽣姓名:" + sname);
 // 到这⾥之后,想获取班级名字了
 String cname = student.getClazz().getCname();
 System.out.println("学⽣的班级名称:" + cname);
 } 
}

image-20221008172426891

通过以上的测试可以看出,我们已经开启了全局延迟加载策略。

开启全局延迟加载之后,所有的sql都会⽀持延迟加载,如果某个sql你不希望它⽀持延迟加载怎么办呢?

将fetchType设置为eager:

<resultMap id="studentResultMap" type="Student">
 <id property="sid" column="sid"/>
 <result property="sname" column="sname"/>
 <association property="clazz"
 select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
 column="cid"
 fetchType="eager"/>
</resultMap>

image-20221008172501714

这样的话,针对某个特定的sql,你就关闭了延迟加载机制。

后期我们要不要开启延迟加载机制,主要看实际的业务需求是怎样的。

9.3 ⼀对多

⼀对多的实现,通常是在⼀的⼀⽅中有List集合属性。

在Clazz类中添加List<Student> stus; 属性。

public class Clazz {
 private Integer cid;
 private String cname;
 private List<Student> stus;
 // set get⽅法
 // 构造⽅法
 // toString⽅法 }

⼀对多的实现通常包括两种实现⽅式:

  • 第⼀种⽅式:collection
  • 第⼆种⽅式:分步查询
第⼀种⽅式:collection
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Clazz;
/**
 * Clazz映射器接⼝
 * @version 1.0
 * @since 1.0
 */
public interface ClazzMapper {
 /**
 * 根据cid获取Clazz信息
 * @param cid
 * @return
 */
 Clazz selectByCid(Integer cid);
 /**
 * 根据班级编号查询班级信息。同时班级中所有的学⽣信息也要查询。
 * @param cid
 * @return
 */
 Clazz selectClazzAndStusByCid(Integer cid); }
<resultMap id="clazzResultMap" type="Clazz">
 <id property="cid" column="cid"/>
 <result property="cname" column="cname"/>
 <collection property="stus" ofType="Student">
 <id property="sid" column="sid"/>
 <result property="sname" column="sname"/>
 </collection>
</resultMap> <select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
 select * from t_clazz c join t_student s on c.cid = s.cid where c.cid =
#{cid}
</select>

注意是ofType,表示“集合中的类型”

package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.ClazzMapper;
import com.powernode.mybatis.pojo.Clazz;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
public class ClazzMapperTest {
 @Test
 public void testSelectClazzAndStusByCid() {
 ClazzMapper mapper = SqlSessionUtil.openSession().getMapper(ClazzMapper.class);
 Clazz clazz = mapper.selectClazzAndStusByCid(1001);
 System.out.println(clazz);
 } 
}

执⾏结果:

image-20221008185003193

第⼆种⽅式:分步查询(比较常用)

修改以下三个位置即可:

<resultMap id="clazzResultMap" type="Clazz">
 <id property="cid" column="cid"/>
 <result property="cname" column="cname"/>
 <!--主要看这⾥-->
 <collection property="stus"
 select="com.powernode.mybatis.mapper.StudentMapper.selectByC
id"
 column="cid"/>
</resultMap>
<!--sql语句也变化了-->
<select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
 select * from t_clazz c where c.cid = #{cid}
</select>
/**
* 根据班级编号获取所有的学⽣。
* @param cid
* @return
*/
List<Student> selectByCid(Integer cid);
<select id="selectByCid" resultType="Student">
 select * from t_student where cid = #{cid}
</select>

执⾏结果:

image-20221008185103148

9.4 ⼀对多延迟加载

⼀对多延迟加载机制和多对⼀是⼀样的。同样是通过两种⽅式:

  • 第⼀种:fetchType=“lazy”
  • 第⼆种:修改全局的配置setting,lazyLoadingEnabled=true,如果开启全局延迟加载,想让某个 sql不使⽤延迟加载:fetchType=“eager”

第十章 MyBatis的缓存

缓存:cache

缓存的作⽤:通过减少IO的⽅式,来提⾼程序的执⾏效率。

mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下⼀次还是这条select语句的话,直 接从缓存中取,不再查数据库。⼀⽅⾯是减少了IO。另⼀⽅⾯不再执⾏繁琐的查找算法。效率⼤⼤提升。

mybatis缓存包括:

⼀级缓存:将查询到的数据存储到SqlSession中。

⼆级缓存:将查询到的数据存储到SqlSessionFactory中。

或者集成其它第三⽅的缓存:⽐如EhCache Java语⾔开发的】、Memcache【C语⾔开发的】 等

缓存只针对于DQL语句,也就是说缓存机制只对应select语句。

10.1 ⼀级缓存

⼀级缓存默认是开启的。不需要做任何配置。

原理:只要使⽤同⼀个SqlSession对象执⾏同⼀条SQL语句,就会⾛缓存

package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
public interface CarMapper {
 /**
 * 根据id获取Car信息。
 * @param id
 * @return
 */
 Car selectById(Long id); }
<?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.powernode.mybatis.mapper.CarMapper">
 <select id="selectById" resultType="Car">
 select * from t_car where id = #{id}
 </select>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
public class CarMapperTest {
 @Test
 public void testSelectById() throws Exception{
 // 注意:不能使⽤我们封装的SqlSessionUtil⼯具类。
 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
 SqlSessionFactory sqlSessionFactory = builder.build(Resources.getR
esourceAsStream("mybatis-config.xml"));
 SqlSession sqlSession1 = sqlSessionFactory.openSession();
 CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
 Car car1 = mapper1.selectById(83L);
 System.out.println(car1);
 CarMapper mapper2 = sqlSession1.getMapper(CarMapper.class);
 Car car2 = mapper2.selectById(83L);
 System.out.println(car2);
 SqlSession sqlSession2 = sqlSessionFactory.openSession();
 CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
 Car car3 = mapper3.selectById(83L);
 System.out.println(car3);
 CarMapper mapper4 = sqlSession2.getMapper(CarMapper.class);
 Car car4 = mapper4.selectById(83L);
 System.out.println(car4);
 } 
}

执行结果 :

image-20221008185626009

什么情况下不⾛缓存?

第⼀种:不同的SqlSession对象。

第⼆种:查询条件变化了。

⼀级缓存失效情况包括两种:

  • 第⼀种:第⼀次查询和第⼆次查询之间,⼿动清空了⼀级缓存。

清空一级缓存

sqlSession.clearCache(); 
  • 第⼆种:第⼀次查询和第⼆次查询之间,执⾏了增删改操作。【这个增删改和哪张表没有关系,只要 有insert delete update操作,⼀级缓存就失效。】

10.4 ⼆级缓存

⼆级缓存的范围是SqlSessionFactory。 使⽤⼆级缓存需要具备以下⼏个条件:

  1. 全局性地开启或关闭所有映射器配置⽂件中已配置的任何缓存。默认就是true,⽆需设置。
  2. 在需要使⽤⼆级缓存的SqlMapper.xml⽂件中添加配置:
  3. 使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝
  4. SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤。

测试⼆级缓存:

<cache/>
public class Car implements Serializable {
//......
}
@Test
public void testSelectById2() throws Exception{
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
 SqlSession sqlSession1 = sqlSessionFactory.openSession();
 CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
 Car car1 = mapper1.selectById(83L);
 System.out.println(car1);
 // 关键⼀步
 sqlSession1.close();
 SqlSession sqlSession2 = sqlSessionFactory.openSession();
 CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
 Car car2 = mapper2.selectById(83L);
 System.out.println(car2); }

image-20221008190139922

⼆级缓存的失效:只要两次查询之间出现了增删改操作。⼆级缓存就会失效。【⼀级缓存也会失效】

⼆级缓存的相关配置

image-20221008190213512

  1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采⽤LRU策略。

a. LRU:Least Recently Used。最近最少使⽤。优先淘汰在间隔时间内使⽤频率最低的对象。(其

实还有⼀种淘汰算法LFU,最不常⽤。)

b. FIFO:First In First Out。⼀种先进先出的数据缓存器。先进⼊⼆级缓存的对象最先被淘汰。

c. SOFT:软引⽤。淘汰软引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。

d. WEAK:弱引⽤。淘汰弱引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。

  1. flushInterval:

a. ⼆级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存⾜够⼤,⼀

直会向⼆级缓存中缓存数据。除⾮执⾏了增删改。

  1. readOnly:a. true:多条相同的sql语句执⾏之后返回的对象是共享的同⼀个。性能好。但是多线程并发可能

    会存在安全问题。

    b. false:多条相同的sql语句执⾏之后返回的对象是副本,调⽤了clone⽅法。性能⼀般。但安

    全。

  2. size:

    a. 设置⼆级缓存中最多可存储的java对象数量。默认值1024。

第十一章 MyBatis的逆向⼯程

所谓的逆向⼯程是:根据数据库表逆向⽣成Java的pojo类,SqlMapper.xml⽂件,以及Mapper接⼝类 等。要完成这个⼯作,需要借助别⼈写好的逆向⼯程插件。

思考:使⽤这个插件的话,需要给这个插件配置哪些信息?

● pojo类名、包名以及⽣成位置。

● SqlMapper.xml⽂件名以及⽣成位置。

● Mapper接⼝名以及⽣成位置。

● 连接数据库的信息。

● 指定哪些表参与逆向⼯程。

● …

11.1 逆向⼯程配置与⽣成

第⼀步:基础环境准备

新建模块:mybatis-011-generator

打包⽅式:jar

第⼆步:在pom中添加逆向⼯程插件
<!--定制构建过程-->
<build>
 <!--可配置多个插件-->
 <plugins>
 <!--其中的⼀个插件:mybatis逆向⼯程插件-->
 <plugin>
 <!--插件的GAV坐标-->
 <groupId>org.mybatis.generator</groupId>
 <artifactId>mybatis-generator-maven-plugin</artifactId>
 <version>1.4.1</version>
 <!--允许覆盖-->
 <configuration>
 <overwrite>true</overwrite>
 </configuration>
 <!--插件的依赖-->
 <dependencies>
 <!--mysql驱动依赖-->
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.30</version>
 </dependency>
 </dependencies>
 </plugin>
 </plugins>
</build>
第三步:配置generatorConfig.xml

该⽂件名必须叫做:generatorConfig.xml

该⽂件必须放在类的根路径下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
 PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//E
N"
 "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
 <!--
 targetRuntime有两个值:
 MyBatis3Simple:⽣成的是基础版,只有基本的增删改查。
 MyBatis3:⽣成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
 -->
 <context id="DB2Tables" targetRuntime="MyBatis3">
 <!--防⽌⽣成重复代码-->
 <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersP
lugin"/>
 
 <commentGenerator>
 <!--是否去掉⽣成⽇期-->
 <property name="suppressDate" value="true"/>
 <!--是否去除注释-->
 <property name="suppressAllComments" value="true"/>
 </commentGenerator>
 <!--连接数据库信息-->
 <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
 connectionURL="jdbc:mysql://localhost:3306/powerno
de"
 userId="root"
 password="root">
 </jdbcConnection>
 <!-- ⽣成pojo包名和位置 -->
 <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" tar
getProject="src/main/java">
 <!--是否开启⼦包-->
 <property name="enableSubPackages" value="true"/>
 <!--是否去除字段名的前后空⽩-->
 <property name="trimStrings" value="true"/>
 </javaModelGenerator>
 <!-- ⽣成SQL映射⽂件的包名和位置 -->
 <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targ
etProject="src/main/resources">
 <!--是否开启⼦包-->

 <property name="enableSubPackages" value="true"/>
 </sqlMapGenerator>
 <!-- ⽣成Mapper接⼝的包名和位置 -->
 <javaClientGenerator
 type="xmlMapper"
 targetPackage="com.powernode.mybatis.mapper"
 targetProject="src/main/java">
 <property name="enableSubPackages" value="true"/>
 </javaClientGenerator>
 <!-- 表名和对应的实体类名-->
 <table tableName="t_car" domainObjectName="Car"/>
 </context>
</generatorConfiguration>
第四步:运⾏插件

image-20221008190626618

第十二章 MyBatis使⽤PageHelper

12.1 limit分⻚

mysql的limit后⾯两个数字:

第⼀个数字:startIndex(起始下标。下标从0开始。)

第⼆个数字:pageSize(每⻚显示的记录条数)

假设已知⻚码pageNum,还有每⻚显示的记录条数pageSize,第⼀个数字可以动态的获取吗?

startIndex = (pageNum - 1) * pageSize

所以,标准通⽤的mysql分⻚SQL:

select
 *
from
 tableName ......
limit
 (pageNum - 1) * pageSize, pageSize

在MyBatis中使用

package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface CarMapper {
 
 /**
 * 通过分⻚的⽅式获取Car列表
 * @param startIndex ⻚码
 * @param pageSize 每⻚显示记录条数
 * @return
 */
 List<Car> selectAllByPage(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
}
<?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.powernode.mybatis.mapper.CarMapper">
 <select id="selectAllByPage" resultType="Car">
 select * from t_car limit #{startIndex},#{pageSize}
 </select>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.util.List;
public class PageTest {
 @Test
 public void testPage()throws Exception{
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
 SqlSession sqlSession = sqlSessionFactory.openSession();
 CarMapper mapper = sqlSession.getMapper(CarMapper.class);
 // ⻚码
 Integer pageNum = 2;
 // 每⻚显示记录条数
 Integer pageSize = 3;
 // 起始下标
 Integer startIndex = (pageNum - 1) * pageSize;
 List<Car> cars = mapper.selectAllByPage(startIndex, pageSize);
 cars.forEach(car -> System.out.println(car));
 sqlSession.commit();
 sqlSession.close();
 } }

执⾏结果:

image-20221008190927054

12.2 PageHelper插件

使⽤PageHelper插件进⾏分⻚,更加的便捷。

第⼀步:引⼊依赖
<dependency>
 <groupId>com.github.pagehelper</groupId>
 <artifactId>pagehelper</artifactId>
 <version>5.3.1</version>
</dependency>
第⼆步:在mybatis-config.xml⽂件中配置插件

typeAliases标签下⾯进⾏配置:

<plugins>
 <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
第三步:编写Java代码
List<Car> selectAll();
<select id="selectAll" resultType="Car">
 select * from t_car
</select>

关键点:

  • 在查询语句之前开启分⻚功能。
  • 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在⻚⾯上展示。)
@Test
public void testPageHelper() throws Exception{
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().b
uild(Resources.getResourceAsStream("mybatis-config.xml"));
 SqlSession sqlSession = sqlSessionFactory.openSession();
 CarMapper mapper = sqlSession.getMapper(CarMapper.class);
 // 开启分⻚
 PageHelper.startPage(2, 2);
 // 执⾏查询语句
 List<Car> cars = mapper.selectAll();
 // 获取分⻚信息对象
 PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
 System.out.println(pageInfo); }

执⾏结果:

PageInfo{pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3,

list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=6, pages=3,

reasonable=false, pageSizeZero=false}[Car{id=86, carNum=‘1234’, brand=‘丰⽥霸道’,

guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油⻋’}, Car{id=87, carNum=‘1234’,

brand=‘丰⽥霸道’, guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油⻋’}], prePage=1,

nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,

navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}

对执⾏结果进⾏格式化:

PageInfo{
 pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3,
 list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=
6, pages=3, reasonable=false, pageSizeZero=false}
 [Car{id=86, carNum='1234', brand='丰⽥霸道', guidePrice=50.5, produceTime
='2020-10-11', carType='燃油⻋'},
 Car{id=87, carNum='1234', brand='丰⽥霸道', guidePrice=50.5, produceTime
='2020-10-11', carType='燃油⻋'}],
 prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPa
ge=true, hasNextPage=true,
 navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNum
s=[1, 2, 3]
}