MyBatis 从初识到掌握

news2024/10/7 16:21:23

目录

今日良言:与其抱怨于黑暗,不如提灯向前行

一、初识MyBatis

1.MyBatis定义

2.为什么要学习MyBatis

3.MyBatis的创建

二、MyBatis的相关操作

1.增删改查操作

2.动态SQL使用


今日良言:与其抱怨于黑暗,不如提灯向前行

一、初识MyBatis

1.MyBatis定义

MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏ 乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。

简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。

2.为什么要学习MyBatis

对于后端开发来说,程序是由以下两个重要的部分组成的:
1). 后端程序
2). 数据库
而这两个重要的组成部分要通讯,就要依靠数据库连接⼯具,那数据库连接⼯具有哪些?比如之前我们学习的 JDBC,还有今天我们将要介绍的 MyBatis,那已经有了 JDBC 了,为什么还要学习 MyBatis?
这是因为 JDBC 的操作太繁琐了,回顾⼀下 JDBC 的操作流程:

1). 创建数据库连接池 DataSource

2). 通过 DataSource 获取数据库连接 Connection

3). 编写要执⾏带 ? 占位符的 SQL 语句

4). 通过 Connection 及 SQL 创建操作命令对象 Statement

5). 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值

6). 使⽤ Statement 执⾏ SQL 语句

7). 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量

8). 处理结果集

9). 释放资源

因此可以通过MyBatis简化上述步骤,使得操作数据库更加简单。

学习MyBatis的步骤大致可以分为两步:

 ● 配置 MyBatis 开发环境;

使⽤ MyBatis 模式和语法操作数据库。

3.MyBatis的创建

创建 MyBatis 之前,我们先来看⼀下 MyBatis 在整个框架中的定位,框架交互流程图:

 MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。

接下来添加MyBatis 相关依赖。

如果是旧老项目(原有项目基础上添加MyBatis的相关依赖),使⽤EditStarters插件进行快速添加。

安装EditStarters插件。

 在pom.xml 配置文件中进行如下操作:

 

 由于MyBatis 是一个中介,是连接数据库和程序的中介,所以需要选择对应的数据库,这里选择MySQL Driver。

然后点击OK即可添加成功、。

 上述是原有项目添加 MyBatis的相关依赖步骤。

 新项目添加MyBatis依赖流程如下:

创建 Spring Boot 项⽬的时候添加引⽤就可以

接下来由于相关操作都是和MySQL数据库相关联,这里使用如下SQL语句创建数据库和相关表。

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
use mycnblog;

-- 创建表[用户表]
drop table if exists  userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    `state` int default 1
) default charset 'utf8mb4';

-- 创建文章表
drop table if exists  articleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';

-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
  	vid int primary key,
  	`title` varchar(250),
  	`url` varchar(1000),
		createtime timestamp default current_timestamp,
		updatetime timestamp default current_timestamp,
  	uid int
)default charset 'utf8mb4';

-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES 
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

-- 文章添加测试数据
insert into articleinfo(title,content,uid)
    values('Java','Java正文',1);
    
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);

 接下来,需要配置连接字符串和MyBatis。

此步骤需要进⾏两项设置,数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。
首先配置连接字符串,如果是application.properties 代码如下:
# 配置数据库的连接字符串
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
注:如果使⽤ mysql-connector-java 是 5.x 之前的使⽤的是“ com.mysql.jdbc.Driver ” ,如果是⼤于 5.x,使⽤的是“ com.mysql.cj.jdbc.Driver ” 。
接下来, 配置 MyBatis 中的 XML 路径
#设置MyBatis的配置
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml

这里的classpath 表示当前根路径,mybatis 表示所有mybatis的配置都会放在这个文件夹下,*Mapper.xml 表示所有和MyBatis 相关的 xml文件都叫做*Mapper.xml,比如和用户相关的叫做 UserMapper.xml 和 文章相关的叫做 ArticleMapper.xml.

如下图:

MyBatis 模式开发由两部分组成:

1.interface :让其它层可以注入使用的接口

 添加实体类,代码如下:

package com.example.demo.entity;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * @author 26568
 * @date 2023-06-24 16:34
 */
@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;// 表示状态,是1就是正常用户
}

这里的@Data 注解来自lombok 插件,针对lombok的学习可以借鉴如下这篇博客:

IDEA从零到精通(24)之lombok插件的安装与使用_编程界小明哥的博客-CSDN博客

上述实体类中的属性需要和数据库表中的对应

接下来,以查询实体类(用户)操作为例,继续完善代码:

2.mybatis: xml—> 具体实现sql(是上面interface的“实现”)

创建UserMapper.xml文件,实现具体sql.

UserMapper.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="com.example.demo.mapper.UserMapper">

</mapper>

需要注意,这里的路径要和一个具体的接口对应:

然后继续在UserMapper.xml 文件中完善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="com.example.demo.mapper.UserMapper">
    <select id="getAll" resultType="com.example.demo.entity.UserInfo">
        select * from userInfo
    </select>
</mapper>

 注:

<select>查询标签 :是⽤来执⾏数据库的查询操作的:
id: 是和 Interface(接⼝)中定义的⽅法名称⼀样的,表示对接⼝的具体实现⽅法。
resultType :是返回的数据类型,也就是开头我们定义的实体类
当sql 代码完善以后,实现服务层(Service)和控制层(Controller)代码。
服务层(Service)代码:
package com.example.demo.service;

import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author 26568
 * @date 2023-06-24 19:54
 */
@Service  // 将这个类存放到 Spring 中
public class UserService {
    @Autowired
    private UserMapper userMapper;// 这是接口
    public List<UserInfo> getAll() {
        return userMapper.getAll();
    }
}

控制层(Controller)代码:

package com.example.demo.controlleer;

import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author 26568
 * @date 2023-06-24 19:53
 */
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    
    @RequestMapping("/getall")
    public List<UserInfo> getAll() {
        return userService.getAll();
    }
}

所有目录结构如下图所示:

当启动项目后,输入网址:127.0.0.1:8080/user/getall 得到如下结果:

查询userinfo表中数据:

 

 此时,当继续往userinfo 表中插入一条数据以后,查询会再次显示:

 

 以上就是一个完整的MyBatis模式开发流程。

MyBatis模式开发的具体业务流程如下图:


二、MyBatis的相关操作

1.增删改查操作

在进行相关操作之前,先来介绍一下单元测试,

单元测试(unit testing),是指对软件中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试。
单元测试是开发者编写的⼀⼩段代码,⽤于检验被测代码的⼀个很⼩的、很明确的(代码)功能是否正确。执⾏单元测试就是为了证明某段代码的执⾏结果是否符合我们的预期。如果测试结果符合我们的预期,称之为测试通过,否则就是测试未通过(或者叫测试失败)

单元测试的好处:

1.)可以非常简单、直观、快速的测试某⼀个功能是否正确。
2)、使⽤单元测试可以帮我们在打包的时候,发现⼀些问题,因为在打包之前,所以的单元测试必须通 过,否则不能打包成功。
3)、使⽤单元测试,在测试功能的时候,可以不污染连接的数据库,也就是可以不对数据库进行任何改变的情况下,测试功能。
Spring Boot 项⽬创建时会默认单元测试框架 spring-boot-test,⽽这个单元测试框架主要是依靠另⼀ 个著名的测试框架 JUnit 实现的,打开 pom.xml 就可以看到,以下信息是 Spring Boot 项⽬创建是⾃ 动添加的:

     

单元测试的实现步骤:
在要进行测试的类中右键进行如下操作:

 点击OK以后会生成如下类:

package com.example.demo.mapper;

import com.example.demo.entity.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;

import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest  // 添加这个注解 表示当前单元测试的类是运行在 Spring Boot环境中的
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void getAll() {
        List<UserInfo> list = userMapper.getAll();
        for (UserInfo userInfo:list) {
            System.out.println(userInfo);
        }
    }
}

 完善生成的单元测试的类的代码:

package com.example.demo.mapper;

import com.example.demo.entity.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;

import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest  // 添加这个注解 表示当前单元测试的类是运行在 Spring Boot环境中的
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void getAll() {
        List<UserInfo> list = userMapper.getAll();
        for (UserInfo userInfo:list) {
            System.out.println(userInfo);
        }
    }
}

 点击运行单元测试,查看打印结果:

接下来继续进行MyBatis的相关操作。

查询操作:

通过id查询用户:

UserMapper 接口的代码如下:

@Mapper
public interface UserMapper {
    List<UserInfo> getAll();
    // 根据id查询用户
    UserInfo getById(@Param("id")Integer id);
}

UserMapper.xml 新增sql语句如下:

<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="getAll" resultType="com.example.demo.entity.UserInfo">
        select * from userInfo
    </select>
    <select id="getById" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where id = ${id}
    </select>
</mapper>

此时,再次使用单元测试,在UserMapper中右键。。。(和上面流程一样),只是此处会报如图所示的错误:

点击OK即可,这里的意思是当前类已经创建了单元测试类,是否进行修改。

单元测试代码如图所示:

 点击进行运行,查看打印结果:

 注:

这两处名字要保持一致,Use

MyBatis 获取动态参数有两种实现:

1)、${ }:直接替换。

直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

2)、#{ }: 预编译处理。

预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使用PreparedStatement 的 set ⽅法来赋值。
在application.properties文件添加如下配置可以观察到上述两种获取参数的方法所对应的最终 sql 执行语句:
# 打印MyBatis 执行SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 配置日志的级别
logging.level.com.example.demo=debug

此时,再次启动单元测试,观察直接替换的SQL执行语句:

使用#{} 观察打印结果:

 使用${ } 的方式可能会造成SQL注入问题:

 简单的Integer 传参不会有问题,通过如下根据名称查询用户对象会造成SQL注入问题:

 UserMapper 接口中新增代码:

   // 根据名称查询对象
    UserInfo getUserByUserName(@Param("username")String username);

UserMapper.xml 新增代码:

    <select id="getUserByUserName" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where username = #{username}
    </select>

使用单元测试:

@Test
    void getUserByUserName() {
        UserInfo userInfo = userMapper.getUserByUserName("lisi");
        System.out.println(userInfo);
    }

运行单元测试查看结果:

使用#{ } 运行正常,此时,使用${} 再次查看打印结果:

 

 发生错误。

正常的SQL语句,字符串应该加单引号,但是${ } 是直接替换,没有单引号,所以报错。想要不报错,手动加上单引号可以解决,但是这会造成SQL注入问题。

SQL注入安全一般发生在登录,以登录为例,来解释SQL注入:

UserMapper 接口中的新增代码如下:

// 登录
    UserInfo login(UserInfo userInfo);

这里传对象就不需要使用@Param注解了。

UserMapper.xml 新增代码如下:

<select id="login" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where username = '${username}' and password = '${password}'
    </select>

这里虽然传入的是对象,但是由于框架会帮助我们完成自动映射,所以可以直接使用用户属性。

单元测试代码如下:

   @Test
    void login() {
        UserInfo userInfo = new UserInfo();
        userInfo.setPassword("admin");
        userInfo.setUsername("admin");
        UserInfo user = userMapper.login(userInfo);
        System.out.println(user);
    }

运行单元测试,查看结果,运行正确:

 当密码或者结果输入错误,运行结果为null:

发送SQL注入的代码如下:

运行单元测试查看结果,由于数据库中只有一个用户admin:

 

 上述密码输入错误,应该无法查到结果,但是实际输出却是得到了正确的结果:

上述就可以认为是发生了SQL注入。

直接替换的SQL语句如下:

 使用#{ } 可以避免SQL注入:

运行单元测试,查看结果:

综上,⽤于查询的字段,尽量使用 #{} 预查询的方式 。

使用${ } 优点:

使用${ } 可以实现排序查询,而#{ } 无法实现排序查询。

UserMapper 接口中的新增代码如下:

// 排序
    List<UserInfo> getAll2(@Param("ord")String ord);

UserMapper.xml 新增代码如下:

 <select id="getAll2" resultType="com.example.demo.entity.UserInfo">
        select * from userInfo order by id ${ord}
    </select>

单元测试代码如下(观察SQL语句执行是否正确):

@Test
    void getAll2() {
        List<UserInfo> list = userMapper.getAll2("desc");
        System.out.println(list.size());
    }

运行单元测试查看结果:

 结果正确。

将${ } 替换成 #{ },运行单元测试查看打印结果:

 结果出错。

like 查询 使用 #{} 会出错。

UserMapper 接口中的新增代码如下:

// 使用用户名进行模糊查询
    List<UserInfo> getListByName(@Param("username")String username);

UserMapper.xml 新增代码如下:

 <select id="getListByName" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where username like '%#{username}%'
    </select>

单元测试代码如下:

@Test
    void getListByName() {
        String username = "ad";
        List<UserInfo> list = userMapper.getListByName(username);
        System.out.println(list.size());
    }

运行单元测试,查看执行结果:

 发生错误。

不能直接使⽤ ${},可以考虑使⽤ mysql 的内置函数 concat() 来纠正上述错误。

concat 的作用是将传入的参数拼接起来。

UserMapper.xml 代码更新如下:

 此时再次运行单元测试,执行结果正确:

 返回类型:resultType >:

绝⼤数查询场景可以使用 resultType 进行返回,它的优点是使用方便,直接定义到某个实体类即可。

但是,有的场景下,就不能使用resultType 进行返回了。如下:

数据库的密码字段是password,但是创建的实体类属性是pwd:

此时,运行上述根据id查询用户的单元测试,查看执行结果:

会发现,由于password 和 pwd 不相同,所以数据库查询到的数据无法与实体类相对应的属性进行映射。

 针对这种情况,就需要使用字典映射resultMap

resultMap 的使用场景有两个:

1)、字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;
2)、一对一和一对多关系可以使用resultMap 映射并查询数据

上述就是resultMap的第一个使用场景,使用步骤:

首先需要在xml文件中定义resultMap:

 <resultMap id="BaseMap" type="com.example.demo.entity.UserInfo">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="pwd"column="password"></result>
    </resultMap>

此时需要修改 根据id查询用户的xml 代码:

再次运行单元测试,查看执行结果:

 另外一种解决方案就是使用使用重命名,给数据库字段password 起别名为 pwd:

 再次运行单元测试,查看执行结果:

 以上所有操作均是单表查询。接下来学习一下多表查询。

多表查询

 首先创建一个实体类 ArticleInfo,代码如下:

@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer uid;
    private Integer rcount;
    private Integer state;
}

这里需要再创建一个实体类ArticleInfoVO,因为后续多表操作返回结果可能有用户名,直接在实体类ArticleInfo 中添加字段不太好,所以需要创建ArticleInfoVO ,代码如下:

package com.example.demo.entity.vo;

import com.example.demo.entity.ArticleInfo;
import lombok.Data;

/**
 * @author 26568
 * @date 2023-06-26 13:56
 */
@Data
public class ArticleInfoVO extends ArticleInfo {
    private String username;

    @Override
    public String toString() {
        return "ArticleInfoVO{" +
                "username='" + username + '\'' +
                "} " + super.toString();
    }
}

创建ArticleMapper接口:

package com.example.demo.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ArticleMapper {
}

 在mybatis包下创建ArticleMapper.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="com.example.demo.mapper.ArticleMapper">
    
</mapper>

接下来执行如下操作:根据文章id查询文章详情。

UserMapper接口 新增代码如下:

@Mapper
public interface ArticleMapper {
    // 查询文章详情
    ArticleInfoVO getDetail(@Param("id")Integer id);
}

UserMapper.xml 新增代码如下:

<select id="getDetail" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select * from userinfo,articleinfo
        where articleinfo.uid = userinfo.id and articleinfo.id = #{id}
    </select>

创建在ArticleMapper 接口中,进行创建单元测试步骤......

getDatail方法的单元测试的代码如下:

package com.example.demo.mapper;

import com.example.demo.entity.vo.ArticleInfoVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleMapperTest {
    @Autowired
    private ArticleMapper articleMapper;
    @Test
    void getDetail() {
        ArticleInfoVO articleInfoVO = articleMapper.getDetail(1);
        System.out.println(articleInfoVO);
    }
}

执行单元测试,查看打印结果:

接下来,进行查询一个用户的多篇文章操作:

UserMapper 接口新增代码如下:

  // 查询用户的所有文章
    List<ArticleInfoVO> getListByUid(@Param("uid")Integer uid);

UserMapper.xml 新增代码如下:

  <select id="getListByUid" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select * from articleinfo join userinfo on articleinfo.uid = userinfo.id
        where  articleinfo.uid = #{uid}
    </select>

 新增getListByUid方法的单元测试,代码如下:

@Test
    void getListByUid() {
        Integer uid = 1;
        List<ArticleInfoVO> list = articleMapper.getListByUid(uid);
        System.out.println(list.size());
    }

运行单元测试,查看执行结果:

修改操作:

修改用户密码:

UserMapper 接口新增代码:

 // 修改密码
    int update(@Param("id")Integer id,
               @Param("password")String password,
               @Param("newPassword")String newPassword);

 UserMapper.xml 新增代码如下:

<update id="update">
        update userinfo set password = #{newPassword} where id = #{id} and password = #{password}
    </update>

单元测试代码如下:

@Test
    void update() {
        int result = userMapper.update(1,"admin","123456");
        System.out.println("修改:"+result);
    }

 运行单元测试,查看结果:

 查看数据库用户密码,发现被修改:

 前面介绍说使用单元测试的优点之一是不会污染连接的数据库,但是这里显然是污染了数据库,这不是自相矛盾吗? 

其实不然,默认情况下,单元测试会污染连接的数据库,但是,当为单元测试添加@Transactional (事务)注解以后就不会污染数据库,关于事务相关介绍,博主之前的博客有过详细介绍:

(2条消息) Spring 事务和事务传播机制_程序猿小马的博客-CSDN博客

 此时,运行单元测试,查看结果:

查看数据库:

会发现密码并没有被修改。 

删除操作:

删除用户:

UserMapper 接口新增代码:

   // 删除用户
    int delete(@Param("id")Integer id);

 UserMapper.xml 新增代码如下:

<delete id="delete">
        delete from userinfo where id = #{id}
    </delete>

单元测试代码如下:

 @Transactional
    @Test
    void delete() {
        int result = userMapper.delete(1);
        System.out.println("删除:"+result);
    }

运行单元测试,查看结果:

 删除操作执行成功,但是由于事务的存在,发生回滚,不会污染连接的数据库。

添加操作:

添加用户:

添加用户传入的参数是对象,如果传入的是参数,后续发生修改的话,整个调用链都需要进行修改。

UserMapper 接口新增代码:

   // 添加用户
    int addUser(UserInfo userInfo);

 UserMapper.xml 新增代码如下:

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

单元测试代码如下:

@Test
    void addUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("张三");
        userInfo.setPassword("1111111");
        int result = userMapper.addUser(userInfo);
        System.out.println("添加:"+result);
    }

运行单元测试,查看执行结果和数据库数据:

 

上述添加用户操作的返回结果只是一个受影响的行数,如果想要返回添加用户的id该如何操作?

 UserMapper 接口新增代码:

   // 添加用户返回id
    int addUser1(UserInfo userInfo);

UserMapper.xml 新增代码:

 <insert id="addUser1" useGeneratedKeys="true" keyProperty="id">
        insert into userinfo (username,password) values(#{username},#{password})
    </insert>

 这里的 useGeneratedKeys="true" 意思就是返回添加成功的用户的id。

keyProperty="id" 的意思是返回添加成功的用户的id放在哪个"id" 字段中。

单元测试的代码:

  @Test
    void addUser1() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("李四");
        userInfo.setPassword("22222");
        int result = userMapper.addUser1(userInfo);
        System.out.println("添加成功的用户的id是:"+userInfo.getId());
    }

运行单元测试,查看执行结果和数据库中的数据:

2.动态SQL使用

动态 sql 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接。

动态sql 就是为了能在sql语句中进行逻辑判断。

介绍一下动态SQL中常用的几个标签:

1)、<if>标签

在注册⽤户的时候,可能会有这样⼀个问题:
注册分为两种字段:必填字段和⾮必填字段,那如果在添加用户的时候有不确定的字段传⼊,程序应该如何实现呢?

这个时候就需要使⽤动态标签 <if> 来判断了。

以添加用户为例,假设现在要添加用户,传入的属性必须填的有姓名(username) 密码(password) ,以及非必填的头像属性(photo):
UserMapper 接口新增代码如下:
 // 添加用户(包含非必填字段)
    int addUser2(UserInfo userInfo);

UserMapper.xml 新增代码如下:

 <insert id="addUser2">
        insert into userinfo (username,password
        <if test="photo != null">
            ,photo
        </if>
        ) values (#{username,#{password}
        <if test="photo != null">
            ,#{photo}
        </if>
        )
    </insert>

 

新增单元测试代码如下:

  @Transactional
    @Test
    void addUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("张三");
        userInfo.setPassword("11111");
        int ret = userMapper.addUser2(userInfo);
        System.out.println("添加:"+ret);
    }

运行单元测试,查看执行结果:

传入两个字段:

 修改单元测试代码,传入photo 属性,运行并查看结果:

 注:test 中的photo 是属性,不是数据库字段。

2)、<trim>标签

如果所有字段都是非必填项,就考虑使⽤ <trim>标签结合<if>标签,对多个字段都采取动态⽣成的方式。
<trim> 标签中有如下属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀

还是以添加用户为例:这里假设 username,password,photo 都是非必传字段

UserMapper 新增代码如下:

// 添加用户
    int addUser3(UserInfo userInfo);

UserMapper.xml 新增代码如下:

 <insert id="addUser3">
        insert into userinfo
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username != null and username !=''">
                username,
            </if>
            <if test="pwd != null and pwd !=''">
                password,
            </if>
            <if test="photo!= null and photo != ''">
                photo
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username != null and username !=''">
                #{username},
            </if>
            <if test="pwd != null and pwd !=''">
                #{pwd},
            </if>
            <if test="photo!= null and photo != ''">
                #{photo}
            </if>
        </trim>
    </insert>

新增单元测试的代码如下:

  @Transactional
    @Test
    void addUser3() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("张三");
        userInfo.setPwd("111111");
        int ret = userMapper.addUser3(userInfo);
        System.out.println("添加:"+ret);
    }

 运行单元测试,查看执行结果:

 当传入photo属性:

3)、<where> 标签 

传入的用户对象,根据属性做 where 条件查询,⽤户对象中属性不为 null 的,都为查询条件。使用where标签还有一个好处,可以去掉前缀

以查询文章为例:

ArticleMapper 接口新增代码如下:

    List<ArticleInfoVO> getListByIdOrTitle(@Param("id")Integer id,
                                           @Param("title")String title);

ArticleMapper.xml 文件新增代码如下:

 <select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select * from articleinfo
       <where>
           <if test="id != null and id > 0">
              id = #{id}
           </if>
           <if test="title != null and title !=''">
              and title like concat('%','#{title}','%')
           </if>
       </where>
    </select>

新增单元测试的代码如下:

@Test
    void getListByIdOrTitle() {
        List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(null,null);
        System.out.println(list.size());
    }

 运行单元测试,查看执行结果:

传入title:

 再次运行单元测试,查看结果:

 4)、<set> 标签

根据传入的用户对象属性来更新⽤户数据,可以使⽤<set>标签来指定动态内容。 set标签会去掉

最后一个逗号。

set 标签的用法和 where标签比较相似,这里就不做过多介绍。

5)、<froeach> 标签

对集合进⾏遍历时可以使⽤该标签。<foreach>标签有如下属性:

collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象。
item:遍历时的每⼀个对象。
open:语句块开头的字符串。
close:语句块结束的字符串。
separator:每次遍历之间间隔的字符串

使用foreach标签最常用的场景是批量操作。

 以批量删除文章为例:

ArticleMapper 接口新增代码如下:

    // 根据id删除文章集合
    int delByList(List<Integer> idList);

ArticleMapper.xml 新增代码如下:

 <delete id="delByList">
        delete from articleinfo
        where id in
        <foreach collection="idList" open="(" close=")" item="aid" separator=",">
            #{aid}
        </foreach>
    </delete>

新增单元测试代码:

 @Test
    void delByList() {
        List<Integer> idList = new ArrayList<>();
        idList.add(1);
        idList.add(2);
        int ret = articleMapper.delByList(idList);
    }

运行单元测试,查看执行结果:


以上就是关于MyBatis 的所有内容。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/692163.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE4/5 通过Control rig的FullBody【蜘蛛模型,不用basic ik】

目录 根设置 FullBody IK 额外骨设置 ​编辑 晃动效果 根设置 第一步你需要准备一个蜘蛛模型&#xff0c;不论是官方示例或者是epic上购买的模型 然后我用的是epic上面购买的一个眼球蜘蛛&#xff1a; 第一步&#xff0c;我们从根创建一个空项【这个记得脱离父子级到root之…

SQLServer 2016 R2数据库新建、附加、分离、备份、还原、复制等基本操作

一、打开Microsoft SQL Server Management Studio 在桌面上找到图标&#xff0c;双击运行 打开Microsoft SQL Server Management Studio 17 输入服务器名称&#xff0c;选择SQL Server 身份验证&#xff0c;sa和sa密码&#xff0c;可以勾选记住密码&#xff0c;以便以后的登录…

分享基于安卓项目的单元测试总结

前言&#xff1a; 负责公司的单元测试体系的搭建&#xff0c;大约有一两个月的时间了&#xff0c;从最初的框架的调研&#xff0c;到中期全员的培训&#xff0c;以及后期对几十个项目单元测试的引入和推进&#xff0c;也算是对安卓的单元测试有了一些初步的收获以及一些新的认…

【雕爷学编程】Arduino动手做(131)---跑马灯矩阵键盘模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

线性代数基础--矩阵

矩阵 矩阵是由排列在矩形阵列中的数字或其他数学对象组成的表格结构。它由行和列组成&#xff0c;并且在数学和应用领域中广泛使用。 基本概念 元素&#xff1a;矩阵中的每个数字称为元素。元素可以是实数、复数或其他数学对象。 维度&#xff1a;矩阵的维度表示矩阵的行数和…

vtk创建点

使用vtk库创建三维空间中的点 引言开发环境示例一项目结构实现代码 运行效果示例二项目结构实现代码 运行效果总结 引言 本文仅适合初学者。 本文不提供vtk动态库的生成&#xff0c;以及在QtCreator中的引进vtk时的配置。 本文先由示例一开始&#xff0c;然后再在示例一的基础…

aws使用外部 ID对其他账号授权

点击前往授权,进入控制台 https://signin.aws.amazon.com/signin?redirect_urihttps%3A%2F%2Fconsole.aws.amazon.com%2Fconsole%2Fhome%3FhashArgs%3D%2523%26isauthcode%3Dtrue%26state%3DhashArgsFromTB_eu-north-1_f2d9c316b93c0026&client_idarn%3Aaws%3Asignin%3A%…

Glassdoor美国公司员工及面试者评价数据

一、数据简介 除了股东、债权人、政府等外部利益相关者外&#xff0c;员工的利益更应该得到公司的恰当保护&#xff0c;因为员工才是公司创造价值的真正主体。提高企业在产品市场的竞争力&#xff0c;首先就是要提高员工对企业的满意度&#xff0c;只有员工的满意度更高、幸福感…

7个技巧,助你同时轻松管理和跟踪多个项目

仅仅想到要兼顾这么多重要的职责&#xff0c;就会让许多专业的项目经理感到焦虑。当涉及多个项目的多种项目管理工具的处理&#xff0c;即使对于了解项目管理的项目经理来说&#xff0c;也是一项艰巨的任务&#xff0c;而对于在这个领域没有经过适当培训的人来说&#xff0c;这…

强化学习从基础到进阶--案例与实践[7.1]:深度确定性策略梯度DDPG算法、双延迟深度确定性策略梯度TD3算法详解项目实战

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

计算机网络—数据链路层

文章目录 数据链路层服务差错编码多路访问协议信道划分随机访问MAC协议 数据链路层服务 该层中的帧数据结构&#xff1a; 帧头部会因为不同的局域网协议而不同&#xff0c;因此会在另一篇博文中继续介绍不同的帧数据报&#xff0c;不在本博文介绍。&#xff08;不过除了PPP协…

Docker学习笔记11

Docker容器镜像&#xff1a; 1&#xff09;docker client 向docker daemon发起创建容器的请求&#xff1b; 2&#xff09;docker daemon查找本地有客户端需要的镜像&#xff1b; 3&#xff09;如无&#xff0c;docker daemon则到容器的镜像仓库中下载客户端需要的镜像&#…

线性代数基础--向量

目录 向量的概念 基本概念 抽象概念 向量的意义 几何意义 物理意义 欧式空间 特点和性质 行向量与列向量 行向量 列向量 两者的关系 向量的基本运算与范数 向量的基本运算 向量的加法 数乘运算&#xff08;实数与向量相乘&#xff09; 转置 向量的范数 向量…

echart 设置柱状图y轴最大刻度

start 最近接到需求希望柱状图 y轴最大高度可以略高一些&#xff1b;柱状图的数据能展示在柱状图的上方 记录一下相关配置项 解决方案 官方文档说明 https://echarts.apache.org/zh/option.html#xAxis.max 效果 代码 {key: business,title: {text: 业务领域分类,textSt…

DAY32:回溯算法(七)全排列+全排列Ⅱ(排列问题)

文章目录 46.全排列思路树形图used数组的作用 伪代码完整版时间复杂度总结 47.全排列Ⅱ思路树形图 完整版时间复杂度总结 46.全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xf…

C#和LABVIEW的对决:哪种上位机编程语言更适合你?

今天&#xff0c;我们将谈论主流的上位机编程语言。你听说过C#和LABVIEW吗&#xff1f;它们是的上位机编程语言&#xff0c;C#作为自动化主流编程语言特别受欢迎&#xff0c;LABVIEW用于自动化测试&#xff0c; 首先&#xff0c;我们来了解C#语言。C#是一种文本语言&#xff0c…

2023年江西省研究生数模竞赛植物的多样性

2023年江西省研究生数模竞赛 植物的多样性 原题再现 植物作为食物链中的生产者&#xff0c;通过光合作用吸收二氧化碳&#xff0c;制造氧气&#xff0c;同时为其他生物提供食物和栖息地&#xff0c;支持它们的生存。植物在生态系统中还起到防止水土流失、缓解温室效应等作用。…

新手小白编程利器!Debug 断点调试工具IDEA

前言 很多新手小白在学习的时候总会遇到一个问题&#xff1a; 我们一运行程序&#xff0c;只能看到程序最后的结果&#xff0c;但是这个程序究竟是怎么一步步运行出这样的结果呢&#xff1f;如果有一个工具能够让我们看到我们程序的执行流程该有多好~ 这就需要用到新手小白编程…

modbus转MQTT网关支持自定义JSON格式

在工业自动化系统中&#xff0c;Modbus是一种非常常见的通信协议&#xff0c;而OPC UA则是近年来兴起的一种新型通信协议。由于各种设备之间使用的通信协议不尽相同&#xff0c;因此需要一种能够实现多种协议转换的网关产品。BL110网关就是一款能够实现Modbus到OPC UA转换的产品…

硬件设计-PLL篇(下)

目录 概要 整体架构流程 技术名词解释 技术细节 1.环路滤波器采用有源滤波器还是无源滤波器&#xff1f;、 2.如何设计 VCO 输出功率分配器&#xff1f;、 3.如何设置电荷泵的极性&#xff1f; 4.锁定指示电路如何设计&#xff1f; 小结 概要 提示&#xff1a;这里可以添加技术…