MyBatis
目标:
能够使用 MyBatis 代理方式进行开发; 能够理解并配置 MyBatis 核心配置文件; 能够使用 MyBatis 注解进行开发
# MyBatis 概述
# JDBC 缺点
下面是 JDBC 的代码,接下来我们分析一下这块代码有什么缺点:
- 问题一:第一部分代码主要完成注册驱动、获取连接的工作,在这部分代码中,加载数据库驱动以及连接数据库的四个基本信息,都是写死的,如果后面我们要更换数据库或者修改连接用户、密码都要修改 Java 源代码;
- 问题二:第二部分定义 SQL 语句,也是硬编码到我们 Java 代码中的,修改 SQL 同样需要修改源代码;
- 问题三:第三部分我们预编译 SQL 执行对象,并设置参数,当前只需设置一个参数,如果参数过多,我们操作起来会比较繁琐,而且多为重复性工作;
- 问题四:第四部分查询结果集封装为对象的过程需要我们手动操作,耗费时间、技术含量又不高。
为了解决 JDBC 连接数据库需要频繁创建、销毁连接的问题,我们引入了 Druid 数据库连接池,那么为了解决上面的问题,我们还需要引入一个新的技术,MyBatis 框架。下面我们就来看下针对上面的问题 MyBatis 是如何解决的?
# MyBatis 优化
针对 JDBC 操作数据库的缺点,MyBatis 做了优化,来简化开发人员的工作,总结出来也就两点内容:
- 硬编码问题通过配置文件解决;
- 注册驱动、获取连接
- SQL 语句
- 自动完成操作繁琐的工作
- 手动设置参数
- 手动封装结果集
# MyBatis 概念
MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发工作。
下面咱们简单了解一下持久层、框架的概念。
# 持久层概述
在我们的学习、工作生活中,大部分人应该都遇到过这样的问题。比如用 Word 写材料,在编写过程中未及时保存,如果此时出现停电或者系统崩溃,我们这段时间的工作可能就白做了。实际上,Word 软件运行期间的我们的文档内容都是保存在内存中的,但是内存中的数据断电后就会丢失,所以就会出现上面的问题,因此我们就要定时保存文档,将文档数据以文件的形式保存到我们的磁盘上。
在 Web 应用软件开发领域我们可以用数据库来持久化我们的数据,前面我们学习数据库的时候也看到了,数据库对应磁盘上的一个文件夹,数据表对应磁盘上的两个文件。
简单理解,持久化就是将内存中的数据保存到磁盘等存储设备中。当数据落盘以后,我们的数据库就可以独立于应用程序而存在,数据就可以比任何应用都存在更久的时间,而且,不同的应用程序也可以通过共享数据库的形式来共享数据。
持久层的实现是和数据库紧密相连的,我们需要注意的是,持久层中层的含义。经典的软件用用体系结构有三层、表示层、业务逻辑层和数据持久层。
- 表示层:提供与用户交互的接口。实现用户操作界面,展示用户需要的数据;
- 业务逻辑层:完成业务流程,处理表示层提交的数据请求,并将要保存的数据提交给数据库;
- 数据库层:存储需要持久化的业务数据。
在 3 层体系结构中,业务逻辑层除了负责业务逻辑以外,还要负责相关的数据库操作,即对业务数据的增、删、改、查。为了使业务逻辑层的开发人员专注于业务逻辑的开发,可以把数据库访问从业务逻辑中分离出来,形成一个新的、单独的持久化层。
持久层对数据访问逻辑进行抽象,业务层通过持久化层提供的数据访问接口来访问底层数据库中的数据。这样分层,不仅将应用开发人员从底层操作中解放出来,而且业务逻辑也更加清晰。同时,由于业务逻辑与数据库访问分离,使得开发人员的分工可以更加细化。
# 什么是框架?
聊完持久化,咱们再来看下什么是框架?。
随着技术的发展,无论是后端工程狮,还是前端攻城狮,开发的时候限制都已经不用原生的 JDBC、JS 等技术。取而代之的是各种开发框架,如:
- 后端常用框架
- MyBatis
- Spring
- SpringBoot
- 前端常用框架
- Vue
- Node 这些框架都有共同的特点--简单、高效,极大提高开发效率。
框架的英文为 Framework,最早源于建筑行业。通俗的说,在软件领域框架是实现某种功能的半成品,提供了一些常用的工具类和一些基础通用化的组件,可以供开发人员在此基础上,更高效的满足各自的业务需求。
当然这些概念比较抽象,下面用一个例子帮助大家理解,PPT 相信大家都有用过,在写 PPT 的时候,通常是直接打开Office Power Point或者WPS,然后直接新建空白演示文稿就可以写内容了,需要什么背景、字体、风格、主题等,都可以在空白演示文稿中添加。实际上在这个过程中,我们就在使用框架,这个框架就是 PPT 给我准备好的内容,如:空白的模板、字体库、风格库、动画库等等。这些基础的内容就是框架搭建好的基础支撑,或者说一个半成品。我们在写 PPPT 的时候,只需要在这些基础上来定制我们自己的内容即可。
和上面的举例类比,在软件开发领域,MyBatis 就是为我们准备好了操作数据库的基础功能,包括参数传递、结果集封装等等。
# MyBatis 快速入门
简单介绍完 MyBatis,下面咱们按照流程开发一个简单的示例来学习下如何使用 MyBatis 进行开发,刚开始我们可能不太理解,后面我们会针对每个技术点进行详细介绍。
- 创建
mybatis
库 和tb_user
表,并初始化数据
-- 创建数据库,使用数据库
create database mybatis;
use mybatis;
-- 删除表
drop table if exists tb_user;
-- 创建表
create table tb_user (
id int not null auto_increment,
username varchar(20),
password varchar(20),
gender char(1),
addr varchar(30),
constraint pk_tb_user primary key(id)
);
-- 初始化数据
insert into tb_user(username, password, gender, addr)
values
('zhangsan', '123', '男', '北京'),
('李四', '234', '女', '天津'),
('王五', '11', '男', '西安');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 创建 Maven 项目,并在
pom.xml
文件中添加依赖坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>mybatis-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--mybatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!--junit 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
注意
需要再项目的 resources
目录下创建 logback
的配置文件
- 编写 MyBatis 核心配置文件,设置数据库连接信息,解决硬编码问题;在模块的
resources
目录下创建 MyBatis 的配置文件mybatis-config.xml
,内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
environments:配置数据库连接环境信息。
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false&useServerPrepStmts=true"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 编写 SQL 映射文件 -> 统一管理 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="test">
<select id="selectAll" resultType="com.itheima.pojo.User">
select * from tb_user;
</select>
</mapper>
2
3
4
5
6
7
- 定义用户实体类
package com.itheima.pojo;
/**
* 用户实体
* @author sunyy
* @version 0.0.1
* @since 2022.10.24
*/
public class User {
/**
* 用户id
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 性别
*/
private String gender;
/**
* 地址
*/
private String addr;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- 在
com.itheima
包下编写MyBatisDemo
演示类
package com.itheima;
import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* MyBatis 快速入门
*
* @author sunyy
* @version 0.0.1
* @since 2022.10.24
*/
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
// 1. 加载MyBatis核心配置,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取用来执行SQL的SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 执行SQL
// 参数是字符串,该参数值必须是SQL映射配置文件的namespace.id
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
// 4. 释放资源
sqlSession.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
解决 SQL 映射文件的告警提示
在上面的案例中,SQL 映射配置文件中有可能存在告警或者报红的情况,如图:
出现上面的情况主要是,IDEA 没有与数据库建立连接,不能识别表名信息。不过,这个问题并不影响程序的运行。可以通过在 IDEA 中配置 MySQL 数据库连接来解决。
在 IDEA 中配置 MySQL 数据库连接可以参照下面步骤进行:
- 点击 IDEA 右边的
Database
,在展开的界面点击+
选择Data Source
,再选择MySQL
- 在弹出的界面填写数据库连接信息
- 点击完成后就可以看到如下界面
在这个界面中我们可以和使用 navicat
工具一样进行数据库的操作。
# Mapper 代理开发
# Mapper 代理开发概述
上面我们已经用一个简单的入门案例了解了 MyBatis 的简单使用,咱们回顾一下快速入门的代码:
// 1. 加载MyBatis核心配置,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取用来执行SQL的SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 执行SQL
// 参数是字符串,该参数值必须是SQL映射配置文件的namespace.id
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
// 4. 释放资源
sqlSession.close();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面是 MyBatis 的基本使用方式,大家可以那上面的代码与 JDBC 代码对比一下:
从对比结果来看,MyBatis 的代码更简洁,为我们解决了结果集封装以及提供 SQL 映射文件来统一管理 SQL 语句,并且在一定程度上解决了硬编码问题,为什么不是完全解决硬编码问题?
// test.selectAll 也是硬编码
List<User> users = sqlSession.selectList("test.selectAll");
2
这里调用的 selectList
方法传递的参数是 SQL 映射文件中的 namespace.id
值,这样写同样不利于我们后续维护的,接下我们就来学习解决这个问题的方法--通过 Mapper 代理开发,这也是 MyBatis 官网推荐我们使用的一种方式。
# 使用 Mapper 代理开发
使用 Mapper 代理开发,我们的代码就需要满足下面 3 个要求:
首先需要定义一个与 SQL 映射文件同名的接口,并且将 Mapper 接口和 SQL 映射文件放置在同一个目录下。为了集中管理 Java、XML 文件,通常的做法是在 resources 目录下创建与 Mapper 接口所在报名同样的目录,用于存放
XxxxMapper.xml
文件,这样也可以确保在项目编译后,Mapper 接口和 Mapper 的 XML 文件在同一目录下。设置 SQL 映射文件的
namespace
属性为 Mapper 接口的全限定名在 Mapper 接口中定义方法,方法名就是 SQL 映射文件中 sql 语句的
id
,并要保持参数类型和返回值类型一致修改 MyBatis 核心配置文件,加载 SQL 映射文件
<mappers> <!--加载sql映射文件--> <!--<mapper resource="UserMapper.xml"/>--> <mapper resource="com/itheima/mapper/UserMapper.xml" /> </mappers>
1
2
3
4
5接下来我们修改一下
MyBatisDemo
类,将其修改改为 Mapper 代理方式// 1. 加载MyBatis核心配置,获取SqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2. 获取用来执行SQL的SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 3. 基本方式 执行SQL // 参数是字符串,该参数值必须是SQL映射配置文件的namespace.id // List<User> users = sqlSession.selectList("test.selectAll"); // 3. Mapper代理方式 执行SQL UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.selectAll(); System.out.println(users); // 4. 释放资源 sqlSession.close();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20运行结果如下:
提示
如果 Mapper 接口名称和 SQL 映射文件名称相同,并在同一目录层级下,则可以使用包扫描的方式简化 SQL 映射文件的加载,也就是可以将核心配置文件 mybatis-config.xml
中加载映射配置文件的地方修改为:
<mappers>
<!--基本方式,加载sql映射文件-->
<!--<mapper resource="UserMapper.xml"/>-->
<!--Mapper代理方式-->
<!--<mapper resource="com/itheima/mapper/UserMapper.xml" />-->
<!--Mapper代理方式-->
<package name="com.itheima.mapper" />
</mappers>
2
3
4
5
6
7
8
# 核心配置文件
在讲解入门开发、Mapper 代理开发时我们已经使用了核心配置文件中的一些配置,比如:环境信息、加载 SQL 映射文件等,而 MyBatis 的核心配置文件还可以配置其他内容,我们可以通过官网查询到可以配置的内容。
接下来我们先对里面一些常用的配置进行讲解:
# 多环境配置
在核心配置文件中的 environments
标签中其实可以配置多个 environment
,使用 id
给每个环境配置起名,在 environments
中使用 default='环境id'
来指定使用哪儿段配置。在实际工作中,我们可以通过这种方式来区分开发、测试、生产等环境的配置。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
environments:配置数据库连接环境信息。
-->
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis2?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<!--<mapper resource="UserMapper.xml"/>-->
<!--<mapper resource="com/itheima/mapper/UserMapper.xml" />-->
<!--Mapper代理方式-->
<package name="com.itheima.mapper" />
</mappers>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 类型别名
在映射配置文件中的 resultType
属性需要配置数据封装的类型(类的全限定名)。而每次这样写显得特别麻烦,因此 MyBatis 提供给了 类型别名 (typeAliases
)配置,可以简化这部分的书写。
简单的说,类型别名可以为 Java 类型设置一个缩写名字。它仅用于 XML 配置,意在降低冗余的全限定类名的书写。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
2
3
4
5
6
7
8
当这样配置时,Blog
可以用在任何使用domain.blog.Blog
的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的类,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
2
3
每一个在包 domain.blog
中的类,在没有注解的情况下,会使用类名的首字母小写的非限定类名来作为它的别名。比如domain.blog.Author
的别名为author
;若有注解,则别名为其注解值,如:
@Alias("author")
public class Author {
...
}
2
3
4
# 注解开发
MyBatis 最初的配置信息是基于 XML,映射语句(SQL)也是定义在 XML 中的,而到 MyBatis 3,它提供了一种新的基于注解的配置。但是受限于 Java 注解的表达力和灵活性,使用 MyBatis 注解只能开发一些简单的功能,如果特别复杂的功能就需要使用 MyBatis 提供的 SQL 构建器来完成。
MyBatis 针对 CRUD 操作提供了对应的注解,这些注解已经做到见名知意,很好理解,如下:
- 查询:
@Select
- 添加:
@Insert
- 修改:
@Update
- 删除:
@Delete
接下来我们就基于注解来开发一个案例:使用 MyBatis 注解查询用户表的所有数据。
使用注解就不再需要在
UserMapper.xml
中定义statement
的注释,直接在UserMapper.java
中添加方法:package com.itheima.mapper; import com.itheima.pojo.User; import org.apache.ibatis.annotations.Select; import java.util.List; /** * MyBatis Mapper代理开发 * @author sunyy * @version 0.0.1 * @since 2022.10.24 */ public interface UserMapper { /** * 查询所有用户信息 * @return */ List<User> selectAll(); /** * 通过注解实现查询所有用户信息 * @return */ @Select("select * from tb_user") List<User> selectAllByAnnotation(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28由于快速入门时已经在
mybatis-config.xml
做了加载 SQL 映射文件的配置,所以咱们这第二步就可以省略了,但是如果没有配置,一定要记得加上,否则会报错的<mappers> <!--加载sql映射文件--> <package name="com.itheima.mapper" /> </mappers>
1
2
3
4
我们在课堂上就只演示这一个查询的注解开发,其他的同学们可以下来当作练习自己实现。
注意: 官方文档中*入门*章节有这样一段话
由于 Java 注解的一些限制以及某些 MyBatis 映射的复杂性,要使用大多数高级映射(比如:嵌套联合映射),仍然需要使用 XML 配置。
针对一些复杂的 SQL 语句,如果用注解的化,就需要使用到 MyBatis 提供的 SQL 构建器来完成,就比如这样一个需求:
根据网页发送的请求参数查找用户数据
- 如果请求参数有
id
,就根据id
进行精确匹配; - 如果请求参数有
username
,就根据username
进行模糊匹配;
其对应的 SQL 构建器代码如下:
String sql = new SQL() {{
SELECT("u.id, u.username, u.password, u.gender, u.addr");
FROM("tb_user u");
if (id != null) {
WHERE("u.id = #{id}");
}
if (username != null && !username.equals("")) {
WHERE("u.username like #{username}");
}
}}.toString();
2
3
4
5
6
7
8
9
10
可以看到,上述代码将SQL语句和Java代码融合到了一起,代码的可读性大幅度降低了,因此针对复杂的SQL语句建议大家还是采用XML映射的方式实现。
# MyBatis 练习
目标:
- 掌握使用映射配置文件实现CRUD操作
如上图所示的产品原型图,里面包含了品牌数据的查询
、按条件查询
、添加
、删除
、批量删除
、修改
等功能,而这些功能其实就是对数据库表中的数据进行CRUD操作。
接下来我们就是用MyBatis
完成品牌数据的增删改查等操作,下面我们来梳理一下所需完成的功能列表:
- 查询品牌
- 查询所有数据
- 查询详情
- 条件查询
- 添加品牌
- 修改品牌
- 修改品牌全部字段
- 修改品牌动态字段
- 删除品牌
- 删除一个
- 批量删除
在开始编码前,我们需要先将必要的环境、数据准备一下。
# 环境、数据准备
# 创建品牌表并初始化数据
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand (
-- id 主键
id int not null auto_increment,
-- 品牌名称
brand_name varchar(20),
-- 企业名称
company_name varchar(20),
-- 排序字段
ordered int,
-- 描述信息
description varchar(100),
-- 状态:0-禁用 1-启用
status int,
-- 主键约束
constraint pk_tb_brand primary key(id)
);
-- 添加数据
insert into tb_brand (
brand_name,
company_name,
ordered,
description,
status
) values (
'三只松鼠',
'三只松鼠股份有限公司',
5,
'好吃不上火',
0
), (
'华为',
'华为技术有限公司',
100,
'华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界',
1
), (
'小米',
'小米科技有限公司',
50,
'are you ok',
1
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 创建mybatis-config.xml
核心配置文件
创建mybatis-config.xml
可以事先在Idea中设置mybatis-config.xml
的模板文件
- 在Idea中:打开 File -> Settings
- 在 Editor -> File and Code Templates 中添加
mybatis-config.xml
配置文件的模板,模板内容可以自定义
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
environments:develop配置数据库连接环境信息。
-->
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<!--Mapper代理方式-->
<package name="com.itheima.mapper" />
</mappers>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 通过File -> New -> MyBatis Config XML 在
resources
目录下创建mybatis-config.xml
核心配置文件,将模板中的内容修改为我们模块对应的内容
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
environments:配置数据库连接环境信息。
-->
<environments default="develop">
<environment id="develop">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--Mapper代理方式-->
<package name="com.itheima.mapper" />
</mappers>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建品牌实体类Brand
在com.itheima.pojo
包下创建Brand
实体类
package com.itheima.pojo;
import java.io.Serializable;
/**
* 品牌实体类
* @author sunyy
* @version 1.0
* @since 2022.11.6
*/
public class Brand implements Serializable {
private static final long serialVersionUID = -1L;
/**
* id 主键
*/
private Integer id;
/**
* 品牌名称
*/
private String brandName;
/**
* 企业名称
*/
private String companyName;
/**
* 排序字段
*/
private Integer ordered;
/**
* 描述信息
*/
private String description;
/**
* 状态:0-禁用 1-启用
*/
private Integer status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public Integer getOrdered() {
return ordered;
}
public void setOrdered(Integer ordered) {
this.ordered = ordered;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", brandName='" + brandName + '\'' +
", companyName='" + companyName + '\'' +
", ordered=" + ordered +
", description='" + description + '\'' +
", status=" + status +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# 安装MyBatisX插件
MyBatisX是一款基于Idea的MyBatis快速开发插件,为提高MyBatis开发效率而生,主要功能有:
- XML映射配置文件 和 接口方法 间互相跳转
- 根据接口方法生成
statement
安装方式:
通过 File -> Settings -> Plugins 搜索 MyBatisX
插件
注意
安装完毕后需要重启Idea
插件效果:
红色头绳的标识映射配置文件,蓝色头绳的标识Mapper
接口,在Mapper
接口点击红色头绳的小鸟图标会自动跳转到对应的映射配置文件,在映射配置文件中点击蓝色头绳的小鸟会自动跳转到对应的Mapper
接口,也可以在Mapper
接口中定义方法,自动生成映射配置文件中的Statement
。
# 查询所有数据
如上图所示就是页面上展示的数据,而这些数据需要从数据库进行查询。接下来我们就来学习下如何查询品牌表中所有数据,我们将通过以下步骤实现该功能:
编写接口方法:
BrandMapper
接口中selectAll
- 参数:无;查询所有数据不需要根据任何条件进行查询,所以无需参数。
List<Brand> selectAll();
1- 结果:
List<Brand>
;我们会将查询出来的每一条数据封装为一个Brand
对象,而多个Brand
对象封装到List
集合中作为返回值
编写Mapper映射文件及SQL语句:
BrandMapper.xml
<select id="selectAll" resultType="com.itheima.pojo.Brand"> select * from tb_brand </select>
1
2
3编写测试方法,并执行
# 创建BrandMapper.java
接口类,编写接口方法
创建com.itheima.mapper
包,在该包下新建BrandMapper
接口,并在接口里面定义查询所有品牌数据的方法:
package com.itheima.mapper;
import com.itheima.pojo.Brand;
import java.util.List;
public interface BrandMapper {
/**
* 查询所有品牌
*/
List<Brand> selectAll();
}
2
3
4
5
6
7
8
9
10
11
12
13
# 创建 BrandMapper.xml
文件,编写 SQL
语句
与mybatis-config.xml
文件类似,我们也可以在Idea中创建 XxxMapper.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.itheima.mapper.XxxMapper">
</mapper>
2
3
4
5
然后,通过File -> New -> MyBatis Mapper XML在resources
目录下创建BrandMapper.xml
文件,注意改文件需要与BrandMapper.java
的包名路径相同,也即是需要在resources
目录下创建com/itheima/mapper
目录,并在创建的目录下新建BrandMapper.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.itheima.mapper.BrandMapper">
<select id="selectAll" resultType="com.itheima.pojo.Brand">
select * from tb_brand
</select>
</mapper>
2
3
4
5
6
7
# 在模块test/java
目录下,新建com.itheima.test
包,在该包下创建测试类MyBatisTest
类
测试方法实现步骤:
- 获取SqlSessionFactory
- 获取SqlSession对象
- 获取Mapper接口的代理对象
- 执行查询所有品牌数据方法
- 释放资源
package com.itheima.test;
import com.itheima.mapper.BrandMapper;
import com.itheima.pojo.Brand;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
@Test
public void testSelectAll() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 4. 执行方法
List<Brand> brands = brandMapper.selectAll();
System.out.println(brands);
// 5. 释放资源
session.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
注意
现在我们写的测试这部分代码特别麻烦,可以先忍忍,以后我们只会写上面第3步的代码,其他的都不需要我们来编写了。
执行测试方法,结果如下:
从上面解雇我们看到了一个问题,有些数据封装成功了,但是有些数据并没有封装成功,比如brandName
和companyName
,出现了什么问题?
这是因为实体类 Brand和数据表 tb_brand中的属性和字段名没有完全匹配,属性名为brandName
、companyName
,而字段名是brand_name
、company_name
,我们可以挺过两种方式来解决这个问题:
- 给字段起别名
- 使用
resultMap
定义字段和属性之间的映射关系
解决上面问题的核心思想是,保持字段名和属性名一致,或者配置两者之间的映射关系。如下图:
第一种方式:我们可以在写SQL语句时给这两个字段起别名,将别名定义成和属性名一致:
<select id="selectAll" resultType="com.itheima.pojo.Brand">
select
id,
brand_name brandName,
company_name companyName,
ordered,
description,
status
from tb_brand
</select>
2
3
4
5
6
7
8
9
10
但上面的SQL语句中书写字段列表及别名时比较麻烦,如果表中还有更多的字段,同时其他的功能如果也需要查询这些字段时就显得我们的代码不够精炼,我们可以使用MyBatis提供的sql
片段提高代码的复用性。
SQL片段:
- 将需要复用的
SQL
片段抽取到sql
标签中
<sql id="brand_column">
id,
brand_name brandName,
company_name companyName,
ordered,
description,
status
</sql>
2
3
4
5
6
7
8
id="brand_column"
是唯一标识,引用时通过该值进行引用。
- 在原SQL语句中进行引用:使用
include
标签引用上述的SQL片段,而refid
指定上述SQL片段的id值
<select id="selectAll" resultType="com.itheima.pojo.Brand">
select
<include refid="brand_column"/>
from tb_brand
</select>
2
3
4
5
第二种方式:使用resultMap
解决上述问题
起别名 + SQL片段 的方式可以解决上述的问题,但是还不够优雅,如果还有功能只需要查询部分字段,而不是查询所有字段,那么我们就需要再定义一个SQL片段,就显得不够灵活。
那么我们就可以使用resultMap
来定义字段和属性的映射关系来解决上述问题。
- 在映射配置文件中使用
resultMap
定义字段和属性的映射关系
<resultMap id="" type="com.itheima.pojo.Brand">
<!--
id: 完成主键字段的映射
column: 表的列名
property: 实体类的属性名
result: 完成一般字段的映射
column: 表的列名
property: 实体类的属性名
-->
<result column="brand_name" property="brandName" />
<result column="company_name" property="companyName" />
</resultMap>
2
3
4
5
6
7
8
9
10
11
12
注意
在上面只需要定义 字段名 和 属性名 不一样的映射,而一样的则不需要专门定义出来。
- SQL语句正常编写
<select id="selectAll" resultMap="brandResultMap">
select
*
from tb_brand
</select>
2
3
4
5
# 查询所有数据小结
实体类属性名 和 数据库表列名 不一致,不能自动封装数据,解决方式:
- 起别名:在SQL语句中,对不一样的列名其别名,别名和属性名一致
- 可以定义
<sql>
片段,提升代码复用性
- 可以定义
- resultMap:定义
<resultMap>
完成不一致的属性名和列名的映射
# 查询单个品牌详情
有些数据的属性比较多,在页面表格中无法全部实现,而只会显示部分,因此其他属性就需要通过查看详情功能来进行查询,如上图所示。
查看详情功能实现步骤:
编写接口方法:
BrandMapper
接口Brand selectById(int id);
方法- 参数:
id
,查看详情实际就是查询某一行数据,因此需要根据id进行查询,所以需要传入数据的唯一标识,也就是主键id; - 结果:
Brand
,根据id
查询出来的数据只要一条,所以将查询到的这条数据封装为一个Brand
对象即可
- 参数:
编写SQL语句:SQL映射文件
<select id="selectById" resultMap="brandResultMap" parameterType="int"> select * from tb_brand where id = #{id} </select>
1
2
3编写测试方法并执行
# 在BrandMapper.java
接口类中编写接口方法
在BrandMapper
接口中定义根据id
查询品牌数据的方法
/**
* 根据id查询品牌信息
* @param id
* @return
*/
Brand selectById(int id);
2
3
4
5
6
# 在BrandMapper.xml
映射文件中编写Statement
和SQL
语句
在BrandMapper.xml
映射配置文件中编写Statement
,使用resultMap
而不是使用resultType
<select id="selectById" resultMap="brandResultMap" parameterType="int">
select * from tb_brand where id = #{id}
</select>
2
3
注意
上述SQL中的 #{id}
先这样写,后面我们在进行详细讲解
# 编写测试方法testSelectById
在test/java
下的com.itheima.test
包下的MyBatisTest
类中定义测试方法,开发测试方法的步骤为:
- 获取SqlSessionFactory
- 获取SqlSession对象
- 获取Mapper接口的代理对象
- 执行
selectById
方法 - 释放资源
/**
*
* @throws IOException
*/
@Test
public void testSelectById() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 4. 执行方法
Brand brand = brandMapper.selectById(1);
System.out.println(brand);
// 5. 释放资源
session.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
执行测试方法结果如下:
# 参数占位符#{id}
查询到的结果很好理解就是id
为1的这行数据,而这里我们需要看控制台显示的SQL语句,能看到使用?
进行占位,说明我们在映射配置文件中写的#{id}
最终会被?
进行占位。接下来我们就聊聊配置文件找那个的参数占位符。
MyBatis提供了两种参数占位符:
#{}
:执行SQL时,会将#{}
占位符替换为?
,将来自动设置为对应的参数值。从上述例子可以看出#{}
底层使用的是PreparedStatement
${}
:拼接SQL。底层使用的是Statement
,会存在SQL注入问题,如下图将映射配置文件中的#{}
替换为${}
来看下效果
<select id="selectById" resultMap="brandResultMap" parameterType="int">
select * from tb_brand where id = ${id}
</select>
2
3
重新运行查看结果如下:底层使用Statement
,采用直接将数据拼接到SQL语句中的方式
注意
从上面的例子可以看出,为了避免SQL注入漏洞,我们后面开发要使用#{}
参数占位符
# parameterType
的使用
对于有参数的Mapper
接口方法,我们在映射配置文件中应该配置parameterType
来指定参数类型,只不过该属性都可以省略。如下:
<select id="selectById" resultMap="brandResultMap" parameterType="int">
select * from tb_brand where id = #{id}
</select>
2
3
# SQL语句中特殊字符的处理
后续我们在开发时肯定会在SQL语句中写一些特殊字符,比如某一个字段大于某个值,如下图:
可以看到在Idea中提示错误了,因为映射配置文件是XML,而<
、&
这些字符在XML中有特殊含义,所以此时我们需要将这些符号进行转义,可以使用一下两种方式进行:
- 实体字符转义
字符实体 | 特殊字符 | 说明 |
---|---|---|
< | < | 小于 |
> | > | 大于 |
& | & | 并且 |
' | ' | 单引号 |
" | " | 双引号 |
<select id="selectById" resultMap="brandResultMap" parameterType="int">
select * from tb_brand where id < #{id}
</select>
2
3
<![CDATA[ 内容 ]]>
<select id="selectById" resultMap="brandResultMap" parameterType="int">
select * from tb_brand where id <![CDATA[ < ]]> #{id}
</select>
2
3
# 多条件查询
我们经常会遇到如上图所示的多条件查询,将多条件查询的结果展示在下方的数据列表中,因此我们做这个功能之前需要先分析SQL语句应该怎么写,思考两个问题:
- 有哪些条件表达式
- 条件表达式之间如何连接
条件字段企业名称
和品牌名称
需要进行模糊查询,所以条件应该是:
简单的分析后,我们来看实现功能的具体步骤:
- 编写接口方法
- 参数:所有插叙条件
- 结果:
List<Brand>
- 在映射配置文件中编写SQL语句
- 编写测试方法并执行
# 编写selectByCondition
接口方法
在BrandMapper
接口中定义多条件查询的方法,而该功能有是三个参数,因此我们就要考虑定义接口时,参数应该如何定义。MyBatis针对多参数传递有多种实现:
使用
@Param("参数名称")
标记每一个参数,在映射配置文件中就需要使用#{参数名称}
进行占位List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
1
2将多个参数封装为一个 实体对象 ,将该实体对象作为接口方法的参数,该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和实体类属性名保持一致List<Brand> selectByCondition(Brand brand);
1将多个参数封装到
Map
集合中,将Map集合作为接口方法的参数,该方式要求在映射配置文件的SQL中使用#{内容}
时,里面的内容必须和Map集合中键的名称一致List<Brand> selectByCondition(Map map);
1
# 编写selectByCondition
SQL语句
在BrandMapper.xml
映射配置文件中编写Statement
,使用resultMap
而不是resultType
<select id="selectByCondition" resultMap="brandResultMap">
select
*
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
2
3
4
5
6
7
8
# 编写testSelectByCondition
测试方法
在test/java
下的com.itheima.test
包下的MyBatisTest
类中定义测试方法
/**
* @throws IOException
*/
@Test
public void testSelectByCondition() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 模拟接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
// 4. 执行方法
List<Brand> brands = brandMapper.selectByCondition(status, brandName, companyName);
System.out.println(brands);
// 5. 释放资源
session.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 动态SQL
上述功能实现存在很大问题,比如,用户在输入条件时,不一定所有的条件都填写,这个时候我们的SQL语句就不能那样写了
例如,用户只输入 当前状态 时,SQL语句就是
select * from tb_brand where status = #{status}
而用户如果只输入 企业名称 时,SQL语句就是
select * from tb_brand where company_name like #{companyName}
而用户如果输入了 当前状态 和 企业名称 时,SQL语句又不一样
select * from tb_brand where status = #{status} and company_name like #{companyName}
针对上述的需要,MyBatis对动态SQL有很强大的支撑:
- if
- choose(when, otherwise)
- trim(where, set)
- foreach
我们下面先学习if
标签和where
标签:
if
标签:条件判断test
属性:逻辑表达式
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand where <if test="status != null"> status = #{status} </if> <if test="brandName != null and brandName != ''"> and brand_name like #{brandName} </if> <if test="companyName != null and companyName != ''"> and company_name like #{companyName} </if> </select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15如上述的这种SQL语句就会根据传递的参数值进行动态的拼接,如果此时
status
和companyName
有值那么就会拼接这两个条件。执行结果如下:
但是它也存在问题,如果此时给的参数通过Map集合进行传递,且只传递
brandName
和companyName
:Map<String, Object> param = new HashMap<>(); // param.put("status", status); param.put("brandName", brandName); param.put("companyName", companyName); // 4. 执行方法 List<Brand> brands = brandMapper.selectByCondition(param);
1
2
3
4
5
6
7List<Brand> selectByCondition(Map<String, Object> param);
1执行测试方法时就提示错误了:
从图中可以看到,拼接的SQL语句就变成了:
select * from tb_brand where and brand_name like ? and company_name like ?
1而上面的语句中
where
关键字后面直接跟and
关键字,这就是一条错误的SQL语句。这个就可以使用where
标签解决where
标签- 作用:
- 替换
where
关键字 - 会动态的去掉第一个条件前的
and
- 如果所有的参数没有值则不加
where
关键字
- 替换
<select id="selectByCondition" resultMap="brandResultMap" parameterType="map"> select * from tb_brand <where> <if test="status != null"> and status = #{status} </if> <if test="brandName != null and brandName != ''"> and brand_name like #{brandName} </if> <if test="companyName != null and companyName != ''"> and company_name like #{companyName} </if> </where> </select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16- 作用:
注意
使用<where>
标签,需要给每个条件前都加上and
关键字
# 单条件查询(动态SQL)
如上图所示,在查询时只能选择 品牌名称、当前状态、企业名称 这三个条件中的一个,但是用户到底选择哪一个,我们不确定,这种就属于单个条件的动态SQL语句。
这样的需求我们就需要使用到choose (when, otherwise)
标签来实现,而choose
标签类似于Java中的switch
语句。
switch语句的格式:
switch(expression) {
case value1:
// 语句1
break;
case value2:
// 语句2
break;
// 可以有任意数量的case语句
default:
// 默认语句
}
2
3
4
5
6
7
8
9
10
11
switch case
语句有如下规则:
switch
语句中的变量类型可以是:byte
、short
、int
或char
。从Java SE 7
开始,switch
支持字符串String
类型,同时case
标签必须为字符串常量或字面量;switch
语句可以拥有多个case
语句,每个case
后面跟一个要比较的值和冒号;case
语句中的值的数据类型必须与变量的数据类型相同,而且只能是常量或者字面常量;- 当变量的值与
case
语句的值相等时,那么case
语句之后的语句开始执行,直到break
语句出现才会跳出switch
语句; - 当遇到
break
语句时,switch
语句终止。程序跳转到switch
语句后面的语句执行,case
语句不必须要包含break
语句,如果没有break
语句出现,程序会继续执行下一条case
语句,直到出现break
语句; switch
语句可以包含一个default
分支,该分支一般是switch
语句的最后一个分支(可以在任何位置,但建议在最后一个),default
在没有case
语句的值和变量值相等的时候执行,default
分支不需要break
语句。
注意
switch case
执行是,一定会先进行匹配,匹配成功返回当前case
的值,再根据是否有break
,判断是否继续输出,或者跳出switch
。
上面我们回顾了JavaSE中switch case
选择结构语句的书写,下面我们就通过上图的案例来学习使用MyBatis的choose (when, otherwise)
标签。
# 编写selectByConditionSingle
接口方法
在BrandMapper
接口中定义单条件查询的方法:
/**
* 单条件动态查询
* @param brand
* @return
*/
List<Brand> selectByConditionSingle(Brand brand);
2
3
4
5
6
# 编写selectByConditionSingle
SQL语句
在BrandMapper.xml
映射配置文件中编写Statement
,使用resultMap
而不是使用resultType
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
<where>
<!-- 相当于switch -->
<choose>
<when test="status != null">
status = #{status}
</when>
<when test="brandName != null and brandName != ''">
brand_name like #{brandName}
</when>
<when test="companyName != null and companyName != ''">
company_name like #{companyName}
</when>
<!--<otherwise>-->
<!--1 = 1-->
<!--</otherwise>-->
</choose>
</where>
</select>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 编写testSelectByConditionSingle
测试方法
在test/java
下的com.itheima.test
包下的MyBatisTest
类中定义测试方法
/**
* 单条件动态查询SQL
*/
@Test
public void testSelectByConditionSingle() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 模拟接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
// 封装Brand对象
Brand brand = new Brand();
// brand.setStatus(status);
// brand.setCompanyName(companyName);
brand.setBrandName(brandName);
// 4. 执行方法
List<Brand> brands = brandMapper.selectByConditionSingle(brand);
System.out.println(brands);
// 5. 释放资源
session.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
执行测试方法结果如下:
# 添加品牌数据
如上图是我们经常见到的添加数据时展示的页面,而我们在该页面输入想要的数据后添加 提交 按钮,就会将这些数据添加到数据库中,接下来我们就来实现添加数据的操作。
实现步骤:
编写接口方法
/** * 参数:除了id之外的所有数据, * id对应的是表中主键值,而主键我们设置的是自动增长 */ void add(Brand brand);
1
2
3
4
5编写SQL语句
<insert id="add"> insert into tb_brand(brand_name, company_name, ordered, description, status) values(#{brandName}, #{companyName}, #{ordered}, #{description}, #{status}) </insert>
1
2
3
4编写测试方法并执行
明确了该功能的实现步骤后,接下来我们就进行具体的编码操作。
# 编写add
接口方法
在BrandMapper.java
接口中定义添加方法
/**
* 添加品牌
* @param brand
*/
void add(Brand brand);
2
3
4
5
# 编写add
SQL语句
在BrandMapper.xml
映射配置文件中编写添加数据的Statement
<insert id="add">
insert into tb_brand(brand_name, company_name, ordered, description, status)
values(#{brandName}, #{companyName}, #{ordered}, #{description}, #{status})
</insert>
2
3
4
# 编写testAdd
测试方法
在test/java
下的com.itheima.test
包下的MyBatisTest
类中定义测试方法
/**
* 测试添加品牌
* @throws IOException
*/
@Test
public void testAdd() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 设置自动提交事务,这种情况不需要手动提交事务了
// SqlSession session = factory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 模拟接收参数
int status = 1;
String companyName = "波导手机";
String brandName = "波导";
String description = "手机中的战斗机";
int ordered = 100;
// 封装对象
Brand brand = new Brand();
brand.setBrandName(brandName);
brand.setCompanyName(companyName);
brand.setDescription(description);
brand.setOrdered(ordered);
brand.setStatus(status);
// 4. 执行添加方法
brandMapper.add(brand);
// 提交事务
session.commit();
// 5. 释放资源
session.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
执行结果如下:
# 添加-主键返回
在数据添加成功后,有时候需要获取插入数据库数据的主键值(主键是自增长)。
比如:添加订单和订单项,如下图就是京东上的订单
订单数据存储在订单表中,订单项存储在订单项表中。
- 添加订单数据
<insert id="addOrder" useGeneratedKeys="true" keyProperty="id">
insert into tb_order(payment, payment_type, status)
values(#{payment}, #{paymentType}, #{status})
</insert>
2
3
4
- 添加订单项数据,订单项中需要设置所属订单的id
<insert id="addOrderItem" useGeneratedKeys="true" keyProperty="id">
insert into tb_order_item(goods_name, goods_number, order_id)
values(#{goodsName}, #{goodsNumber}, #{orderId})
</insert>
2
3
4
知道了什么时候需要返回 主键,接下来我们就简单模拟一下,在添加完数据后打印品牌的id
属性值,能打印出来就说明我们获取到了插入数据库中数据的主键。
我们将上面添加品牌数据的案例中映射配置文件里的Statement
修改一下,如下:
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand(brand_name, company_name, ordered, description, status)
values(#{brandName}, #{companyName}, #{ordered}, #{description}, #{status})
</insert>
2
3
4
在`insert`标签上添加如下属性:
useGeneratedKeys
:是否获取自动增长的主键值,true
表使获取keyProperty
:指定获取到的主键值封装到哪个属性里面
# 修改品牌数据
如上图所示是修改页面,用户在该页面书写需要修改的数据,点击 提交 按钮,就会将数据库中对应的数据进行修改。注意一点,如果哪个输入框没有输入内容,我们是将表中对应的字段值替换为空白还是保留字段之前的值?答案是保留之前的数据。
接下来我们来实现一下
# 编写update
接口方法
在BrandMapper.java
接口中定义修改方法
/**
* 修改品牌
* @param brand
* @return
*/
int update(Brand brand);
2
3
4
5
6
提示
上述方法的参数Brand
就是封装了需要修改的数据,而id
肯定是有数据的,这也是和添加方法的区别。
# 编写update
SQL语句
在BrandMapper.xml
映射配置文件中编写修改数据的Statement
<update id="update">
update tb_brand
<set>
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
</if>
<if test="companyName != null and companyName != ''">
company_name = #{companyName},
</if>
<if test="ordered != null">
ordered = #{ordered},
</if>
<if test="description != null and description != ''">
description = #{description},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
where id = #{id}
</update>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
提示
set
标签可以用于动态包含需要更新的列,忽略其他不更新的列。
# 编写testUpdate
测试方法
在test/java
下的com.itheima.test
包下的MyBatisTest
类中定义测试方法
/**
* 测试修改品牌
* @throws IOException
*/
@Test
public void testUpdate() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 设置自动提交事务,这种情况不需要手动提交事务了
// SqlSession session = factory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 模拟接收参数
int id = 7;
int status = 0;
String companyName = "波导手机";
String brandName = "波导";
String description = "波导手机,手机中的战斗机";
int ordered = 500;
// 封装对象
Brand brand = new Brand();
brand.setId(id);
// brand.setBrandName(brandName);
// brand.setCompanyName(companyName);
// brand.setDescription(description);
brand.setOrdered(ordered);
// brand.setStatus(status);
// 4. 执行方法
int count = brandMapper.update(brand);
System.out.println(count);
// 提交事务
session.commit();
// 5. 释放资源
session.commit();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
执行测试结果如下:
从结果中SQL语句可以看出,只修改了ordered
字段值,因为我们给的数据中只给Brand
实体对象的ordered
属性设置了值,这就是set
标签的作用。
# 删除一行数据
如上图所示,每行数据后面都有一个 删除 按钮,当用户点击了该按钮,就会将该行数据删除掉。那我们就需要想一下,这种删除是根据什么条件进行删除呢?答案是通过主键id
删除,因为id
是表中数据的唯一标识。
接下来我们就来实现该功能。
# 编写deleteById
接口方法
在BrandMapper.java
接口中定义根据id
删除方法
/**
* 根据id删除品牌
* @param id
*/
void deleteById(int id);
2
3
4
5
# 编写deleteById
SQL语句
在BrandMapper.xml
映射配置文件中编写删除一行数据的Statement
<delete id="deleteById">
delete from tb_brand where id = #{id}
</delete>
2
3
# 编写testDeleteById
测试方法
在test/java
下的com.itheima.test
包下的MyBatisTest
类中定义测试方法
/**
* 测试根据id删除品牌
* @throws IOException
*/
@Test
public void testDeleteById() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 设置自动提交事务,这种情况不需要手动提交事务了
// SqlSession session = factory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 模拟接收参数
int id = 7;
// 4. 执行方法
brandMapper.deleteById(id);
// 提交事务
session.commit();
// 5. 释放资源
session.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
运行过程只要没报错,直接到数据库中查询被删除的数据是否还存在。
# 批量删除
如上图所示,用户可以选择多条数据,然后点击上面的 删除 按钮,就会删除数据库中对应的多行数据。
# 编写deleteByIds
接口方法
在BrandMapper.java
接口中定义删除多行数据的方法
/**
* 批量删除品牌
* @param ids
*/
void deleteByIds(int[] ids);
2
3
4
5
注意
参数是一个数组,数组中存储的是多条数据的id
# 编写deleteByIds
SQL语句
在BrandMapper.xml
映射配置文件中编写删除多条数据的Statement
编写SQL时需要遍历数组来拼接SQL语句,MyBatis提供了foreach
标签供我们使用
foreach标签:用来迭代任何可迭代的对象(如数组,集合)。
collection
属性:- MyBatis会将数组参数,封装为一个Map集合
- 默认:
array
= 数组 - 使用
@Param
注解改变map
集合的默认key
的名称
- 默认:
- MyBatis会将数组参数,封装为一个Map集合
item
属性:本次迭代获取到的元素separator
属性:集合项迭代之间的分隔符,foreach
标签不会错误地添加多余的分隔符,也就是最后一次迭代不会加分隔符。open
属性:该属性值在拼接SQL语句之前拼接的内容,只会拼接一次close
属性:该属性值是在拼接SQL语句拼接后拼接的内容,只会拼接一次
<delete id="deleteByIds">
delete from tb_brand where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
2
3
4
5
6
提示
假如数组中的id
数据是{1, 2, 3}
,那么拼接后的SQL语句就是:
delete from tb_brand where id in (1, 2, 3);
# 编写deleteByIds
测试方法
在test/java
下的com.itheima.test
包下的MyBatisTest
类中定义测试方法
/**
* 测试批量删除
* @throws IOException
*/
@Test
public void testDeleteByIds() throws IOException {
// 1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 2. 获取SqlSession对象
SqlSession session = factory.openSession();
// 设置自动提交事务,这种情况不需要手动提交事务了
// SqlSession session = factory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper brandMapper = session.getMapper(BrandMapper.class);
// 模拟接收参数
int[] ids = {2, 3, 6};
// 4. 执行方法
brandMapper.deleteByIds(ids);
// 提交事务
session.commit();
// 5. 释放资源
session.commit();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# MyBatis参数传递
MyBatis接口方法中可以接收各种各样的参数,如下:
- 多个参数
- 单个参数:单个参数又可以是如下类型
POJO
类型Map
集合类型Collection
集合类型List
集合类型Array
类型- 其他类型
# 传递多个参数
如下面的代码,就是接收两个参数,而接收多个参数需要使用@Param
注解,那么为什么要加该注解呢?这个问题想要弄明白必须要研究MyBatis底层对于这些参数是如何处理的。
User select(@Param("username") String username, @Param("password") String password);
<select id="select" resultMap="userResultMap">
select *
from tb_user
where username = #{username} and password = #{password}
</select>
2
3
4
5
我们在接口方法中定义多个参数,MyBatis会将这些参数封装成Map
集合对象,值就是参数值,而键在没有使用@Param
注解时有一下命名规则:
以
arg
开头:第一个参数就是arg0
,第二个参数是arg1
,以此类推。如map.put("arg0", 参数值1); map.put("arg1", 参数值2);
1
2以
param
开头:第一个参数就叫param1
,第二个参数就叫param2
,依次类推。如:map.put("param1", 参数值1); map.put("param2", 参数值2);
1
2
源码位置:org.apache.ibatis.reflection.ParamNameResolver
代码验证:
在
UserMapper
接口中定义如下方法User select(String username,String password);
1在
UserMapper.xml
映射配置文件中定义SQL<select id="select" resultMap="userResultMap"> select * from tb_user where username = #{arg0} and password = #{arg1} </select>
1
2
3
4
5或者
<select id="select" resultMap="userResultMap"> select * from tb_user where username = #{param1} and password = #{param2} </select>
1
2
3
4
5运行代码结果如下:
在映射配置文件的SQL语句中使用arg
开头的和param
书写,代码的可读性会变得特别差,此时可以使用@Param
注解。
在接口方法参数上使用@Param
注解,MyBatis会将arg
开头的键名替换为对应注解的属性值。
代码验证:
在
UserMapper
接口中定义如下方法,在username
参数前加上@Param
注解:User select(@Param("username") String username, String password);
1MyBatis在封装
Map
集合时,键名就会编程如下:map.put("username", 参数值1); map.put("arg1", 参数值2); map.put("param1", 参数值1); map.put("param2", 参数值2);
1
2
3
4在
UserMapper.xml
映射配置文件中定义SQL<select id="select" resultMap="userResultMap"> select * from tb_user where username = #{username} and password = #{param2} </select>
1
2
3
4
5运行程序结果输出正常,没有报错,而如果将
#{username}
还写成#{arg0}
<select id="select" resultMap="userResultMap"> select * from tb_user where username = #{arg0} and password = #{param2} </select>
1
2
3
4
5运行程序则可以看到错误
结论
以后接口参数是多个时,在每个参数上都使用@Param
注解,这样代码的可读性更高。
# 传递单个参数
POJO
类型直接使用,要求属性名和参数占位符名称一致
Map
集合类型直接使用,要求Map集合的键名和参数占位符名称一致
Collection
集合类型MyBatis会将集合封装到
map
集合中,如下:map.put("arg0", collection集合); map.put("collection", collection集合);
1
2可以使用
@Param
注解替换map
集合中默认的arg
键名。List
集合类型MyBatis会将集合封装到
map
集合中,如下:map.put("arg0", list集合); map.put("collection", list集合); map.put("list", list集合);
1
2
3可以使用
@Param
注解替换map
集合中默认的arg
键名。Array
类型MyBatis会将集合封装到
map
集合中,如下:map.put("arg0", 数组); map.put("array", 数组);
1
2可以使用
@Param
注解替换map
集合中默认的arg
键名。其他类型
比如
int
类型,参数占位符名称 叫什么都可以,尽量做到见名知意。
建议
将来都使用@Param
注解来修改Map
集合中默认的键名,并使用修改后的名称来获取值,这样可读性更高。
- MyBatis 概述
- JDBC 缺点
- MyBatis 优化
- MyBatis 概念
- MyBatis 快速入门
- Mapper 代理开发
- Mapper 代理开发概述
- 使用 Mapper 代理开发
- 核心配置文件
- 多环境配置
- 类型别名
- 注解开发
- MyBatis 练习
- 环境、数据准备
- 查询所有数据
- 创建BrandMapper.java接口类,编写接口方法
- 创建 BrandMapper.xml 文件,编写 SQL 语句
- 在模块test/java目录下,新建com.itheima.test包,在该包下创建测试类MyBatisTest类
- 查询所有数据小结
- 查询单个品牌详情
- 在BrandMapper.java接口类中编写接口方法
- 在BrandMapper.xml映射文件中编写Statement和SQL语句
- 编写测试方法testSelectById
- 参数占位符#{id}
- parameterType的使用
- SQL语句中特殊字符的处理
- 多条件查询
- 编写selectByCondition接口方法
- 编写selectByConditionSQL语句
- 编写testSelectByCondition测试方法
- 动态SQL
- 单条件查询(动态SQL)
- 编写selectByConditionSingle接口方法
- 编写selectByConditionSingleSQL语句
- 编写testSelectByConditionSingle测试方法
- 添加品牌数据
- 编写add接口方法
- 编写addSQL语句
- 编写testAdd测试方法
- 添加-主键返回
- 修改品牌数据
- 编写update接口方法
- 编写updateSQL语句
- 编写testUpdate测试方法
- 删除一行数据
- 编写deleteById接口方法
- 编写deleteByIdSQL语句
- 编写testDeleteById测试方法
- 批量删除
- 编写deleteByIds接口方法
- 编写deleteByIdsSQL语句
- 编写deleteByIds测试方法
- MyBatis参数传递
- 传递多个参数
- 传递单个参数