Mybatis

目录

  • 概述
  • 1 入门
    • 1.1 起步
    • 1.2 编写SQL语句
  • 2 数据库连接池技术
    • 2.1 介绍
    • 2.2 成熟产品
  • 3 Mybatis基础操作
    • 3.1 日志输入
    • 3.2 预编译SQL
    • 3.3 参数占位符
    • 3.4 主键返回
    • 3.5 数据封装
    • 3.6 条件查询
    • 3.7 参数名说明
  • 4 Mybatis的XML配置文件
    • 4.1 XML配置文件规范
    • 4.2 XML配置文件实现
  • 5 Mybatis动态SQL
    • 5.1 什么是动态SQL
    • 5.2 动态SQL-if
    • 5.3 条件查询
    • 5.4 更新操作
    • 5.5 动态SQL-foreach
    • 5.6 动态SQL-sql&include

概述

什么是MyBatis?

  • MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发。

  • MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

  • 官网:https://mybatis.org/mybatis-3/zh_CN/index.html

在上面我们提到了两个词:一个是持久层,另一个是框架。

  • 持久层:指的是就是数据访问层(dao),是用来操作数据库的。 Mybatis_第1张图片
  • 框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。在框架的基础上进行软件开发更加高效、规范、通用、可拓展。

1 入门

1.1 起步

要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatisgroupId>
  <artifactId>mybatisartifactId>
  <version>x.x.xversion>
dependency>

spring-boot 整合mybatis起步依赖:


<dependency>
	<groupId>org.mybatis.spring.bootgroupId>
	<artifactId>mybatis-spring-boot-starterartifactId>
	<version>2.3.0version>
dependency>

        
<dependency>
	<groupId>com.mysqlgroupId>
	<artifactId>mysql-connector-jartifactId>
	<scope>runtimescope>
dependency>

数据库连接信息,application.properties:

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

1.2 编写SQL语句

在MyBatis中,你可以通过注解直接在Mapper接口的方法上编写SQL语句,而不需要XML文件。这种方式被称为MyBatis的注解开发方式。以下是一些最常见的MyBatis注解:

  1. @Select - 用于查询操作。
  2. @Insert - 用于插入操作。
  3. @Update - 用于更新操作。
  4. @Delete - 用于删除操作。
  5. @Results - 定义结果集映射规则。
  6. @Result - 结果集的细节映射。
  7. @Param - 给方法参数指定一个名字。
  8. @Mapper注解 - 表示是mybatis中的Mapper接口

示例代码如下:

首先,定义一个Mapper接口:

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM user WHERE id = #{id}")
    User getUserById(@Param("id") int id);

    @Insert("INSERT INTO user(name, email) VALUES(#{name}, #{email})")
    int insertUser(@Param("name") String name, @Param("email") String email);

    @Update("UPDATE user SET name = #{name}, email = #{email} WHERE id = #{id}")
    int updateUser(@Param("id") int id, @Param("name") String name, @Param("email") String email);

    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteUser(@Param("id") int id);
}

这些注解中的字符串表示SQL语句,#{}内部表示参数绑定,@Param注解用于给参数命名,以便在SQL语句中指引参数的来源。

如果需要复杂的结果映射,可以使用@Results@Result注解:

@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name"),
    @Result(property = "email", column = "email")
})
@Select("SELECT id, name, email FROM user")
List<User> getAllUsers();

@Results注解定义了如何从数据库结果集中提取数据并填充到对象属性中。property是对象的属性名,column是数据库表中的列名。

为了使上述Mapper工作,还需要配置MyBatis以识别对应的Mapper接口,具体配置取决于你是使用纯MyBatis框架还是集成在Spring Boot项目中。

在纯MyBatis项目中,你通常在MyBatis的配置文件中指定Mapper接口的位置:

<mappers>
    <mapper class="your.package.UserMapper"/>
mappers>

在Spring Boot项目中,你可能会使用@MapperScan注解或者在每个Mapper接口上使用@Mapper注解来自动扫描并注册:

@MapperScan("your.package")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

或者使用@Mapper注解单独标记每个接口:

@Mapper
public interface UserMapper {
    // ...方法定义
}

确保正确配置了MyBatis和数据源,这样上述注解的方法就可以在应用程序中正常工作了。

2 数据库连接池技术

数据库连接池技术,避免频繁的创建连接、销毁连接而带来的资源浪费。

2.1 介绍

Mybatis_第2张图片
没有使用数据库连接池:

  • 客户端执行SQL语句:要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行后又需要关闭连接对象从而释放资源,每次执行SQL时都需要创建连接、销毁链接,这种频繁的重复创建销毁的过程是比较耗费计算机的性能。

Mybatis_第3张图片
数据库连接池是个容器,负责分配、管理数据库连接(Connection)

  • 程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象

允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个

  • 客户端在执行SQL时,先从连接池中获取一个Connection对象,然后在执行SQL语句,SQL语句执行完之后,释放Connection时就会把Connection对象归还给连接池(Connection对象可以复用)

释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

  • 客户端获取到Connection对象了,但是Connection对象并没有去访问数据库(处于空闲),数据库连接池发现Connection对象的空闲时间 > 连接池中预设的最大空闲时间,此时数据库连接池就会自动释放掉这个连接对象

数据库连接池的好处:

  1. 资源重用
  2. 提升系统响应速度
  3. 避免数据库连接遗漏

2.2 成熟产品

要怎么样实现数据库连接池呢?

  • 官方(sun)提供了数据库连接池标准(javax.sql.DataSource接口)

    • 功能:获取连接

      public Connection getConnection() throws SQLException;
      
    • 第三方组织必须按照DataSource接口实现

常见的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari (springboot默认)

现在使用更多的是:HikariDruid (性能更优越)

  • Hikari(追光者) [默认的连接池]

在这里插入图片描述

  • Druid(德鲁伊)

    • Druid连接池是阿里巴巴开源的数据库连接池项目

    • 功能强大,性能优秀,是Java语言最好的数据库连接池之一

如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要完成以下两步操作即可:

参考官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

  1. 在pom.xml文件中引入依赖
<dependency>
    
    <groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>1.2.8version>
dependency>
  1. 在application.properties中引入数据库连接配置

方式1:

spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.druid.username=root
spring.datasource.druid.password=123456

方式2:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.username=root
spring.datasource.password=123456

3 Mybatis基础操作

3.1 日志输入

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

  1. 打开application.properties文件

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

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

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

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

3.2 预编译SQL

介绍

预编译SQL有两个优势:

  1. 性能更高
  2. 更安全(防止SQL注入)

Mybatis_第5张图片

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

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

SQL注入

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

由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
SQL注入(SQL Injection)是针对基于SQL语言的数据库系统的一种注入攻击技术。攻击者通过在应用程序的输入域中输入恶意的SQL代码片段来欺骗后端数据库执行不当的SQL命令。这种攻击可以用来绕过应用程序的安全措施,获取未授权的数据访问,甚至是控制数据库服务器的更严重的攻击。

以下是一个非常简化的SQL注入攻击的示例:

假设一个登录表单提交后台的SQL查询语句是这样的:

SELECT * FROM users WHERE username = '$username' AND password = '$password';

其中 $username$password 是用户的输入。

如果用户在用户名或密码的输入框内输入:

' OR '1'='1

那么合并后的SQL语句将会成为:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '' OR '1'='1';

这个条件 '1'='1' 始终为真,所以该SQL语句实质上会选择表中所有的用户,从而绕过了身份验证。

3.3 参数占位符

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

  • #{…}

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

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

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

3.4 主键返回

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

在使用Spring Boot整合Mybatis进行数据插入操作的时候,可以将生成的主键返回到实体类中。Mybatis提供了几种方式来实现主键的返回。最常用的两种是使用useGeneratedKeys@Options注解。

使用useGeneratedKeys

在Mybatis的Mapper XML配置文件中,你可以在标签中设置useGeneratedKeys="true"keyProperty属性来自动接受数据库生成的主键值。

例如,假设有一个名为User的实体类和对应的users数据表,id字段是自增主键:

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
  INSERT INTO users(name, email) VALUES(#{name}, #{email})
insert>

这里,keyProperty对应到实体类User中的id属性。当执行此插入语句后,Mybatis会将生成的主键值回填到传入的User对象的id属性中。

使用@Options注解

在Mapper接口的方法上使用@Options注解也可以返回主键。注解里要设置useGeneratedKeys=true以及keyProperty指向对应的主键属性。

例如:

public interface UserMapper {
  //会自动将生成的主键值,赋值给user对象的id属性
  @Options(useGeneratedKeys = true, keyProperty = "id")
  @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
  int insertUser(User user);
}

使用@Options注解后,执行insertUser方法插入数据时,User实体对象的id字段将会被自动填充。

3.5 数据封装

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

Mybatis_第6张图片
原因如下:

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

解决方案:

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

起别名:在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);

再次执行测试类:

在这里插入图片描述

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

@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);

@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.properties中添加:
mybatis.configuration.map-underscore-to-camel-case=true

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

3.6 条件查询

假设我们现在有一个需求:在员工管理的列表页面中,我们需要根据条件查询员工信息,查询条件包括:姓名、性别、入职时间。

Mybatis_第7张图片

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

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

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;

接口方法:

  • 方式一
@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);
}

在这里插入图片描述
以上方式注意事项:

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

  2. 模糊查询使用${…}进行字符串拼接,这种方式呢,由于是字符串拼接,并不是预编译的形式,所以效率不高、且存在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语句(性能高、安全)
在这里插入图片描述

3.7 参数名说明

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

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

Mybatis_第8张图片

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

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

在这里插入图片描述

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

在这里插入图片描述

  • 在springBoot的1.x版本/单独使用mybatis(使用@Param注解来指定SQL语句中的参数名)

在这里插入图片描述

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

在这里插入图片描述

4 Mybatis的XML配置文件

Mybatis的开发有两种方式:

  1. 注解
  2. XML

4.1 XML配置文件规范

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

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

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

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

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