Mybatis核心讲解练习及各种问题解决

发布于:2022-12-03 ⋅ 阅读:(101) ⋅ 点赞:(0)

  • 能够使用映射配置文件实现CRUD操作
  • 能够使用注解实现CRUD操作

在这里插入图片描述
如上图所示产品原型,里面包含了品牌数据的 查询 、 按条件查询 、 添加 、 删除 、 批量删除 、 修改 等功能,而这些功能其实就是对数据库表中的数据进行CRUD操作。接下来我们就使用Mybatis完成品牌数据的增删改查操作。以下是我们要完成功能列表

  • 查询
    • 查询所有数据
    • 查询详情
  • 条件查询
  • 添加
  • 修改
    • 修改全部字段
    • 修改动态字段
  • 删除
    • 删除一个
    • 批量删除

一. 准备文件和类

案例结构图

在这里插入图片描述

数据库表的准备(tb_brand)

-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
  -- id 主键
 id      int primary key auto_increment,
  -- 品牌名称
 brand_name  varchar(20),
  -- 企业名称
 company_name varchar(20),
  -- 排序字段
 ordered    int,
  -- 描述信息
 description  varchar(100),
  -- 状态:0:禁用 1:启用
   status    int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values 
('篮球射', '鸡你太美有限公司', 5, '时长两年半,玩的是花样', 0),
('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联
的智能世界', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1),
('香飘飘', '香飘飘', 1000, '绕地球三圈', 0);

在这里插入图片描述

实体类 (Brand)

package com.sgs.pojo;

public class Brand {
    // id 主键
    private Integer id;

    // 品牌名称
    private String brandName;

    // 企业名称
    private String companyName;

    // 排序字段
    private String ordered;

    // 描述信息
    private String description;

    // 状态:0:禁用 1:启用
    private Integer status;

    public Brand() {
    }

    public Brand(Integer id, String brandName, String companyName, String ordered, String description, Integer status) {
        this.id = id;
        this.brandName = brandName;
        this.companyName = companyName;
        this.ordered = ordered;
        this.description = description;
        this.status = status;
    }

    public Integer getId() {
        return id;
    }

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

    public String getbrandName() {
        return brandName;
    }

    public void setbrandName(String brandName) {
        this.brandName = brandName;
    }

    public String getcompanyName() {
        return companyName;
    }

    public void setcompanyName(String companyName) {
        this.companyName = companyName;
    }

    public String getOrdered() {
        return ordered;
    }

    public void setOrdered(String ordered) {
        this.ordered = ordered;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", companyName='" + companyName + '\'' +
                ", ordered='" + ordered + '\'' +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}

编写测试用例

测试代码需要在 test/java 目录下创建包及测试用例。

     //构建SqlSessionFactory
        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过Mapper代理获取相对应的接口
        SqlSession session = sqlSessionFactory.openSession();
        /* */ mapper = session.getMapper(/* */);

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>org.example</groupId>
    <artifactId>Mybatis-case</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <!--  添加slf4j日志api  -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!--  添加logback-classic依赖  -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--  添加logback-core依赖  -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>


</project>

logback.xml日志文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%level]  %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>

    <logger name="com.sgs" level="DEBUG" additivity="false">
        <appender-ref ref="Console"/>
    </logger>


    <!--

      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
     , 默认debug
      <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
      -->
    <root level="DEBUG">
        <appender-ref ref="Console"/>
    </root>
</configuration>

mybatis.xml配置文件

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


<!--    给resultType起别名-->
    <typeAliases>
        <package name="com.sgs.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
<!--                登录信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="JDBC:mysql://localhost:3306/db1?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="o676448"/>
            </dataSource>
        </environment>
    </environments>


<!--    映射器-->
    <mappers>
<!--        映射在文件中-->
<!--        <mapper resource="com/sgs/mapper/BrandMapper.xml"/>-->

<!--        通过包扫描映射-->
        <package name="com.sgs.mapper"/>
    </mappers>
</configuration>

BrandMapper.xml映射器文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sgs.mapper.BrandMapper">

    <!--    数据库表的名称  和  实体类表的属性名称 不一样, 则不能封装数据-->
    <resultMap id="BrandResultMap" type="Brand">
        <result column="brand_name" property="brandName"/>
        <result column="company_name" property="companyName"/>
    </resultMap>

    <!--    <select id="...." resultType="Brand">-->
    <!--             ..........  后边详细如下 -->
    <!--    </select>-->

    <select id="....." resultMap="BrandResultMap">
        ..........  后边详细如下
    </select>

BrandMapper接口

package com.sgs.mapper;

import com.sgs.pojo.Brand;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface BrandMapper {
    //查询所有
   // List<Brand> selectAll();

    //查询单个
    //Brand selectById(int id);

    //按条件查询
        //按散装查询
//    List<Brand> selectByCondition(@Param("status") int status,@Param("companyName") String companyName,@Param("brandName") String brandName);
//
//       //按对象参数的查询
//    List<Brand> selectByCondition1(Brand brand);
//
//       //按Map查询
//    List<Brand> selectByCondition1(Map map);


    //按条件查询
   // List<Brand> selectByConditionSingle(Map map);


//                后边详细介绍

}

安装 MyBatisX 插件

  • MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
  • 主要功能
    • XML映射配置文件 和 接口方法 间相互跳转
    • 根据接口方法生成 statement
  • 安装方式
    • 点击 file ,选择 settings ,就能看到如下图所示界面(安装后重新启动IDEA
      在这里插入图片描述
  • 红色头绳的表示映射配置文件,蓝色头绳的表示mapper接口。在mapper接口点击红色头绳的小鸟图标会自动跳转到对应的映射配置文件,在映射配置文件中点击蓝色头绳的小鸟图标会自动跳转到对应的mapper接口。也可以在mapper接口中定义方法,自动生成映射配置文件中的 statement ,如图所示

在这里插入图片描述

一. 查询

1.1 查询所有数据

分以下步骤进行实现:

  • 编写接口方法:BrandMapper接口
    • 参数:无
      查询所有数据功能是不需要根据任何条件进行查询的,所以此方法不需要参数。
      在这里插入图片描述
public interface BrandMapper {
  /**
  * 查询所有
  */
  List<Brand> selectAll();
}
  • 结果:List
    我们会将查询出来的每一条数据封装成一个 Brand 对象,而多条数据封装多个 Brand 对象,需要将这些对象封装到List集合中返回
    在这里插入图片描述

<!-- 写法2 -->
    <!--    数据库表的名称  和  实体类表的属性名称 不一样, 则不能封装数据-->
    <resultMap id="BrandResultMap" type="Brand">
        <result column="brand_name" property="brandName"/>
        <result column="company_name" property="companyName"/>
    </resultMap>


<!-- 写法1    会出现变量映射失败问题!!!!-->
    <!--    <select id="selectAll" resultType="Brand">-->
    <!--            select * from tb_brand;-->
    <!--    </select>-->


<!-- 写法2 -->
    <select id="selectAll" resultMap="BrandResultMap">
        select * from tb_brand;
    </select>

<!-- 写法3   麻烦不灵活,但是也是要知道的-->

    <sql id="brand_column">
        id,brand_name as brandName,company_name as companyName,ordered,description,status
    </sql>

    <select id="selectAll" resultType="Brand">
        select
        <include refid="brand_column"/>
        from tb_brand;
    </select>

  • 在 CaseDemo类中编写测试查询所有的方法
 @Test
    public void testSelect() throws Exception {
        //构建SqlSessionFactory
        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过Mapper代理获取相对应的接口
        SqlSession session = sqlSessionFactory.openSession();
        BrandMapper mapper = session.getMapper(BrandMapper.class);

        //获取BrandMapper相对应的方法
        List<Brand> brands = mapper.selectAll();

        System.out.println(brands);

        //释放资源
        session.close();

    }

注意:现在我们感觉测试这部分代码写起来特别麻烦,我们可以先忍忍。以后我们只会写上面的第3步的代码,其他的都不需要我们来完成

执行测试方法结果如下(写法2):
在这里插入图片描述

注意:有些数据封装成功了,而有些数据并没有封装成功的时候
写法1:在这里插入图片描述
解决方法

  • 给字段起别名
    • 从上面结果可以看到 brandName 和 companyName 这两个属性的数据没有封装成功,查询 实体类和 表中的字段 发现,在实体类中属性名是 brandName 和 companyName ,而表中的字段名为 brand_name 和 company_name,如下图所示 。那么我们只需要保持这两部分的名称一致这个问题就迎刃而解。
      在这里插入图片描述
  • 使用resultMap定义字段和属性的映射关系
    • id:完成主键字段的映射
      column:表的列名
      property:实体类的属性名
    • iresult:完成一般字段的映射
      column:表的列名
      property:实体类的属性名
      在这里插入图片描述

1.2 查询详情

在这里插入图片描述

有些数据的属性比较多,在页面表格中无法全部实现,而只会显示部分,而其他属性数据的查询可以通过 查看详情 来进行查询,如上图所示。

查看详情功能实现步骤:

  • 编写接口方法:Mapper接口
/**
 * 查看详情:根据Id查询
 */
Brand selectById(int id);

在这里插入图片描述

  • 参数:id
    查看详情就是查询某一行数据,所以需要根据id进行查询。而id以后是由页面传递过来
    结果:Brand
    根据id查询出来的数据只要一条,而将一条数据封装成一个Brand对象即可
  • 编写SQL语句:SQL映射文件BrandMapper.xml
 <resultMap id="BrandResultMap" type="Brand">
        <result column="brand_name" property="brandName"/>
        <result column="company_name" property="companyName"/>
    </resultMap>

<select id="selectById" parameterType="int" resultMap="BrandResultMap">
            select *from tb_brand where id=#{id};
        </select>

在 BrandMapper.xml 映射配置文件中编写 statement ,使用 resultMap 而不是使用 resultType在这里插入图片描述

  • 编写测试方法
    在 test/java 下的 CaseDemo类中 定义测试方法
 @Test
    public void testSelectById() throws Exception {
        //构建SqlSessionFactory
        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过Mapper代理获取相对应的接口
        SqlSession session = sqlSessionFactory.openSession();
        BrandMapper mapper = session.getMapper(BrandMapper.class);

        //获取BrandMapper相对应的方法
        Brand brand = mapper.selectById(1);
        System.out.println(brand);

        //释放资源
        session.close();

    }

在这里插入图片描述

注意:上述SQL中的 #{id}先这样写,下面我们详细讲解

参数占位符:

查询到的结果很好理解就是id为1的这行数据。而这里我们需要看控制台显示的SQL语句,能看到使用?进行占位。说明我们在映射配置文件中的写的 #{id} 最终会被?进行占位。接下来我们就聊聊映射配置文件中的参数占位符。

mybatis提供了两种参数占位符:

  • #{} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。从上述例子可以看出使用#{} 底层使用的是PreparedStatement
  • ${} :拼接SQL。底层使用的是 Statement ,会存在SQL注入问题。如下图将 映射配置文件中的 #{} 替换成 ${} 来看效果
 <select id="selectById" parameterType="int" resultMap="BrandResultMap">
            select *from tb_brand where id=${id};
</select>

重新运行查看结果如下:
在这里插入图片描述

注意:从上面两个例子可以看出,以后开发我们使用 #{} 参数占位符。

parameterType使用:

对于有参数的mapper接口方法,我们在映射配置文件中应该配置 ParameterType 来指定参数类型。只不过该属性都可以省略。如下图
在这里插入图片描述

SQL语句中特殊字段处理:

以后肯定会在SQL语句中写一下特殊字符,比如某一个字段大于某个值,可以看出报错了,因为映射配置文件是xml类型的问题:
在这里插入图片描述

而 > < 等这些字符在xml中有特殊含义,所以此时我们需要将这些符号进行转义,可以使用以下两种方式进行转义

  • 转义字符
    & l t; 就是 < 的转义字符

  • CDATA区

在这里插入图片描述

1.3 多条件查询

在这里插入图片描述
我们经常会遇到如上图所示的多条件查询,将多条件查询的结果展示在下方的数据列表中。而我们做这个功能需要分析最终的SQL语句应该是什么样,思考两个问题

  • 条件表达式
  • 如何连接
    条件字段 企业名称品牌名称 需要进行模糊查询,所以条件应该是:
    在这里插入图片描述
    简单的分析后,我们来看功能实现的步骤:
    • 编写接口方法
      而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现
      参数:所有查询条件
      结果:List
      在这里插入图片描述
      • 使用 @Param("参数名称") 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位
      • 将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用#{内容}时,里面的内容必须和实体类属性名保持一致
      • 将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容}时,里面的内容必须和map集合中键的名称一致
   //按条件查询
        //按散装查询
    List<Brand> selectByCondition(@Param("status") int status,@Param("companyName") String companyName,@Param("brandName") String brandName);

       //按对象参数的查询
    List<Brand> selectByCondition1(Brand brand);

       //按Map查询
    List<Brand> selectByCondition1(Map map);  
  • 在映射配置文件中编写SQL语句

注意:此语句有问题,动态SQL会详细说明

  <select id="selectByCondition" resultMap="BrandResultMap">
        select *
        from tb_brand
        where
        status = #{status}
        and company_name like #{companyName},
        and brand_name like #{brandName};
    </select>
  • 编写测试方法并执行
 @Test
    public void selectByCondition() throws Exception {
        int status = 1;
        String companyName="鸡你太美有限公司";
        String brandName="篮球射";
        //构建SqlSessionFactory
        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过Mapper代理获取相对应的接口
        SqlSession session = sqlSessionFactory.openSession();
        BrandMapper mapper = session.getMapper(BrandMapper.class);

        
        companyName="%"+companyName+"%";
        brandName="%"+brandName+"%";

        //获取BrandMapper相对应的方法
            //按散装查询
//        List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
//        System.out.println(brands);

/*
             //按对象查询
        Brand brand = new Brand();
        brand.setStatus(1);
        brand.setcompanyName(companyName);
        brand.setbrandName(brandName);
        List<Brand> brands = mapper.selectByCondition1(brand);
        System.out.println(brands);
*/

            //按Map查询
        Map map = new HashMap();
        map.put("status","status");
        map.put("companyName",companyName);
        map.put("brandName",brandName);

        List<Brand> brands = mapper.selectByCondition1(map);
        System.out.println(brands);

        //释放资源
        session.close();
    }

注意:上述的问题仅限于所有条件都有情况下,但是,也会有条件不全的情况下的,下面我们讲解动态SQL来解决

动态SQL

针对上述的需要,Mybatis对动态SQL有很强大的支撑:

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

我们先学习 if 标签和 where 标签:

  • if 标签:条件判断
    • test 属性:逻辑表达式
<select id="selectByCondition" resultMap="brandResultMap">
 select *
 from tb_brand
 where
    <if test="status != null">
    and status = #{status}
    </if>
    <if test="companyName != null and companyName != '' ">
     and company_name like #{companyName}
    </if>
    <if test="brandName != null and brandName != '' ">
     and brand_name like #{brandName}
    </if>
</select>

如上的这种SQL语句就会根据传递的参数值进行动态的拼接。如果此时status和companyName有值那么就会值拼接这两个条件。
在这里插入图片描述

  • 而下面的语句中 where 关键后直接跟 and 关键字,这就是一条错误的SQL语句。这个就可以使用 where 标签解决
    在这里插入图片描述
    解决:

1 恒等式 where 1=1

    <select id="selectByCondition1" resultMap="BrandResultMap">
        select *
        from tb_brand
        where 1=1
        <if test="status != null">
           and status = #{status}
        </if>
       <if test="companyName != null and companyName != ''">
           and company_name like #{companyName}

       </if>

        <if test="brandName != null and brandName!=''">
            and brand_name like #{brandName}
        </if>

    </select>

2 where 标签

  • 作用:
    • 替换where关键字
    • 会动态的去掉第一个条件前的 and
    • 如果所有的参数没有值则不加where关键字
<select id="selectByCondition" resultMap="brandResultMap">
 select *
 from tb_brand
  <where>
    <if test="status != null">
     and status = #{status}
    </if>
    <if test="companyName != null and companyName != '' ">
     and company_name like #{companyName}
    </if>
    <if test="brandName != null and brandName != '' ">
     and brand_name like #{brandName}
    </if>
  </where>
</select>

单个条件(动态SQL)

在这里插入图片描述
如上图所示,在查询时只能选择 品牌名称 、 当前状态 、 企业名称 这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句
这种需求需要使用到 choose(when,otherwise)标签 实现, 而 choose 标签类似于Java 中的switch语句。

  • BrandMapper接口
public interface BrandMapper {
    //按条件查询
    List<Brand> selectByConditionSingle(Map map);
}

  • CaseDemo测试类
  @Test
    public void selectByConditionSingle() throws Exception {
        Integer status = 0;
        String companyName=null;
        String brandName=null;
        //构建SqlSessionFactory
        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过Mapper代理获取相对应的接口
        SqlSession session = sqlSessionFactory.openSession();
        BrandMapper mapper = session.getMapper(BrandMapper.class);

//        companyName="%"+companyName+"%";
//        brandName="%"+brandName+"%";

        //获取BrandMapper相对应的方法


     //按Map查询
        Map map = new HashMap();

        map.put("status",status);
//        map.put("companyName",companyName);
//        map.put("brandName",brandName);


        List<Brand> brands = mapper.selectByConditionSingle(map);
        System.out.println(brands);


        //释放资源
        session.close();
    }
  • BrandMapper.xml映射文件下
<select id="selectByConditionSingle" resultMap="brandResultMap">
 select *
 from tb_brand
  <where>
    <choose><!--相当于switch-->
      <when test="status != null"><!--相当于case-->
       status = #{status}
      </when>
      <when test="companyName != null and companyName != '' "><!--相当于case-->
       company_name like #{companyName}
      </when>
      <when test="brandName != null and brandName != ''"><!--相当于case-->
       brand_name like #{brandName}
      </when>
    </choose>
  </where>
</select>

执行的内容:
在这里插入图片描述

二. 添加和修改数据

下一章节


网站公告

今日签到

点亮在社区的每一天
去签到