Bootstrap

超靠谱教程速成MyBatis!

MyBatis

1.内容介绍

image-20240714193329454

image-20240714193431906

2.介绍MyBatis

什么是MyBatis?

  • MyBatis是优秀的持久层框架
  • MyBatis使用XML将SQL与程序解耦,便于维护
  • MyBatis学习简单,执行高效,是JDBC的延伸

MyBatis开发流程

  1. 引入MyBatis依赖
  2. 创建核心配置文件
  3. 创建实体(Entity)类
  4. 创建Mapper映射文件
  5. 初始化SessionFactory
  6. 利用SqlSession对象操作数据

3.单元测试与JUnit4

单元测试

  • 单元测试是指对软件中的最小可测试单元进行检查和验证
  • 测试用例是指编写一段代码对已有功能(方法)进行校验
  • JUnit 4是Java中最著名的单元测试工具,主流IDE内置支持

JUnit 4使用方法

  1. 引入JUnit Jar包或增加Maven依赖
  2. 编写测试用例验证目标方法是否正确运行
  3. 在测试用例上增加@Test注解开始单元测试

在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.imooc</groupId>
    <artifactId>maven-junit4</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

</project>

编写Calculator类

package com.imooc.junit;

public class Calculator {
    //加法运算
    public int add(int a , int b){
        return a + b;
    }
    //减法运算
    public int subtract(int a , int b){
        return a - b;
    }
    //乘法运算
    public int multiply(int a , int b){
        return a * b;
    }
    //除法运算
    public float divide(int a,int b){
        if(b==0){
            throw new ArithmeticException("除数不能为0");
        }
        return (a*1f) / b;
    }

}

编写CalculatorTest类验证各个方法

package com.imooc.junit;

import org.junit.Test;

import static org.junit.Assert.*;

public class CalculatorTest {
    private Calculator cal = new Calculator();
    @Test
    public void add() {
        int result = cal.add(1, 2);
        System.out.println(result);
    }

    @Test
    public void subtract() {
        int result = cal.subtract(1, 2);
        System.out.println(result);
    }

    @Test
    public void multiply() {
        int result = cal.multiply(1, 2);
        System.out.println(result);
    }

    @Test
    public void divide() {
        float result = cal.divide(1, 2);
        System.out.println(result);
    }
}

4. MyBatis环境配置

image-20240713233347064

image-20240713233403952

首先在pom.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 default="dev">
        <!--配置环境,不同的环境不同的id名字-->
        <environment id="dev">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

5. SqlSessionFactory

image-20240713234041287

image-20240713234129884

创建MyBatisTestor类,获取SqlSessionFactory和SqlSession对象

//JUNIT单元测试类
public class MyBatisTestor {
    /**
     * 初始化SqlSessionFactory
     * @throws IOException
     */
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try {
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于与数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }

}

6. MyBatis数据查询

初始化工具类

初始化工具类MyBatisUtils,使得SqlSessionFactory全局唯一

/**
 * MyBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
 */
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    //利用静态块在初始化类时实例化sqlSessionFactory
    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * openSession 创建一个新的SqlSession对象
     * @return SqlSession对象
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    /**
     * 释放一个有效的SqlSession对象
     * @param session 准备释放SqlSession对象
     */
    public static void closeSession(SqlSession session){
        if(session != null){
            session.close();
        }
    }
}

于是可以直接通过openSession方法获取SqlSession对象,closeSession释放SqlSession对象

MyBatis数据查询

image-20240714144134660

生成实体操作

package com.imooc.mybatis.entity;

import java.util.List;

public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    private List<GoodsDetail> goodsDetails;

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public Float getOriginalCost() {
        return originalCost;
    }

    public void setOriginalCost(Float originalCost) {
        this.originalCost = originalCost;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }

    public Float getDiscount() {
        return discount;
    }

    public void setDiscount(Float discount) {
        this.discount = discount;
    }

    public Integer getIsFreeDelivery() {
        return isFreeDelivery;
    }

    public void setIsFreeDelivery(Integer isFreeDelivery) {
        this.isFreeDelivery = isFreeDelivery;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public List<GoodsDetail> getGoodsDetails() {
        return goodsDetails;
    }

    public void setGoodsDetails(List<GoodsDetail> goodsDetails) {
        this.goodsDetails = goodsDetails;
    }
}

生成映射器mapper,goods.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="goods">
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
        select * from t_goods order by goods_id desc limit 10
    </select>
</mapper>

在mybatis-config.xml中进行声明,这样mybatis在初始化时才知道goods.xml的存在

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

接着打开测试用例类MyBatisTestor.java,执行该selectAll语句得到结果

/**
 * select查询语句执行
 * @throws Exception
 */
@Test
public void testSelectAll() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        //参数为命名空间.语句ID
        List<Goods> list = session.selectList("goods.selectAll");
        for(Goods g : list){
            System.out.println(g.getTitle());
        }
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

另:mybatis-config.xml中设置驼峰命名转换,有些属性获取不到的数据也能正常获取到了

<settings>
    <!-- goods_id ==> goodsId 驼峰命名转换 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

7. SQL传参

goods.xml

<!-- 单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数-->
<select id="selectById" parameterType="Integer" resultType="com.imooc.mybatis.entity.Goods">
    select * from t_goods where  goods_id = #{value}
</select>

<!-- 多参数传递时,使用parameterType指定Map接口,SQL中#{key}提取参数 -->
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
    select * from t_goods
    where
      current_price between  #{min} and #{max}
    order by current_price
    limit 0,#{limt}
</select>

MyBatisTestor.java

/**
 * 传递单个SQL参数
 * @throws Exception
 */
@Test
public void testSelectById() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        Goods goods = session.selectOne("goods.selectById" , 1603);
        System.out.println(goods.getTitle());
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

/**
 * 传递多个SQL参数
 * @throws Exception
 */
@Test
public void testSelectByPriceRange() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        Map param = new HashMap();
        param.put("min",100);
        param.put("max" , 500);
        param.put("limt" , 10);
        List<Goods> list = session.selectList("goods.selectByPriceRange", param);
        for(Goods g:list){
            System.out.println(g.getTitle() + ":" + g.getCurrentPrice());

        }
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

8. 获取多表关联查询结果

大量企业级应用都是多表联合查询产生一个复杂的结果集

goods.xml

<!-- 利用LinkedHashMap保存多表关联结果
    MyBatis会将每一条记录包装为LinkedHashMap对象
    key是字段名  value是字段对应的值 , 字段类型根据表结构进行自动判断
    优点: 易于扩展,易于使用
    缺点: 太过灵活,无法进行编译时检查
 -->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
    select g.* , c.category_name,'1' as test from t_goods g , t_category c
    where g.category_id = c.category_id
</select>

MyBatisTestor.java

    /**
     * 利用Map接收关联查询结果
     * @throws Exception
     */
    @Test
    public void testSelectGoodsMap() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Map> list = session.selectList("goods.selectGoodsMap");
            for(Map map : list){
                System.out.println(map);
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

9. ResultMap结果映射

用对象保存多表关联查询的数据

image-20240714151915966

创建一个全新的类GoodDTO,不要破坏Goods类

package com.imooc.mybatis.dto;

import com.imooc.mybatis.entity.Category;
import com.imooc.mybatis.entity.Goods;
//Data Transfer Object--数据传输对象
public class GoodsDTO {
    private Goods goods = new Goods();
    private Category category = new Category();
    private String test;

    public Goods getGoods() {
        return goods;
    }

    public void setGoods(Goods goods) {
        this.goods = goods;
    }

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }
}

使用ResultMap结果映射,使得mybatis自动为该类对象的属性赋值

goods.xml

<!--结果映射-->
    <resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <!--设置主键字段与属性映射-->
        <id property="goods.goodsId" column="goods_id"></id>
        <!--设置非主键字段与属性映射-->
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"></result>
        <result property="category.categoryName" column="category_name"></result>
        <result property="category.parentId" column="parent_id"></result>
        <result property="category.categoryLevel" column="category_level"></result>
        <result property="category.categoryOrder" column="category_order"></result>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' as test from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

MyBatisTestor.java

    /**
     * 利用ResultMap进行结果映射
     * @throws Exception
     */
    @Test
    public void testSelectGoodsDTO() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list) {
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

10. MyBatis数据插入操作

image-20240714152855651

要么数据全部插入成功,要么出现了问题全部回滚

goods.xml

    <insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
      <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
    <!--获取当前连接最后产生的id号-->
        select last_insert_id()
    </selectKey>
  </insert>

MyBatisTestor.java

/**
 * 新增数据
 * @throws Exception
 */
@Test
public void testInsert() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        Goods goods = new Goods();
        goods.setTitle("测试商品");
        goods.setSubTitle("测试子标题");
        goods.setOriginalCost(200f);
        goods.setCurrentPrice(100f);
        goods.setDiscount(0.5f);
        goods.setIsFreeDelivery(1);
        goods.setCategoryId(43);
        //insert()方法返回值代表本次成功插入的记录总数
        int num = session.insert("goods.insert", goods);
        session.commit();//提交事务数据
        System.out.println(goods.getGoodsId());
    }catch (Exception e){
        if(session != null){
            session.rollback();//回滚事务
        }
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

11. selectKey与useGeneratedKeys的区别

image-20240714161349007

image-20240714161406051

image-20240714161425183

image-20240714161441638

image-20240714161500433

12. 更新与删除操作

goods.xml

<update id="update" parameterType="com.imooc.mybatis.entity.Goods">
    UPDATE t_goods
    SET
      title = #{title} ,
      sub_title = #{subTitle} ,
      original_cost = #{originalCost} ,
      current_price = #{currentPrice} ,
      discount = #{discount} ,
      is_free_delivery = #{isFreeDelivery} ,
      category_id = #{categoryId}
    WHERE
      goods_id = #{goodsId}
</update>
<!--delete from t_goods where goods_id in (1920,1921)-->
  <delete id="delete" parameterType="Integer">
      delete from t_goods where goods_id = #{value}
  </delete>

MyBatisTestor.java

/**
 * 更新数据
 * @throws Exception
 */
@Test
public void testUpdate() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        Goods goods = session.selectOne("goods.selectById", 739);
        goods.setTitle("更新测试商品");
        int num = session.update("goods.update" , goods);
        session.commit();//提交事务数据
    }catch (Exception e){
        if(session != null){
            session.rollback();//回滚事务
        }
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

/**
 * 删除数据
 * @throws Exception
 */
@Test
public void testDelete() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        int num = session.delete("goods.delete" , 739);
        session.commit();//提交事务数据
    }catch (Exception e){
        if(session != null){
            session.rollback();//回滚事务
        }
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

13. 预防SQL注入攻击

image-20240714162146090

image-20240714162225268

goods.xml

<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
    select * from t_goods where title = #{title}
    ${order}
</select>
/**
 * 预防SQL注入
 * @throws Exception
 */
@Test
public void testSelectByTitle() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        Map param = new HashMap();
        /*
            ${}原文传值
            select * from t_goods
            where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'
        */
        /*
           #{}预编译
           select * from t_goods
            where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"
        */

        param.put("title","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
        param.put("order" , " order by title desc");
        List<Goods> list = session.selectList("goods.selectByTitle", param);
        for(Goods g:list){
            System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
        }
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

14. MyBatis工作流程

image-20240714162635646

第一步创建mybatis-config.xml文件,包含环境配置、全局配置项、mapper声明这样的核心配置信息,该文件起到全局配置的作用

第二步在程序运行的过程中,通过SqlSessionFactoryBuilder这个构建器对象来build构建SqlSessionFactory,SqlSessionFactory(全局唯一)用于创建SqlSession,SqlSession用于对数据表进行增删改查,其中SqlSession要解析与之对应的mapper.xml文件,每一个mapper文件中都包含了大量sql语句,通过SqlSession访问mapper.xml进而执行与之对应的sql语句,最后需要关闭SqlSession。

15. 第二章(进阶):MyBatis日志管理

image-20240714165726082

image-20240714165736083

通过统一的门面屏蔽了底层的实现

门面:为Java提供统一的日志调用接口

实现:有各种各样的组织开发不同的产品,提供具体实现

为程序迁移提供了极大遍历。

pom.xml增加依赖

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

在控制台中会输出很多信息

image-20240714170433537

logback.xml 提供logback配置项

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
<!--           规定日志输出的格式-->
           <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

    <!--
        日志输出级别(优先级高到低):
        error: 错误 - 系统的故障日志
        warn: 警告 - 存在风险或使用不当的日志
        info: 一般性消息
        debug: 程序内部用于调试信息
        trace: 程序运行的跟踪信息
		此时,debug级别及以上的信息都会输出
     -->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

16. MyBatis动态SQL

image-20240714171919378

goods.xml

    <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>
          <if test="categoryId != null">
              and category_id = #{categoryId}
          </if>
          <if test="currentPrice != null">
              and current_price &lt; #{currentPrice}
          </if>
        </where>
    </select>

MyBatisTestor.java

    /**
     * 动态SQL语句
     * @throws Exception
     */
    @Test
    public void testDynamicSQL() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("categoryId", 44);
            param.put("currentPrice", 500);
            //查询条件
            List<Goods> list = session.selectList("goods.dynamicSQL", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" +
                        g.getCategoryId()  + ":" + g.getCurrentPrice());

            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

17. MyBatis二级缓存

用于数据优化,提高效率

比如用sql语句第一次查询时获得了婴幼儿奶粉的数据,紧接着,因为某种需要,还需要再获取一次婴幼儿奶粉的数据,按照原先的设计,需要再次从数据库中提取出来,可MySQL是把数据存储在硬盘上的,硬盘提取的速度并不是很快,若要进行优化,则可以使用缓存,直接从内存中提取。

image-20240714172135569

image-20240714172152467

image-20240714172202700

一级缓存

/**
 * 测试一级缓存
 * @throws Exception
 */
@Test
public void testLv1Cache() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        Goods goods = session.selectOne("goods.selectById" , 1603);
        Goods goods1 = session.selectOne("goods.selectById" , 1603);
        System.out.println(goods.hashCode() + ":" + goods1.hashCode());
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }

    try{
        session = MyBatisUtils.openSession();
        Goods goods = session.selectOne("goods.selectById" , 1603);
        session.commit();//commit提交时对该namespace缓存强制清空
        Goods goods1 = session.selectOne("goods.selectById" , 1603);
        System.out.println(goods.hashCode() + ":" + goods1.hashCode());
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

第一个try块里面的两个hashCode()返回值相同,第二个try块中,由于对commit提交时对该namespace缓存强制清空,两个hashCode()返回值不同

二级缓存

手动开启,在对应的xml文件中增加配置

goods.xml:

image-20240714173146342

/**
 * 测试二级缓存
 * @throws Exception
 */
@Test
public void testLv2Cache() throws Exception {
    SqlSession session = null;
    try{
        session = MyBatisUtils.openSession();
        Goods goods = session.selectOne("goods.selectById" , 1603);
        System.out.println(goods.hashCode());
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }

    try{
        session = MyBatisUtils.openSession();
        Goods goods = session.selectOne("goods.selectById" , 1603);
        System.out.println(goods.hashCode());
    }catch (Exception e){
        throw e;
    }finally {
        MyBatisUtils.closeSession(session);
    }
}

此时从控制台的输出可以看到,只执行了一次sql语句,并且内存地址是一样的。

18. 对象关联查询

OneToMany对象关联查询

接下来进行商品和详情对象关联查询,商品为1的一方,详情为多的一方。

首先创建goods_detail对象

package com.imooc.mybatis.entity;

public class GoodsDetail {
    private Integer gdId;
    private Integer goodsId;
    private String gdPicUrl;
    private Integer gdOrder;
    private Goods goods;

    public Integer getGdId() {
        return gdId;
    }

    public void setGdId(Integer gdId) {
        this.gdId = gdId;
    }

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getGdPicUrl() {
        return gdPicUrl;
    }

    public void setGdPicUrl(String gdPicUrl) {
        this.gdPicUrl = gdPicUrl;
    }

    public Integer getGdOrder() {
        return gdOrder;
    }

    public void setGdOrder(Integer gdOrder) {
        this.gdOrder = gdOrder;
    }

    public Goods getGoods() {
        return goods;
    }

    public void setGoods(Goods goods) {
        this.goods = goods;
    }
}

Goods.java中,

private List<GoodsDetail> goodsDetails;

goods.xml

<!--
    resultMap可用于说明一对多或者多对一的映射逻辑
    id 是resultMap属性引用的标志
    type 指向One的实体(Goods)
-->
<resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
    <!-- 映射goods对象的主键到goods_id字段 -->
    <id column="goods_id" property="goodsId"></id>
    <!--
        collection的含义是,在
        select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历得到goods_id字段值,
        并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,
        将得到的"商品详情"集合赋值给goodsDetails List对象.
    -->
    <collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
                column="goods_id"/>
</resultMap>
<select id="selectOneToMany" resultMap="rmGoods1">
    select * from t_goods limit 0,10
</select>

mybatis-config.xml新增

image-20240714174121902

MyBatisTestor.java

/**
 * 一对多对象关联查询
 * @throws Exception
 */
@Test
public void testOneToMany() throws Exception {
    SqlSession session = null;
    try {
        session = MyBatisUtils.openSession();
        List<Goods> list = session.selectList("goods.selectOneToMany");
        for(Goods goods:list) {
            System.out.println(goods.getTitle() + ":" + goods.getGoodsDetails().size());
        }
    } catch (Exception e) {
        throw e;
    } finally {
        MyBatisUtils.closeSession(session);
    }
}

ManyToOne对象关联查询

GoodsDetail.java中

private Goods goods;

goods_detail.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="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer"
            resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>

    <resultMap id="rmGoodsDetail" type="com.imooc.mybatis.entity.GoodsDetail">
        <id column="gd_id" property="gdId"/>
        <result column="goods_id" property="goodsId"/>
        <!--        从多的一方关联一的一方-->
        <association property="goods" select="goods.selectById" column="goods_id">	
        </association>
    </resultMap>
    <select id="selectManyToOne" resultMap="rmGoodsDetail">
        select * from t_goods_detail limit 0,20
    </select>
</mapper>

MyBatisTestor.java

/**
 * 测试多对一对象关联映射
 */
@Test
public void testManyToOne() throws Exception {
    SqlSession session = null;
    try {
        session = MyBatisUtils.openSession();
        List<GoodsDetail> list = session.selectList("goodsDetail.selectManyToOne");
        for(GoodsDetail gd:list) {
            System.out.println(gd.getGdPicUrl() + ":" + gd.getGoods().getTitle());
        }
    } catch (Exception e) {
        throw e;
    } finally {
        MyBatisUtils.closeSession(session);
    }
}

19. 分页处理

image-20240714184451852

image-20240714184502476

image-20240714184511855

pom.xml文件增加依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>2.0</version>
</dependency>

mybatis-config.xml增加配置

image-20240714184704355

goods.xml

<select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
    select * from t_goods where current_price &lt; 1000
</select>

MyBatisTestor.java

@Test
/**
 * PageHelper分页查询
 */
public void testSelectPage() throws Exception {
    SqlSession session = null;
    try {
        session = MyBatisUtils.openSession();
        /*startPage方法会自动将下一次查询进行分页 返回从第二页开始,每页10行的数据*/
        PageHelper.startPage(2,10);
        //返回page对象 不仅包含原始数据,还有分页的信息
        Page<Goods> page = (Page) session.selectList("goods.selectPage");
        System.out.println("总页数:" + page.getPages());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("开始行号:" + page.getStartRow());
        System.out.println("结束行号:" + page.getEndRow());
        System.out.println("当前页码:" + page.getPageNum());
        List<Goods> data = page.getResult();//当前页数据
        for (Goods g : data) {
            System.out.println(g.getTitle());
        }
        System.out.println("");
    } catch (Exception e) {
        throw e;
    } finally {
        MyBatisUtils.closeSession(session);
    }
}

20. MyBatis整合C3P0连接池

加入依赖

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.4</version>
</dependency>

image-20240714185327781

这里的type为POOLED 表示使用的是mybatis自己的连接池,但主流应用开发中,并不会使用mybatis自带的,有更好的连接池。

若需要mybatis进行支持C3P0连接池,需要额外扩展一个类 C3P0DataSourceFactory

/**
 * C3P0与MyBatis兼容使用的数据源工厂类
 */
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
    public C3P0DataSourceFactory(){
        this.dataSource = new ComboPooledDataSource();
    }
}

然后在mybatis-config.xml中

image-20240714185702938

同时属性的命名要求需要相应更改

比如原来的property名称url需要改为jdbcUrl等

MyBatisTestor.java

    /**
     * 批量插入测试
     * @throws Exception
     */
    @Test
    public void testBatchInsert() throws Exception {
        SqlSession session = null;
        try {
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            for (int i = 0; i < 10000; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                //insert()方法返回值代表本次成功插入的记录总数

                list.add(goods);
            }
            session.insert("goods.batchInsert", list);
            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et - st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        } catch (Exception e) {
            if (session != null) {
                session.rollback();//回滚事务
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

21. MyBatis批处理

利用集合保存批处理的数据,然后再利用批处理sql一次性完成,提供程序执行效率

goods.xml

    <!--INSERT INTO table-->
    <!--VALUES ("a" , "a1" , "a2"),("b" , "b1" , "b2"),(....)-->
    <!--设置与迭代相关的属性,list表示数据源 也就是从外侧传入的数据源集合,item表示循环中的循环变量,index表示循环的索引,separator为逗号,也就是values那一行,都是以逗号分割一个个对象的-->
    <insert id="batchInsert" parameterType="java.util.List">
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES
            <foreach collection="list" item="item" index="index" separator=",">
                (#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
            </foreach>
    </insert>
        <!--in (1901,1902)-->
    <delete id="batchDelete" parameterType="java.util.List">
        DELETE FROM t_goods WHERE goods_id in
        <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </delete>

MyBatisTestor.java

    /**
     * 批量删除测试
     * @throws Exception
     */
    @Test
    public void testBatchDelete() throws Exception {
        SqlSession session = null;
        try {
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            list.add(1920);
            list.add(1921);
            list.add(1922);
            session.delete("goods.batchDelete", list);
            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et - st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        } catch (Exception e) {
            if (session != null) {
                session.rollback();//回滚事务
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

22.Mybatis注解开发方式

注解的作用就是替换掉xml的各种标签,简化程序配置过程

image-20240714191310312

在mybatis-config.xml中进行基本配置,保留了驼峰转换和dev数据源,里面没有任何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>
    <settings>
        <!-- goods_id ==> goodsId 驼峰命名转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--设置默认指向的数据库-->
    <environments default="dev">
        <!--配置环境,不同的环境不同的id名字-->
        <environment id="dev">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

文件夹结构如下:

image-20240714191633851

不需要mapper文件,但需要创建GoodsDAO这个接口,利用接口+注解替代原有xml文件

package com.imooc.mybatis.dao;

import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface GoodsDAO {
    //基本查询 这三个参数增加@Param()使得参数与sql的参数一一对应
    @Select("select * from t_goods where current_price between  #{min} and #{max} order by current_price limit 0,#{limt}")
    public List<Goods> selectByPriceRange(@Param("min") Float min ,@Param("max") Float max ,@Param("limt") Integer limt);

    @Insert("INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id) VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})")
    //<selectKey>
    @SelectKey(statement = "select last_insert_id()" , before = false , keyProperty = "goodsId" , resultType = Integer.class)
    public int insert(Goods goods);

    @Select("select * from t_goods")
    //<resultMap>
    //进行结果映射
    @Results({
            //<id>
          @Result(column = "goods_id" ,property = "goodsId" , id = true) ,
            //<result>
            @Result(column = "title" ,property = "title"),
            @Result(column = "current_price" ,property = "currentPrice")
    })
    public List<GoodsDTO> selectAll();
}

在mybatis-config.xml中增加相应说明

<mappers>
    <!--<mapper class="com.imooc.mybatis.dao.GoodsDAO"/>-->
    <package name="com.imooc.mybatis.dao"/>
</mappers>

MyBatisTestor.java

package com.imooc.mybatis;

import com.imooc.mybatis.dao.GoodsDAO;
import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import com.imooc.mybatis.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

//JUNIT单元测试类
public class MyBatisTestor {

    @Test
    public void testSelectByPriceRange() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            List<Goods> list = goodsDAO.selectByPriceRange(100f, 500f, 20);
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);

        }
    }

    /**
     * 新增数据
     * @throws Exception
     */
    @Test
    public void testInsert() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = goodsDAO.insert(goods);
            session.commit();//提交事务数据
            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            List<GoodsDTO> list = goodsDAO.selectAll();
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);

        }
    }
}

;