什么是 MyBatis?

news2024/11/18 17:20:28
经过前几篇博客的学习 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 article
    where id in
    <foreach collection="list" item="item" open="(" close=")" separator=",">
        #{item}
    </foreach>
</delete>

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

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

相关文章

Vue中$route和$router的区别

$router&#xff1a;用来操作路由&#xff0c;$route&#xff1a;用来获取路由信息 $router其实就是VueRouer的实例&#xff0c;对象包括了vue-router使用的实例方法&#xff0c;还有实例属性&#xff0c;我们可以理解为$router有一个设置的含义&#xff0c;比如设置当前的跳转…

二叉树的基本操作函数(先、中、后、层次遍历)

//链式二叉树数据结构 typedef struct Node { TElemType data;//数据域 struct Node* lchild, * rchild;//左右孩子 }Node, * Tree; #include <queue> #define TElemType int //方便 应用的时候 改//链式二叉树数据结构 typedef struct Node {TElemType data;//数据域s…

ceph集群---使用RBD块存储

文章目录 创建和删除池RBD设备的配置及使用RBD 块设备数据的导出和导入 块存储接口是一种主流的存储访问接口&#xff0c;也是常见的存储形态&#xff0c;比如服务器下的/dev/sdx都是块存储设备。你可以像使用磁盘一样来使用Ceph提供的块存储设备。 在创建块存储设备之前&#…

Python缔造超级马里奥世界,带你畅游冒险与创造的奇境!

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 马里奥大叔这次发现了一个很大的宝藏&#xff0c;这次他终于可以不当马里奥大叔&#xff01; 可以当一次有钱的富人喽&#xff0c;让我们一起来玩一玩吧&#xff01; 帮马里奥大叔闯关并收集到金币。只有所有的金币都收集完…

删除(取消)亚马逊的云服务器Amazon EC2的操作方法、教程

删除&#xff08;取消&#xff09;亚马逊的云服务器Amazon EC2的操作方法、教程 关于Amazon EC2服务器 1.亚马逊的服务器只能终止&#xff0c;不能删除&#xff0c;终止后卷&#xff08;存储、硬盘会自动关闭&#xff0c;如果没有快照不可恢复&#xff09; 2.终止的服务器会存…

简单工厂模式VS策略模式

简单工厂模式VS策略模式 今天复习设计模式&#xff0c;由于简单工厂模式和策略模式太像了&#xff0c;重新整理梳理一下 简单工厂模式MUL图&#xff1a; 策略模式UML图&#xff1a; 1、简单工厂模式中只管创建实例&#xff0c;具体怎么使用工厂实例由调用方决定&#xff0c…

TSINGSEE青犀视频监控管理平台EasyNVR如何配置鉴权?

视频监控汇聚平台EasyNVR是基于RTSP/Onvif协议的视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端的分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。为了满足用户的集成与二次开发需求&#xff0c;我们也提供了丰富的API接口供…

基于Java+SpringBoot+vue前后端分离古典舞在线交流平台设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

DP83867IS SGMII eye diagram问题调试记录

1. 前言 使用的是带CPU的DP83867IS,通过SGMII接口 从PHY到CPU的眼图看起来很好 而从CPU到PHY的眼图很差 2. 问题梳理 (1)能证实SGMII道有100欧姆的阻抗吗? (2)能不能做一个误码率测试来看看眼图是否仍然是可以接受的? (3)因为从PHY到CPU的眼睛是好的,可能有一个…

【初中数学竞赛题】已知m,n为正整数,且m^2+n^2=2009,求m+n的值。

已知 m m m, n n n为正整数&#xff0c;且 m 2 n 2 2009 m^2n^22009 m2n22009,求 m n mn mn的值。 一、使用因式分解加枚举 ① 对2009进行短除 7 | 2009 ‾ 7 | 287 ‾ 41 7 \underbar{|\enspace\enspace2009\enspace} \\ \enspace\enspace 7 \underbar{|\enspace287\enspa…

android首页申请android.permission.READ_PHONE_STATE权限合规处理办法?

1、像闲鱼一样&#xff0c;申请的时候在顶部弹个解释说明对话框&#xff1f;

如何解决使用Elsivier默认latex模板,显示多位作者名字而不是et.al形式

问题描述&#xff1a; 使用Elsivier默认模板&#xff0c;编辑论文的时候,使用\citep{论文缩写}命令&#xff0c;发现在编译之后的.pdf文件中&#xff0c;会显示出该论文所有作者的姓&#xff08;红色部分&#xff09;&#xff0c;而不是使用et.al的形式&#xff08;绿色部分&a…

线程阻塞和线程中断

本专栏学习内容又是来自尚硅谷周阳老师的视频 有兴趣的小伙伴可以点击视频地址观看 中断机制 简介 Java线程中断机制是一种用于协作式线程终止的机制。它通过将一个特殊的中断标志设置为线程的状态来实现。当线程被中断时&#xff0c;它可以检查这个中断标志并采取相应的措施…

TSINGSEE青犀视频安防监控管理平台EasyNVR如何配置鉴权?

视频监控汇聚平台EasyNVR是基于RTSP/Onvif协议的视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端的分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。为了满足用户的集成与二次开发需求&#xff0c;我们也提供了丰富的API接口供…

无涯教程-jQuery - show( speed, callback )方法函数

show(speed&#xff0c;[callback])方法使用优美的动画显示所有匹配的元素&#xff0c;并在完成后触发可选的回调。 show( speed, [callback] ) - 语法 selector.show( speed, [callback] ); 这是此方法使用的所有参数的描述- speed - 代表三个预定义速度("slow&q…

No100.精选前端面试题,享受每天的挑战和学习(事件循环)

文章目录 1. 请解释一下JavaScript中的事件循环&#xff08;Event Loop&#xff09;是什么&#xff0c;并描述其工作原理。2. 请解释一下JavaScript中的宏任务&#xff08;macro-task&#xff09;和微任务&#xff08;micro-task&#xff09;的区别3. 在事件循环中&#xff0c;…

PS - Photoshop 抠图与剪贴蒙版功能与 Stable Diffusion 重绘

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131978632 Photoshop 的剪贴蒙版是一种将上层图层的内容限制在下层图层的形状范围内的方法&#xff0c;也就是说&#xff0c;上层图层只能在下层图…

PLC的高端版本通常具有以下特点:

高速处理能力&#xff1a;高端PLC通常具有更快的处理速度和更高的运行频率&#xff0c;可以处理更复杂的控制逻辑和更多的输入/输出信号。 大容量存储&#xff1a;高端PLC通常具有更大的存储容量&#xff0c;可以保存更多的程序和数据&#xff0c;以满足更复杂的应用需求。 多种…

Java 源码打包 降低jar大小

这里写目录标题 Idea maven 插件配置pom.xml 配置启动技巧 Idea maven 插件配置 pom.xml 配置 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!-- 只…

html学习1

1、<!DOCTYPE html>用来告知 Web 浏览器页面使用了哪种 HTML 版本。 2、对于中文网页需要使用 <meta charset"utf-8"> 声明编码&#xff0c;否则会出现乱码。 3、html的结构图&#xff0c;<body> </body>之间的部分可以显示。 4、HTML元素…