什么是 MyBatis?

经过前几篇博客的学习 Spring 系列的基本操作已经实现的差不多了,接下来,我们来学习更重要的知识,将前端传递的数据存储起来,或者查询数据库里面的数据。

一、MyBatis 是什么?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL 、存储过程以及高级映射。 MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO Plain Old Java Objects ,普通老式 Java 对象)为数据库中的记录。
简单来说 MyBatis 是更简单完成程序和数据库交互的工具,也就是更简单的操作和读取数据库工具。
Mybatis官网

二、为什么要学习 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. 释放资源

JDBC 操作示例回顾

下面的一个完整案例,展示了通过 JDBC API 向数据库中添加一条记录,修改一条记录,查询一条记录的操作。
-- 创建数据库
create database if not exists `library` default character set utf8mb4;
-- 使用数据库
use library;
-- 创建表
create table if not exists `soft_bookrack` (
`book_name` varchar(32) NOT NULL,
`book_author` varchar(32) NOT NULL,
`book_isbn` varchar(32) NOT NULL primary key
) ;
以下是 JDBC 操作的具体实现代码:
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class SimpleJdbcOperation {
private final DataSource dataSource;
public SimpleJdbcOperation(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 添加一本书
*/
public void addBook() {
Connection connection = null;
PreparedStatement stmt = null;
try {
//获取数据库连接
connection = dataSource.getConnection();
//创建语句
stmt = connection.prepareStatement(
"insert into soft_bookrack (book_name, book_author, book_isbn) values (?,?,?);"
);
//参数绑定
stmt.setString(1, "Spring in Action");
stmt.setString(2, "Craig Walls");
stmt.setString(3, "9787115417305");
//执行语句
stmt.execute();
} catch (SQLException e) {
//处理异常信息
} finally {
//清理资源
try {
if (stmt != null) {
stmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
//
}
}
}
/**
* 更新一本书
*/
public void updateBook() {
Connection connection = null;
PreparedStatement stmt = null;
try {
//获取数据库连接
connection = dataSource.getConnection();
//创建语句
stmt = connection.prepareStatement(
"update soft_bookrack set book_author=? where book_isbn=?;"
);
//参数绑定
stmt.setString(1, "张卫滨");
stmt.setString(2, "9787115417305");
//执行语句
stmt.execute();
} catch (SQLException e) {
//处理异常信息
} finally {
//清理资源
try {
if (stmt != null) {
stmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
//
}
}
}
/**
* 查询一本书
*/
public void queryBook() {
Connection connection = null;
PreparedStatement stmt = null;
ResultSet rs = null;
Book book = null;
try {
//获取数据库连接
connection = dataSource.getConnection();
//创建语句
stmt = connection.prepareStatement(
"select book_name, book_author, book_isbn from soft_bookrack where book_isbn =?"
);
//参数绑定
stmt.setString(1, "9787115417305");
//执行语句
rs = stmt.executeQuery();
if (rs.next()) {
book = new Book();
book.setName(rs.getString("book_name"));
book.setAuthor(rs.getString("book_author"));
book.setIsbn(rs.getString("book_isbn"));
}
System.out.println(book);
} catch (SQLException e) {
//处理异常信息
} finally {
//清理资源
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
//
}
}
}
public static class Book {
private String name;
private String author;
private String isbn;
//省略 setter getter 方法
}
}
从上述代码和操作流程可以看出,对于 JDBC 来说,整个操作非常的繁琐,我们不但要拼接每一个参数,而且还要按照模板代码的方式,一步步的操作数据库,并且在每次操作完,还要手动关闭连接等,而所有的这些操作步骤都需要在每个方法中重复书写。于是我们就想,那有没有一种方法,可以更简单、更方便的操作数据库呢?
答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更方便、更快速的操作数据库。

三、怎么学MyBatis

MyBatis 学习只分为两部分:
  • 配置 MyBatis 开发环境;
  • 使用 MyBatis 模式和语法操作数据库。

四、第一个MyBatis查询

开始搭建 MyBatis 之前,我们先来看一下 MyBatis 在整个框架中的定位,框架交互流程图:
MyBatis 也是一个 ORM 框架, ORM Object Relational Mapping) ,即对象关系映射。在面向对象编程语言中,将关系型数据库中的数据与对象建立起映射关系,进而自动的完成数据与对象的互相转换:
1. 将输入数据(即传入对象) +SQL 映射成原生 SQL
2. 将结果集映射为返回对象,即输出对象
ORM 把数据库映射为对象:
  • 数据库表(table--> 类(class
  • 记录(record,行数据)--> 对象(object
  • 字段(field --> 对象的属性(attribute
一般的 ORM 框架,会将数据库模型的每张表都映射为一个 Java 类。
也就是说使用 MyBatis 可以像操作对象一样来操作数据库中的表,可以实现对象和数据库表之间的转换,接下来我们来看 MyBatis 的使用吧。

4.1 创建数据库和表

接下来我们要实现的功能是:使用 MyBatis 的方式来读取用户表中的所有用户,我们使用个人博客的数据库和数据包,具体 SQL 如下。
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8;
-- 使用数据数据
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 datetime default now(),
updatetime datetime default now(),
`state` int default 1
);
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime datetime default now(),
updatetime datetime default now(),
uid int not null,
rcount int not null default 1,
`state` int default 1
);
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime datetime default now(),
updatetime datetime default now(),
uid int
);
-- 添加一个用户信息
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);

4.2 添加MyBatis框架支持

添加 MyBatis 框架支持分为两种情况:一种情况是对自己之前的 Spring 项目进行升级,另一种情况是创建一个全新的 MyBatis Spring Boot 的项目,下面我们分别来演示这两种情况的具体实现。

4.2.1 老项目添加MyBatis

如果是在老项目中新增功能,添加框架支持:
<!-- 添加 MyBatis 框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- 添加 MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
添加了 MyBatis 之后,为什么还需要添加 MySQL 驱动呢?
MyBatis 就像一个平台(类似京东),而数据库相当于商家有很多种,不止有 MySQL ,还有 SQL Server DB2 等等 ..... 因此这两个都是需要添加的。
扩展:在老项目中快速添加框架,跟简单的操作方式是使用 EditStarters 插件

EditStarters 插件的使用方法:

 搜索“MyBatis”添加即可:

4.2.2 新项目添加MyBatis

如果是新项目创建 Spring Boot 项目的时候添加引用就可以了,如下图所示:

4.3 配置连接字符串和MyBatis

此步骤需要进行两项设置,数据库连接字符串设置和 MyBatis XML 文件配置。

4.3.1 配置连接字符串

如果是 application.yml 添加如下内容:
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
注意事项:
如果使用 MySQL 5.x 之前的使用的是 “com.mysql.jdbc.Driver” ,如果是大于 5.x 使用的是 “com.mysql.cj.jdbc.Driver”

4.3.2 配置 MyBatis 中的 XML 路径

MyBatis XML 中保存是查询数据库的具体操作 SQL ,配置如下:
# 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml

4.4 添加业务代码

 下面按照后端开发的工程思路,也就是下面的流程来实现 MyBatis 查询所有用户的功能:

4.4.1 添加实体类

先添加用户的实体类:
import lombok.Data;
import java.util.Date;
@Data
public class User {
private Integer id;
private String username;
private String password;
private String photo;
private Date createTime;
private Date updateTime;
}

4.4.2 添加 mapper 接口

数据持久层的接口定义:
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
public List<User> getAll();
}

4.4.3 添加 UserMapper.xml

数据持久成的实现, mybatis 的固定 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.model.User">
select * from userinfo
</select>
</mapper>
以下是对以上标签的说明:
1. <mapper> 标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接口的全限定名,包括全包名 . 类名。
2. <select>查询标签:是用来执行数据库的查询操作的:
  • id:是和 Interface(接口)中定义的方法名称一样的,表示对接口的具体实现方法。
  • resultType:是返回的数据类型,也就是开头我们定义的实体类。

4.4.4 添加 Service

服务层实现代码如下:
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public List<User> getAll() {
return userMapper.getAll();
}
}

4.4.5 添加 Controller

控制器层的实现代码如下:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/u")
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/getall")
public List<User> getAll(){
return userService.getAll();
}
}
以上代码写完,整个 MyBatis 的查询功能就实现完了,接下来使用 postman 来测试一下。

4.4.6 使用 postman 测试

五、增、删、改操作

接下来,我们来实现一下用户的增加、删除和修改的操作,对应使用 MyBatis 的标签如下:
  • 标签:插入语句
  • 标签:修改语句
  • 标签:删除语句
具体实现如下。

5.1 增加用户操作

controller 实现代码:
@RequestMapping(value = "/add",method = RequestMethod.POST)
public Integer add(@RequestBody User user){
return userService.getAdd(user);
}
mapper interface
Integer add(User user);
mapper.xml
<insert id="add">
insert into userinfo(username,password,photo,state)
values(#{username},#{password},#{photo},1)
</insert>

Postman 添加访问:

默认情况下返回的是受影响的行数,如上图所示,用到的 json 数据如下:
{"username":"mysql","password":"mysql","photo":"img.png"}

特殊的添加:返回自增 id

默认情况下返回的是受影响的行号,如果想要返回自增 id ,具体实现如下。
controller 实现代码:
@RequestMapping(value = "/add2", method = RequestMethod.POST)
public Integer add2(@RequestBody User user) {
userService.getAdd2(user);
return user.getId();
}
mapper 接口:
@Mapper
public interface UserMapper {
// 添加,返回自增id
void add2(User user);
}
mapper.xml 实现如下:
<!-- 返回自增id -->
<insert id="add2" useGeneratedKeys="true" keyProperty="id">
insert into userinfo(username,password,photo,state)
values(#{username},#{password},#{photo},1)
</insert>
  • useGeneratedKeys:这会令 MyBatis 使用 JDBC getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false
  • keyColumn:设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
  • keyProperty:指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
postman 返回结果:

5.2 修改用户操作

controller
/**
* 修改操作
*
* @param id
* @param name
* @return
*/
@RequestMapping("/update")
public Integer update(Integer id, String name) {
return userService.update(id, name);
}
mapper.xml 实现代码:
<update id="update">
update userinfo set username=#{name} where id=#{id}
</update>

5.3 删除用户操作

<delete id="delById" parameterType="java.lang.Integer">
delete from userinfo where id=#{id}
</delete>

六、查询操作

6.1 单表查询

下面我们来实现一下根据用户 id 查询用户信息的功能。
Controller 实现代码如下:
@RequestMapping("/getuser")
public User getUserById(Integer id) {
return userService.getUserById(id);
}
Mapper.xml 实现代码如下:
<select id="getUserById" resultType="com.example.demo.model.User">
select * from userinfo where id=#{id}
</select>

6.1.1 参数占位符 #{} ${}

  • #{}:预编译处理。
  • ${}:字符直接替换。
  • 预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使用 PreparedStatement set 方法来赋值。
  • 直接替换:是 MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。
预编译处理和字符串替换的区别故事(头等舱和经济舱乘机分离的故事): 在坐飞机的时候头等舱和经济舱的区别是很大的,如下图所示:
一般航空公司乘机都是头等舱和经济舱分离的,头等舱的人先登机,登机完之后,封闭经济舱,然后再让经济舱的乘客登机,这样的好处是可以避免浑水摸鱼,经济舱的人混到头等舱的情况,这就相当于预处理,可以解决程序中不安全(越权处理)的问题。

而直接替换的情况相当于,头等舱和经济舱不分离的情况,这样经济舱的乘客在通过安检之后可能越权摸到头等舱,如下图所示:

这就相当于参数直接替换,它的问题是可能会带来越权查询和操作数据等问题,比如后面的 SQL 注入问题。

6.1.2 ${} 优点

<select id="getAllBySort" parameterType="java.lang.String" resultType="com.example.demo.model.User">
select * from userinfo order by id ${sort}
</select>
使用 ${sort} 可以实现排序查询,而使用 #{sort} 就不能实现排序查询了,因为当使用 #{sort} 查询时,如果传递的值为 String 则会加单引号,就会导致 sql 错误。

6.1.3 SQL 注入问题

<select id="isLogin" resultType="com.example.demo.model.User">
select * from userinfo where username='${name}' and password='${pwd}'
</select>
sql 注入代码: “' or 1='1”

结论:用于查询的字段,尽量使用 #{} 预查询的方式。

6.1.4 like 查询

like 使用 #{} 报错
<select id="findUserByName2" resultType="com.example.demo.model.User">
select * from userinfo where username like '%#{username}%';
</select>
相当于: select * from userinfo where username like '%'username'%';
这个是不能直接使用 ${} ,可以考虑使用 mysql 的内置函数 concat() 来处理,实现代码如下:
<select id="findUserByName3" resultType="com.example.demo.model.User">
select * from userinfo where username like concat('%',#{username},'%');
</select>

6.2 多表查询

如果是增、删、改返回搜影响的行数,那么在 mapper.xml 中是可以不设置返回的类型的,如下图所示:

然而即使是最简单查询用户的名称也要设置返回的类型,否则会出现如下错误。

查询不设置返回类型的错误示例演示

controller 代码:
@RequestMapping("/getname")
public String getNameById(Integer id) {
return userService.getNameById(id);
}
mapper.xml 实现代码:
<select id="getNameById">
select username from userinfo where id=#{id}
</select>
访问接口执行结果如下:

显示运行了一个查询但没有找到结果映射,也就是说对于它的优点是使用方便,直接定义到某个实体类即可。

6.2.2 返回字典映射:resultMap

resultMap 使用场景:

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

 

程序中的属性如下:

 mapper.xml 代码如下:

<select id="getUserById" resultType="com.example.demo.model.User">
select * from userinfo where id=#{id}
</select>

 查询的结果如下:

这个时候就可以使用 resultMap 了,resultMap 的使用如下:

mapper.xml

<resultMap id="BaseMap" type="com.example.demo.model.User">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="pwd"></result>
</resultMap>
<select id="getUserById" resultMap="com.example.demo.mapper.UserMapper.BaseMap">
select * from userinfo where id=#{id}
</select>

 查询的结果就有值了,如下图所示:

6.2.3 多表查询

6.2.3.1 一对一的表映射

一对一映射要使用 标签,具体实现如下(一篇文章只对应一个作者):

<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
<id property="id" column="id"></id>
<result property="title" column="title"></result>
<result property="content" column="content"></result>
<result property="createtime" column="createtime"></result>
<result property="updatetime" column="updatetime"></result>
<result property="uid" column="uid"></result>
<result property="rcount" column="rcount"></result>
<result property="state" column="state"></result>
<association property="user"
resultMap="com.example.demo.mapper.UserMapper.BaseMap"
columnPrefix="u_">
</association>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select a.*,u.username u_username from articleinfo a
left join userinfo u on a.uid=u.id
</select>
以上使用 标签,表示一对一的结果映射:
  • property 属性:指定 Article 中对应的属性,即用户。
  • resultMap 属性:指定关联的结果集映射,将基于该映射配置来组织用户数据。
  • columnPrefix 属性:绑定一对一对象时,是通过 columnPrefix+association.resultMap.column 来映射结果集字段。association.resultMap.column是指 标签中 resultMap属性,对应的结果集映射中,column字段。
注意事项:column不能省略
articleinfo:
import java.time.LocalDateTime;
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer rcount;
private User user;
}
ArticleController
import com.example.demo.model.ArticleInfo;
import com.example.demo.service.ArticleService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RequestMapping("/a")
@RestController
public class ArticleController {
@Resource
private ArticleService articleService;
@RequestMapping("/getall")
public List<ArticleInfo> getAll() {
return articleService.getAll();
}
}
columnPrefix 如果省略,并且恰好两个表中如果有相同的字段,那么就会导致查询出错,示例如下:
<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
<id property="id" column="id"></id>
<result property="title" column="title"></result>
<result property="content" column="content"></result>
<result property="createtime" column="createtime"></result>
<result property="updatetime" column="updatetime"></result>
<result property="uid" column="uid"></result>
<result property="rcount" column="rcount"></result>
<result property="state" column="state"></result>
<association property="user"
resultMap="com.example.demo.mapper.UserMapper.BaseMap">
</association>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select a.*,u.id from articleinfo a
left join userinfo u on a.uid=u.id
</select>
service
import com.example.demo.mapper.ArticleMapper;
import com.example.demo.model.ArticleInfo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class ArticleService {
@Resource
private ArticleMapper articleMapper;
public List<ArticleInfo> getAll() {
return articleMapper.getAll();
}
}
Mapper
import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ArticleMapper {
List<ArticleInfo> getAll();
}
mapper.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">
<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
<id column="id" property="id"></id>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<association property="user"
resultMap="com.example.demo.mapper.UserMapper.BaseMap">
</association>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select a.*,u.* from articleinfo a
left join userinfo u on u.id=a.uid
</select>
</mapper>
userinfo 表中有 id 字段, articleinfo 中也有一个 id 字段,会导致查询出错结果如下:

 

两篇文章都是 uid=1 的作者写的,但这块显示的 user id 有两个,查询出错了。
正确的做法是添加 columnPrefix ,实现代码如下:
<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
<id column="id" property="id"></id>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<association property="user"
resultMap="com.example.demo.mapper.UserMapper.BaseMap"
columnPrefix="u_">
</association>
</resultMap>
执行结果如下:

6.2.3.2 一对多:一个用户多篇文章案例 

 一对多需要使用 标签,用法和 相同,如下所示:

<resultMap id="BaseMap" type="com.example.demo.model.User">
<id column="id" property="id" />
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="photo" property="photo"></result>
<collection property="alist" resultMap="com.example.demo.mapper.ArticleInfoMapper.BaseMap"
columnPrefix="a_">
</collection>
</resultMap>
<select id="getUserById" resultMap="BaseMap">
select u.*,a.title a_title from userinfo u
left join articleinfo a on u.id=a.uid where u.id=#{id}
</select>

七、复杂情况:动态SQL使用

动态 sql Mybatis 的强大特性之一,能够完成不同条件下不同的 sql 拼接。
可以参考官方文档: Mybatis 动态 sql

7.1 <if>标签

在注册用户的时候,可能会有这样一个问题,如下图所示:
注册分为两种字段:必填字段和非必填字段,那如果在添加用户的时候有不确定的字段传入,程序应该如何实现呢?
这个时候就需要使用动态标签来判断了,比如添加的时候性别 sex 为非必填字段,具体实现如下:
<insert id="insert" parameterType="org.example.model.User" useGeneratedKeys="true" keyProperty="id">
insert into user(
username,
password,
nickname,
<if test="sex != null">
sex,
</if>
birthday,
head
) values (
#{username},
#{password},
#{nickname},
<if test="sex != null">
#{sex},
</if>
#{birthday},
#{head}
)
</insert>
注意 test 中的 sex ,是传入对象中的属性,不是数据库字段。

7.2 <trim>标签

之前的插入用户功能,只是有一个 sex 字段可能是选填项,如果有多个字段,一般考虑使用标签结合标签,对多个字段都采取动态生成的方式。
标签中有如下属性:
  • prefix:表示整个语句块,以prefix的值作为前缀
  • suffix:表示整个语句块,以suffix的值作为后缀
  • prefixOverrides:表示整个语句块要去除掉的前缀
  • suffixOverrides:表示整个语句块要去除掉的后缀

 调整 UserMapper.xml 的插入语句为:

<insert id="insert" parameterType="org.example.model.User" useGeneratedKeys="true" keyProperty="id">
insert into user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
username,
</if>
<if test="password != null">
password,
</if>
<if test="nickname != null">
nickname,
</if>
<if test="sex != null">
sex,
</if>
<if test="birthday != null">
birthday,
</if>
<if test="head != null">
head,
</if>
<if test="createTime != null">
create_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="username != null">
#{username},
</if>
<if test="password != null">
#{password},
</if>
<if test="nickname != null">
#{nickname},
</if>
<if test="sex != null">
#{sex},
</if>
<if test="birthday != null">
#{birthday},
</if>
<if test="head != null">
#{head},
</if>
<if test="createTime != null">
#{createTime},
</if>
</trim>
</insert>
在以上 sql 动态解析时,会将第一个 部分做如下处理:
  • 基于 prefix 配置,开始部分加上 (
  • 基于 suffix 配置,结束部分加上 )
  • 多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于 suffixOverrides 配置去掉最后一个 ,
  • 注意 <if test=“createTime != null”> 中的 createTime 是传入对象的属性

7.3 <where>标签

传入的用户对象,根据属性做 where 条件查询,用户对象中属性不为 null 的,都为查询条件。如 user.username "a" ,则查询条件为 where username="a"
UserMapper 接口中新增条件查询方法:
List<User> selectByCondition(User user);
UserMapper.xml 中新增条件查询 sql
<select id="selectByCondition" parameterType="org.example.model.User" resultMap="BaseResultMap">
select id, username, password, nickname, sex, birthday, head, create_time
from user
<where>
<if test="username != null">
and username=#{username}
</if>
<if test="password != null">
and password=#{password}
</if>
<if test="nickname != null">
and nickname=#{nickname}
</if>
<if test="sex != null">
and sex=#{sex}
</if>
<if test="birthday != null">
and birthday=#{birthday}
</if>
<if test="head != null">
and head=#{head}
</if>
<if test="createTime != null">
and create_time=#{createTime}
</if>
</where>
</select>

以上标签也可以使用 <trim prefix="where" prefixOverrides="and"> 替换。

7.4 <set>标签

根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容。
UserMapper 接口中修改用户方法:根据传入的用户 id 属性,修改其他不为 null 的属性:
int updateById(User user);
UserMapper.xml 中添加更新用户 sql
<update id="updateById" parameterType="org.example.model.User">
update user
<set>
<if test="username != null">
username=#{username},
</if>
<if test="password != null">
password=#{password},
</if>
<if test="nickname != null">
nickname=#{nickname},
</if>
<if test="sex != null">
sex=#{sex},
</if>
<if test="birthday != null">
birthday=#{birthday},
</if>
<if test="head != null">
head=#{head},
</if>
<if test="createTime != null">
create_time=#{createTime},
</if>
</set>
where id=#{id}
</update>
以上标签也可以使用 <trim prefix="set" suffixOverrides=","> 替换。

7.4 <foreach>标签

对集合进行遍历时可以使用该标签。标签有如下属性:
  • collection:绑定方法参数中的集合,如 ListSetMap或数组对象
  • item:遍历时的每一个对象
  • open:语句块开头的字符串
  • close:语句块结束的字符串
  • separator:每次遍历之间间隔的字符串
示例:根据多个文章 id 来删除文章数据。
ArticleMapper 中新增接口方法:
int deleteByIds(List<Integer> ids);
ArticleMapper.xml 中新增删除 sql
<delete id="deleteByIds">delete from articlewhere id in<foreach collection="list" item="item" open="(" close=")" separator=",">#{item}</foreach>
</delete>

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

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

相关文章

东南大学轴承故障诊断(Python代码,CNN模型,适合复合故障诊断研究)

运行代码要求&#xff1a; 代码运行环境要求&#xff1a;Keras版本>2.4.0&#xff0c;python版本>3.6.0 本次实验主要是在两种不同工况数据下&#xff0c;进行带有复合故障的诊断实验&#xff0c;没有复合故障的诊断实验。 实验结果证明&#xff0c;针对具有复合故障的…

面试—Redis相关

文章目录 一、概述二、缓存1、缓存穿透2、缓存击穿3、缓存雪崩4、双写一致性5、持久化6、数据过期策略7、数据淘汰策略 三、分布式锁四、其它面试题1、主从复制2、哨兵3、分片集群结构4、I/O多路复用 一、概述 使用场景&#xff1a; Redis的数据持久化策略有哪些什么是缓存穿透…

智能安全配电装置应用场景有哪些?

安科瑞 华楠 一、应用背景 电力作为一种清洁能源&#xff0c;给人们带来了舒适、便捷的电气化生活。与此同时&#xff0c;由于使用不当&#xff0c;维护不及时等原因引发的漏电触电和电气火灾事故&#xff0c;也给人们的生命和财产带来了巨大的威胁和损失。 为了防止低压配电…

SkyEye与Jenkins的DevOps持续集成解决方案

在技术飞速发展的当下&#xff0c;随着各行各业的软件逻辑复杂程度提升带来的需求变更&#xff0c;传统测试已无法满足与之相对应的一系列测试任务&#xff0c;有必要引入一个自动化、可持续集成构建的DevOps平台来解决此类问题。本文将主要介绍SkyEye与Jenkins的持续集成解决方…

C++OpenCV(5):图像模糊操作(四种滤波方法)

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 &#x1f506; OpenCV项目地址及源代码&#xff1a;点击这里 文章目录 图像模糊操作均值滤波高斯滤波中值滤波双边滤波 图像模糊操作 关于图片的噪声&#xff1a;指的是图片中存在的不必要或者多余的干扰数…

windows下搭建php开发环境

http://wed.xjx100.cn/news/139397.html?actiononClick https://www.bilibili.com/read/cv23429835/ https://www.php.cn/faq/498307.html 安装iis 选择卸载程序 安装php 官网https://www.php.net/下载 选择线程安全 国内地址 下载完成后解压放到想存放的路径 添加p…

数据可视化 - 动态柱状图

基础柱状图 通过Bar构建基础柱状图 from pyecharts.charts import Bar from pyecharts.options import LabelOpts # 使用Bar构建基础柱状图 bar Bar() # 添加X轴 bar.add_xaxis(["中国", "美国", "英国"]) # 添加Y轴 # 设置数值标签在右侧 b…

深入浅出之Docker Compose详解

目录 1.Docker Compose概述 1.1 Docker Compose 定义 1.2 Docker Compose产生背景 1.3 Docker Compose 核心概念 1.4 Docker Compose 使用步骤 1.5 Docker Compose 常用命令 2. Docker Compose 实战 2.1 Docker Compose下载和卸载 2.2 Docker Compose 项目概述 2.3 Do…

北航投资已投企业四象科技成功发射三颗卫星

1箭4星&#xff01;2023年7月23日10时50分&#xff0c;我国在太原卫星发射中心使用长征二号丁运载火箭&#xff0c;成功将四象科技“矿大南湖号”SAR遥感卫星、“虹口复兴号”光学遥感卫星、“中电农创号”热红外遥感卫星以及银河航天灵犀03星共4颗卫星发射升空&#xff0c;卫星…

idea springBoot 部署多个项目打开Run Dashboard 窗口

在部署springcloud 项目的时候 本地调试&#xff0c;有可能需要全部启动所有服务&#xff0c;单个部署比较麻烦&#xff0c;通过Run DashBoard 窗口可以完美实现 1.先打开项目的文件地址找到workspace.xml文件&#xff0c;在项目下的.idea\workspace.xml 2. ctrlf 找到RunDash…

SpringMVC-mybatis,SQL语句中误用了desc关键字,导致报错。

17-Jul-2023 21:26:22.295 淇℃伅 [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.ApplicationContext.log 1 Spring WebApplicationInitializers detected on classpath 17-Jul-2023 21:26:22.621 淇℃伅 [RMI TCP Connection(2)-127.0.0.1] org.apache.catalin…

小白的机器学习之路(四)神经网络的初步认识:基于pytorch搭建自己的神经网络

小白的机器学习之路&#xff08;四&#xff09; 引子神经网络的基本结构反向传播算法和激活函数优化器如何通过pytorch搭建自己的BP network 引子 当前交通大数据业务的需要&#xff0c;需要承担一部分算法工作&#xff08;数据处理&#xff09;&#xff0c;考虑到上次研究深度…

springboot开放实验室管理系统【纯干货分享,免费领源码03361】

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是使用动态网页开发技术java作为系统的开发语言&#xff0c;M…

【Leetcode】二叉树进阶面试题

文章目录 二叉树创建字符串二叉树分层遍历&#xff08;从前开始&#xff09;二叉树分层遍历&#xff08;从后开始&#xff09;二叉树的最近公共祖先二叉搜索树与双向链表从前序与中序遍历序列构造二叉树从中序与后序遍历序列构造二叉树二叉树的前序遍历&#xff08;非递归&…

GitLab 删除项目

1.点击头像 2.点击Profile 3.选择要删除的项目点进去 4.settings-general-Advances-expand 5.然后在弹出框中输入你要删除的项目名称即可

Java - 注解开发

注解开发定义bean Component的衍生注解 Service&#xff1a; 服务层的注解 Repository&#xff1a; 数据层的注解 Controller&#xff1a; 控制层的注解 纯注解开发 bean管理 bean作用范围 在类上面添加Scope(“singleton”) // prototype: 非单例 bean生命周期 PostCon…

关于Spring的bean的相关注解以及其简单使用方法

一、前置工作 第一步&#xff1a;创建一个maven项目 第二步&#xff1a;在resource中创建一个名字叫做spring-config.xml的文件&#xff0c;并把以下代码复制粘贴 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.sprin…

redis-cluster 创建及监控

集群命令 cluster info&#xff1a;打印集群的信息。 cluster nodes&#xff1a;列出集群当前已知的所有节点&#xff08;node&#xff09;的相关信息。 cluster meet <ip> <port>&#xff1a;将ip和port所指定的节点添加到集群当中。 cluster addslots <slot…

《Federated Unlearning via Active Forgetting》论文精读

文章目录 1、概述2、方法实验主要贡献框架概述 3、实验结果比较方法实验结果忘却完整性忘却效率模型实用性 4、总结 原文链接&#xff1a; Federated Unlearning via Active Forgetting 1、概述 对机器学习模型隐私的⽇益关注催化了对机器学习的探索&#xff0c;即消除训练数…

基于JAVA SpringBoot和Vue高考志愿填报辅助系统

随着信息技术在管理中的应用日益深入和广泛&#xff0c;管理信息系统的实施技术也越来越成熟&#xff0c;管理信息系统是一门不断发展的新学科&#xff0c;任何一个机构要想生存和发展&#xff0c;要想有机、高效地组织内部活动&#xff0c;就必须根据自身的特点进行管理信息时…