SpringBoot-5-MyBatis最佳实践分享

1.MyBatis最佳实践分享

学习完mybatis入门后,我们继续学习mybatis基础操作。

1.1 需求

需求说明:完成学生管理的需求开发。

  1. 查询

    • 根据主键ID查询
    • 条件查询
  2. 新增

  3. 更新

  4. 删除

    • 根据主键ID删除
    • 根据主键ID批量删除

1.2 准备

实施前的准备工作:

  1. 准备数据库表
  2. 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
  3. application.yml中引入数据库连接信息
  4. 创建对应的实体类 Student(实体类属性采用驼峰命名)
  5. 准备Mapper接口 StudentMapper

准备数据库表

create database if not exists mybatis_curd;
use mybatis_curd;
-- 学生管理
create table student
(
    id          int unsigned primary key auto_increment comment 'ID',
    username    varchar(20)      not null unique comment '用户名',
    password    varchar(32default '123456' comment '密码',
    name        varchar(10)      not null comment '姓名',
    gender      tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
    image       varchar(300comment '图像',
    entrydate   date comment '入职时间',
    create_time datetime         not null comment '创建时间',
    update_time datetime         not null comment '修改时间'
comment '学生表';
-- 学生表测试数据
INSERT INTO student (id, username, passwordname, gender, image, entrydate, create_time, update_time)
VALUES
(1'jinyong''123456''金庸'1'1.jpg',  '2000-01-01',  now(), now()),
(2'zhangwuji''123456''张无忌'1'2.jpg''2015-01-01',  now(), now()),
(3'yangxiao''123456''杨逍'1'3.jpg',  '2008-05-01',  now(), now()),
(4'weiyixiao''123456''韦一笑'1'4.jpg',  '2007-01-01',  now(), now()),
(5'changyuchun''123456''常遇春'1'5.jpg''2012-12-05',  now(), now()),
(6'xiaozhao''123456''小昭'2'6.jpg',  '2013-09-05',  now(), now()),
(7'jixiaofu''123456''纪晓芙'2'7.jpg',  '2005-08-01',  now(), now()),
(8'zhouzhiruo''123456''周芷若'2'8.jpg',  '2014-11-09'now(), now()),
(9'dingminjun''123456''丁敏君'2'9.jpg',  '2011-03-11',  now(), now()),
(10'zhaomin''123456''赵敏'2'10.jpg',  '2013-09-05',  now(), now()),
(11'luzhangke''123456''鹿杖客'1'11.jpg',  '2007-02-01',  now(), now()),
(12'hebiweng''123456''鹤笔翁'1'12.jpg',  '2008-08-18',  now(), now()),
(13'fangdongbai''123456''方东白'1'13.jpg',  '2012-11-01',  now(), now()),
(14'zhangsanfeng''123456''张三丰'1'14.jpg',  '2002-08-01',  now(), now()),
(15'yulianzhou''123456''俞莲舟'1'15.jpg',  '2011-05-01',  now(), now()),
(16'songyuanqiao''123456''宋远桥'1'16.jpg''2010-01-01',  now(), now()),
(17'chenyouliang''123456''陈友谅'1'17.jpg',  '2015-03-21',  now(), now());

-- 查询学生所有数据
select * from student;

创建一个新的springboot工程,添加对应的起步依赖(mybatis、mysql驱动、lombok)

<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.0</version>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.9</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yml中引入数据库连接信息

提示:可以把之前项目中已有的配置信息复制过来即可

spring:
  datasource:
    druid:
      #驱动类名称
      driver-class-name: com.mysql.cj.jdbc.Driver
      #数据库连接的url
      url: jdbc:mysql://localhost:3306/mybatis_curd
      #连接数据库的用户名
      username: root
      #连接数据库的密码
      password: root

创建对应的实体类Student(实体类属性采用驼峰命名)

package com.zbbmeta.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor //无参构造
@AllArgsConstructor //全参构造
public class Student {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Short gender;
    private String image;
    private LocalDate entrydate;     //LocalDate类型对应数据表中的date类型
    private LocalDateTime createTime;//LocalDateTime类型对应数据表中的datetime类型
    private LocalDateTime updateTime;
}

准备Mapper接口:StudentMapper

/*@Mapper注解:表示当前接口为mybatis中的Mapper接口
  程序运行时会自动创建接口的实现类对象(代理对象),并交给Spring的IOC容器管理
*/

@Mapper
public interface StudentMapper {

}

完成以上操作后,项目工程结构目录如下:

SpringBoot-5-MyBatis最佳实践分享

1.3 删除

1.3.1 功能实现

根据ID删除数据即可。

功能:根据主键删除数据

  • SQL语句
-- 删除id=3的数据
delete from student where id=3;

Mybatis框架让程序员更关注于SQL语句

  • 接口方法
@Mapper
public interface StudentMapper {
    
/*    @Delete("delete from student where id = 17")
    public Integer delete();
    以上delete操作的SQL语句中的id值写成固定的17,就表示只能删除id=17的学生数据
    SQL语句中的id值不能写成固定数值,需要变为动态的数值
    解决方案:在delete方法中添加一个参数(学生id),将方法中的参数,传给SQL语句*/


    /**
     * 根据id删除数据
     * @param id    学生id
     */

    @Delete("delete from student where id = #{id}")//使用#{key}方式获取方法中的参数值
    public Integer delete(Integer id);
    
}

@Delete注解:用于编写delete操作的SQL语句

如果mapper接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如:#{id}、#{value}。但是建议保持名字一致

  • 测试
    • 在单元测试类中通过@Autowired注解注入StudentMapper类型对象
@SpringBootTest
class D18SpringbootMybatisCurdApplicationTests {
    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testDel(){
        //调用删除方法
        Integer delete = studentMapper.delete(16);
        System.out.println(delete>0?true:false);
    }
}

1.3.2 日志输入

在Mybatis当中我们可以借助日志,查看到sql语句的执行、执行传递的参数以及执行结果。具体操作如下:

  1. 打开application.yml文件

  2. 开启mybatis的日志,并指定输出到控制台

#指定mybatis输出日志的位置, 输出控制台
# mybatis 日志输出
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

开启日志之后,我们再次运行单元测试,可以看到在控制台中,输出了以下的SQL语句信息:

SpringBoot-5-MyBatis最佳实践分享

但是我们发现输出的SQL语句:delete from student where id = ?,我们输入的参数16并没有在后面拼接,id的值是使用?进行占位。那这种SQL语句我们称为预编译SQL。

1.3.3 预编译SQL

1.3.3.1 介绍

预编译SQL有两个优势:

  1. 性能更高
  2. 更安全(防止SQL注入)
SpringBoot-5-MyBatis最佳实践分享

性能更高:预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译。(只是输入的参数不同)

更安全(防止SQL注入):将敏感字进行转义,保障SQL的安全性。

1.3.3.2 SQL注入

SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

参数占位符

在Mybatis中提供的参数占位符有两种:${…} 、#{…}

  • #{…}

    • 执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值,防止SQL注入
    • 使用时机:参数传递,都使用#{…}
  • ${…}

    • 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题
    • 使用时机:如果对表名、列表进行动态设置时使用

注意事项:在项目开发中,建议使用#{…},生成预编译SQL,防止SQL注入安全。

测试2:验证SQL注入问题

第1步:在StudentMapper中编写一个根据用户和密码进行查询Student的方法

@Select("select * from student where name=#{name} and password=${password} limit 1")
public Student selectByNameAndPassword(String name,String password);

**注意:password 使用的是

{}:


@Test
public void selectByNameAndPasswordTest(){
    studentMapper.selectByNameAndPassword("张三三","''  or 1=1");
}

在表中没有name=张三三的学生,但是还是查出来了数据

SpringBoot-5-MyBatis最佳实践分享
SpringBoot-5-MyBatis最佳实践分享

第3步:在测试类中创建一个方法验证SQL无法注入#{}:

SpringBoot-5-MyBatis最佳实践分享

把整个' or '1'='1作为一个完整的参数,赋值给第2个问号(' or '1'='1进行了转义,只当做字符串使用)

1.4 新增

功能:新增学生信息

1.4.1 基本新增

SQL语句:

-- 新增学生数据
insert into student(username, name, gender, image, entrydate,  create_time, update_time)
values ('wangwu''王五'5'1.jpg',  '2018-10-09',  '2021-10-01 10:00:00''2022-10-01 10:00:00');

接口方法:

@Mapper
public interface StudentMapper {

    @Insert("insert into student(username, name, gender, image, entrydate, create_time, update_time) " +
            "values (#{username}, #{name}, #{gender}, #{image}, #{entrydate}, " +
            " #{createTime}, #{updateTime})")
    public Integer insert(Student student);

}

说明:#{…} 里面写的名称是对象的属性名

测试类:

@Test
public void testInsert(){
    //学生对象
    Student student = new Student();
    student.setUsername("lisi");
    student.setName("李四");
    student.setImage("4.jpg");
    student.setGender((short)1);
    student.setEntrydate(LocalDate.of(2010,1,1));
    student.setCreateTime(LocalDateTime.now());
    student.setUpdateTime(LocalDateTime.now());
    //调用添加方法
    studentMapper.insert(student);
}

日志输出:

==>  Preparing: insert into student(username, name, gender, image, entrydate, create_time, update_time) values (?, ?, ?, ?, ?, ?, ?)

==> Parameters: lisi(String), 李四(String), 1(Short), 4.jpg(String), 2010-01-01(LocalDate), 2023-08-13T23:38:38.621256600(LocalDateTime), 2023-08-13T23:38:38.621256600(LocalDateTime) <==    Updates: 1

1.4.2 主键返回

概念:在数据添加成功后,需要获取插入数据库数据的主键。

那要如何实现在插入数据之后返回所插入行的主键值呢?

  • 默认情况下,执行插入操作时,是不会主键值返回的。如果我们想要拿到主键值,需要在Mapper接口中的方法上添加一个Options注解,并在注解中指定属性useGeneratedKeys=true和keyProperty=”实体类属性名”

主键返回代码实现:

@Mapper
public interface StudentMapper {
    
    //会自动将生成的主键值,赋值给Student对象的id属性
    @Options(useGeneratedKeys = true,keyProperty = "id")
    @Insert("insert into student(username, name, gender, image, entrydate, create_time, update_time) " +
            "values (#{username}, #{name}, #{gender}, #{image}, #{entrydate}, " +
            " #{createTime}, #{updateTime})")
    public Integer insert(Student student);

}

测试:

@Test
public void testInsert(){
    //学生对象
    Student student = new Student();
    student.setUsername("lisi1");
    student.setName("李四1");
    student.setImage("41.jpg");
    student.setGender((short)1);
    student.setEntrydate(LocalDate.of(2011,1,1));
    student.setCreateTime(LocalDateTime.now());
    student.setUpdateTime(LocalDateTime.now());
    System.out.println("插入前id = "+student.getId());
    //调用添加方法
    studentMapper.insert(student);
    //输出id
    System.out.println("插入后id = "+student.getId());
}
SpringBoot-5-MyBatis最佳实践分享

1.5 更新

功能:修改学习信息

思考:在修改学生数据时,要以什么做为条件呢?

答案:学生id

SQL语句:

-- 根据id更新学生数据
update student
set username    = '老六',
    name        = '令狐少侠',
    gender      = 1,
    image       = '1.jpg',
    entrydate   = '2012-01-01',
    update_time = '2023-08-01 13:13:13'
where id = 18;

接口方法:

@Mapper
public interface StudentMapper {
    /**
     * 根据id修改学生数据
     * @param student
     */

    @Update("update student  " +
            "set username    = #{username}," +
            "    name        = #{name}," +
            "    gender      = #{gender}," +
            "    image       = #{image}," +
            "    entrydate   = #{entrydate}," +
            "    update_time = #{updateTime} " +
            "where id = #{id};")
    public void update(Student student);
    
}

测试类:

@Test
public void testUpdate(){
    //要修改的学生信息
    Student student = new Student();
    student.setId(20);
    student.setUsername("lisi20");
    student.setPassword("56789");
    student.setName("李四20");
    student.setImage("420.jpg");
    student.setGender((short)1);
    student.setEntrydate(LocalDate.of(2023,1,1));
    student.setUpdateTime(LocalDateTime.now());
    //调用方法,修改学生数据
    studentMapper.update(student);
}

测试结果

==>  Preparing: update student set username = ?, name = ?, gender = ?, image = ?, entrydate = ?, update_time = ? where id = ?;

==> Parameters: lisi20(String), 李四20(String), 1(Short), 420.jpg(String), 2023-01-01(LocalDate), 2023-08-13T23:54:07.231186600(LocalDateTime), 20(Integer)

==    Updates: 1

1.6 查询

1.6.1 根据ID查询

SQL语句:

-- 根据ID查询学生数据
select id,
       username,
       password,
       name,
       gender,
       image,
       entrydate,
       create_time,
       update_time
from student
where id = 10;

接口方法:

@Mapper
public interface StudentMapper {
    @Select("select id, username, password, name, gender, image, entrydate, create_time, update_time  from student where id=10;")
    public Student getById(Integer id);
}

测试类:

@Test
public void testGetById(){
    Student student = studentMapper.getById(1);
    System.out.println(student);
}

执行结果:

SpringBoot-5-MyBatis最佳实践分享

而在测试的过程中,我们会发现有几个字段(createTime、updateTime)是没有数据值的

1.6.2 数据封装

我们看到查询返回的结果中大部分字段是有值的,但是createTime,updateTime这几个字段是没有值的,而数据库中是有对应的字段值的,这是为什么呢?

SpringBoot-5-MyBatis最佳实践分享

原因如下:

  • 实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装。
  • 如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。

解决方案:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名

起别名:在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样

@Select("select id, username, password, name, gender, image, entrydate, create_time as createTime, update_time as  updateTime from student where id=10;")
public Student getById(Integer id);

再次执行测试类:

SpringBoot-5-MyBatis最佳实践分享

手动结果映射:通过 @Results及@Result 进行手动结果映射

@Results({
        @Result(column = "create_time", property = "createTime"),
        @Result(column = "update_time", property = "updateTime")})
@Select("select id, username, password, name, gender, image, entrydate, create_time , update_time  from student where id=10;")
public Student getById(Integer id);

@Results源代码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Results {
 String id() default "";
 Result[] value() default {};  //Result类型的数组
}

@Result源代码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Repeatable(Results.class)
public @interface Result 
{
   boolean id() default false;//表示当前列是否为主键(true:是主键)
   String column() default "";//指定表中字段名
   String property() default "";//指定类中属性名
   Class<?> javaType() default void.class;
   JdbcType jdbcType() default JdbcType.UNDEFINED;
   Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
   One one() default @One;
   Many many() default @Many;
}

**开启驼峰命名(推荐)**:如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射

驼峰命名规则:   abc_xyz    =>   abcXyz

  • 表中字段名:abc_xyz
  • 类中属性名:abcXyz
# 在application.yml中添加:
mybatis:
  configuration:
    map-underscore-to-camel-case: true #开启驼峰命名

要使用驼峰命名前提是 实体类的属性 与 数据库表中的字段名严格遵守驼峰命名。

1.6.3 条件查询

根据条件查询学生信息,查询条件包括:姓名、性别、入学时间。 我们要实现的查询:

  • 姓名:要求支持模糊匹配
  • 性别:要求精确匹配
  • 入学时间:要求进行范围查询
  • 根据最后修改时间进行降序排序

SQL语句:

-- 根据条件进行学生查询
select id, username, passwordname, gender, image, entrydate, create_time, update_time
from student
where name like '%李%'
  and gender = 1
  and entrydate between '2020-01-01' and '2023-01-01 '
order by update_time desc;

接口方法:

  • 方式一
@Mapper
public interface StudentMapper {
    @Select("select id, username, password, name, gender, image, entrydate, create_time, update_time " +
            "from student " +
            "where name like '%${name}%' " +
            "  and gender = #{gender} " +
            "  and entrydate between #{begin} and #{end} " +
            "order by update_time desc;")
    public List<Student> list(String name, Short gender, LocalDate begin, LocalDate end);
}
SpringBoot-5-MyBatis最佳实践分享

以上方式注意事项:

  1. 方法中的形参名和SQL语句中的参数占位符名保持一致

  2. 模糊查询使用${…}进行字符串拼接,这种方式呢,由于是字符串拼接,并不是预编译的形式,所以效率不高、且存在sql注入风险。

  • 方式二(解决SQL注入风险)
    • 使用MySQL提供的字符串拼接函数:concat(‘%’ , ‘关键字’ , ‘%’)
@Mapper
public interface StudentMapper {

    @Select("select id, username, password, name, gender, image, entrydate, create_time, update_time " +
            "from student " +
            "where name like  concat('%',#{name},'%') " +
            "  and gender = #{gender} " +
            "  and entrydate between #{begin} and #{end} " +
            "order by update_time desc;")
    public List<Student> list(String name, Short gender, LocalDate begin, LocalDate end);
}

执行结果:生成的SQL都是预编译的SQL语句(性能高、安全)

SpringBoot-5-MyBatis最佳实践分享

1.6.4 参数名说明

在上面我们所编写的条件查询功能中,我们需要保证接口中方法的形参名和SQL语句中的参数占位符名相同。

当方法中的形参名和SQL语句中的占位符参数名不相同时,就会出现以下问题:

SpringBoot-5-MyBatis最佳实践分享

参数名在不同的SpringBoot版本中,处理方案还不同:

  • 在springBoot的2.x版本(保证参数名一致)

springBoot的父工程对compiler编译插件进行了默认的参数parameters配置,使得在编译时,会在生成的字节码文件中保留原方法形参的名称,所以#{…}里面可以直接通过形参名获取对应的值

SpringBoot-5-MyBatis最佳实践分享
  • 在springBoot的1.x版本/单独使用mybatis(使用@Param注解来指定SQL语句中的参数名)

在编译时,生成的字节码文件当中,不会保留Mapper接口中方法的形参名称,而是使用var1、var2、…这样的形参名字,此时要获取参数值时,就要通过@Param注解来指定SQL语句中的参数名

SpringBoot-5-MyBatis最佳实践分享

2. Mybatis的XML配置文件

Mybatis的开发有两种方式:

  1. 注解
  2. XML

2.1 XML配置文件规范

使用Mybatis的注解方式,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中。

在Mybatis中使用XML映射文件方式开发,需要符合一定的规范:

  1. XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)

  2. XML映射文件的namespace属性为Mapper接口全限定名一致

  3. XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。

SpringBoot-5-MyBatis最佳实践分享

<select>标签:就是用于编写select查询语句的。

  • resultType属性,指的是查询返回的单条记录所封装的类型。

2.2 XML配置文件实现

第1步:创建XML映射文件

SpringBoot-5-MyBatis最佳实践分享
SpringBoot-5-MyBatis最佳实践分享
SpringBoot-5-MyBatis最佳实践分享

第2步:编写XML映射文件

xml映射文件中的dtd约束,直接从mybatis官网复制即可

<?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="">

</mapper>

配置:XML映射文件的namespace属性为Mapper接口全限定名

<?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.zbbmeta.mapper.StudentMapper">

</mapper>

配置:XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致

SpringBoot-5-MyBatis最佳实践分享
<?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.zbbmeta.mapper.StudentMapper">

    <select id="list" resultType="com.zbbmeta.entity.Student">
        select id, username, password, name, gender, image, entrydate, create_time, update_time
        from student
        where name like concat('%',#{name},'%')
        and gender = #{gender}
        and entrydate between #{begin} and #{end}
        order by update_time desc;
    </select>
</mapper>

运行测试类,执行结果:

SpringBoot-5-MyBatis最佳实践分享

2.3 MybatisX的使用

MybatisX是一款基于IDEA的快速开发Mybatis的插件,为效率而生。

MybatisX的安装:

SpringBoot-5-MyBatis最佳实践分享

可以通过MybatisX快速定位:

SpringBoot-5-MyBatis最佳实践分享

MybatisX的使用在后续学习中会继续分享

学习了Mybatis中XML配置文件的开发方式了,大家可能会存在一个疑问:到底是使用注解方式开发还是使用XML方式开发?

官方说明:https://mybatis.net.cn/getting-started.html

结论:使用Mybatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。

3. Mybatis动态SQL

3.1 什么是动态SQL

而在我们刚才编写的SQL语句中,我们会看到,我们将三个条件直接写死了。 如果页面只传递了参数姓名name 字段,其他两个字段 性别 和 入学时间没有传递,那么这两个参数的值就是null。

此时,执行的SQL语句为:

SpringBoot-5-MyBatis最佳实践分享

这个查询结果是不正确的。正确的做法应该是:传递了参数,再组装这个查询条件;如果没有传递参数,就不应该组装这个查询条件。

比如:如果姓名输入了”张”, 对应的SQL为:

select *  from student where name like '%张%' order by update_time desc;

如果姓名输入了”张”,,性别选择了”男”,则对应的SQL为:

select *  from student where name like '%张%' and gender = 1 order by update_time desc;

SQL语句会随着用户的输入或外部条件的变化而变化,我们称为:动态SQL

SpringBoot-5-MyBatis最佳实践分享

在Mybatis中提供了很多实现动态SQL的标签,我们学习Mybatis中的动态SQL就是掌握这些动态SQL标签。

3.2 动态SQL-if

<if>:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。

<if test="条件表达式">
   要拼接的sql语句
</if>

接下来,我们就通过<if>标签来改造之前条件查询的案例。

3.2.1 条件查询

示例:把SQL语句改造为动态SQL方式

  • 原有的SQL语句
<select id="list" resultType="com.zbbmeta.entity.Student">
    select id, username, password, name, gender, image, entrydate, create_time, update_time
    from student
   where name like concat('%',#{name},'%')
        and gender = #{gender}
        and entrydate between #{begin} and #{end}
    order by update_time desc;
</select>
  • 动态SQL语句
<select id="list" resultType="com.zbbmeta.entity.Student">
    select id, username, password, name, gender, image, entrydate, create_time, update_time
    from student
   where
        <if test="name !=null">
            name like concat('%',#{name},'%')
        </if>
        <if test="gender !=null">
            and gender = #{gender}
        </if>
        <if test="begin !=null and end !=null">
            and entrydate between #{begin} and #{end}
        </if>
    order by update_time desc;
</select>

测试方法:

@Test
public void testList(){
    //性别数据为null、开始时间和结束时间也为null
     List<Student> studentList = studentMapper.list("张"nullnullnull);
    for(Student student : studentList){
        System.out.println(student);
    }
}

执行的SQL语句:

SpringBoot-5-MyBatis最佳实践分享

下面呢,我们修改测试方法中的代码,再次进行测试,观察执行情况:

@Test
public void testList(){
    //姓名为null
    List<Student> studentList = studentMapper.list(null, (short)1nullnull);
    for(Student student : studentList){
        System.out.println(student);
    }
}

执行结果:

SpringBoot-5-MyBatis最佳实践分享

再次修改测试方法中的代码,再次进行测试:

@Test
public void testList(){
    //传递的数据全部为null
    List<Student> studentList = studentMapper.list(nullnullnullnull);
    for(Student student : studentList){
        System.out.println(student);
    }
}

执行的SQL语句:

SpringBoot-5-MyBatis最佳实践分享

以上问题的解决方案:使用<where>标签代替SQL语句中的where关键字

  • <where>只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的AND或OR
<select id="list" resultType="com.zbbmeta.entity.Student">
    select id, username, password, name, gender, image, entrydate,
    create_time, update_time
    from student
   <where>
        <if test="name !=null">
            name like concat('%',#{name},'%')
        </if>
        <if test="gender !=null">
            and gender = #{gender}
        </if>
        <if test="begin !=null and end !=null">
            and entrydate between #{begin} and #{end}
        </if>
   </where>
    order by update_time desc;
</select>

测试方法:

@Test
public void testList(){
    //只有性别
    List<Student> studentList = studentMapper.list(null, (short)1nullnull);
    for(Student student : studentList){
        System.out.println(student);
    }
}

执行的SQL语句:

SpringBoot-5-MyBatis最佳实践分享

3.2.2 更新学生数据

案例:完善更新学生功能,修改为动态更新学生数据信息

  • 动态更新学生信息,如果更新时传递有值,则更新;如果更新时没有传递值,则不更新
  • 解决方案:动态SQL

修改Mapper接口:

@Mapper
public interface StudentMapper {
    //删除@Update注解编写的SQL语句
    //update操作的SQL语句编写在Mapper映射文件中
    public void update(Student student);
}

修改Mapper映射文件:

<!--更新操作-->
<update id="update">
    update student
    set
    <if test="username != null">
        username=#{username},
    </if>
    <if test="name != null">
        name=#{name},
    </if>
    <if test="gender != null">
        gender=#{gender},
    </if>
    <if test="image != null">
        image=#{image},
    </if>
    <if test="entrydate != null">
        entrydate=#{entrydate},
    </if>
    <if test="updateTime != null">
        update_time=#{updateTime}
    </if>
    where id=#{id}
</update>

测试方法:

@Test
public void testUpdate2(){
    //要修改的学生信息
    Student student = new Student();
    student.setId(20);
    student.setUsername("Tom111");
    student.setName("汤姆111");

    student.setUpdateTime(LocalDateTime.now());

    //调用方法,修改学生数据
    studentMapper.update(student);
}

执行的SQL语句:

SpringBoot-5-MyBatis最佳实践分享

再次修改测试方法,观察SQL语句执行情况:

@Test
public void testUpdate2(){
    //要修改的学生信息
    Student student = new Student();
    student.setId(20);
    student.setUsername("Tom111");
    //调用方法,修改学生数据
    studentMapper.update(student);
}

执行的SQL语句:

SpringBoot-5-MyBatis最佳实践分享

以上问题的解决方案:使用<set>标签代替SQL语句中的set关键字

  • <set>:动态的在SQL语句中插入set关键字,并会删掉额外的逗号。(用于update语句中)
    <!--更新操作-->
    <update id="update">
        update student
        <set>
            <if test="username != null">
                username=#{username},
            </if>
            <if test="name != null">
                name=#{name},
            </if>
            <if test="gender != null">
                gender=#{gender},
            </if>
            <if test="image != null">
                image=#{image},
            </if>
            <if test="entrydate != null">
                entrydate=#{entrydate},
            </if>
            <if test="updateTime != null">
                update_time=#{updateTime}
            </if>
        </set>
        where id=#{id}
    </update>

再次执行测试方法,执行的SQL语句:

SpringBoot-5-MyBatis最佳实践分享

小结

  • <if>

    • 用于判断条件是否成立,如果条件为true,则拼接SQL

    • 形式:

      <if test="name != null"> … </if>
  • <where>

    • where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的AND或OR
  • <set>

    • 动态地在行首插入 SET 关键字,并会删掉额外的逗号。(用在update语句中)

3.3 动态SQL-foreach

案例:学生删除功能(既支持删除单条记录,又支持批量删除)

SQL语句:

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

Mapper接口:

@Mapper
public interface StudentMapper {
    //批量删除
    public void deleteByIds(List<Integer> ids);
}

XML映射文件:

  • 使用<foreach>遍历deleteByIds方法中传递的参数ids集合
<foreach collection="集合名称" item="集合遍历出来的元素/项" separator="每一次遍历使用的分隔符" 
         open="遍历开始前拼接的片段" close="遍历结束后拼接的片段">

</foreach>
<?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.zbbmeta.mapper.StudentMapper">
    <!--删除操作-->
    <delete id="deleteByIds">
        delete from student where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
</mapper> 
SpringBoot-5-MyBatis最佳实践分享
//批量删除学生 - 13,14,15
@Test
public void testDeleteByIds(){
    List<Integer> ids = Arrays.asList(131415);
    studentMapper.deleteByIds(ids);
}

执行的SQL语句:

SpringBoot-5-MyBatis最佳实践分享

3.4 动态SQL-sql&include

问题分析:

  • 在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码
SpringBoot-5-MyBatis最佳实践分享

我们可以对重复的代码片段进行抽取,将其通过<sql>标签封装到一个SQL片段,然后再通过<include>标签进行引用。

  • <sql>:定义可重用的SQL片段

  • <include>:通过属性refid,指定包含的SQL片段

SpringBoot-5-MyBatis最佳实践分享

SQL片段: 抽取重复的代码

<sql id="commonSelect">
    select id, username, password, name, gender, image, entrydate, create_time, update_time
    from student
</sql>

然后通过<include> 标签在原来抽取的地方进行引用。操作如下:

<select id="list" resultType="com.zbbmeta.entity.Student">
    <include refid="commonSelect" />

   <where>
        <if test="name !=null">
            name like concat('%',#{name},'%')
        </if>
        <if test="gender !=null">
            and gender = #{gender}
        </if>
        <if test="begin !=null and end !=null">
            and entrydate between #{begin} and #{end}
        </if>
   </where>
    order by update_time desc;
    </select>

如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!

原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!


原文始发于微信公众号(springboot葵花宝典):SpringBoot-5-MyBatis最佳实践分享

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/183646.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!