Mybatis初学

入门

Mybatis 入门

  • spring boot 项目里 pojo 文件夹中放实体类

  • mapper 文件下创建接口 ( 就相当于替换了原 dao 的接口 ),接口类前用 @Mapper 注解:// 在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给 IOC 容器管理

    • 对方法也加以 sql 操作的注解如:

      @Select("select * from user")
      public List<User> list();
      
  • 项目自带 application.properties 配置文件内配置数据库的连接信息

    # 配置数据库的连接信息(注意:不要乱改key=value的key)
    
    #驱动类名称
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    #数据库连接的url(协议://本机:端口号/数据库名)
    spring.datasource.url=jdbc:mysql://localhost:3306/test?&useUnicode=true&characterEncoding=utf-8
    spring.datasource.username=root
    spring.datasource.password=123456
    
  • spring boot 在 test 目录下有一测试类,加以了 @SpringBootTest 注解:boot 整合单元测试的注解

    • 在方法前加以 @Test 注解
  • 出错:

    image-20230911160543283

    1. 没有启动类文件
      • 一般不会有这种,除非删除文件删错了 ( 删除文件之前要备份! )
    2. 启动类文件和测试类文件不在同样的包路径下
      • 路径相对应统一的
  • 运行可以看到下图并且显示查询到的数据

    image-20230911161611758

  • 了解:

    • @Select 内编写 sql 语句不会被检测到错误,可以选择右键 Show Context Actions ——> Inject language or reference ——> MySQL(SQL)

    • 想连数据库表也提示的话就在 Idea 中配置 MySQL 数据库连接:

      image-20230911162940033

JDBC 介绍

  • JDBC ( Java DataBase Connectivity ),就是用 Java 语言操作关系型数据库的一套 API

    image-20230911163536982

    • 驱动:就像是 pom 里 mysql 的 mysql-connector-java
  • 本质:sun 公司官方定义的一套操作所有关系型数据库的规范,即接口

    • 各个数据库厂商去实现这套接口,提供数据库驱动 jar 包
    • 我们可以使用这套接口 ( JDBC ) 编程,真正执行的代码是驱动 jar 包中的实现类
  • JDBC 七大步骤

    1. 项目导入对应的数据库 jar 包(数据库驱动)

    2. 加载驱动(jar 包内类加载到内存中)

    3. 建立连接

    4. 建立 SQL 执行器 ( 执行 sql 语句所用 )

    5. 编写 SQL 语句并接收 ( 查询用 ResultSet 结果集接收 )

    6. 处理结构

    7. 关闭连接

数据库连接池

  • 数据库连接池是个容器,负责分配、管理数据库连接 ( Connection )

    • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
    • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
  • 优势:

    • 资源复用
      • 连接池就内含多个连接,用就给,不用就归还
    • 提升系统响应速度
      • 每次新连接费时费资源
    • 避免数据库连接遗漏
      • 内设最大空闲时间,保证连接的归还
  • 标准接口:DataSource

    • 官方 ( sun ) 提供的数据库连接池接口,由第三方组织实现此接口
    • 功能:通过 ConnectiongetConnection 获取连接
      • 如:Connection getConnection() throws SQLException;
  • 常见产品:

    • C3P0、DBCP、Druid、Hikari ( springboot ) —— 都是要实现 DataSource 接口

    • Druid ( 德鲁伊 )

      • Druid 连接池是阿里巴巴开源的数据库连接池项目
      • 功能强大,性能优秀,是 Java 语言最好的数据库连接池之一
    • springboot:

      image-20230912213232694

    • 切换连接池:

      1. pom.xml 加依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>
        
      2. 配置连接四要素

        spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
        spring.datasource.url=jdbc:mysql://localhost:3306/test?&useUnicode=true&characterEncoding=utf-8
        spring.datasource.username=root
        spring.datasource.password=123456
        # 或者都加上一个druid,如:spring.datasource.druid.password=123456
        
    • 查看连接池更换成功

      image-20230912215139383

lombok

  • getter、setter、toString 每次都写会很臃肿

  • Lombok 是一个实用的 Java 类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString 等方法,并可以自动化生成日志变量,简化 java 开发、提高效率

  • 注解 作用
    @Getter / @Setter 为所有的属性提供 get / set 方法
    @ToString 会给类自动生成易阅读的 toString 方法
    @EqualsAndHashCode 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法
    @Data 提供了更综合的生成代码功能 ( @Getter + @Setter + @ToString + @EqualsAndHashCode )
    @NoArgsConstructor 为实体类生成无参的构造器方法
    @AllArgsConstructor 为实体类生成除了 static 修饰的字段之外带有各参数的构造器方法 ( 全参构造 )
    • 添加依赖 ( 不用写版本号,因为 spring-boot-starter-parent 内已经管理了版本 ):

      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      
    • 看编译后的字节码文件就会发现实体类把所有的注解都变成了一个个对应的方法

      image-20230913213344245

    • Lombok 会在编译时,自动生成对应的 Java 代码,使用时还需要安装一个 lombok 的插件 ( idea 较新的版本自带 )

      image-20230913213659048

Mybatis 基础操作

准备

  • 实施前的准备工作:

    1. 准备数据库表

    2. 创建一个新的 springboot 工程,选择引入对应的起步依赖 ( mybatis、mysql 驱动、lombok )

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

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

    5. 准备 Mapper 接口 EmpMapper

准备数据库表

  • 新建并填充数据于表内

  • 创建一个新的 springboot 工程,选择引入对应的起步依赖:mybatis、mysql 驱动、lombok

    image-20230911151929789

  • 在最新版的 mysql 与旧版不太一样

    <!--    官方刚发布的最新的版本的mysql驱动包    -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!--    下面这个是上一个版本的mysql驱动包(常见)    -->
    <!--        <dependency>-->
    <!--            <groupId>mysql</groupId>-->
    <!--            <artifactId>mysql-connector-java</artifactId>-->
    <!--            <scope>runtime</scope>-->
    <!--        </dependency>-->
    
  • application.properties 中引入数据库连接信息

# 配置数据库的连接信息(注意:不要乱改key=value的key)

#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url(协议://本机:端口号/数据库名)
spring.datasource.url=jdbc:mysql://localhost:3306/test?&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
  • 创建对应的实体类 Emp ( 实体类属性采用驼峰命名 )
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Short gender;
    private String image;
    private Short job;
    private LocalDate entrydate;     //LocalDate类型对应数据表中的date类型
    private Integer deptId;
    private LocalDateTime createTime;//LocalDateTime类型对应数据表中的datetime类型
    private LocalDateTime updateTime;
}
  • 数据库对应到实体类:
    • int —— Integer
    • varchar —— String
    • tinyint —— Short
    • date —— LocalDate
    • datetime —— LocalDateTime
    • 数据库是下划线分隔,实体类里是标准小驼峰
  • 准备 Mapper 接口:EmpMapper
/*@Mapper注解:表示当前接口为mybatis中的Mapper接口
  程序运行时会自动创建接口的实现类对象(代理对象),并交给Spring的IOC容器管理
*/
@Mapper
public interface EmpMapper {

}

删除

功能实现

  • SQL 语句
-- 删除id=17的数据
delete from emp where id = 17;
  • 接口方法
@Mapper
public interface EmpMapper {
    @Delete("delete from emp where id = #{id}")//使用#{key}方式获取方法中的参数值,mybatis 的占位符:#{}
    public void delete(Integer id);
}
  • 如果 mapper 接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如:#{id}、#

    • 但建议保持名字一致
  • 测试

    • 在单元测试类中通过 @Autowired 注解注入 EmpMapper 类型对象
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
    @Autowired //从Spring的IOC容器中,获取类型是EmpMapper的对象并注入
    private EmpMapper empMapper;

    @Test
    public void testDel(){
        //调用删除方法
        empMapper.delete(16);
    }
}

日志输出

  • 在 Mybatis 当中我们可以借助日志,查看到 sql 语句的执行、执行传递的参数以及执行结果
  1. 打开 application.properties 文件

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

# 快捷:mybatislog;选StdOutImpl
#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  • 开启日志之后,我们再次运行单元测试,可以看到在控制台中,输出了以下的 SQL 语句信息:

    image-20230913220922870

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

预编译 SQL

介绍

  • 预编译 SQL 有两个优势:

    1. 性能更高

    2. 更安全 ( 防止 SQL 注入 )

  • java 里使用 sql

    image-20230913221232886

    • 正常 sql 是:语法检查、优化、编译、执行

    • 而 java 执行 sql 语句前会先看缓存,缓存没有再一步一步来

      • 例如删除语句:若是直接把 id 值拼在后面,每次都无法从缓存中取,每次都要从检查开始执行
    • 而若是用了预编译,会把预编译语句 ( 含 ?占位的 ) 和 id 值一起发给数据库,数据库将预编译的语句缓存起来,等到下次时就不用再重新来,直接从缓存中取即可,只需要改 id 即可

      image-20230913221836214

  • 性能更高:预编译 SQL,编译一次之后会将编译后的 SQL 语句缓存起来,后面再次执行这条语句时,不会再次编译

    • 只是输入的参数不同
  • 更安全 ( 防止 SQL 注入 ):将敏感字进行转义,保障 SQL 的安全性

SQL 注入

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

    • 由于没有对用户输入进行充分检查,而 SQL 又是拼接而成,在用户输入参数时,在参数中添加一些 SQL 关键字,达到改变 SQL 运行结果的目的,也可以完成恶意攻击
  • 若是用户名随意输入,密码输入:' or '1' = '1,就会发现仍然可以登录成功

    • 由于没有对用户输入内容进行充分检查,而 SQL 又是字符串拼接方式而成,在用户输入参数时,在参数中添加一些 SQL 关键字,达到改变 SQL 运行结果的目的,从而完成恶意攻击
    • 即:用户在页面提交数据的时候人为的添加一些特殊字符,如上述的 “ or ”,使得 sql 语句的结构发生了变化,最终可以在没有用户名或者密码的情况下进行登录 ( 成了判断用户名和密码是否正确,或者判断 1 是否等于 1 的语句了 )
  • 而若是用预编译的、?占位的 sql 语句,就会是:

    • 不管密码是啥,处理时都是把整个 ' or '1' = '1 作为一个完整的参数,赋值给第二个问号 ( ' or '1' = '1 进行了转义,只当做字符串使用 )

参数占位符

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

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

    • 拼接 SQL,直接将参数拼接在 SQL 语句中,存在 SQL 注入问题
    • 使用时机:如果对表名、列表进行动态设置时使用
  • 注意事项:在项目开发中,建议使用 #{...},生成预编译 SQL,防止 SQL 注入安全

新增

基本新增

  • SQL 语句:
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values ('songyuanqiao','宋远桥',1,'1.jpg',2,'2012-10-09',2,'2022-10-01 10:00:00','2022-10-01 10:00:00');
  • 接口方法:
@Mapper
public interface EmpMapper {

    @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
    public void insert(Emp emp);
}
  • 注意:#{...} 里面写的名称是对象实体类的属性名
    • 不是 sql 内的带下划线的列名
  • 测试类:
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
    @Autowired
    private EmpMapper empMapper;

    @Test
    public void testInsert(){
        //创建员工对象
        Emp emp = new Emp();
        emp.setUsername("tom");
        emp.setName("汤姆");
        emp.setImage("1.jpg");
        emp.setGender((short)1);
        emp.setJob((short)1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(1);
        //调用添加方法
        empMapper.insert(emp);
    }
}

主键返回

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

    • 如:添加套餐数据时,还需要维护套餐菜品关系表数据 ( 某套餐都关联了哪些菜品的中间表 )
      • 一个套餐对应多个菜品,而菜品和套餐之间是多对多的关系,就需要有一张套餐菜品中间表来维护它们之间的关系 ( 有两个外键字段 )
  • 如何实现在插入数据之后返回所插入行的主键值 ?

    • 默认情况下,执行插入操作时,是不会主键值返回的,如果我们想要拿到主键值,需要在 Mapper 接口中的方法上添加一个 Options 注解,并在注解中指定属性:useGeneratedKeys=true 和 keyProperty= " 实体类属性名 "
      • true:代表我们要拿到生成的主键值
      • 实体属性名若是 id 的话:拿到的、自动生成的主键值最终会封装到 emp 对应的 id 中
  • 主键返回代码实现:

    @Mapper
    public interface EmpMapper {
        
        //会自动将生成的主键值,赋值给emp对象的id属性
        @Options(useGeneratedKeys = true,keyProperty = "id")
        @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
        public void insert(Emp emp);
    }
    
    • 因为传入的属性较多,所以这里用对象 pojo
  • 测试:

    @Test
    public void testInsert(){
        //创建员工对象
        Emp emp = new Emp();
        emp.setUsername("jack");
        emp.setName("杰克");
        emp.setImage("1.jpg");
        emp.setGender((short)1);
        emp.setJob((short)1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(1);
        //调用添加方法
        empMapper.insert(emp);
    	
        // 自动生成的主键值,已赋值给emp对象的id属性了
        System.out.println(emp.getId()); // 获取插入后的自增的id主键(不是输入的,而是分配的)
    }
    

更新

  • SQL 语句:

    update emp set username = 'linghushaoxia', name = '令狐少侠', gender = 1 , image = '1.jpg' , job = 2, entrydate = '2012-01-01', dept_id = 2, update_time = '2022-10-01 12:12:12' where id = 18;
    
  • 接口方法:

    @Mapper
    public interface EmpMapper {
    
        @Update("update emp set username=#{username}, name=#{name}, gender=#{gender}, image=#{image}, job=#{job}, entrydate=#{entrydate}, dept_id=#{deptId}, update_time=#{updateTime} where id=#{id}")
        public void update(Emp emp);
        
    }
    
  • 测试类:

    @SpringBootTest
    class SpringbootMybatisCrudApplicationTests {
        @Autowired
        private EmpMapper empMapper;
    
        @Test
        public void testUpdate(){
            //要修改的员工信息
            Emp emp = new Emp();
            emp.setId(23);
            emp.setUsername("songdaxia");
            emp.setName("老宋");
            emp.setImage("2.jpg");
            emp.setGender((short)1);
            emp.setJob((short)2);
            emp.setEntrydate(LocalDate.of(2012,1,1));
            emp.setUpdateTime(LocalDateTime.now());
            emp.setDeptId(2);
            //调用方法,修改员工数据
            empMapper.update(emp);
        }
    }
    
    • 更新时间直接设置为当前时间 LocalDateTime.now() 而非自行输入

查询

根据 ID 查询

  • 在员工管理的页面中,当我们进行更新数据时,会点击 “ 编辑 ” 按钮,然后此时会发送一个请求到服务端,会根据 Id 查询该员工信息,并将员工数据返回在页面上

  • SQL 语句:

    select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp;
    
  • 接口方法:

    @Mapper
    public interface EmpMapper {
        @Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}")
        public Emp getById(Integer id);
    }
    
  • 测试类:

    @SpringBootTest
    class SpringbootMybatisCrudApplicationTests {
        @Autowired
        private EmpMapper empMapper;
    
        @Test
        public void testGetById(){
            Emp emp = empMapper.getById(1);
            System.out.println(emp);
        }
    }
    
    • 查询结果会发现 deptId,createTime,updateTime 这几个字段是没有值的,这就涉及到了数据封装的部分

数据封装

  • 我们看到查询返回的结果中大部分字段是有值的,但是 deptId,createTime,updateTime 这几个字段是没有值的,而数据库中是有对应的字段值的,那是因为:

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

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

  • 解决方案:

    1. 起别名

    2. 结果映射

    3. 开启驼峰命名

  • F1:起别名:在 SQL 语句中,对不一样的列名起别名

    • 注意:别名和实体类属性名要一样
    @Select("select id, username, password, name, gender, image, job, entrydate, " +
            "dept_id AS deptId, create_time AS createTime, update_time AS updateTime " +
            "from emp " +
            "where id=#{id}")
    public Emp getById(Integer id);
    
  • F2:手动结果映射:通过 @Results 及 @Result 进行手动结果映射

    • 在 Mapper 的 select 注解前加手动映射
    @Results({@Result(column = "dept_id", property = "deptId"),
              @Result(column = "create_time", property = "createTime"),
              @Result(column = "update_time", property = "updateTime")})
    @Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}")
    public Emp getById(Integer id);
    
    • 在此处的 @Select 注解中,可以将数据库中的字段 ( 带下划线 ) 全写上去,或者直接加星号:*
  • F3:开启驼峰命名 ( 推荐 ):如果字段名与属性名符合驼峰命名规则,mybatis 会自动通过驼峰命名规则映射

    驼峰命名规则:abc_xyz  =>  abcXyz
    
    表中字段名:abc_xyz
    类中属性名:abcXyz
    
    • 在 application.properties 中添加:

      # camel (abc_xyz  =>  abcXyz)
      mybatis.configuration.map-underscore-to-camel-case=true
      
    • 要使用驼峰命名前提是:实体类的属性与数据库表中的字段名严格遵守驼峰命名

条件查询

  • 通过页面原型以及需求描述我们要实现的查询:

    • 姓名:要求支持模糊匹配

    • 性别:要求精确匹配

    • 入职时间:要求进行范围查询

    • 根据最后修改时间进行降序排序

  • SQL 语句:

    select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time 
    from emp 
    where name like '%张%' 
          and gender = 1 
          and entrydate between '2010-01-01' and '2020-01-01 ' 
    order by update_time desc;
    
  • 方式一 ( 模糊查询用 ${ ... } 有注入风险,而 #{ ... } 会转成 ?不能出现在单引号之间,所以只能用 ${ ... },日志里可见 like 是拼接的方式,那就不是预编译的形式了 )

    @Mapper
    public interface EmpMapper {
        @Select("select * from emp " +
                "where name like '%${name}%' " +
                "and gender = #{gender} " +
                "and entrydate between #{begin} and #{end} " +
                "order by update_time desc")
        public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
    }
    
    • 注意:
      • 方法中的形参名和 SQL 语句中的参数占位符名要保持一致
      • 模糊查询是使用 ${...} 进行字符串拼接,这种方式呢,由于是字符串拼接,并不是预编译的形式,所以效率不高、且存在 sql 注入风险
  • 方式二 ( 解决 SQL 注入风险 )

    • 使用 MySQL 提供的字符串拼接函数:concat ('%', '关键字', '%')
    • 此函数内就可以用 #{ ... } 占位了
    @Mapper
    public interface EmpMapper {
    
        @Select("select * from emp " +
                "where name like concat('%', #{name}, '%') " +
                "and gender = #{gender} " +
                "and entrydate between #{begin} and #{end} " +
                "order by update_time desc")
        public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
    }
    
    • 执行结果:生成的 SQL 都是预编译的 SQL 语句 ( 性能高、安全 )

参数名说明

  • 在先前编写的条件查询功能中,需要保证接口中方法的参数列表内的形参名要与 SQL 语句中的参数占位符名相同
    • 只有一个参数的话除外 ( 接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写 )
    • 不相同时,就会出现问题
  • 参数名在不同的 SpringBoot 版本中,处理方案还不同:
    • 在 springBoot 的 2.x 版本 ( 保证参数名一致即可 )

      • springBoot 的父工程对 compiler 编译插件进行了默认的参数 parameters 配置,使得在编译时,会在生成的字节码文件中保留原方法形参的名称,所以 #{ … } 里面可以直接通过形参名获取对应的值
    • 在 springBoot 的 1.x 版本 / 单独使用 mybatis ( 使用 @Param 注解来指定 SQL 语句中的参数名 )

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

Mybatis 的 XML 配置文件

  • Mybatis 的开发有两种方式:

    1. 注解

    2. XML

XML 配置文件规范

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

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

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

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

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

  • xml 文件中:

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

    • resultType 属性,指的是查询返回的单条记录所封装的类型,即实体类所在路径

XML 配置文件实现

  • 创建 XML 映射文件

    1. 包名相同,同目录

      image-20230915090727630

    2. 对应接口的 xml 配置文件

      image-20230915091018183

  • 编写 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 接口全限定名

    <mapper namespace="com.demo_mybatis.mapper.EmpMapper">
    
    </mapper>
    
  • 配置:XML 映射文件中 sql 语句的 id 与 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.demo_mybatis.mapper.EmpMapper">
        <!--  查询操作  -->
        <select id="list" resultType="com.demo_mybatis.pojo.Emp">
            select * from emp
            where name like concat('%', #{name}, '%')
            and gender = #{gender}
            and entrydate between #{begin} and #{end}
            order by update_time desc
        </select>
    </mapper>
    
    • 注意在 xml 中的 #{name} 不要加单引号 ( 接口中的 @Select 中添加了 )
  • 测试

    @Test
    public void testSelect(){
        LocalDate localDate = LocalDate.parse("2000-01-01");
        LocalDate localDate2 = LocalDate.parse("2020-01-01");
        Short short1 = Short.parseShort("1");
        List<Emp> list = empMapper.list("张", short1, localDate, localDate2);
    
        for (Emp emp: list) {
            System.out.println(emp);
        }
    }
    
    • 结果:

      image-20230915093216382

MybatisX 的使用

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

  • MybatisX 的安装:

    image-20230915094232103

  • 可以通过 MybatisX 快速定位:

    image-20230915094314573

  • 官方解释:

    • 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂点的语句,注解不仅力不以心,还会让你本就复杂的 SQL 语句更加杂乱不堪,因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句
    • 选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你的团队,换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换
  • 结论:

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

Mybatis 动态 SQL

了解动态 SQL

  • 在页面中,列表上方进行筛选的条件是动态的,是可以不传递的,也可以只传递其中的一个或者两个或者全部

    • 如招聘网站,只选中职位可以进行筛选,选择好职位和地区会更精确,也可以职位、地区、薪资都选择上,多种方式
  • 但刚才编写的 SQL 语句中,会看到,三个条件直接写死了

    • 如果页面只传递了参数姓名 name 字段,其他两个字段 性别入职时间 没有传递,那么这两个参数的值就是 null
  • 正确的做法应该是:传递了参数,再组装这个查询条件;如果没有传递参数,就不组装这个查询条件

    • 比如:如果姓名输入了 " 张 ",对应的 SQL 为:select * from emp where name like '%张%' order by update_time desc;
    • 如果姓名输入了 " 张 ",性别选择了 " 男 ",则对应的 SQL 为:select * from emp where name like '%张%' and gender = 1 order by update_time desc;
  • SQL 语句会随着用户的输入或外部条件的变化而变化,我们称为:动态 SQL
  • 在 Mybatis 中提供了很多实现动态 SQL 的标签,我们学习 Mybatis 中的动态 SQL 就是掌握这些动态 SQL 标签

动态 SQL-if

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

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

条件查询

  • 原有的 SQL 转动态 SQL 语句

    <!--  查询操作  -->
    <select id="list" resultType="com.demo_mybatis.pojo.Emp">
        select * from emp 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 testSelect(){
        LocalDate localDate = LocalDate.parse("2000-01-01");
        LocalDate localDate2 = LocalDate.parse("2020-01-01");
        Short short1 = Short.parseShort("1");
        List<Emp> list = empMapper.list(null, short1, localDate, localDate2);
    
        for (Emp emp: list) {
            System.out.println(emp);
        }
    }
    
  • 因为拼接错误造成:

    image-20230915100905723

  • 虽然改成 "张", null, null, null 或者其他 where 后拼接不了 and 的语句不会出错,但明显问题无法解决,所以要使用 <where> 标签代替 where 关键字

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

    <!--  查询操作  -->
    <select id="list" resultType="com.demo_mybatis.pojo.Emp">
        select * from emp
        <where>
            <!--      if 标签作为 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 testSelect(){
        LocalDate localDate = LocalDate.parse("2000-01-01");
        LocalDate localDate2 = LocalDate.parse("2020-01-01");
        Short short1 = Short.parseShort("1");
        List<Emp> list = empMapper.list(null, short1, localDate, localDate2);
    
        for (Emp emp: list) {
            System.out.println(emp);
        }
    }
    
  • 控制台可见

    image-20230915101649688

更新员工

  • 注意:if 标签中 test 的值是与 #{ ... } 中相对应的,等于号前面的才是唯一的对应 sql 数据库字段的命名

    • 还有不要顺手把 from 写上去了 ( 只有查询和删除语句会有 from 关键字 )
  • 完善更新员工功能,修改为动态更新员工数据信息

    • 动态更新员工信息,如果更新时传递有值,则更新;如果更新时没有传递值,则不更新

    • 解决方案:动态 SQL

  • 修改 Mapper 接口:

    //    @Update("此注解删去")
    public void update(Emp emp);
    
  • 修改Mapper映射文件:

    <update id="update" >
        update emp set
        <if test="username != null">
            username = #{username},
        </if>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="gender != null">
            gender = #{gender},
        </if>
        <if test="updateTime != null">
            update_time = #{updateTime}
        </if>
        where id = #{id}
    </update>
    
  • 测试:

    @Test
    public void testUpdate(){
        Emp emp = new Emp();
        emp.setUsername("jack+1");
        emp.setGender((short)2);
        //        emp.setUpdateTime(LocalDateTime.now()); // 若是加上此句,因为mapper中的语句顺序,就不会在最后多出来一个逗号,也就不会错了
        emp.setDeptId(20);
    
        empMapper.update(emp);
    }
    
  • 会发现同先前 select 的 and 一样,根据输入会有多余的逗号

    image-20230915104010389

    • 针对以上问题的解决方案:需要使用 <set> 标签代替 SQL 语句中的 set 关键字
  • <set>:动态的在 SQL 语句中插入 set 关键字,并会删掉额外的逗号 ( 用于 update 语句中 )

    <update id="update" >
            update emp
            <set>
                <if test="username != null">
                    username = #{username},
                </if>
                <if test="name != null">
                    name = #{name},
                </if>
                <if test="gender != null">
                    gender = #{gender},
                </if>
                <if test="updateTime != null">
                    update_time = #{updateTime}
                </if>
            </set>
            where id = #{id}
        </update>
    
  • 测试:

    @Test
    public void testUpdate(){
        Emp emp = new Emp();
        emp.setUsername("jack+2");
        emp.setGender((short)2);
        //        emp.setUpdateTime(LocalDateTime.now());
        emp.setId(20);
    
        empMapper.update(emp);
    }
    
  • 结果 ( 没有逗号了 ):

    image-20230915104612700

小结

  • <if>

  • 用于判断条件是否成立,如果条件为 true,则拼接 SQL,<if test="username != null">

  • <where>

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

  • <set>

    • 动态地在行首插入 set 关键字,并会删掉额外的逗号 ( update )

动态 SQL-foreach

  • 列表中在每个员工信息前都有一个单选框,想要删除某个员工就要点击前面的单选框,可同时点击多个,既支持删除单条记录,又支持批量删除

  • 原 SQL 语句:

    delete from emp where id in (1,2,3);
    
  • Mapper接口:

    //批量删除
    public void deleteByIds(List<Integer> ids);
    
  • XML 映射文件:

    • 使用 <foreach> 遍历 deleteByIds 方法中传递的参数 ids 集合

    • 使用语法:

      <foreach collection="集合名称" item="集合遍历出来的元素/项" separator="每一次遍历使用的分隔符" 
               open="遍历开始前拼接的片段" close="遍历结束后拼接的片段">
      </foreach>
      
    • mapper.xml:

      <delete id="deleteByIds">
          delete from emp where id in
          <!--    注意:此处的ids是对应接口参数列表里的参数名,要一致    -->
          <foreach collection="ids" item="id" separator="," open="(" close=")">
              #{id}
          </foreach>
      </delete>
      
      • 注意:如代码中注释所描述,collection 属性的值是接口中参数列表的值

        image-20230915152720432

  • 运行结果:

    image-20230915152539978

动态 SQL-sql & include

问题分析:

  • 在 xml 映射文件中配置的 SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码

  • 如:在根据 id 进行查询时,select 后要获取自定义的字段 ( 非全部不能用 * ) 很长的时候,与同情况下根据 name、gender、age 等动态进行获取同自定义字段

    • 这两个方法就都会写很多的字段名造成冗余
  • 解决方式:

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

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

    • include 标签中 refid 就等于 sql 标签中 id 属性对应的值

      image-20230915154334123

  • 实例操作:

    • SQL 片段: 抽取重复的代码

      <sql id="commonSelect">
              select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
          </sql>
      
    • 然后通过 <include> 标签在原来抽取的地方进行引用

      <!--  查询操作  -->
          <select id="list" resultType="com.demo_mybatis.pojo.Emp">
              <include refid="commonSelect"/>
              <where>
      <!--      if 标签作为 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>
      
  • 运行结果:

    image-20230915154117300

posted @ 2023-10-20 15:15  朱呀朱~  阅读(4)  评论(0编辑  收藏  举报