引言
MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
在学习MySQL数据库时,都是利用图形化客户端工具(如:idea、datagrip),来操作数据库的。
在客户端工具中,编写增删改查的SQL语句,发给MySQL数据库管理系统,由数据库管理系统执行SQL语句并返回执行结果。
增删改操作:返回受影响行数
查询操作:返回结果集(查询的结果)
做为后端程序开发人员,通常会使用Java程序来完成对数据库的操作。Java程序操作数据库,现在主流的方式是:Mybatis。
1. 什么是MyBatis
- MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发。
- MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
- 官网:https://mybatis.org/mybatis-3/zh/index.html
- 持久层:指的是就是数据访问层(dao),是用来操作数据库的。
2. JDBC介绍
虽然说通过Mybatis可以很方便的进行数据库的访问操作。但是大家要明白,其实java语言操作数据库呢,只能通过一种方式:使用sun公司提供的 JDBC 规范。而Mybatis框架,就是对原始的JDBC程序的封装。
2.1 介绍
JDBC: ( Java DataBase Connectivity ),就是使用Java语言操作关系型数据库的一套API。用于连接和执行查询以访问不同类型的数据库。JDBC 提供了一个标准库,用于建立数据库连接、执行 SQL 语句以及处理查询结果。它作为 Java 应用程序和各种关系数据库之间的桥梁,使得数据库操作可以在 Java 中以一种平台无关的方式进行。
2.1.1 主要组件
- 驱动程序(Driver): 数据库厂商提供的 JDBC 驱动允许 Java 应用程序与数据库建立连接。
- Connection: 一旦驱动程序加载,程序员可以利用 Connection 对象与数据库建立连接。
- Statement: Statement 对象用于执行 SQL 命令。
- ResultSet: 执行查询后,结果保存在 ResultSet 对象中。
2.1.2 基本流程
- 加载数据库驱动
- 建立到数据库的连接
- 创建一个 Statement 或 PreparedStatement 对象
- 执行 SQL 查询或更新
- 处理结果(如果有)
- 关闭连接和释放资源
2.1.3 示例代码
以下是使用 JDBC 连接 MySQL 数据库的 Java 示例代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcExample {
public static void main(String[] args) {
try {
// 第一步:加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 第二步:建立数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testDB", "用户名", "密码");
// 第三步:创建 Statement 对象
Statement stmt = conn.createStatement();
// 第四步:执行 SQL 查询
ResultSet rs = stmt.executeQuery("SELECT * FROM employees");
// 第五步:处理查询结果
while (rs.next()) {
System.out.println(rs.getString("name") + ", " + rs.getInt("age"));
}
// 第六步:关闭资源
rs.close();
stmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
DriverManager(类):数据库驱动管理类。
- 作用:
- 注册驱动
- 创建java代码和数据库之间的连接,即获取Connection对象
Connection(接口):建立数据库连接的对象
- 作用:用于建立java程序和数据库之间的连接
Statement(接口): 数据库操作对象(执行SQL语句的对象)。
- 作用:用于向数据库发送sql语句
ResultSet(接口):结果集对象(一张虚拟表)
- 作用:sql查询语句的执行结果会封装在ResultSet中
通过上述代码,我们看到直接基于JDBC程序来操作数据库,代码实现非常繁琐,所以在项目开发中,我们很少使用。 在项目开发中,通常会使用Mybatis这类的高级技术来操作数据库,从而简化数据库操作、提高开发效率。
2.1.4 问题分析
原始的JDBC程序,存在以下几点问题:
- 数据库链接的四要素(驱动、链接、用户名、密码)全部硬编码在java代码中
- 查询结果的解析及封装非常繁琐
- 每一次查询数据库都需要获取连接,操作完毕后释放连接, 资源浪费, 性能降低
2.1.5 技术对比
分析在mybatis中,是如何解决这些问题的:
- 数据库连接四要素(驱动、链接、用户名、密码),都配置在springboot默认的配置文件 application.properties中
- 查询结果的解析及封装,由mybatis自动完成映射封装,我们无需关注
- 在mybatis中使用了数据库连接池技术,从而避免了频繁的创建连接、销毁连接而带来的资源浪费。
而对于Mybatis来说,我们在开发持久层程序操作数据库时,需要重点关注以下两个方面:
- application.properties
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=1234
- Mapper接口(编写SQL语句)
@Mapper
public interface UserMapper {
@Select("select id, name, age, gender, phone from user")
public List<User> list();
}
3. 数据库连接池
3.1 介绍
数据库连接池是个容器,负责分配、管理数据库连接(Connection)
没有使用数据库连接池:
- 客户端执行SQL语句:要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行后又需要关闭连接对象从而释放资源,每次执行SQL时都需要创建连接、销毁链接,这种频繁的重复创建销毁的过程是比较耗费计算机的性能。
使用了数据库连接池
- 程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象
允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个- 客户端在执行SQL时,先从连接池中获取一个Connection对象,然后在执行SQL语句,SQL语句执行完之后,释放Connection时就会把Connection对象归还给连接池(Connection对象可以复用)释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
- 客户端获取到Connection对象了,但是Connection对象并没有去访问数据库(处于空闲),数据库连接池发现Connection对象的空闲时间 > 连接池中预设的最大空闲时间,此时数据库连接池就会自动释放掉这个连接对象
3.2 常用数据库连接池
- C3P0
- Druid(德鲁伊)
- Hikari(追光者) [默认的连接池]
3.3 使用示例(Hikari)
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class HikariCPExample {
public static void main(String[] args) {
// 配置 HikariCP
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/testDB");
config.setUsername("用户名");
config.setPassword("密码");
// 创建连接池
HikariDataSource ds = new HikariDataSource(config);
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 从连接池中获取连接
conn = ds.getConnection();
// 创建 Statement 对象
stmt = conn.createStatement();
// 执行 SQL 查询
rs = stmt.executeQuery("SELECT * FROM employees");
// 处理查询结果
while (rs.next()) {
System.out.println(rs.getString("name") + ", " + rs.getInt("age"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 关闭连接池
ds.close();
}
}
4. Lombok
4.1 定义和目的
Lombok 是一个 Java 库,旨在通过注解(Annotations)减少 Java 代码的样板代码(Boilerplate Code)。Lombok 可以帮助开发者自动生成诸如 getter、setter、equals、hashCode 和 toString 方法等常用代码。这样不仅减少了代码量,还提高了代码的可读性和维护性。
4.2 常用注解
注解 | 作用 |
---|---|
@Getter/@Setter | 为所有的属性提供get/set方法 |
@ToString | 会给类自动生成易阅读的 toString 方法 |
@EqualsAndHashCode | 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法 |
@Data | 提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode) |
@NoArgsConstructor | 为实体类生成无参的构造器方法 |
@AllArgsConstructor | 为实体类生成除了static修饰的字段之外带有各参数的构造器方法 |
4.3 使用
第1步:在pom.xml文件中引入依赖
<!-- 在springboot的父工程中,已经集成了lombok并指定了版本号,故当前引入依赖时不需要指定version -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
第2步:在实体类上添加注解
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private Short age;
private Short gender;
private String phone;
}
在实体类上添加了@Data注解,那么这个类在编译时期,就会生成getter/setter、equals、hashcode、toString等方法。
说明:@Data注解中不包含全参构造方法,通常在实体类上,还会添加上:全参构造、无参构造
import lombok.Data;
@Data //getter方法、setter方法、toString方法、hashCode方法、equals方法
@NoArgsConstructor //无参构造
@AllArgsConstructor//全参构造
public class User {
private Integer id;
private String name;
private Short age;
private Short gender;
private String phone;
}
Lombok的注意事项:
- Lombok会在编译时,会自动生成对应的java代码
- 在使用lombok时,还需要安装一个lombok的插件(新版本的IDEA中自带)
5. Mybatis基础操作
MyBatis 是一个开源的 SQL 映射框架,它允许你直接使用 SQL 语句,而不需要像 JPA 那样使用 HQL 或 JPQL。MyBatis 可以与 Java 对象模型 (POJOs) 直接映射 SQL 数据库记录,为开发者提供更多的灵活性。
5.1 查询
查询是数据库交互中非常常见的一种操作。在 MyBatis 中,我们可以通过多种方式来进行查询。
5.1.1 根据主键 ID 查询
XML 配置
在 XML 映射文件中,你可以像下面这样定义一个根据 ID 查询的 SQL 语句:
<select id="getEmployeeById" parameterType="int" resultType="com.example.Employee">
SELECT * FROM employee WHERE id = #{id}
</select>
Java 代码
在 Java 代码中,你可以这样使用:
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmployeeById(1);
System.out.println(employee);
}
5.1.2 条件查询
XML 配置
条件查询通常更复杂,但也可以通过 MyBatis 的 XML 配置来实现:
<select id="findEmployees" resultType="com.example.Employee">
SELECT * FROM employee
<where>
<if test="name != null">
name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
Java 代码
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.findEmployees("John", 30);
for (Employee employee : employees) {
System.out.println(employee);
}
}
5.2 新增
XML 配置
<insert id="insertEmployee" parameterType="com.example.Employee">
INSERT INTO employee (name, age) VALUES (#{name}, #{age})
</insert>
Java 代码
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee newEmployee = new Employee("John", 30);
mapper.insertEmployee(newEmployee);
session.commit();
}
5.3 更新
XML 配置
<update id="updateEmployee" parameterType="com.example.Employee">
UPDATE employee SET name = #{name}, age = #{age} WHERE id = #{id}
</update>
Java 代码
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee existingEmployee = new Employee(1, "Jane", 25);
mapper.updateEmployee(existingEmployee);
session.commit();
}
5.4 删除
删除操作通常需要小心进行,以避免误删数据。
5.4.1 根据主键 ID 删除
XML 配置
<delete id="deleteById" parameterType="int">
DELETE FROM employee WHERE id = #{id}
</delete>
Java 代码
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
mapper.deleteById(1);
session.commit();
}
5.4.2 根据主键 ID 批量删除
XML 配置
<delete id="deleteByIds" parameterType="list">
DELETE FROM employee WHERE id IN
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</delete>
Java 代码
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Integer> ids = Arrays.asList(1, 2, 3);
mapper.deleteByIds(ids);
session.commit();
}
6. Mybatis的XML配置文件
6.1 XML配置文件规范
使用Mybatis的注解方式,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中。
在Mybatis中使用XML映射文件方式开发,需要符合一定的规范:
- XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
- XML映射文件的namespace属性为Mapper接口全限定名一致
- XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。
<select>标签:就是用于编写select查询语句的。
- resultType属性,指的是查询返回的单条记录所封装的类型。
6.2 XML配置文件实现
第1步:创建XML映射文件
第2步:编写XML映射文件
xml映射文件中的dtd约束,直接从mybatis官网复制即可
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
</mapper>
配置:XML映射文件的namespace属性为Mapper接口全限定名
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
</mapper>
配置:XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
<!--查询操作-->
<select id="list" resultType="com.itheima.pojo.Emp">
select * from emp
where name like concat('%',#{name},'%')
and gender = #{gender}
and entrydate between #{begin} and #{end}
order by update_time desc
</select>
</mapper>
6.3 MybatisX的使用
MybatisX是一款基于IDEA的快速开发Mybatis的插件,为效率而生。
MybatisX的安装:
可以通过MybatisX快速定位:
7. Mybatis动态SQL
动态 SQL 是 MyBatis 非常强大的一个特性,它允许你在 SQL 语句中使用动态元素,从而实现更灵活的 SQL 查询。
7.1 动态 SQL - if 元素
在 MyBatis 中,if 元素是用于构造条件 SQL 语句的一种常用动态 SQL 元素。通过使用 if 元素,你可以根据特定条件来动态地添加或修改 SQL 语句,这样可以使得你的 SQL 语句更加灵活和可维护。
7.1.1 基本用法
if 元素允许你根据条件来决定是否需要添加某个 SQL 片段。
XML 配置示例
以下面这个用于搜索员工的 SQL 语句为例:
<select id="findEmployees" resultType="com.example.Employee">
SELECT * FROM employee
<where>
<!-- 如果 name 不为 null,则添加 "AND name = #{name}" 这个条件 -->
<if test="name != null">
AND name = #{name}
</if>
<!-- 如果 age 不为 null,则添加 "AND age = #{age}" 这个条件 -->
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
Java 代码示例
Map<String, Object> params = new HashMap<>();
params.put("name", "John");
params.put("age", null);
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.findEmployees(params);
for (Employee employee : employees) {
System.out.println(employee);
}
}
在这个例子中,如果 name 和 age 都不为 null,则生成的 SQL 语句会同时包括这两个条件。如果其中任何一个为 null,对应的条件则不会被包括在内。
7.1.2 高级用法
也可以将多个 if 元素组合在一起,以构造更复杂的动态 SQL。
XML 配置示例
<select id="findEmployees" resultType="com.example.Employee">
SELECT * FROM employee
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null and age >= 18">
AND age = #{age}
</if>
<if test="department != null">
AND department = #{department}
</if>
</where>
</select>
这里,我们添加了更多的 if 条件,并且每个条件都更加详细。例如,对于 age,除了判断是否为 null,还额外判断了其是否大于或等于 18。
7.2 动态SQL-foreach
在 MyBatis 中,foreach 是一个非常有用的动态 SQL 元素,用于迭代一个集合,并在每一次迭代中都执行某种操作。这个元素特别适用于执行 IN 查询或批量操作。
7.2.1 基本用法
foreach 元素主要用于生成 SQL 语句中的 IN 子句。
XML 配置示例
假设我们有一个需求:根据一组员工 ID 查找所有符合条件的员工记录。
<select id="findEmployeesByIds" resultType="com.example.Employee">
SELECT * FROM employee WHERE id IN
<foreach item="id" index="index" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</select>
在这个例子中,foreach 元素会遍历名为 ids 的集合,并将集合中的每一个元素插入到 SQL 语句中。
Java 代码示例
List<Integer> employeeIds = Arrays.asList(1, 2, 3);
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.findEmployeesByIds(employeeIds);
for (Employee employee : employees) {
System.out.println(employee);
}
}
7.2.2 高级用法
foreach 元素不仅可以用于 IN 查询,还可以用于批量的 INSERT、UPDATE 或 DELETE 操作。
批量插入
<insert id="insertEmployees">
INSERT INTO employee (name, age) VALUES
<foreach item="employee" index="index" collection="employeeList" separator=",">
(#{employee.name}, #{employee.age})
</foreach>
</insert>
Java 代码示例
List<Employee> newEmployees = Arrays.asList(
new Employee("John", 25),
new Employee("Jane", 30),
new Employee("Doe", 35)
);
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
mapper.insertEmployees(newEmployees);
session.commit();
}
7.3 动态SQL-sql&include
在 MyBatis 中,sql 和 include 元素用于复用 SQL 代码片段,这样可以减少冗余代码并提高可维护性。通过定义一个可复用的 sql 元素并在需要的地方使用 include 元素来插入这个 SQL 片段,你可以让你的 MyBatis 映射文件更加整洁。
7.3.1 sql 元素
sql 元素用于定义一个可复用的 SQL 代码片段。
XML 配置示例
<sql id="employeeColumns">
id, name, age, department
</sql>
在这个例子中,我们定义了一个名为 employeeColumns 的 sql 元素,这个元素包含了 employee 表中的列名。
7.3.2 include 元素
include 元素用于插入一个已定义的 sql 元素。
XML 配置示例
<select id="findEmployees" resultType="com.example.Employee">
SELECT
<include refid="employeeColumns"/>
FROM employee
</select>
在这个例子中,include 元素通过 refid 属性引用了前面定义的 employeeColumns SQL 片段。
Java 代码示例
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.findEmployees();
for (Employee employee : employees) {
System.out.println(employee);
}
}
7.3.3 高级用法
你还可以在 sql 元素中使用动态 SQL。
XML 配置示例
<sql id="dynamicWhere">
<where>
<if test="name != null">
name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</sql>
<select id="findEmployeesByCondition" resultType="com.example.Employee">
SELECT
<include refid="employeeColumns"/>
FROM employee
<include refid="dynamicWhere"/>
</select>
这样,你可以在多个查询中复用相同的 WHERE 子句,而不需要每次都重新编写这些逻辑。
7.3.4 注意事项
- 使用 sql 和 include 元素可以提高 SQL 代码的复用性,但也可能导致映射文件变得更加复杂。因此,在使用这些元素时,务必保持代码的整洁和可维护性。
- 和其他所有 SQL 元素一样,注意防止 SQL 注入的风险。