MyBatis——Java 持久层框架

2023-12-13 11:56:34

MyBatis 是什么

  • 是一款持久层框架
  • 基于 JDBC
  • 可以通过方便的设置实现数据库操作

Mybatis(之前称为 iBatis)也是一个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。

ORM 把数据库映射为对象:

  • 数据库表(table)–>类(class)
  • 记录(record,行数据)–>对象(object)
  • 字段(field)–>对象的属性(attribute)

准备工作

创建一个数据库和表

create database mycnblog default character set utf8mb4;

use mycnblog;

create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime datetime default now(),
    updatetime datetime default now(),
    `state` int default 1
) default charset 'utf8mb4';

insert into userinfo values(1, 'admin', '123', '', '2023-12-10 10:09:30', '2023-12-10 10:09:30', 1);

引入依赖

MyBatis Framework 和 MySQL Driver

引入之后,我们的项目是启动不了的,还需要进行配置。

配置连接字符串和 MyBatis

配置 application.yml,指定要连接的数据库在哪

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

如果 mysql-connector-java 是5.x 之前的使用的是 com.mysql.jdbc.Driver,之后的使用的是 com.mysql.cj.jdbc.Driver

指定 MyBatis 在哪里查找映射器(Mapper)的 XML 文件:

在 MyBatis 中,Mapper 文件定义了 SQL 映射和与数据库的交互。

mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

img

classpath:mapper/**Mapper.xml:表示在类路径(classpath)下的 mapper 目录中查找所有以 Mapper.xml 结尾的文件。

** 是通配符,它和 * 的区别是:

  • ** 包括子目录,可以递归匹配多级子目录。
  • * 不包括子目录,只匹配当前目录下的文件。

如果想看 MyBatis 最终执行的 SQL 语句,还可以进行如下配置:

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

完成上述操作,启动项目,如果可以启动,就说明我们的环境配置完成了。


MyBatis 组成

MyBatis 主要由两部分组成:

  1. 接口(Mapper 接口): MyBatis 的接口用于定义数据库操作的方法。每个接口方法通常对应一个数据库操作,比如查询、插入、更新或删除数据。这些接口方法的签名定义了输入参数和返回类型,而实际的 SQL 查询则是通过 XML 文件或注解来配置的。
  2. XML 文件(Mapper XML 文件): MyBatis 的 XML 文件用于存放 SQL 映射配置,即将接口方法与实际的 SQL 语句进行关联。XML 文件中包含了数据库操作的具体 SQL 语句,以及输入参数和输出结果的映射关系。每个 Mapper 接口通常对应一个同名的 XML 文件。

MyBatis 使用步骤

定义一个类

这个类的属性名和数据库表中的字段名一致

package org.example.mybatisdemo.model;

import lombok.Data;

import java.util.Date;

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
    private int state;
}

创建 MyBatis 接口(以查询所有为例)

@Mapper 注解是 MyBatis 框架中用于标识一个接口为 MyBatis 映射器(Mapper)的注解。

当你在一个接口上使用 @Mapper 注解时,MyBatis 将会扫描这个接口,并为其创建一个实现类,用于执行与数据库相关的 SQL 操作。

Spring 将会在运行时为这个接口生成代理对象,使其可以被注入到其他组件中

package org.example.mybatisdemo.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.example.mybatisdemo.model.UserInfo;

import java.util.List;

@Mapper // 此注解表示这是一个 mybatis 接口
public interface UserMapper {
    List<UserInfo> getAll();
}

创建与接口对应的 xml 文件(实现接口中的所有方法)

img

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="">
    
</mapper>

namespace 里填要实现的接口的路径。

例:mapper 里面添加 select 标签,表示要实现查询方法,id 指定实现的方法名(和接口中的一致),result 指定返回的记录的类型,标签内部填写具体的 sql 语句

<?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="org.example.mybatisdemo.mapper.UserMapper">
    <select id="getAll" resultType="org.example.mybatisdemo.model.UserInfo">
        select * from userinfo
    </select>
</mapper>

使用单元测试进行验证

在要测试的代码上右键,选择 Generate->Test

img

生成的测试类会进入到 test 目录下面

设置当前测试环境为 SpringBoot,并注入要测试的类:

package org.example.mybatisdemo.mapper;

import org.example.mybatisdemo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest // 当前测试的上下文环境为SpringBoot
class UserMapperTest {
    @Autowired // 注入要测试的类
    private UserMapper userMapper;

    @Test // 测试
    void getAll() {
        List<UserInfo> list = userMapper.getAll();
        for (UserInfo user : list) {
            System.out.println(user.toString());
        }
    }
}

运行测试方法,查看测试结果:

UserInfo(id=1, username=admin, password=123, photo=, createtime=Sun Dec 10 10:09:30 HKT 2023, updatetime=Sun Dec 10 10:09:30 HKT 2023, state=1)

查询单条记录

查询单条记录需要提供参数,供 where 语句使用。

在抽象方法的参数列表中,用 @Param 注解指定传递给 SQL 语句的参数名

@Mapper // 此注解表示这是一个 mybatis 接口
public interface UserMapper {
    List<UserInfo> getAll();

    UserInfo getUserById(@Param("id") Integer id);
}

在 XML 映射文件中,可以使用 #{id} 来引用方法参数。

<select id="getUserById" resultType="org.example.mybatisdemo.model.UserInfo">
    select * from userinfo where id = #{id}
</select>

注:@Param("id") 也可以省略,通常建议写上。

测试:

@Test
void getUserById() {
    UserInfo userInfo = userMapper.getUserById(1);
    System.out.println(userInfo);
}

增加数据

@Mapper // 此注解表示这是一个 mybatis 接口
public interface UserMapper {
    List<UserInfo> getAll();

    UserInfo getUserById(@Param("id") Integer id);

    int add(UserInfo userInfo); // 传入的是对象,不加 @Param 注解
}

xml 文件中使用 insert 标签,直接用 #{属性名} 来获取对象中的属性:

<insert id="add">
    insert into userinfo(username, password)
    values (#{username}, #{password})
</insert>

如果传入对象的时候加了 @Param 注解,如:

int add(@Param("userInfo") UserInfo userInfo);

那么 xml 获取属性的时候,必须要使用 .

<insert id="add">
    insert into userinfo(username, password)
    values (#{userInfo.username}, #{userInfo.password})
</insert>

测试:

@Test
void add() {
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername("zhangsan");
    userInfo.setPassword("123");
    int effect = userMapper.add(userInfo);
    System.out.println("影响的行数:" + effect);
}

增加数据的时候返回自增主键

在 insert 标签属性中添加 useGeneratedKeys=“true” keyProperty=“id”

<insert id="add" useGeneratedKeys="true" keyProperty="id">
  • useGeneratedKeys="true":这个属性用于指定是否使用数据库自动生成的主键。当设置为 true 时,表示插入数据后,将从数据库获取生成的主键值,并将其设置到相应的属性中。
  • keyProperty="id":如果 useGeneratedKeys 设置为 true,则需要使用 keyProperty 属性指定将生成的主键值设置到哪个 Java 对象的属性中。在这个例子中,表示将生成的主键值设置到 Java 对象的 id 属性中。

测试:

@Test
void add() {
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername("zhangsan");
    userInfo.setPassword("123");
    int effect = userMapper.add(userInfo);
    System.out.println("影响的行数:" + effect + " id: " + userInfo.getId());
}
// 输出:影响的行数:1 id: 3

修改和删除

在 xml 中分别使用的是 update 标签和 delete 标签,除此以外,其他的关于 MyBatis 的操作都和查询、增加一样,这里不再赘述。

#{} 和 ${} 的区别

#{} 和 ${} 在构造 SQL 语句的方式上存在差异:

  1. #{} 表达式(预编译):
    • #{} 表达式是 MyBatis 中用于预编译参数的占位符。这种方式会在 SQL 语句中使用占位符 ?,然后通过预编译的方式向 SQL 语句中传递参数。这样可以有效防止 SQL 注入攻击。
  2. ${} 表达式(字符串拼接):
    • ${} 表达式是用于字符串拼接的方式。在 SQL 语句中,${} 会直接替换成参数的字符串表示形式,不会进行预编译。

预编译的方式具有以下优点:

  1. 参数类型校验: 预编译时数据库引擎能够获取到参数的类型信息,可以对参数进行类型校验。如果传递给数据库的参数类型与预期的类型不匹配,数据库引擎通常会拒绝执行。
  2. 转义处理: 预编译会对参数值进行转义处理,确保特殊字符不会破坏 SQL 语句的结构。这有助于防止恶意构造的参数值破坏 SQL 查询语句。

绝大部分场景都是使用 #{} ,但是也有部分场景适合使用 ${}

比如我要传递的参数是 SQL 中的关键字,比如 asc 和 desc

<select id="getAllByOrder" resultType="org.example.mybatisdemo.model.UserInfo">
    select * from userinfo order by ${order}
</select>

如果使用 #{} 则会将关键字识别成字符串,在最终生成的 SQL 语句中画蛇添足地增加 '',比如 select * from userinfo order by 'desc' 导致 SQL 语法错误。直接使用 ${} 字符串拼接的方式就简便很多。

而且我们可以在代码中 if 判断,保证传入的参数只能是 asc 和 desc,从而防止 SQL 注入。

like 查询

模糊查询获取参数也必须用 #{} ,不能用 ${} 因为传入的参数是无法穷举的,有 SQL 注入的风险。

#{} 会画蛇添足,和通配符 % _ 配合使用不太方便。

<select id="searchUsers" resultType="org.example.mybatisdemo.model.UserInfo">
    select * from userinfo
    where username like '%#{pattern}%'
</select>

以上代码最后构造的 SQL 语句形如 select * from userinfo where username like '%'s'%'

解决方法:

要么写成下面的形式,% 直接通过 Java 代码传入:

<select id="searchUsers" resultType="org.example.mybatisdemo.model.UserInfo">
    select * from userinfo
    where username like #{pattern}
</select>

Java 代码:

List<UserInfo> list = userMapper.searchUsers("%s%");

这样自动添加 '' 后就正确了。

要么使用 concat:

<select id="searchUsers" resultType="org.example.mybatisdemo.model.UserInfo">
    select * from userinfo
    where username like concat('%', #{pattern}, '%')
</select>

Java 代码:

List<UserInfo> list = userMapper.searchUsers("s");

resultMap

resultMap 是一种用于映射查询结果集的配置元素。它允许你定义一个映射规则,将数据库查询结果的列映射到 Java 对象的属性中。

简单来说,这种方式可以让 Java 代码中的类的属性名,和数据库中的字段名在不一致的情况下也能一一对应。

<resultMap id="userResultMap" type="org.example.mybatisdemo.model.UserInfo">
    <id property="id" column="user_id"/>
    <result property="username" column="user_name"/>
    <result property="password" column="user_password"/>
    <!-- 其他属性的映射... -->
</resultMap>
  • id 元素用于定义主键映射,property 指定了 Java 对象中的属性名,column 指定了数据库结果集中的列名。
  • result 元素用于定义其他属性的映射规则。
<select id="getUserById" resultMap="userResultMap">
    select * from users where user_id = #{userId}
</select>

getUserById 查询将使用名为 userResultMapresultMap 进行结果映射。MyBatis 将查询结果中的列按照 resultMap 中定义的映射规则进行映射,从而创建一个包含查询结果的 Java 对象。

动态 SQL

动态 SQL 是指在 SQL 语句中根据不同的条件或参数动态生成不同的 SQL 片段的技术。在 MyBatis 中,动态 SQL 主要通过使用 <if>、<choose>、<when>、<otherwise>、<trim>、<where>、<set> 等 XML 元素来实现。

<if>

<if> 标签用于在 SQL 语句中根据条件动态包含或排除某段 SQL 语句。

基本的 <if> 标签语法如下:

<if test="condition">
    <!-- 要包含的 SQL 语句片段 -->
</if>

test 属性用于指定一个条件表达式,如果条件表达式为真,那么 <if> 内部定义的 SQL 语句片段将会被包含在最终的 SQL 语句中。如果条件表达式为假,则这部分 SQL 语句将被忽略。

例子:

<select id="getUserByCondition" resultType="User">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            AND username = #{username}
        </if>
        <if test="password != null and password != ''">
            AND password = #{password}
        </if>
    </where>
</select>

<trim>

<trim> 标签,用于在生成 SQL 语句时处理多余的空白字符,同时可以根据条件来动态添加或移除 SQL 片段的开头或结尾。

<trim> 元素的基本语法结构如下:

<trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
    <!-- SQL 语句片段 -->
</trim>
  • prefix 属性用于指定在 SQL 语句片段前添加的内容。
  • prefixOverrides 属性用于指定需要从 SQL 语句片段开头移除的前缀,可以是逗号分隔的前缀列表。
  • suffix 属性用于指定在 SQL 语句片段后添加的内容。
  • suffixOverrides 属性用于指定需要从 SQL 语句片段结尾移除的后缀,可以是逗号分隔的后缀列表。

示例:

<select id="getUserByCondition" resultType="User">
    select * from users
    <trim prefix="where" prefixOverrides="and | or">
        <if test="username != null and username != ''">
            and username = #{username}
        </if>
        <if test="password != null and password != ''">
            or password = #{password}
        </if>
    </trim>
</select>

在这个例子中,<trim> 元素的作用是处理 WHERE 子句,根据动态条件来生成合适的查询语句。具体解释如下:

  • prefix="where" 指定了在 SQL 语句片段前添加的内容,即添加了 where
  • prefixOverrides="and | or" 指定了需要从 SQL 语句片段开头移除的前缀,即移除了可能出现的多余的 andor
  • <trim> 元素内部,根据动态条件使用 <if> 元素生成相应的查询条件。

假设在执行这个查询时,传入的参数是 username="John"password=null,最终生成的 SQL 语句会是:

select * from users
where
    username = 'John'

<where>

<where> 标签用于在 where 子句中动态生成条件。<where> 元素会自动处理生成的条件之间的逻辑关系,避免不必要的 and 或 or 连接符。

基本的 <where> 标签语法如下:

<select id="getUserByCondition" resultType="User">
    SELECT * FROM users
    <where>
        <!-- 动态生成的条件 -->
    </where>
</select>

其示例在 if 标签已经涉及到。

例:

<select id="selectUsers" resultType="User">
  SELECT * FROM users
  <where>
    <if test="name != null">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
    <!-- 可以根据需要添加更多的条件 -->
  </where>
</select>

假设传入的参数是 name = 'John'age = 25,生成的 SQL 语句将类似于:

SELECT * FROM users
WHERE
  name = 'John'
  AND age = 25;

如果只传入了 name = 'John',而没有传入 age 参数,生成的 SQL 语句将是:

SELECT * FROM users
WHERE
  name = 'John';

如果两个条件都没有被传入,生成的 SQL 语句将仅包含基本的 SELECT 语句,而不包含 WHERE 子句:

SELECT * FROM users;

where 标签更简单地完成了上面 trim 标签的例子

<set>

<set> 元素针对 update 语句。它主要用于处理动态更新表中的列,根据传入的参数动态生成 set 子句。<set> 元素可以使得在更新记录时只更新传入的非空字段,从而避免更新所有字段,提高了 SQL 语句的灵活性。

例子:

<update id="updateUser">
  UPDATE users
  <set>
    <if test="name != null">
      name = #{name},
    </if>
    <if test="age != null">
      age = #{age},
    </if>
    <!-- 可以根据需要添加更多的更新字段 -->
  </set>
  WHERE id = #{id}
</update>

例如,如果调用上述的更新方法时传入了 name = 'John'age = 25,生成的 SQL 语句将类似于:

UPDATE users
SET
  name = 'John',
  age = 25
WHERE id = #{id};

如果只传入了 name = 'John' 而没有传入 age 参数,生成的 SQL 语句将是:

UPDATE users
SET
  name = 'John'
WHERE id = #{id};

<foreach>

<foreach> 通常用于动态生成 SQL 语句中的 IN 子句,允许我们在查询或更新操作中使用一组值。<foreach> 主要用于遍历集合或数组,并根据集合中的元素动态生成相应的 SQL 片段。

例子:

<select id="selectUsersByIds" resultType="User" parameterType="java.util.List">
  SELECT * FROM users
  WHERE id IN
  <foreach collection="ids" item="id" open="(" separator="," close=")">
    #{id}
  </foreach>
</select>

在上述例子中,<foreach> 元素用于循环遍历传入的 ids 集合,并根据集合中的元素动态生成 IN 子句。其中,collection 属性指定了要迭代的集合,item 属性指定了集合中的元素变量名,open 属性定义了 IN 子句的开头,separator 属性定义了元素之间的分隔符,close 属性定义了 IN 子句的结尾。

例如,如果传入的 ids 集合为 [1, 2, 3],生成的 SQL 语句将类似于:

SELECT * FROM users
WHERE id IN (1, 2, 3);

<foreach> 元素也可以用于其他操作,比如在插入操作中批量插入数据。下面是一个简单的插入示例:

<insert id="insertUsers" parameterType="java.util.List">
  INSERT INTO users (name, age)
  VALUES
  <foreach collection="list" item="user" separator="," >
    (#{user.name}, #{user.age})
  </foreach>
</insert>

<foreach> 元素用于循环遍历传入的 list 集合,并根据集合中的元素动态生成插入语句。这种方式可以在一次数据库操作中插入多条记录,提高了效率。

文章来源:https://blog.csdn.net/CegghnnoR/article/details/134901359
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。