mybatis笔记(老杜版本)

一、MyBatis概述

1.1框架

  • Java常⽤框架:

    ​ SSM三⼤框架:Spring + SpringMVC + MyBatis
    ​ SpringBoot
    ​ SpringCloud 等。。

  • SSM三⼤框架的学习顺序:MyBatis、Spring、SpringMVC(建议)

1.2 三层架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1uxyElgS-1670084450231)(D:\mybatis笔记\三层架构.png)]

  • 表现层(UI):直接跟前端打交互(⼀是接收前端ajax请求,⼆是返回json数据给前端)
  • 业务逻辑层(BLL):⼀是处理表现层转发过来的前端请求(也就是具体业务),⼆是将从持久层获取的数据返回到表现层。
  • 数据访问层(DAL):直接操作数据库完成CRUD,并将获得的数据返回到上⼀层(也就是业务逻辑层)。

1.3 JDBC的不足

  • SQL语句写死在Java程序中,不灵活。改SQL的话就要改Java代码。违背开闭原则OCP。
  • 给占位符 ? 传值是繁琐的。能不能⾃动化???
  • 将结果集封装成Java对象是繁琐的。能不能⾃动化???

1.4 了解MyBatis

  • MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD。
  • MyBatis在三层架构中负责持久层的,属于持久层框架。
  • ORM:对象关系映射
  • O(Object):Java虚拟机中的Java对象
    R(Relational):关系型数据库
    M(Mapping):将Java虚拟机中的Java对象映射到数据库表中⼀⾏记录,或是将数据库表中⼀⾏记录映射成Java虚拟机中的⼀个Java对象。
    ORM图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJbt1LlO-1670084450232)(D:\mybatis笔记\ORM对象关系映射.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ptwUXgH8-1670084450233)(D:\mybatis笔记\ORM对象关系映射2.png)]

  • MyBatis框架特点:

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

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

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

    • XML⽅式开发。】

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

    • 记录

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

    • 完全做到sql解耦合。

    • 提供了基本映射标签。

    • 提供了⾼级映射标签。

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

二、MyBatis入门程序

2.1 版本

2.2 MyBatis下载

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】

  • 先创建一个空项目,再空项目中创建一个模块

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

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

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</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/ziv_mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--sql映射⽂件创建好之后,需要将该⽂件路径配置到这⾥-->
            <mapper resource="CarMapper.xml"/>
        </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">
    <mapper namespace="bbb">
        <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>
    

    注意1:sql语句最后结尾可以不写“;”
    注意2:CarMapper.xml⽂件的名字不是固定的。可以使⽤其它名字。
    注意3:CarMapper.xml⽂件的位置也是随意的。这⾥选择放在resources根下,相当于放到了类的根路径下。
    注意4:将CarMapper.xml⽂件路径配置到mybatis-config.xml:

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

    package com.ziv.mybatis.test;
    
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.InputStream;
    
    public class MyBatisIntroductionTest {
        public static void main(String[] args) {
            // 1. 创建SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            // 2. 创建SqlSessionFactory对象
            InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
            // 3. 创建SqlSession对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 4. 执⾏sql
            int count = sqlSession.insert("insertCar"); // 这个"insertCar"必须是sql的id
            System.out.println("插⼊⼏条数据:" + count);
            // 5. 提交(mybatis默认采⽤的事务管理器是JDBC,默认是不提交的,需要⼿动提交。)
            sqlSession.commit();
            // 6. 关闭资源(只关闭是不会提交的)
            sqlSession.close();
        }
    }
    

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

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

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

  • 经过测试说明mybatis核⼼配置⽂件的名字是随意的,存放路径也是随意的。

  • 虽然mybatis核⼼配置⽂件的名字不是固定的,但通常该⽂件的名字叫做:mybatis-config.xml

  • 虽然mybatis核⼼配置⽂件的路径不是固定的,但通常该⽂件会存放到类路径当中,这样让项⽬的移植更加健壮。

  • maven工程中的resources目录就相当于项目的根目录

  • 在mybatis中提供了⼀个类:Resources【org.apache.ibatis.io.Resources】,该类可以从类路径当
    中获取资源,我们通常使⽤它来获取输⼊流InputStream,代码如下

    // 这种⽅式只能从类路径当中获取资源,也就是说mybatis-config.xml⽂件需要在类路径下
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    

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

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

public class MyBatisCompleteCodeTest {
    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 {
            // 6.关闭
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

2.6 引入JUnit

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

    • 测试⽤例的名字:XxxTest
    • 测试⽅法声明格式:public void test业务⽅法名(){}
    @Test
    public void testDeleteById(){
    }
    @Test
    public void testInsertCar(){
    }
    
  • 第三步:可以在类上执⾏,也可以在⽅法上执⾏

    • 在类上执⾏时,该类中所有的测试⽅法都会执⾏。
    • 在⽅法上执⾏时,只执⾏当前的测试⽅法。

2.7 引入日志框架logback

  • 引⼊⽇志框架的⽬的是为了看清楚mybatis执⾏的具体sql。
  • 启⽤标准⽇志组件,只需要在mybatis-config.xml⽂件中添加以下配置:【可参考mybatis⼿册】
<settings>
    <!--STDOUT_LOGGING是mybatis自带的标准版日志,不需要引入相关依赖-->
    <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
  • 我们这里使用logback日志框架,使用第三方框架,可以不用在settings中配置

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

      <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.2.11</version>
      </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.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>
      
  • 执行程序,查看打印日志:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDckUhmc-1670084450233)(D:\mybatis笔记\日志图片.png)]

2.8 MyBatis工具类SqlSessionUtil

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

import java.io.IOException;

public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private SqlSessionUtil(){}
    public static SqlSession openSession() {
        return sqlSessionFactory.openSession();
    }
}

三、使用MyBatis完成CRUD

什么是CRUD

  • C:Create 增
  • R:Retrieve 查(检索)
  • U:Update 改
  • D:Delete 删

pojo类Car

public class Car {
    private Long id;
    private String carNum;
    private String brand;
    private Double guidePrice;
    private String produceTime;
    private String carType;
    ...//省略getter setter等其他方法
}

3.1 insert

<insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,#{},#{},#{},#{},#{})
</insert>

JDBC中使用 ? 当作占位符,mybatis中使用 #{} 当作占位符,? 和 #{} 是等效的。

  • mybatis可以使用map集合给sql语句中的占位符传值:#{ 这里写map集合中的key }

    • 注意:当#{}中的key写错的时候,会传入null,底层就是调用map.get()方法
  • mybatis可以使用pojo类给sql语句中的占位符传值:#{ 这里写pojo类中的属性名 }

    • <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>
      
    • 注意:当#{}中的属性名写错的话,会报错

      org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘carNum2’ in ‘class com.ziv.mybatis.pojo.Car’

      mybatis会去找:Car类中的getXxx()方法,也就是说,#{} 中写的是将Car类中getXxx()方法的get去掉得到Xxx首字母变小写得到xxx。例如:

      getCarNum() --> #{carNum}

      getBrand() --> #{brand}

​ java代码

@Test
public void testInsertCar(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    Car car = new Car(null,"3333","比亚迪秦",30.0,"2020-10-10","新能源");
    int count = sqlSession.insert("insertCar",car);
    System.out.println("count = " + count);
    sqlSession.commit();
    sqlSession.close();
}

3.2 delete

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

当只传一个值的时候,#{}里面的内容随意,但是不能空着。

java代码

@Test
public void testDeleteById(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    int count = sqlSession.delete("deleteById", 10);
    System.out.println("count = " + count);
    sqlSession.commit();
    sqlSession.close();
}

3.3 update

<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>
@Test
public void testUpdateById() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    Car car = new Car(9L,"1004","凯美瑞",30.0,"1999-10-10","燃油车");
    sqlSession.update("updateById",car);
    sqlSession.commit();
    sqlSession.close();
}

3.4 select

  • select(查一个,返回结果是一个)

    <select id="selectById" resultType="com.ziv.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 id = #{id}
    </select>
    
    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Car car = sqlSession.selectOne("selectById", 12);
        System.out.println("car = " + car);
        sqlSession.commit();
        sqlSession.close();
    }
    

    mybatis执行了select语句之后,会返回一个ResultSet对象,然后从中取出数据,再封装成Java对象。所以需要通过select标签中的 resultType属性指明需要封装成什么对象。

    注意:select语句中查询的列别名要和pojo类中的属性名一致

  • select(查所有)

    <select id="selectAll" resultType="com.ziv.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
    </select>
    
    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        List<Object> carList = sqlSession.selectList("selectAll");
        System.out.println("carList = " + carList);
        sqlSession.commit();
        sqlSession.close();
    }
    

3.5 关于SQL Mapper 的namespace

  • 在SQL Mapper配置⽂件中标签的namespace属性可以翻译为命名空间,这个命名空间主要是为了防⽌sqlId冲突的。

  • 当有两个不同的Mapper.xml文件中的sql语句id相同的时候,就需要使用命名空间加id的形式。

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

四、MyBatis核心配置文件详解

4.1 environment

<?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>
    <!--可以配置多个environment环境标签-->
    <!--default 属性指明默认使用哪个环境配置-->
    <environments default="development">
        <!--一般一个数据库会对应一个SqlSessionFactory对象-->
        <!--一个环境environment会与对应一个SqlSessionFactory对象-->
        <!--开发环境配置-->
        <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/ziv_mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </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/ziv_mybatis2"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>
@Test
public void configTest() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //build方法的第二个参数用来指定使用哪个环境配置,如果没有指定使用默认配置。
    SqlSession sqlSession = new SqlSessionFactoryBuilder().build(is).openSession();
    SqlSession sqlSession2 = new SqlSessionFactoryBuilder().build(is,"production").openSession();

}

4.2 transactionManager

<environment id="development">
    <!--
        transactionManager标签:
            1.作用:配置事务管理器。指定mybatis具体使用什么方式去管理事务。
            2.type属性有两个值(不区分大小写):
                a) JDBC:使用原生的JDBC代码来管理事务
                    conn.setAutoCommit(false);
                    ...
                    conn.commit();
                b) MANAGED:mybatis不再负责事务的管理,将事务交给其他容器管理,如spring。
						 当mybatis找不到容器的支持时:没有事务
            3.mybatis中提供了一个事务管理器接口:Transaction
                该接口下有两个实现类:
                    JdbcTransaction
                    ManagedTransaction
                type中写哪个,底层机会实例化哪个对象
    -->
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/ziv_mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
</environment>

4.3 dataSource

<environment id="development">
    <transactionManager type="JDBC"/>
    <!--
        dataSource配置:
            1.dataSource被称为数据源。
            2.dataSource作用:为程序提供Connection对象。(只要是给程序提供Connection对象的,都可以叫做数据源)
            3.数据源实际上是一套规范。JDK中有这套规范:javax.sql.DataSource (这个数据源的规范,实际上就是JDK规定的)
            4.我们也可以自己实现javax.sql.DataSource接口,编写数据源组件。
                比如自己写一个数据库连接池
            5.常见的数据源组件(也称常见的数据库连接池):
                druid c3p0 dbcp...
            6.type属性用来指定数据源的类型,就是指定用什么方式来获取Connection对象
                type有三个值,必须三选一:
                UNPOOLED:不使用数据库连接池技术。每次都创建新的Connection对象
                POOLED:使用mybatis自己的数据库连接池
                JNDI:继承第三方的数据库连接池

                JNDI是一套规范,大部分web容器都是先了JNDI规范
                    例如:Tomcat、Jetty、WebLogic、WehSphere
                JNDI是Java命名目录接口。Tomcat服务器实现了这个规范。

    -->
    <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/ziv_mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
</environment>

4.4 properties

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

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

在mybatis核心配置文件中引入并使用:

<!--引⼊外部属性资源⽂件-->
<properties resource="jdbc.properties"/>


<environment id="production">
    <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>
  • properties两个属性:
    • resource:这个属性从类的根路径下开始加载。【常⽤的。】
    • url:从指定的url加载,假设⽂件放在d:/jdbc.properties,这个url可以写成:file:///d:/jdbc.properties。注意是三个斜杠哦。

4.5 mapper

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

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

      <mappers>
          <mapper resource="CarMapper.xml"/>
      </mappers>
      
    • 第⼆种:url,从指定的url位置加载

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

mapper还有其他的指定⽅式,后⾯再看!!!

汇总

<?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>
    <!--java.util.Properties类,是一个Map集合。key和value都是String类型-->
    <!--<properties resource="jdbc.properties">
        &lt;!&ndash;在properties标签中可以配置很多属性&ndash;&gt;
        <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/ziv_mybatis2"/>
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="123456"/>
    </properties>-->
    <properties resource="jdbc.properties"/>
    <!--可以配置多个environment环境标签-->
    <!--default 属性指明默认使用哪个环境配置-->
    <environments default="development">
        <!--一般一个数据库会对应一个SqlSessionFactory对象-->
        <!--一个环境environment会与对应一个SqlSessionFactory对象-->
        <!--开发环境配置-->
        <environment id="development">
            <!--
                transactionManager标签:
                    1.作用:配置事务管理器。指定mybatis具体使用什么方式去管理事务。
                    2.type属性有两个值(不区分大小写):
                        a) JDBC:使用原生的JDBC代码来管理事务
                            conn.setAutoCommit(false);
                            ...
                            conn.commit();
                        b) MANAGED:mybatis不再负责事务的管理,将事务交给其他容器管理,如spring。
                    3.mybatis中提供了一个事务管理器接口:Transaction
                        该接口下有两个实现类:
                            JdbcTransaction
                            ManagedTransaction
                        type中写哪个,底层机会实例化哪个对象
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource配置:
                    1.dataSource被称为数据源。
                    2.dataSource作用:为程序提供Connection对象。(只要是给程序提供Connection对象的,都可以叫做数据源)
                    3.数据源实际上是一套规范。JDK中有这套规范:javax.sql.DataSource (这个数据源的规范,实际上就是JDK规定的)
                    4.我们也可以自己实现javax.sql.DataSource接口,编写数据源组件。
                        比如自己写一个数据库连接池
                    5.常见的数据源组件(也称常见的数据库连接池):
                        druid c3p0 dbcp...
                    6.type属性用来指定数据源的类型,就是指定用什么方式来获取Connection对象
                        type有三个值,必须三选一:
                        UNPOOLED:不使用数据库连接池技术。每次都创建新的Connection对象
                        POOLED:使用mybatis自己的数据库连接池
                        JNDI:继承第三方的数据库连接池

                        JNDI是一套规范,大部分web容器都是先了JNDI规范
                            例如:Tomcat、Jetty、WebLogic、WehSphere
                        JNDI是Java命名目录接口。Tomcat服务器实现了这个规范。
            -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ziv_mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        <!--生产环境配置-->
        <environment id="production">
            <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>

五、手写MyBatis框架(掌握原理)

5.1 dom4j解析XML文件

5.2 GodBatis

5.3 GodBatis使用Maven打包

5.4 使用GodBatis

5.5 总结MyBatis框架的重要实现原理

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

6.1 需求描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eOhSumBB-1670084450234)(D:\mybatis笔记\bank需求分析.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOVqei1G-1670084450234)(D:\mybatis笔记\bank数据库表.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kSoneTTY-1670084450234)(D:\mybatis笔记\bank数据准备.png)]

6.3 实现步骤

第一步 创建模块,配置tomcat,引入依赖

<?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>org.ziv</groupId>
  <artifactId>mybatis-004-web</artifactId>
  <version>1.0</version>
  <packaging>war</packaging>

  <name>mybatis-004-web Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>5.0.0</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.11</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>mybatis-004-web</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <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

    <?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="development">
            <environment id="development">
                <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>
    
  • AccountMapper.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="bank">
        <select id="selectByActno" resultType="com.ziv.bank.pojo.Account">
            select id,actno,balance from t_act where #{actno}
        </select>
        <update id="updateByActno">
            update t_act set(balance=#{balance}) where actno = #{actno}
        </update>
    </mapper>
    
  • logback.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 配置文件修改时重新加载,默认true -->
    <configuration debug="false">
        <!-- 控制台输出 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" charset="UTF-8">
                <!-- 输出日志记录格式 -->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
        </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>
    
  • jdbc.properties

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

第二步 前端页面Index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>银行账户转账</title>
</head>
<body>
<form action="/bank/transfer" method="post">
    转出账户:<input type="text" name="fromAct"><br>
    转入账户:<input type="text" name="toAct"><br>
    转账金额:<input type="text" name="money"><br>
    <input type="submit" value="转账">
</form>
</body>
</html>

注意:web.xml中的web-app标签属性metadata-complete=“false”,其值为false表示启用注解编程,其值为true表示不开启注解。

第三步 创建包

com.ziv.bank.pojo
com.ziv.bank.service
com.ziv.bank.service.impl
com.ziv.bank.dao
com.ziv.bank.dao.impl
com.ziv.bank.web.controller
com.ziv.bank.exception
com.ziv.bank.utils

第四步 定义pojo类:Account 和工具类

package com.ziv.bank.pojo;
/**
 * 账户类,封装数据
 * @author ziv
 * @version 1.0
 * @since 1.0
 */
public class Account {
    private Long id;
    private String actno;
    private Double balance;
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public Double getBalance() {
        return balance;
    }
    public void setBalance(Double balance) {
        this.balance = balance;
    }
    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }
    public Account() {
    }
}
package com.ziv.bank.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;

public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	//ThreadLocal解决事务问题
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    private SqlSessionUtil(){}

    /**
     * 获取会话对象
     * @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();
        }
    }
}

第五步 编写AccountDao接口,已经AccountDaoImpl实现类

package com.ziv.bank.dao;
import com.ziv.bank.pojo.Account;
/**
 * 账户的dao对象,负责t_act的CRUD
 *
 * @author ziv
 * @version 1.0
 * @since 1.0
 */
public interface AccountDao {
    /**
     * 通过账号查询账户信息
     * @param actno 账号
     * @return 账户信息
     */
    Account selectByActno(String actno);
    /**
     * 更新账户信息
     * @param act 需要更新的账户
     * @return 1 表示更新成功,其他表示更新失败
     */
    int updateByActno(Account act);
}
package com.ziv.bank.dao.impl;

import com.ziv.bank.dao.AccountDao;
import com.ziv.bank.pojo.Account;
import com.ziv.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 account = (Account) sqlSession.selectOne("bank.selectByActno", actno);
        return account;
    }

    @Override
    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.update("bank.updateByActno", act);
        return count;
    }
}

第六步 编写SQL映射文件 AccountMapper.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="bank">
    <select id="selectByActno" resultType="com.ziv.bank.pojo.Account">
        select id,actno,balance from t_act where #{actno}
    </select>
    <update id="updateByActno">
        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 TransferException extends Exception {
    public TransferException(){}
    public TransferException(String msg){super(msg);}
}
package com.ziv.bank.service;

import com.ziv.bank.exceptions.MoneyNotEnoughException;
import com.ziv.bank.exceptions.TransferException;

/**
 * 处理账户业务的接口
 *
 * @author ziv
 * @version 1.0
 * @since 1.0
 */
public interface AccountService {
    /**
     * 账户转账业务
     *
     * @param fromAct 转出账户
     * @param toAct   转入账户
     * @param money   转账金额
     */
    void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferException;
}
package com.ziv.bank.service.impl;

import com.ziv.bank.dao.AccountDao;
import com.ziv.bank.dao.impl.AccountDaoImpl;
import com.ziv.bank.exceptions.MoneyNotEnoughException;
import com.ziv.bank.exceptions.TransferException;
import com.ziv.bank.pojo.Account;
import com.ziv.bank.service.AccountService;
import com.ziv.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountServiceImpl implements AccountService {

    AccountDao accountDao = new AccountDaoImpl();

    @Override
    public void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferException {
        //添加事务代码
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //判断转出账户余额是否充足
        Account fromAccount = accountDao.selectByActno(fromAct);
        Account toAccount = accountDao.selectByActno(toAct);
        //余额不足,抛出异常
        if (fromAccount.getBalance() < money) {
            throw new MoneyNotEnoughException("余额不足");
        }
        //余额充足,继续转账
        fromAccount.setBalance(fromAccount.getBalance() - money);
        toAccount.setBalance(toAccount.getBalance() + money);
        int count = accountDao.updateByActno(fromAccount);
        //int i = 12/0;
        count += accountDao.updateByActno(toAccount);
        if (count != 2) {
            throw new TransferException("转账失败");
        }
        //提交事务
        sqlSession.commit();
        //关闭事务
        sqlSession.close();
    }
}

第⼋步:编写AccountController

package com.ziv.bank.web;

import com.ziv.bank.exceptions.MoneyNotEnoughException;
import com.ziv.bank.exceptions.TransferException;
import com.ziv.bank.service.AccountService;
import com.ziv.bank.service.impl.AccountServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

//@WebServlet("/transfer")
public class AccountController extends HttpServlet {

    AccountService accountService = new AccountServiceImpl();

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String fromAct = request.getParameter("fromAct");
        String toAct = request.getParameter("toAct");
        double money = Double.parseDouble(request.getParameter("money"));
        try {
            accountService.transfer(fromAct, toAct, money);
            response.sendRedirect(request.getContextPath()+"/success.html");
        } catch (MoneyNotEnoughException e) {
            e.printStackTrace();
            response.sendRedirect(request.getContextPath()+"/error1.html");
        } catch (TransferException e) {
            response.sendRedirect(request.getContextPath()+"/error2.html");
            e.printStackTrace();
        }
    }
}

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

使用Threadlocal解决事务问题

6.5 分析当前程序存在的问题

我们不难发现,这个dao实现类中的⽅法代码很固定,基本上就是⼀⾏代码,通过SqlSession对象调⽤
insert、delete、update、select等⽅法,这个类中的⽅法没有任何业务逻辑,既然是这样,这个类我们
能不能动态的⽣成,以后可以不写这个类吗?答案:可以。

七、使用javassist生成类

7.1 javassist的使用

7.2 使用javassist生成DaoImpl类

package com.ziv.bank.utils;

import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 工具类:可以动态的生成DAO的实现类。(或者说可以动态生成DAO的代理类)
 */
public class GenerateDaoProxy {

    public static Object generate(SqlSession sqlSession, Class daoInterface) {
        //类池
        ClassPool pool = ClassPool.getDefault();
        //制造类(com.ziv.bank.dao.AccountDao --> com.ziv.bank.dao.AccountDaoProxy)
        CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy");
        //制造借口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        //实现接口
        ctClass.addInterface(ctInterface);
        //实现接口中的所有方法
        Method[] methods = daoInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            //method是接口中的抽象方法
            //实现method这个抽象方法
            // Account selectByActno(String actno);
            // public Account selectByActno(String actno){代码;}
            StringBuilder methodCode = new StringBuilder();
            methodCode.append("public ");
            methodCode.append(method.getReturnType().getName());
            methodCode.append(" ");
            methodCode.append(method.getName());
            methodCode.append("(");
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                methodCode.append(parameterType.getName());
                methodCode.append(" ");
                methodCode.append("arg" + i);
                if (i != parameterTypes.length - 1) {
                    methodCode.append(",");
                }
            }
            methodCode.append(")");
            methodCode.append("{");
            //
            methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.ziv.bank.utils.SqlSessionUtil.openSession();");
            String sqlId = daoInterface.getName() + "." + method.getName();
            SqlCommandType commandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
            if (commandType == SqlCommandType.DELETE) {

            }
            if (commandType == SqlCommandType.INSERT) {

            }
            if (commandType == SqlCommandType.UPDATE) {
                methodCode.append("return sqlSession.update(\"" + sqlId + "\", arg0);");
            }
            if (commandType == SqlCommandType.SELECT) {
                methodCode.append("return (" + method.getReturnType().getName() + ")sqlSession.selectOne(\"" + sqlId + "\", arg0);");
            }
            methodCode.append("}");
            try {
                CtMethod ctMethod = CtMethod.make(String.valueOf(methodCode), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (CannotCompileException e) {
                e.printStackTrace();
            }
        });
        //创建对象
        Object obj = null;
        try {
            Class<?> clazz = ctClass.toClass();
            obj = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

八、MyBatis中接口代理机制及使用

private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);

使⽤以上代码的前提是:AccountMapper.xml⽂件中的namespace必须和dao接⼝的全限定名称⼀致,id必须和dao接⼝中⽅法名⼀致。

九、MyBatis小技巧

9.1 #{ }和${ }

  • #{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防⽌sql注⼊,⽐较常⽤。
  • ${}:先进⾏sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注⼊现象。只有在需要进⾏sql语句关键字拼接的情况下才会⽤到。

使用${}的时机:

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

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

  • 拼接表名

    例如:在前端传参数决定查询哪个表,t_log20221201,t_log20221202等等。

  • 模糊查询

    有三种解决方法

    name like '%${name}%'
    name like concat('%',#{name},'%')
    name like "%"#{name}"%"
    

9.2 typeAliases

在mybatis-config.xml中指定:

<!--起别名-->
<typeAliases>
    <!--
         type:指定给哪个类型起别名
         alias:指定别名
	    别名不区分大小写
	    namespace不能使用别名
		alias属性可以省略,默认就是类的简名(Car)
    -->
    <typeAlias type="com.ziv.mybatis.pojo.Car" alias="car"/>
    <typeAlias type="com.ziv.mybatis.pojo.Car"/>
    <!--使用package将对应包下的所有类全部自动起别名,默认就是类的简名-->
    <package name="com.ziv.mybatis.pojo"/>
</typeAliases>

在CarMapper.xml标签resultType属性中使用

<select id="selectAll" resultType="car">
    select id,
    car_num      as carNum,
    brand,
    guide_price  as guidePrice,
    produce_time as produceTime,
    car_type     as carType
    from t_car
</select>

9.3 mappers

<!--
    mapper的三个属性:
        resource:从类路径中加载
        url:从指定的全限定资源路径中加载
        class:使⽤映射器接⼝实现类的完全限定类名
        package:将包内的映射器接⼝实现全部注册为映射器
-->
<mapper resource="" class="" url=""/>
<!--
	package:将包内的映射器接⼝实现全部注册为映射器
	使用此方法需要将SqlMapper.xml文件和同名接口放在同一个目录下
-->
<package name="com.ziv.mybatis.mapper"/>

9.4 idea配置文件模板

File->Settings->Editor->File and Code Templates

<?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=""/>
    <typeAliases>
        <package name=""/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <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>
        <package name=""/>
    </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="">

</mapper>

9.5 插入数据时获取自动生成的主键

<!--
    useGeneratedKeys="true" 使用自动生成的主键值
    keyProperty="id" 指定主键值赋值给对象的哪个属性。这个表示将自动生成的主键值赋值给Car的id属性
-->
<insert id="insertCarUseGeneratedKey" useGeneratedKeys="true" keyProperty="id">
    insert into t_car(id, car_num, brand, guide_price, produce_time, car_type)
    values (null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
</insert>
@Test
public void testInsertCarUseGeneratedKey() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = new Car(null, "9000", "Kaimeirui", 35.0, "2000-10-10", "燃油车");
    mapper.insertCarUseGeneratedKey(car);
    sqlSession.commit();
    sqlSession.close();
    System.out.println(car);
}

输出car之后发现id被赋上了自动生成的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aQOnRj0C-1670084450235)(D:\mybatis笔记\输出结果.png)]

十、MyBatis参数处理

准备数据库表和数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAcndPdZ-1670084450235)(D:\mybatis笔记\学生表.png)]

10.1 单个简单类型参数

简单类型包括:

  • byte short int long float double char
  • Byte Short Integer Long Float Double Character
  • String
  • java.util.Date
  • java.sql.Date

mybatis可以自动识别简单类型,底层可以自动推断使用哪一个preparedStatement.setXxx()方法。比如传String的值,就会调用preparedStatement.setString()方法。

SQL映射文件中的标签比较完整的写法是:

<select id="selectStudentByName" resultType="student" parameterType="string">
	select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>

resultType和parameterType里面本来应该填写全限定类名,但是mybatis默认提供了别名(详细查看mybatis文档),所以这里直接填写类简名。另外,sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是⽤来帮助mybatis进⾏类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强⼤的⾃动类型推断机制。

  • javaType:可以省略
  • jdbcType:可以省略
  • parameterType:可以省略

10.2 map参数

这种⽅式是⼿动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使⽤的时候通过#{map集合的key}来取值。

10.3 实体类参数

这⾥需要注意的是:#{} ⾥⾯写的是属性名字。这个属性名其本质上是:set/get⽅法名去掉set/get之后的名字。

10.4 多参数

对于多个参数,mybatis底层会创建⼀个map集合,以arg0/param1为key,传入的参数为 value
例如:

List<Student> students = mapper.selectByNameAndSex("张三", '⼥')
//底层封装成了这样的map集合,其中arg从0开始,param从1开始
Map<String,Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);

<select id="selectByNameAndSex">
	select * from t_student where name = #{arg0} and sex = #{arg1}
</select>

10.5 @Param注解(命名参数)

mybatis提供的key可读性太差,可以使用@Param注解自己指定key的名字

List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
<select id="selectByNameAndAge">
	select * from t_student where name = #{name} and sex = #{age}
</select>

注意:@Param替换的是map集合中arg0,arg1… 另外的param1,param2…还是可以继续使用的。

10.6 @Param源码分析

十一、MyBatis查询语句专题

11.1 返回Car

当查询的结果,有对应的实体类,并且查询结果只有⼀条时,可以直接用实体类接收,也可以用List集合接收。

11.2 返回List

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

11.3 返回Map

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXI7LR0s-1670084450236)(D:\mybatis笔记\返回map.png)]

11.4 返回List

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

11.5 返回Map<String,Map>

拿Car的id做key,以后取出对应的Map集合时更⽅便。

/**
  * 获取所有的Car,返回⼀个Map集合。 
  * Map集合的key是Car的id。 
  * Map集合的value是对应Car。 
  * 需要在接口上添加@MapKey注解,指定id作为map的key
  * @return
  */
@MapKey("id")
Map<Long,Map<String,Object>> selectAllRetMap();
    <select id="selectAllRetMap" resultType="map">
        select id,
               car_num      carNum,
               brand,
               guide_price  guidePrice,
               produce_time produceTime,
               car_type     carType
        from t_car
    </select>

11.6 resultMap结果映射

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

  • 第⼀种⽅式:as 给列起别名
  • 第⼆种⽅式:使⽤resultMap进⾏结果映射
  • 第三种⽅式:是否开启驼峰命名⾃动映射(配置settings

使⽤resultMap进⾏结果映射

<!--
    resultMap:
        id:这个结果映射的标识,作为select标签的resultMap属性的值。
        type:结果集要映射的类。可以使用别名。
	    property:POJO类的属性名
		column:数据库表的列名
-->
<resultMap id="carResultMap" type="car">
    <!--对象的唯一标识,如果有主键,建议使用id标签,官方解释是:为了提高mybatis的性能,建议写上-->
    <id property="id" column="id"/>
    <result property="carNum" column="car_num"/>
    <!--当属性名和数据库列名一致时,可以省略,但是建议都写上-->
    <!--javaType用来指定属性类型。jdbcType用来指定列类型,一般可以省略-->
    <result property="brand" column="brand" javaType="string" jdbcType="VARCHAR"/>
    <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>

是否开启驼峰命名⾃动映射
使⽤这种⽅式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
Java命名规范:⾸字⺟⼩写,后⾯每个单词⾸字⺟⼤写,遵循驼峰命名⽅式。
SQL命名规范:全部⼩写,单词之间采⽤下划线分割。

在mybatis-config.xml⽂件中进⾏配置:

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

11.7 返回总记录条数

<select id="selectAll" 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);
}

十二、动态SQL

12.1 if标签

<!--
    if标签中test属性的必须的,值为boolean类型,如果为true则把标签内容拼接到sql语句中,反之不拼接
    test中参数的使用:
        1.当使用了@Param注解时,则使用@Param注解指定的参数名
        2.当没有使用@Param注解时,则使用param1 param2 param3 arg0 arg1 arg2
        3.当使用了pojo类,则使用pojo类的属性名
    在mybatis的动态sql中,不能使用&&,只能使用and
-->
<!--为了解决参数为空的问题,在where后面加一个恒成立的语句(1=1),这样就需要第一个if前加一个and,来保证sql语句的正确-->
<select id="selectByMultiCondition" resultType="car">
    select * from t_car where 1=1
    <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>
</select>

12.2 where标签

where标签的作⽤:让where⼦句更加动态智能。

  • 所有条件都为空时,where标签保证不会⽣成where⼦句。
  • ⾃动去除某些条件前⾯多余的and或or。
<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>

12.3 trim标签

trim标签的属性:

  • prefix:在trim标签中的语句前添加内容
  • suffix:在trim标签中的语句后添加内容
  • prefixOverrides:前缀覆盖掉(去掉)
  • suffixOverrides:后缀覆盖掉(去掉)
<!--
    prefix="where" 是在trim标签所有内容的前面添加 where
    suffixOverrides="and|or" 是把trim标签中内容的后缀and或or去掉
-->
<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>

12.4 set标签

主要使⽤在update语句当中,⽤来⽣成set关键字,同时去掉最后多余的“,”
⽐如我们只更新提交的不为空的字段,如果提交的数据是null或者"",那么这个字段我们将不更新。

<update id="updateBySet">
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},</if>
</set>
where id = #{id};
</update>

12.5 choose when otherwise

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

就等同于

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

有且仅有⼀个分⽀会被选择!!!!

<select id="selectByChoose" 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>
            <when test="carType != null and carType != ''">
                car_type = #{carType}
            </when>
        </choose>
    </where>
</select>

12.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;

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','新能源')

批量删除

定义接口

int deleteByIds(@Param("ids")Long[] ids);

sql语句

<!--
    foreach标签的属性:
        collection:指定数组或者集合,默认值为array或arg0.也可以使用@Param注解自行指定
        item:代表数组或集合中的元素
        separator:循环之间的分隔符
        open:在循坏开始之前拼接的内容
        close:在循坏结束之后拼接的内容
-->
<delete id="deleteByIds">
    delete from t_car where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
</delete>

批量插入

定义接口

int insertBatch(@Param("cars") List<Car> cars);

sql语句

<insert id="insertBatch">
    insert into t_car values
    <foreach collection="cars" item="car" separator=",">
        (null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
    </foreach>
</insert>

12.7 sql标签与include标签

sql标签⽤来声明sql⽚段
include标签⽤来将声明的sql⽚段包含到某个sql语句当中
作⽤:代码复⽤。易维护。

<sql id="carColumnName">
    id,
    car_num      as carNum,
    brand,
    guide_price  as guidePrice,
    produce_time as produceTime,
    car_type     as carType
</sql>

<select id="selectCarById" resultType="com.ziv.mybatis.pojo.Car">
    select 
    <include refid="carColumnName"/>
    from t_car
    where id = #{id}
</select>

十三、MyBatis的高级映射及延迟加载

准备数据库表和数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9PV72KF6-1670084450236)(D:\mybatis笔记\高级映射数据表1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lqE6FX55-1670084450236)(D:\mybatis笔记\高级映射数据表2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkpEPLt6-1670084450237)(D:\mybatis笔记\高级映射数据表数据.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jakb7yVy-1670084450246)(D:\mybatis笔记\高级映射数据表数据2.png)]

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

创建相关的类和文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6WRDMO9-1670084450246)(D:\mybatis笔记\高级映射类和文件.png)]

13.1 多对一

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

  • 第⼀种⽅式:⼀条SQL语句,级联属性映射。
  • 第⼆种⽅式:⼀条SQL语句,association。
  • 第三种⽅式:两条SQL语句,分步查询。(这种⽅式常⽤:优点⼀是可复⽤。优点⼆是⽀持懒加载。)

第⼀种⽅式:级联属性映射

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Dnl6xhe-1670084450247)(D:\mybatis笔记\级联映射.png)]

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

编写sql语句:

<!--多对一映射的第一种方式:一条SQL语句,级联属性映射-->
<resultMap id="studentResultMap" type="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <!--property="clazz.cid" 中填的是Student对象中Clazz属性中的cid属性-->
    <!--column="cid" 中填的是数据库查询出来的列名-->
    <result property="clazz.cid" column="cid"/>
    <result property="clazz.cname" column="cname"/>
</resultMap>

<!--Student selectById(Integer sid);-->
<select id="selectById" resultMap="studentResultMap">
    select s.sid, s.sname, c.cid, c.cname
    from t_stu s left join t_clazz c on s.cid = c.cid
    where s.sid = #{sid}
</select>

测试程序:

@Test
public void testSelectById(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Student student = mapper.selectById(1);
    System.out.println(student);
    sqlSession.close();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fgSNZVjM-1670084450247)(D:\mybatis笔记\image-20221203173344950.png)]

第⼆种⽅式:association

其他位置都不需要修改,只需要修改resultMap中的配置:association即可。

<resultMap id="studentResultMapAssociation" type="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <!--
        association:翻译为关联。一个Student对象关联一个Clazz对象
            property="clazz" 中填写要映射的pojo类的属性名
            javaType="Clazz" 中填写要映射的Java类型(自动起了别名,不需要写权限的类名)
    -->
    <association property="clazz" javaType="Clazz">
		<id property="cid" column="cid"/>
		<result property="cname" column="cname"/>
    </association>
</resultMap>

<!--Student selectByIdAssociation(Integer sid);-->
<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
    select s.sid, s.sname, c.cid, c.cname
    from t_stu s left join t_clazz c on s.cid = c.cid
    where s.sid = #{sid}
</select>

测试结果和第一种方法一样

第三种⽅式:分步查询

其他位置不需要修改,只需要修改以及添加以下三处:
第⼀处:association中select位置填写sqlId。sqlId=namespace+id。其中column属性作为这条⼦sql语句的条件。

<!--两天SQl语句完成多对一的分步查询-->
<!--第一步:根据学生的id查询学生的所有信息,这些信息中含有班级id(cid)-->
<resultMap id="selectResultMapByStep" type="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <!--property="" 中填写Student对象中的属性名clazz-->
    <!--select="" 中填写第二步需要执行的SQl语句的id-->
    <!--column="" 中填写的是sql语句中查询出来的列名-->
    <association property="clazz"
                 select="com.ziv.mybatis.mapper.ClazzMapper.selectByIdStepTwo"
                 column="cid"/>
</resultMap>

<!--Student selectByIdStepOne(Integer sid);-->
<select id="selectByIdStepOne" resultMap="selectResultMapByStep">
    select sid,sname,cid from t_stu where sid = #{sid}
</select>

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

package com.ziv.mybatis.mapper;

import com.ziv.mybatis.pojo.Clazz;

/**
 * @author ziv
 */
public interface ClazzMapper {

    /**
     * 根据id查询班级的信息
     * @param cid
     * @return
     */
    Clazz selectByIdStepTwo(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.ziv.mybatis.mapper.ClazzMapper">

    <!--Clazz selectByIdStepTwo(Integer cid);-->
    <!--分步查询第二步:根据cid获取班级信息-->
    <select id="selectByIdStepTwo" resultType="Clazz">
        select cid,cname from t_clazz where cid = #{cid}
    </select>
</mapper>

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NtiSpKFU-1670084450248)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203191839504.png)]

分步优点:

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

13.2 多对一延迟加载

要想⽀持延迟加载,⾮常简单,只需要在association标签中添加fetchType="lazy"即可。
修改StudentMapper.xml⽂件:

<resultMap id="selectResultMapByStep" type="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <!--fetchType="lazy" 开启懒加载-->
    <association property="clazz"
                 select="com.ziv.mybatis.mapper.ClazzMapper.selectByIdStepTwo"
                 column="cid" 
                 fetchType="lazy"/>
</resultMap>

测试结果,只执行了一条sql语句

@Test
public void testSelectByIdStepOne(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Student student = mapper.selectByIdStepOne(1);
    //System.out.println(student);
    //只需要看学生的名字
    System.out.println(student.getSname());
    sqlSession.close();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHAMPOKe-1670084450248)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203192651945.png)]

在association标签中设置fetchType=“lazy”,只在当前association关联的sql语句中生效,如果需要添加全局设置,则需要在mybatis-config.xml中配置一下:

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

如果开启了全局懒加载配置,但是在某个association中不希望使用懒加载,则在association中设置属性:fetchType=“eager”

13.3 一对多

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aStfFQ0t-1670084450248)(D:\mybatis笔记\高级映射一对多.png)]

⼀对多的实现,通常是在⼀的⼀⽅中有List集合属性。
在Clazz类中添加List stus; 属性。

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

  • 第⼀种⽅式:collection
  • 第⼆种⽅式:分步查询

第⼀种⽅式:collection

<resultMap id="clazzResultMap" type="Clazz">
    <id property="cid" column="cid"/>
    <result property="cname" column="cname"/>
    <!--一对多。collection就是集合的意思-->
    <!--property属性指定clazz对象中的属性-->
    <!--ofType属性用来指定集合当中的元素类型-->
    <collection property="stus" ofType="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
    </collection>
</resultMap>

<!--Clazz selectByCollection(Integer cid);-->
<select id="selectByCollection" resultMap="clazzResultMap">
    select c.cid, c.cname, s.sid, s.sname
    from t_clazz c left join t_stu s on c.cid = s.cid
    where c.cid = #{cid}
</select>

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-smMWydO6-1670084450249)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203195816354.png)]

第⼆种⽅式:分步查询

与多对一的分步查询类似

<resultMap id="clazzResultMapByStep" type="Clazz">
    <id property="cid" column="cid"/>
    <result property="cname" column="cname"/>
    <!--property属性对应对象想要赋值的属性-->
    <!--select属性对应第二步想要执行的sql语句id-->
    <!--column属性对象想要传给select属性的值,对应下面sql查出来的列名-->
    <collection property="stus"
                select="com.ziv.mybatis.mapper.StudentMapper.selectByCidStepTwo" 
                column="cid"/>
</resultMap>

<!--Clazz selectByStepOne(Integer cid);-->
<select id="selectByStepOne" resultMap="clazzResultMapByStep">
    select cid,cname from t_clazz where cid = #{cid}
</select>

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ps2Lu0Jx-1670084450249)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203201346330.png)]

13.4 一对多延迟加载

与多对一相同

十四、MyBatis的缓存

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

  • ⼀级缓存:将查询到的数据存储到SqlSession中。
  • ⼆级缓存:将查询到的数据存储到SqlSessionFactory中。
  • 或者集成其它第三⽅的缓存:⽐如EhCache【Java语⾔开发的】、Memcache【C语⾔开发的】等。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v7fj7Nvc-1670084450249)(D:\mybatis笔记\mybatis缓存.png)]

14.1 一级缓存

⼀级缓存默认是开启的。不需要做任何配置。
原理:只要使⽤同⼀个SqlSession对象执⾏同⼀条SQL语句,就会⾛缓存。

测试

@Test
public void testSelectByCollection(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz1 = mapper.selectByCollection(1001);
    System.out.println(clazz1);
    Clazz clazz2 = mapper.selectByCollection(1001);
    System.out.println(clazz2);
    sqlSession.close();
}

执行结果,只执行了一次sql语句,第二次是直接从缓存中拿到数据输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAHDYe2P-1670084450250)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203223726884.png)]

什么情况下不会⾛缓存?

  • 第⼀种:不同的SqlSession对象。
  • 第⼆种:查询条件变化了。

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

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

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

14.2 二级缓存

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

  1. 全局性地开启或关闭所有映射器配置⽂件中已配置的任何缓存。默认就是true,⽆需设置。
<setting name="cacheEnabled" value="true">
  1. 在需要使⽤⼆级缓存的SqlMapper.xml⽂件中添加配置:

    <!--默认情况下,二级缓存是开启的。
    只要在对应的SqlMapperx.xml文件中添加如下标签,就表示此文件中使用二级缓存-->
    <cache/>
    
  2. 使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝

  3. SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤。

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

⼆级缓存的相关配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8A1ddhOY-1670084450250)(D:\mybatis笔记\cache标签的属性.png)]

  1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采⽤LRU策略。
    • a. LRU:Least Recently Used。最近最少使⽤。优先淘汰在间隔时间内使⽤频率最低的对象。(其实还有⼀种淘汰算法LFU,最不常⽤。)
    • b. FIFO:First In First Out。⼀种先进先出的数据缓存器。先进⼊⼆级缓存的对象最先被淘汰。
    • c. SOFT:软引⽤。淘汰软引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
    • d. WEAK:弱引⽤。淘汰弱引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
  2. flushInterval:
    • a. ⼆级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存⾜够⼤,⼀直会向⼆级缓存中缓存数据。除⾮执⾏了增删改。
  3. readOnly:
    • a. true:多条相同的sql语句执⾏之后返回的对象是共享的同⼀个。性能好。但是多线程并发可能会存在安全问题。
    • b. false:多条相同的sql语句执⾏之后返回的对象是副本,调⽤了clone⽅法。性能⼀般。但安全。
  4. size:
    • a. 设置⼆级缓存中最多可存储的java对象数量。默认值1024。

14.3 MyBatis集成EhCache

集成EhCache是为了代替mybatis⾃带的⼆级缓存。⼀级缓存是⽆法替代的。
mybatis对外提供了接⼝,也可以集成第三⽅的缓存组件。⽐如EhCache、Memcache等。都可以。
EhCache是Java写的。Memcache是C语⾔写的。所以mybatis集成EhCache较为常⻅,按照以下步骤操作,就可以完成集成:

  • 第⼀步:引⼊mybatis整合ehcache的依赖。

    <!--mybatis集成ehcache的组件-->
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.2.2</version>
    </dependency>
    <!--ehcache需要slf4j的⽇志组件,log4j不好使-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
        <scope>test</scope>
    </dependency>
    
  • 第⼆步:在类的根路径下新建echcache.xml⽂件,并提供以下配置信息。

  • <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
        <!--磁盘存储:将缓存中暂时不使⽤的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
        <diskStore path="e:/ehcache"/>
    
        <!--defaultCache:默认的管理策略-->
        <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
        <!--maxElementsInMemory:在内存中缓存的element的最⼤数⽬-->
        <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
        <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
        <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多⻓时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示⼀直可以访问-->
        <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示⼀直可以访问-->
        <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
        <!--FIFO:first in first out (先进先出)-->
        <!--LFU:Less Frequently Used (最少使⽤).意思是⼀直以来最少被使⽤的。缓存的元素有⼀个hit 属性,hit 值最⼩的将会被清出缓存-->
        <!--LRU:Least Recently Used(最近最少使⽤). (ehcache 默认值).缓存的元素有⼀个时间戳,当缓存容量满了,⽽⼜需要腾出地⽅来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
        <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
                      timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
    
    </ehcache>
    
  • 第三步:修改SqlMapper.xml⽂件中的标签,添加type属性。

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    
  • 第四步:编写测试程序使⽤。

十五、MyBatis的逆向工程

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

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

  • pojo类名、包名以及⽣成位置。
  • SqlMapper.xml⽂件名以及⽣成位置。
  • Mapper接⼝名以及⽣成位置。
  • 连接数据库的信息。
  • 指定哪些表参与逆向⼯程。

15.1 逆向工程配置与生产

第一步: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//EN"
        "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.UnmergeableXmlMappersPlugin"/>
        <commentGenerator>
            <!--是否去掉⽣成⽇期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/ziv_mybatis"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- ⽣成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.ziv.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启⼦包-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空⽩-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- ⽣成SQL映射⽂件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.ziv.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启⼦包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- ⽣成Mapper接⼝的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.ziv.mybatis.mapper"
                targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 表名和对应的实体类名-->
        <table tableName="t_car" domainObjectName="Car"/>
    </context>
</generatorConfiguration>

第四步:运行插件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AnyhzyBk-1670084450250)(D:\mybatis笔记\image-20221203233414173.png)]

15.2 测试逆向工程生产的是否好用

@Test
public void test() throws Exception {
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder
        ().build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    // 查⼀个
    Car car = mapper.selectByPrimaryKey(89L);
    System.out.println(car);
    // 查所有,没有条件就是查询所有
    List<Car> cars = mapper.selectByExample(null);
    cars.forEach(c -> System.out.println(c));
    // 多条件查询
    // QBC ⻛格:Query By Criteria ⼀种查询⽅式,⽐较⾯向对象,看不到sql语句。
    CarExample carExample = new CarExample();
    carExample.createCriteria()
        .andBrandEqualTo("丰⽥霸道")
        .andGuidePriceGreaterThan(new BigDecimal(60.0));
    carExample.or().andProduceTimeBetween("2000-10-11", "2022-10-11");
    mapper.selectByExample(carExample);
    sqlSession.commit();
}

十六、MyBatis使用PageHelper

16.1 limit分页

mysql的limit后⾯两个数字:

  • 第⼀个数字:startIndex(起始下标。下标从0开始。)
  • 第⼆个数字:pageSize(每⻚显示的记录条数)

假设已知⻚码pageNum,还有每⻚显示的记录条数pageSize,第⼀个数字可以动态的获取吗?
startIndex = (pageNum - 1) * pageSize
所以,标准通⽤的mysql分⻚SQL:

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

16.2 使⽤mybatis分页

CarMapper接口

List<Car> selectAllByPage(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);

CarMapper.xml

<select id="selectAllByPage" resultType="Car">
    select * from t_car limit #{startIndex},#{pageSize}
</select>

16.3 PageHelper插件

第一步:引入依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>

第二步:在mybatis-config.xml中配置插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

第三步:编写Java代码

@Test
public void testPageHelper() throws Exception {
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(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=15, pages=8,
    list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false}
    [Car{id=4, carNum='1003', brand='丰田霸道', guidePrice=32.00, produceTime='2001-10-11', carType='燃油车'},
    Car{id=5, carNum='1003', brand='丰田霸道', guidePrice=33.00, produceTime='2002-10-11', carType='燃油车'}],
    prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
    navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}
    */
}

十七、MyBatis的注解式开发

mybatis中也提供了注解式开发⽅式,采⽤注解可以减少Sql映射⽂件的配置。
当然,使⽤注解式开发的话,sql语句是写在java程序中的,这种⽅式也会给sql语句的维护带来成本。
原则:简单sql可以注解。复杂sql使⽤xml。

17.1 @Insert

@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);

17.2 @Delete

@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);

17.3 @Update

@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}"
int update(Car car);

17.4 @Select

@Select("select * from t_car where id = #{id}")
	@Results({
        @Result(column = "id", property = "id", id = true), 
        @Result(column = "car_num", property = "carNum"), 
        @Result(column = "brand", property = "brand"),
        @Result(column = "guide_price", property = "guidePrice"), 
        @Result(column = "produce_time", property = "produceTime"), 
        @Result(column = "car_type", property = "carType")
    })
Car selectById(Long id);
  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值