Mybatis

自己学习Mybatis做的记录

目录

一、MyBatis概述

[1.1 框架](#1.1 框架)

[1.2 三层架构](#1.2 三层架构)

[1.3 JDBC的不足](#1.3 JDBC的不足)

[1.4 了解MyBatis](#1.4 了解MyBatis)

二、MyBatis入门

[2.0 MyBatis架构](#2.0 MyBatis架构)

[2.0.1 整体架构](#2.0.1 整体架构)

[2.0.2 原理分析](#2.0.2 原理分析)

[2.1 版本](#2.1 版本)

[2.2 MyBatis下载](#2.2 MyBatis下载)

[2.3 MyBatis入门程序开发步骤](#2.3 MyBatis入门程序开发步骤)

写代码前准备

编写mybatis核心配置文件

编写SQL映射文件

加载映射文件

编写Mybatis程序

[2.3.1 第一个MyBatis程序的细节](#2.3.1 第一个MyBatis程序的细节)

[2.3.2 MyBatis的事务管理机制](#2.3.2 MyBatis的事务管理机制)

[2.5 MyBatis第一个比较完整的代码写法](#2.5 MyBatis第一个比较完整的代码写法)

[2.7 引入日志框架logback](#2.7 引入日志框架logback)

[2.8 MyBatis工具类SqlSessionUtil的封装](#2.8 MyBatis工具类SqlSessionUtil的封装)

[2.9 映射文件中Mapper标签的namespace属性](#2.9 映射文件中Mapper标签的namespace属性)

四、MyBatis的核心配置文件详解

[4.1 environment](#4.1 environment)

[4.2 transactionManager](#4.2 transactionManager)

[4.3 dataSource](#4.3 dataSource)

[4.4 .properties属性文件](#4.4 .properties属性文件)

[4.5 mappers标签](#4.5 mappers标签)

三、使用MyBatis完成CRUD

[3.1 insert(Create)](#3.1 insert(Create))

[3.2 delete(Delete)](#3.2 delete(Delete))

[3.3 update(Update)](#3.3 update(Update))

[3.4 select(Retrieve)](#3.4 select(Retrieve))

查询一条记录

查询多条数据

[3.5 总结](#3.5 总结)

parameterType和resultType

通用总结

六、在WEB中应用MyBatis(使用MVC架构模式)

七、javassist

八、MyBatis中的接口代理机制

[8.1 Mybatis接口代理机制的理解](#8.1 Mybatis接口代理机制的理解)

[8.2 接口代理机制的开发规范](#8.2 接口代理机制的开发规范)

[8.3 接口代理机制实例(005-crud2)](#8.3 接口代理机制实例(005-crud2))

九、总结Mybatis中DAO(Mapper)的开发方式

十、Mybatis核心配置文件的剩余标签

[十一、#{}和{}的区别](#{}和{}的区别)

使用#{}

[使用{}](#使用{})

[什么情况下必须使用{}](#什么情况下必须使用{})

向SQL语句中拼接表名

批量删除

模糊查询

十二、输入参数映射和输出结果映射专题

十三、动态SQL拼接

十四、Mybatis关联查询

十五、Mybatis的延迟加载

十六、Mybatis的缓存

十七、Mybatis使用PageHelper分页插件

十八、Mybatis注解式开发


在前面我们学习MySQL数据库时,都是利用图形化客户端工具(如:idea、navicat),来操作数据库,但我们做为后端程序开发人员,通常会使用Java程序来完成对数据库的操作。Java程序操作数据库,现在主流的方式是:Mybatis。

MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发。

一、MyBatis概述

1.1 框架

  • 在文献中看到的framework被翻译为框架

  • Java常用框架:

    • SSM三大框架:Spring + SpringMVC + MyBatis

    • SpringBoot

    • SpringCloud

    • 等。。

  • 框架其实就是对通用代码的封装,提前写好的一堆接口和类, 我们可以在做项目的时候直接引入框架,基于这些现有的接口和类进行开发,可以大大提高开发效率。

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

  • SSM三大框架的学习顺序:

    • 方式一:MyBatis、Spring、SpringMVC(建议)

1.2 三层架构

  • 表现层(UI):直接跟前端打交互(一是接收前端ajax请求,二是返回json数据给前端)

  • 业务逻辑层(BLL):一是处理 表现层转发过来的前端请求 (也就是具体业务),二是将从持久层获 取的数据 返回到表现层

  • 数据访问层(DAL):操作数据库完成数据库表的CRUD,并将获得的数据返回到业务逻辑层 层。

  • Java持久层的框架:

    • MyBatis(其实就是对JDBC进行了封装)

    • Hibernate(实现了JPA规范)

    • jOOQ

    • Guzz

    • Spring Data(实现了JPA规范)

    • ActiveJDBC

    • ......

1.3 JDBC的不足

  • 示例代码1:
java 复制代码
// ......
// sql语句写死在java程序中,将来可能有需求将会修改这些java程序,违背OCP原则
String sql = "insert into t_user(id,idCard,username,password,birth,gender,
email,city,street,zipcode,phone,grade) values(?,?,?,?,?,?,?,?,?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
// JDBC代码繁琐的赋值:思考一下,这种有规律的代码能不能通过反射机制来做自动化。
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();
// ......

示例代码2:

java 复制代码
// ......
// sql语句写死在java程序中
String sql = "select id,idCard,username,password,birth,gender,email,city,s
treet,zipcode,phone,grade from t_user";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
List<User> userList = new ArrayList<>();
// 思考以下循环中的所有代码是否可以使用反射进行自动化封装。
while(rs.next()){
// 获取数据
String id = rs.getString("id");
String idCard = rs.getString("idCard");
String username = rs.getString("username");
String password = rs.getString("password");
String birth = rs.getString("birth");
String gender = rs.getString("gender");
String email = rs.getString("email");
String city = rs.getString("city");
String street = rs.getString("street");
String zipcode = rs.getString("zipcode");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
// 创建对象
User user = new User();
// 给对象属性赋值
user.setId(id);
user.setIdCard(idCard);
user.setUsername(username);
user.setPassword(password);
user.setBirth(birth);
user.setGender(gender);
user.setEmail(email);
user.setCity(city);
user.setStreet(street);
user.setZipcode(zipcode);
user.setPhone(phone);
user.setGrade(grade);
// 添加到集合
userList.add(user);
}
// ......
  • JDBC不足:

    • SQL语句写死在Java程序中,不灵活,改SQL的话就要改Java代码,违背开闭原则OCP。

    • 给?传值繁琐。

    • 将查询结果集封装成Java对象是繁琐的。

1.4 了解MyBatis

  • MyBatis本质上是对JDBC的封装,使开发者只需要关注 SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等JDBC繁杂的过程代码,通过MyBatis完成数据库表的CRUD。

  • MyBatis通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 Java 对象和statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由MyBatis框架执行sql并将结果映射成Java对象并返回。(在Mybati中的statement是要执行的SQL)

  • 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

  • ORM:对象关系映射(其实是一种思想)

    • O(Object):Java对象

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

    • M(Mapping):Java对象映射成(转换成) 数据库表中的一行记录**,或是将** 数据库表中一行记录映射成Java虚拟机中的一个Java对象。**

    • 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入门

2.0 MyBatis架构

2.0.1 整体架构

1、MyBatis配置:

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

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

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

3、由会话工厂创建SqlSession即会话,SqlSession对象中有很多操作数据库CRUD的方法,SqlSession是对Connection对象的封装。

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

5、MappedStatement也是MyBatis一个底层封装对象,Mybatis将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。在mapper.xml文件中一个标签+sql语句就是一个MappedStatement对象,sql语句用到的参数将通过Executor执行器传过来,sql的id即是MappedStatement的id。

6、MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、字符串类型、实体类类型,Executor在MappedStatement执行sql前将输入的Java对象映射至sql中,输入参数映射就是 JDBC编程中对PreparedStatement设置参数。

7、MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、字符串类型、实体类类型,Executor在MappedStatement执行sql后将输出结果映射至Java对象中,输出结果映射过程 相当于JDBC编程中对结果的解析处理过程。

2.0.2 原理分析

  1. 加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加 载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配 置),存储在内存中。

  2. SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、 JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根 据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。

  3. SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。

  4. 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者 基本数据类型,并将最终结果返回。

2.1 版本

  • 软件版本:

    • IntelliJ IDEA:2022.1.4

    • Navicat for MySQL:16.0.14

    • MySQL数据库:8.0.30

  • 组件版本:

    • MySQL驱动:8.0.30

    • MyBatis:3.5.10

    • JDK:Java17

    • JUnit:4.13.2

    • Logback:1.2.11

2.2 MyBatis下载

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

  • **通过以上解压可以看到,框架一般都是以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工具建表

  • 使用navicat for mysql工具向t_car表中插入两条数据,如下:
  • 创建Project:建议创建Empty Project,设置Java版本以及编译版本等。
  • 在IDEA中集成maven
  • 创建Module:创建普通的Maven Java模块
  • 步骤1:在pom.xml文件中设置此项目的打包方式为:jar(不需要war,因为mybatis封装的是jdbc。)
XML 复制代码
pom.xml

<!--    当前项目的坐标-->
    <groupId>org.example</groupId>
    <artifactId>mybatis-001</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
  • 步骤2:在pom.xml文件中引入需要的依赖jar包(mybatis依赖 + mysql驱动依赖)
XML 复制代码
pom.xml

<!--    引入mybatis的依赖-->
    <dependencies>

        <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>
        
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
      </dependency>

    </dependencies>

编写mybatis核心配置文件

  • 步骤3:在resources根目录下 创建mybatis的核心配置文件mybatis-config.xml,这个文件主要配置连接数据库的信息、加载SQL映射文件等(可以参考mybatis手册拷贝进来)

    • 注意1:其文件名可以自定义,但建议mybatis-config.xml

    • 注意2:mybatis核心配置文件存放的位置也是随意的。这里选择放在resources目录下,相当于放到了类的根路径下。(resources目录相当于类的根路径)

XML 复制代码
mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!-- configuration 文件的根节点 -->
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 四个property标签:配置连接数据库的信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/bjpowernode"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--在mapper标签中配置XxxMapper.xml SQL映射文件的路径-->
        <!--resource属性:指定从类的根路径下开始查找资源-->
        <mapper resource="指定SQL映射文件的路径"/>
    </mappers>
</configuration>

编写SQL映射文件

  • 步骤4:在resources根目录下创建mapper目录,在该目录下创建 编写sql语句的sql映射文件(XxxMapper.xml),一般一张数据库表对应一个,一张数据库表对应一个实体类,一张表对应一个Mapper接口

    • 注意1:sql语句最后结尾可以不写";"

    • 注意2:CarMapper.xml文件的名字不固定,可以自定义。

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

XML 复制代码
CarMapper.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根标签中:使用对应的标签编写对应的sql语句,namespace先随意写一个-->
<mapper namespace="car">
    
    <!--用insert标签写insert语句:插入一条汽车信息,用其id属性指定包裹的这条sql语句的id值,id是唯一标识-->
    <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>

加载映射文件

  • 步骤5:加载映射文件,此时Mybatis还不知道Mapper文件的存在,所以需要将 mapper映射文件的路径 配置到 mybatis-config.xml核心配置文件中(后续有新方法):

XML 复制代码
<!-- 加载映射文件的位置 -->
<mappers>
    <mapper resource="mapper/CarMapper.xml"/>
</mappers>

编写Mybatis程序

  • 步骤6:编写MyBatis程序(MyBatisIntroductionTest代码)

    • 在MyBatis中有一个专门执行sql语句的对象叫SqlSession,它是java程序和数据库之间的一次会话。

      • 要获取SqlSession对象先要获取SqlSessionFactory对象,通过它来生产SqlSession对象**(一般SqlSessionFactory对象对应一个数据库)**

      • 要获取SqlSessionFactory对象又要通过SqlSessionFactoryBuilder对象的build方法来获取一个SqlSessionFactory对象

      • 总结mybatis的核心对象包括:SqlSessionFactoryBuilder对象->SqlSessionFactory对象->SqlSession对象

java 复制代码
MyBatisIntroductionTest.java


package org.example.mybatis.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 java.io.InputStream;

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //1.创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
		
        //创建读取Mybatis核心配置文件的流
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); 
        
        //2.获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);

        //3.获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //4.通过SqlSession对象调用对应的方法执行 指定的sql语句
        //方法传入要执行的sql语句的id值,返回值是影响数据库表中的记录数
        //(4.后续:通过Mapper接口的代理类实例调用其方法执行 指定的 sql语句)
        int count = sqlSession.insert("insertCar");  
        System.out.println("插入了几条记录" + count);

        //5.手动提交事务
        sqlSession.commit();
        
        // 6.关闭资源(只关闭是不会提交的)
        sqlSession.close();   //底层分析:如果使用的事务管理器是JDBC,那么底层实际上还是会执行connection.commit()

    }
}

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

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

2.3.1 第一个MyBatis程序的细节

小技巧:凡是遇到resources这个单词,大部分情况下,加载资源的方式都是从类的根路径下开始加载资源

优点:从类路径下开始加载资源的项目 移植性很强,比如从windows移植到Linux下,代码不用修改,因为这个资源一直在类路径下

2.3.2 MyBatis的事务管理机制

为true,表示没有开启事务

2.5 MyBatis第一个比较完整的代码写法

java 复制代码
MyBatisCompleteTest.java

package org.example.mybatis.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 java.io.IOException;

/**
 * 一个完整的MyBatis程序
 */
public class MyBatisCompleteTest {
    public static void main(String[] args) {
        SqlSession sqlSession = null;
        try {
            //1.获取SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

            //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{
            //关闭会话
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

2.7 引入日志框架logback

下图了解即可,不用纠结。

  • 引入日志框架的目的:看清楚mybatis执行的sql语句。

  • 启用标准日志组件:在mybatis-config.xml文件中添加以下配置:【可参考mybatis手册】

  • 标准日志也可以用,但是配置不够灵活,可以集成其他的日志组件,例如:log4j,logback等。
  • logback是目前日志框架中性能较好的,较流行的,这个是用集成的方式。

  • 使用logback日志组件的步骤:

    • 第一步:引入logback相关依赖
    • 第二步:引入logback所必须的xml配置文件

      • 文件名必须叫做logback.xml或logback-test.xml

      • 必须放到类路径中

      • 主要配置日志输出相关的级别以及日志具体的格式

    XML 复制代码
    logback.xml
    
    
    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration debug="false">
        <!-- 控制台输出 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
        </appender>
        <!-- 按照每天生成日志文件 -->
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
                <!--日志文件保留天数-->
                <MaxHistory>30</MaxHistory>
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
            <!--日志文件最大的大小-->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <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"/>
    
        <!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
        <root level="DEBUG">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="FILE"/>
        </root>
    
    </configuration>
    • 再次执行单元测试方法testInsertCar,查看控制台是否有sql语句输出

2.8 MyBatis工具类SqlSessionUtil的封装

  • 目的:(使用后续的)每一次获取SqlSession对象代码太繁琐,封装一个工具类。

java 复制代码
SqlSessionUtil.java

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

/**
 * 获取SqlSession对象的工具类
 * 工具类的构造方法一般都私有化,因为工具类中的方法都是静态的,不用创建对象,直接用类名调用
 */
public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory = null;

    private SqlSessionUtil(){}

    /**
     * 在静态代码块中获取SqlSessionFactory对象;类加载的时候执行获取SqlSessionFactory对象,并且只执行一次
     */
    static {
        try {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取执行SQL语句的SqlSession对象
     * @return SqlSession对象
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }
}
  • 测试工具类,将testInsertCar()改造

2.9 映射文件中Mapper标签的namespace属性

SQL Mapper文件中**<mapper>标签的namespace属性** ,它主要是为了防止sql语句id冲突,其实就是SQL语句id值的前缀。(在接口代理机制中会对namespace有约束,这里随意)

创建CarMapper2.xml文件,代码如下:

不难看出,CarMapper.xml和CarMapper2.xml文件中都有 id="selectCarAll"

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

编写Java代码如下:

运行结果如下:

Java代码修改如下:

运行结果如下:

四、MyBatis的核心配置文件详解

XML 复制代码
mybatis-config.xml

<!-- 第一行是任何一个xml文件的固定写法 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!-- !DOCTYPE后面这个单词是这个xml文档的根标签名 -->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">  //.dtd:对当前XML文件的约束

<configuration>

<!-- 在environments标签中可以配置多个环境,一个environment环境对应一个数据库,通过其default属性指定:默认使用哪个环境-->
    <environments default="development">
<!-- 一个environment环境对应一个SqlSessionFactory对象-->
<!-- 将来在调用sqlSessionFactoryBuilder.build(InputStream inputStream,String environment)方法的时候可以通过环境标签的id属性指定使用的是哪个数据库-->
<!--调用这个sqlSessionFactoryBuilder.build(InputStream inputStream)方法只能获取默认的环境,获取默认的sqlSessionFactory对象-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 这四个property标签:配置连接数据库的信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/bjpowernode"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 这四个property标签是配置连接数据库的信息的 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--XxxMapper.xml SQL映射文件创建好之后,将该文件的路径配置到这里-->
        <!--resource属性自动从类的根路径下开始查找资源-->
        <mapper resource="CarMapper.xml"/>
    </mappers>

</configuration>
  • configuration标签:MyBatis核心配置文件的根标签,表示配置信息。

  • mappers标签:在mappers标签中可以配置多个sql映射文件的路径。

  • mapper:配置sql映射文件的路径

    • resource属性:从类路径下开始加载资源

    • url属性:使用完全限定资源定位符(URL)方式

4.1 environment

mybatis-003-configuration

XML 复制代码
mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 在environments标签中可以配置多个环境,一个environment环境对应一个数据库,通过其default属性指定:默认使用哪个环境-->
    <environments default="development">
<!-- 一个environment环境对应一个SqlSessionFactory对象-->
<!-- 将来在调用sqlSessionFactoryBuilder.build(InputStream inputStream,String environment)方法的时候可以通过环境标签的id属性指定使用的是哪个数据库-->
<!--调用这个sqlSessionFactoryBuilder.build(InputStream inputStream)方法只能获取默认的环境,获取默认的sqlSessionFactory对象-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 这四个property标签:配置连接数据库的信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/bjpowernode"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 这四个property标签是配置连接数据库的信息的 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>
XML 复制代码
CarMapper.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="car">
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>
</mapper>
XML 复制代码
ConfigurationTest.testEnvironment

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 SqlSessionFactoryBuilder();

        // 使用默认数据库
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        int count = sqlSession.insert("insertCar", car);
        System.out.println("插入了几条记录:" + count);

        // 使用指定数据库
        SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "dev");
        SqlSession sqlSession1 = sqlSessionFactory1.openSession(true);
        int count1 = sqlSession1.insert("insertCar", car);
        System.out.println("插入了几条记录:" + count1);
    }
}

执行结果:

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

    • default属性:通过环境environment的id指定默认使用的环境是哪个。
  • environment标签:具体的环境配置(主要包括:事务管理器的配置 + 数据源的配置

    • id属性:设置环境的唯一标识,该标识用在environments的default后面,用来指定默认使用哪个环境。

4.2 transactionManager

XML 复制代码
mybatis-config2.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="dev">
        <environment id="dev">
        <!-- 通过其type属性指定mybatis使用什么方式管理事务(配置事务管理器) -->
            <transactionManager type="MANAGED"/>
            <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>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>
java 复制代码
ConfigurationTest.testTransactionManager

@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 SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config2.xml"));
    // 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 执行SQL
    int count = sqlSession.insert("insertCar", car);
    System.out.println("插入了几条记录:" + count);
}

transactionManager标签:通过其type属性指定mybatis使用什么方式管理事务(配置事务管理器)

  • type属性:可选值包括两个

    • **JDBC:使用JDBC原生的事务管理机制。**底层原理:事务开启conn.setAutoCommit(false); ...处理业务...事务提交conn.commit();

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

当事务管理器是:JDBC

  • 采用JDBC的原生事务机制:

    • 开启事务:conn.setAutoCommit(false);

    • 处理业务......

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

当事务管理器是:MANAGED

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

4.3 dataSource

  • dataSource标签:用其type属性来指定数据源的类型

    • type属性:可选值包括三个:

      • UNPOOLED :虽然也实现Javax.sql.DataSource接口,但是没有使用数据库连接池技术,每次请求过来之后,都会创建新的Connection对象

        • property标签的name可以是:

          • driver:JDBC 驱动的 Java 类全限定名。

          • url :数据库的 JDBC URL 地址。

          • username :登录数据库的用户名。

          • password :登录数据库的密码。

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

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

      • POOLED使用mybatis自己实现的数据库连接池,每次获取数据库连接对象都是从数据库连接池中拿

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

          • poolMaximumActiveConnections 在任意时间可存在的活动(正在使用)连接数量,默认值:10

          • poolMaximumIdleConnections 任意时间可能存在的空闲连接数。

          • 其它....

      • JNDI:集成第三方的数据库连接池

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

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

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

XML 复制代码
mybatis-config3.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
		<!--dataSource标签用type属性来指定数据源的类型,datasource数据源是为程序提供Connection连接对象的-->
            <dataSource type="UNPOOLED">
                <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>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>
java 复制代码
ConfigurationTest.testDataSource

@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 SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.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,控制台输出:

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

XML 复制代码
mybatis-config3.xml

<dataSource type="POOLED">

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

通过测试得出:UNPOOLED不会使用连接池,每一次都会新建JDBC连接对象。POOLED会使用数据库连接池。【这个连接池是mybatis自己实现的。】

XML 复制代码
mybatis-config3.xml

<dataSource type="JNDI">

JNDI的方式:表示对接JNDI服务器中的连接池。这种方式给了我们可以使用第三方连接池的接口。如果想使用dbcp、c3p0、druid(德鲁伊)等,需要使用这种方式。

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

XML 复制代码
mybatis-config3.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="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/powernode"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
                <!--最大连接对象活动的数量-->
                <property name="poolMaximumActiveConnections" value="3"/>
                <!--这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。-->
                <property name="poolTimeToWait" value="20000"/>
                <!--JDBC连接对象强行回归数据库连接池的时间,默认值为20秒-->
                <property name="poolMaximumCheckoutTime" value="20000"/>
                <!--最多空闲时连接对象的数量-->
                <property name="poolMaximumIdleConnections" value="1"/>
            </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个连接要空闲下来的时候,连接池会选择关闭该连接对象。来减少数据库的开销。

需要根据系统的并发情况,来合理调整连接池最大连接数以及最多空闲数量。充分发挥数据库连接池的性能。【可以根据实际情况进行测试,然后调整一个合理的数量。】

下图是默认配置:

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

java 复制代码
ConfigurationTest.testPool

@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");
    }
}
XML 复制代码
CarMapper.xml

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

4.4 .properties属性文件

mybatis提供了更加灵活的配置为:①先配置 连接数据库的信息 到一个属性文件中,假设在类的根路径下创建jdbc.properties文件,配置如下:

XML 复制代码
jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=123456
将来用${}通过key把对应的值从这个文件中取出来

②再在mybatis核心配置文件中用properties单标签引入属性文件,③再通过${}把属性文件中key对应的值取出来:

XML 复制代码
mybatis-config4.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--引入外部属性文件-->
    <properties resource="jdbc.properties"/>

    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--使用${key}从xxx.properties文件中取值-->
                <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>

properties标签的两个属性,在mapper标签中也有:

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

url:从指定的url加载资源,假设文件放在d:/jdbc.properties,这个url可以写成:file:///d:/jdbc.properties。注意是三个斜杠哦。

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

  • 第一种方式:查看dtd约束文件。

  • 第二种方式:通过idea的报错提示信息。【一般采用这种方式】

4.5 mappers标签

mappers标签中可以写多个mapper标签

mapper标签:加载SQL映射文件,包含多种配置方式,这里先主要看其中两种:

第一种:通过mapper单标签的resource属性:配置SQL映射文件的路径,表示从类的根路径下开始查找资源【比url常用】

XML 复制代码
mybatis-config4.xml


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

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

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

XML 复制代码
mybatis-config4.xml

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

第二种:用url属性指定SQL映射文件的路径:从指定的url位置加载

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

XML 复制代码
mybatis-config4.xml

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

后面几种:9.3

三、使用MyBatis完成CRUD

  • 准备工作

    • 创建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 insert(Create)

  • 以前SQL的映射文件中的SQL语句存在的问题

    • SQL语句中的值被写死
  • SQL语句中的值不能写死,值由用户提供,动态传值。之前的JDBC代码是这样写的:

  • 在MyBatis中这样写SQL语句中的值:

    • 在Java程序中,先将数据封装到Map集合中

    • 然后在sql语句中使用 #{} 来动态传值给SQL语句,#{} 等同于JDBC中的占位符 ? 。

    • Java程序:

java 复制代码
        
package org.example.mybatis.test;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import utils.SqlSessionUtil;

import java.util.HashMap;
import java.util.Map;

public class TestCarMapping {
    @Test
    public void testInsertCar(){
        //获取数据库执行对象
        SqlSession sqlSession = SqlSessionUtil.openSession();

        //创建Map集合,用于封装一条数据
        Map<String,Object> map = new HashMap<>();
        map.put("carNum",1111);
        map.put("brand","老汉推车");
        map.put("guidePrice",10.0);
        map.put("produceTime","2020-12-12");
        map.put("carType","电车");

        //用SqlSession对象调用相应的方法执行指定的SQL语句,insert的第一个参数为sql语句的id值,第二个参数是封装数据的对象
        int count = sqlSession.insert("insertCar",map);
        System.out.println(count);

        sqlSession.commit();
        sqlSession.close();

    }
}
    • mapper文件:在**#{} 的里面写map集合的key。**
XML 复制代码
CarMapper.xml 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:可以配置这些SQL语句的命名空间-->
<mapper namespace="car">

    <!--插入
        增删改操作的标签没有resultType属性,只有查询操作才有,
        因为增删改操作都是int类型的返回值,所以不需要指明
    -->
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>

</mapper>
  • 运行测试程序,查看数据库:

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

如果key不存在不会报错,会在数据库表中插入NULL。

  • 使用pojo对象完成传参:

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

    java 复制代码
    Car.java
    
    package org.example.mybatis.test.utils.pojo;
    
    public class Car {
        private Long id;
        private String carNum;
        private String brand;
        private Double guidePrice;
        private String produceTime;
        private String carType;
    
        public Car() {
        }
    
        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程序

    java 复制代码
    TestCarMapping.java
       
    
        @Test
        public void testInsertCarPoJo(){
            //获取数据库执行对象
            SqlSession sqlSession = SqlSessionUtil.openSession();
    
            //创建pojo对象,用于封装一条数据
            Car car = new Car();
            car.setCarNum("2000");
            car.setBrand("大力");
            car.setGuidePrice(30.0);
            car.setProduceTime("2000--3-3");
            car.setCarType("电力车");
    
            //执行SQL语句,insert的第一个参数是sql语句的id值,第二个参数是封装数据的对象
            int count = sqlSession.insert("insertCar",car);
            System.out.println(count);
    
            sqlSession.commit();
            sqlSession.close();
    
        }
    • 第三步:SQL语句,#{}里面写pojo类中的属性名,其实准确来说是get方法的方法名去掉get后,将剩下的单词首字母变小写

    XML 复制代码
    CarMapping.xml
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <!--namespace先随意写一个-->
    <mapper namespace="car">
    
        <!--写insert语句:保存一个汽车信息。id属性可以指定这条sql语句的唯一标识-->
        <insert id="insertCar">
            insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
        </insert>
    
    </mapper>
    • 运行程序,查看数据库表:

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

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

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

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

如果采用POJO对象传参,#{} 里写的是get方法的方法名去掉get之后将剩下的单词首字母变小写(例如:getAge对应的是#{age},getUserName对应的是#{userName}),如果这样的get方法不存在会报错。

注意:其实传参数的时候有一个属性parameterType,这个属性用来指定传参的数据类型,不过这个属性是可以省略的

3.2 delete(Delete)

需求:根据car_num进行删除。

mapper文件:

XML 复制代码
CarMapper.xml


 <delete id="deleteById">
      delete from t_car where id = #{id}
 </delete>

Java程序:

java 复制代码
TestCarMapping.java

    @Test
    public void testDeleteById(){
        //获取数据库执行对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //执行sql语句
        sqlSession.delete("deleteById",3);
        sqlSession.commit();
        sqlSession.close();
    }

运行结果:

注意:当占位符只有一个时,#{} 里面的内容可以随便写,最好见名知意。

3.3 update(Update)

更新表中的数据:

SQL语句如下:

XML 复制代码
CarMapper.xml

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

Java代码如下:

java 复制代码
TestCarMapping.java


    @Test
    public void testUpdateByid(){
        //获取数据库执行对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //封装数据
        Car car = new Car(11L,"3333","路虎",20.0D,"2003-11-30","燃料车");
        //执行sql
        sqlSession.update("updateById",car);
        sqlSession.commit();
        sqlSession.close();
    }

运行结果:

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

3.4 select(Retrieve)

select语句和其它语句的不同:它会返回一个查询结果集,要用普通对象或集合对象来接收。

查询数据时有两个点:

  • 使用<select>标签的 resultType属性:指定将查询结果集 要封装成 什么类型的对象并返回 ,然后在Mybatis程序中用对应类型的对象接收查询结果(类名写全限定类名
  • 查询结果集的字段名需和java类的属性名一致,因为Mybatis要把查询到的数据 赋值(映射)到 java对象的属性中,所以要通过as关键字对查询出的列名起别名,当然还有其它解决方案,我们后面再看。

查询一条记录

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

SQL语句如下:

XML 复制代码
CarMapper.xml


    <select id="selectById" resultType="org.example.mybatis.test.utils.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 id = #{id}
    </select>

Java程序如下:

java 复制代码
   
   @Test
    public void testSelectById(){
        //获取数据库执行对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //执行sql语句
        //方法的返回值:就是映射文件中配置的resultType的类型
        /*
        方法的执行流程:
            Mybatis的底层执行器会通过sql语句的id找到对应的Statement,并把7369给占位符,执行SQL语句,得到查询结果集
            Mybatis底层通过反射机制, 创建resultType属性 类型的对象 ,然后把查询得到的结果集赋值(映射)给该对象
         */
        Car car = sqlSession.selectOne("selectById",1);
        sqlSession.commit();
        sqlSession.close();
        System.out.println(car);
    }

查询多条数据

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

SQL语句如下:

XML 复制代码
CarMapping.xml

    <!--
    查询到数据返回多条记录,每一条封装在一个实体类对象中,然后将所有的实体类对象添加到List集合中
    resultType:指定的并不是集合的类型,而是单条数据所对应实体类类型
    resultType="java.util.List" 错误的配置方式
    -->
    <select id="selectByAll" resultType="org.example.mybatis.test.utils.pojo.Car">
        select
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from t_car
    </select>

Java代码如下:

java 复制代码
TestCarMapping.java


	@Test
    public void testSelectByAll(){
        //获取数据库执行对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //执行sql语句
        List<Object> carList = sqlSession.selectList("selectByAll");
        sqlSession.commit();
        sqlSession.close();
        for(Object obj:carList){
            System.out.println(obj);
        }
    }

运行结果如下:

3.5 总结

parameterType和resultType

parameterType:指定输入参数类型(即要传递什么类型的参数给SQL语句),MyBatis会从输入对象中 取值 设置 在SQL语句中。

resultType:指定输出结果类型,即MyBatis会将Sql查询结果的一行记录 映射成 resultType指定类型的Java对象,并返回。

通用总结

  • 在SQL映射文件中对应的标签中编写对应的SQL语句

  • MyBatis中都是调用SqlSession对象的对应方法来执行对应的SQL语句

  • 在mybatis中,在sql语句中使用 #{} 来动态传值给SQL语句,#{} 等同于JDBC中的占位符 ?

    • 如果一条数据是用Map集合封装的,在#{}里面 填写map集合的key取出对应的value传给SQL语句

    • 如果一条数据是用pojp对象封装的,那么在#{}里面 根据pojo对象的属性名取出其属性值设置给SQL语句(相当于用这个符号取出传过来的pojo对象中的属性)

  • 使用SqlSession对象调用执行sql语句的相应方法

    • 方法的第一个参数是:映射文件中sql语句的id值,通过id值指定要执行哪条SQL语句

    • 方法的第二个参数:向sql语句中传入的数据,传入数据的数据类型必须与映射文件中配置的parameterType保持一致

      • parameterType:输入参数的类型
  • 执行查询语句后,会返回一个查询结果集。

    • 所以必须在<select>标签中用 resultType属性:指定指定输出结果的类型(类名写全限定类名)

六、在WEB中应用MyBatis(使用MVC架构模式)

由于文章内容太多,此节请点这里查看

七、javassist

Mybatis接口代理机制的原理,初学不用管

八、MyBatis中的接口代理机制

8.1 Mybatis接口代理机制的理解

  • 为什么用接口代理机制?:因为dao实现类中的方法很固定,基本上就是一行代码: 通过SqlSession对象调用insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,所以使用接口代理机制动态生成dao接口的实现类
  • MyBatis中的接口代理机制:动态生成DAO接口的实现类(代理类)并创建其实例对象,以后获取到其代理实现类对象直接用即可。
  • mybatis实际上使用了代理模式,在内存中生成dao接口的代理类,并创建代理类的实例。

8.2 接口代理机制的开发规范

编写Mapper接口需要遵循一些规范,MyBatis就可以自动生成Mapper接口的实现类代理对象。

使用接口代理机制的前提:

  1. SQL映射文件中mapper标签的namespace属性需指定为:Mapper接口的全限定名
    1. 将mapper文件和Mapper接口关联起来,将来生成的Mapper接口实现类对象调用方法时,才知道执行的是哪个映射文件中的哪个sql语句
  2. SQL映射文件,sql语句的id值要和Mapper接口中的方法名一致
  3. Mapper.xml中定义的每个sql的parameterType的类型与Mapper接口方法的参数类型相同。
  4. Mapper.xml中定义的每个sql的resultType的类型与Mapper接口方法返回值类型相同。
  5. 注:Mapper.xml映射文件最好和Mapper接口名称一致。

如何获取Mapper接口的代理类对象:通过SqlSession对象调用其getMapper()方法,传入Mapper接口的字节码文件对象,指定获取哪个Mapper接口的代理类对象

java 复制代码
CarMapper mapper = sqlSession.getMapper(CarMapper.class);

8.3 接口代理机制实例(005-crud2)

面向接口进行crud

和以前的不同点:只有Mapper文件变了,和不用写Mapper接口的实现类了。

和以前的区别:

  • 一个是在Mapper接口的实现类中编写SqlSession对象来执行SQL语句;
  • 一个是直接在测试程序中用Mapper接口的代理类对象来执行SQL语句
  • 后者在调用相应方法执行时,不用指定SQL语句的id,因为SQL语句的id已经规范为方法名了,Mybatis的接口代理机制会自动替你完成指定,你调用哪个方法就是在指定执行哪条SQL语句
java 复制代码
CarMapper接口
CarMapper.java


package org.example.mapper;

import org.example.pojo.Car;

import java.util.List;

public interface CarMapper {
    /**
     * 插入一条car记录
     * @param car
     * @return
     */
    int insert(Car car);

    /**
     * 根据id删除car记录
     * @param id
     * @return
     */
    int deleteById(Long id);

    /**
     * 更新汽车信息
     * @param car
     * @return
     */
    int update(Car car);

    /**
     * 根据id查询
     * @param id
     * @return
     */
    Car selectById(Long id);

    /**
     * 查询所有car的记录
     * @return
     */
    List<Car> selectAll();

}
java 复制代码
SQL映射文件
CarMapper.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">

<!--要想使用接口代理机制:sql映射文件中mapper标签的namespace必须是Mapper接口的全限定名,sql语句的id值也必须是dao接口的方法名-->
<mapper namespace="org.example.mapper.CarMapper">
    <insert id="insert">
        insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>

    <delete id="deleteById">
        delete from t_car where id = #{id}
    </delete>

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

    <select id="selectById" resultType="org.example.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 id = #{id}
    </select>

    <select id="selectAll" resultType="org.example.pojo.Car">
        select
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from t_car
    </select>
</mapper>
java 复制代码
TestCarMapper.java


package test;

import org.apache.ibatis.session.SqlSession;
import org.example.mapper.CarMapper;
import org.example.pojo.Car;
import org.example.utils.SqlSessionUtil;
import org.junit.Test;

import java.util.List;

public class TestCarMapper {
    @Test
    public void testInsert(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //获取Mapper接口的代理类对象,传入Mapper接口的字节码文件对象,告诉mybatis生成哪个Mapper接口的代理类对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        //通过CarMapper接口的代理类对象调用其中的方法执行SQL语句
        int count = mapper.insert(new Car(null,"7987","小车车",32.0,"2003-11-04","新能源"));
        System.out.println(count);
        sqlSession.commit();
        SqlSessionUtil.close(sqlSession);
    }

    @Test
    public void testdeleteById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //获取DAO接口的代理类对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteById(17L);
        sqlSession.commit();
        SqlSessionUtil.close(sqlSession);
    }

    @Test
    public void testupdate(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //获取DAO接口的代理类对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.update(new Car(18L,"456","大车车",36.0,"2003-11-04","新能源"));
        System.out.println(count);
        sqlSession.commit();
        SqlSessionUtil.close(sqlSession);
    }

    @Test
    public void selectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //获取DAO接口的代理类对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(1L);
        System.out.println(car);
        SqlSessionUtil.close(sqlSession);
    }

    @Test
    public void testselectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //获取DAO接口的代理类对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        System.out.println(cars);
        SqlSessionUtil.close(sqlSession);
    }

}

九、总结Mybatis中DAO(Mapper)的开发方式

请点这里

十、Mybatis核心配置文件的剩余标签

请点这里

十一、#{}和${}的区别

  • #{}:底层使用的是PreparedStatement对象。它是先编译sql语句,再给占位符传值。可以防止sql注入,比较常用。

  • ${}:底层使用的是Statement对象。它先拼接sql语句,再编译sql语句。存在sql注入的风险。只有在需要进行sql语句关键字拼接的情况下才会用到。

  • 表面区别:#{}传进去的值有单引号,${}传进去的值没有加单引号

  • 优先使用#{},避免SQL注入

需求:根据car_type查询汽车

模块名:mybatis-005-antic

使用#{}

导入依赖

XML 复制代码
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.powernode</groupId>
    <artifactId>mybatis-005-antic</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <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>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>

jdbc.properties放在类的根路径下

XML 复制代码
jdbc.properties

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

logback.xml,可以拷贝之前的,放到类的根路径下

utils:

java 复制代码
SqlSessionUtil.java

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

/**
 * MyBatis工具类
 *
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory;

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

    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    /**
     * 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。
     *
     * @return 新的会话对象
     */
    public static SqlSession openSession() {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            local.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭SqlSession对象
     * @param sqlSession
     */
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
        }
        local.remove();
    }
}

pojo类

java 复制代码
Car.java

package com.powernode.mybatis.pojo;
/**
 * 普通实体类:汽车
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class Car {
    private Long id;
    private String carNum;
    private String brand;
    private Double guidePrice;
    private String produceTime;
    private String carType;
    // 构造方法
    // set get方法
    // toString方法
}

mapper接口(DAO接口)

java 复制代码
CarMapper.java

package com.powernode.mybatis.mapper;

import com.powernode.mybatis.pojo.Car;

import java.util.List;

/**
 * Car的sql映射对象
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public interface CarMapper {

    /**
     * 根据car_num获取Car
     * @param carType
     * @return
     */
    List<Car> selectByCarType(String carType);

}

mybatis-config.xml,放在类的根路径下

XML 复制代码
mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <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="CarMapper.xml"/>
    </mappers>
</configuration>

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

XML 复制代码
CarMapper.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.CarMapper">
    <select id="selectByCarType" resultType="com.powernode.mybatis.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
            car_type = #{carType}
    </select>
</mapper>

测试程序

java 复制代码
CarMapperTest

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;

/**
 * CarMapper测试类
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class CarMapperTest {

    @Test
    public void testSelectByCarType(){
        CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);
        List<Car> cars = mapper.selectByCarType("燃油车");
        cars.forEach(car -> System.out.println(car));
    }
}

执行结果:

通过执行可以清楚的看到,sql语句中是带有 ? 的,这个 ? 就是大家在JDBC中所学的占位符,专门用来接收值的。

把"燃油车"以String类型的值,传递给 ?

结论:这就是 #{},它会先进行sql语句的预编译,然后再给占位符传值

使用${}

同样的需求,我们使用${}来完成

CarMapper.xml文件修改如下:

XML 复制代码
CarMapper.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.CarMapper">
    <select id="selectByCarType" resultType="com.powernode.mybatis.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
            <!--car_type = #{carType}-->
            car_type = ${carType}
    </select>
</mapper>

再次运行测试程序:

出现异常了,这是为什么呢?看看生成的sql语句:

结论:很显然,${} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为 燃油车 是一个字符串,在sql语句中应该添加单引号

修改:

XML 复制代码
CarMapper.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.CarMapper">
    <select id="selectByCarType" resultType="com.powernode.mybatis.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
            <!--car_type = #{carType}-->
            <!--car_type = ${carType}-->
            car_type = '${carType}'
    </select>
</mapper>

再执行测试程序:

通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。

原则:能用 #{} 就不用 ${}

什么情况下必须使用${}

在向sql语句传SQL语句关键字的时候需要使用{},因为#{}是以值的形式放到SQL语句中的。必须使用{}

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

  • 先使用#{}尝试:

CarMapper接口:

java 复制代码
CarMapper

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

CarMapper.xml文件:

XML 复制代码
CarMapper.xml

<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
  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 #{key}
</select>

测试程序

java 复制代码
CarMapperTest.testSelectAll

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

运行:

报错的原因是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语句关键字拼接的时候,必须使用${}

  • 使用${} 改造
XML 复制代码
CarMapper.xml

<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
  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 #{key}-->
  order by carNum ${key}
</select>

再次执行测试程序:

向SQL语句中拼接表名

现实业务中,可能存在分表存储数据的情况。因为表存的话,数据量太大,查询效率比较低。可以将这些数据有规律的分表存储,这样查询效率就比较高,因为扫描的数据量就变少了

业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如: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

XML 复制代码
LogMapper.xml

<select id="selectAllByTableName" resultType="car">
  select
 	 id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
  from
    t_log_
  
</select>
java 复制代码
LogMapper.java
    
public interface LogMapper{    

	/**
	 * 根据日期查询不同的表,获取表中所有的日志
	 * @param tableName
	 * @return
 	*/
	List<Log> selectAllByTable(String date);
}
java 复制代码
public class TestLogMapper{
	@Test
	public void testSelectAllByTable(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        LogMapper mapper = sqlSession.getMapper(LogMapper.class);
        List<Log> logs = mapper.selectAllByTable("20220901");
        logs.forEach(log -> System.out.println(log));
	}
}

执行结果:

批量删除

批量删除:一次删除多条记录。

批量删除的SQL语句有两种写法:

  • 第一种or:delete from t_user where id = 1 or id = 2 or id = 3;

  • 第二种in: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)应该采用{}来取值

java 复制代码
CarMapper接口

	/**
     * 根据id批量删除
     * @param ids
     * @return
     */
int deleteBatch(String ids);
XML 复制代码
CarMapper.xml

<delete id="deleteBatch">
  delete from t_car where id in(${ids})
</delete>
java 复制代码
CarMapperTest.testDeleteBatch

@Test
public void testDeleteBatch(){
    CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
    int count = mapper.deleteBatch("1,2,3");
    System.out.println("删除了几条记录:" + count);
    SqlSessionUtil.openSession().commit();
}

执行结果:

模糊查询

模糊查询的三种方式

1、#{}占位符,防止sql注入

需要在Java中将传入数据的前后拼接%符号

where ename like #{ename}

2、使用字符串拼接函数

where ename like concat('%',#{ename},'%')

3、${}拼接符号,实现sql的拼接

where ename like '%${value}%'

注意:{}不是占位符,如果输入参数为简单类型,{}中的内容必须为value

第一种方式:

mapper文件:

XML 复制代码
<select id="selectByEname1" parameterType="java.lang.String" 
resultType="com.gs.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    where ename like #{ename}
</select>

Java代码:

java 复制代码
@Test
public void testSelectByEname() throws IOException {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = factory.openSession();
String ename = "S";
List<Emp> list = session.selectList("emp.selectByEname1", "%"+ename+"%");
for (Emp emp : list) {
System.out.println(emp);
}
session.close();
}

第二种方式:

mapper文件:

XML 复制代码
<select id="selectByEname2" parameterType="java.lang.String"
resultType="com.gs.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    where ename like concat('%',#{ename},'%')
</select>

Java代码:

java 复制代码
@Test
public void testSelectByEname2() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();
    String ename = "S";
    List<Emp> list = session.selectList("emp.selectByEname2", ename);
    for (Emp emp : list) {
        System.out.println(emp);
    }
    session.close();
}

第三种方式:

mapper文件:

XML 复制代码
<select id="selectByEname3" parameterType="java.lang.String"
resultType="com.gs.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    where ename like '%${value}%'
</select>

Java代码:

java 复制代码
@Test
public void testSelectByEname3() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();
    String ename = "S";
    List<Emp> list = session.selectList("emp.selectByEname3", ename);
    for (Emp emp : list) {
        System.out.println(emp);
    }
    session.close();
}

十二、输入参数映射和输出结果映射专题

点击这里

十三、动态SQL拼接

动态SQL拼接

十四、Mybatis关联查询

点这里

十五、Mybatis的延迟加载

Mybatis的延迟加载

十六、Mybatis的缓存

Mybatis的缓存

十七、Mybatis使用PageHelper分页插件

Mybatis的分页插件

十八、Mybatis注解式开发

mybatis注解开发

相关推荐
弗拉唐3 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
凌冰_5 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
Elaine2023916 小时前
零碎04 MybatisPlus自定义模版生成代码
java·spring·mybatis
一二小选手7 小时前
【MyBatis】全局配置文件—mybatis.xml 创建xml模板
xml·java·mybatis
刘大浪7 小时前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis
蓝染-惣右介9 小时前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
武子康10 小时前
Java-07 深入浅出 MyBatis - 一对多模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据库·sql·mybatis·springboot
一二小选手10 小时前
【Mybatis】@Param注解 resultMap手动映射
java·mybatis
郑祎亦13 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis