一、JDBC入门
1.1 JDBC的设计
JDBC(Java Database Connectivity)是Java提供的一套标准API,用于与关系型数据库进行交互。它的设计遵循了"面向接口编程"的思想,通过定义一系列接口(如Connection、Statement、ResultSet),让不同数据库厂商提供各自的实现(驱动程序)。
这种设计的精妙之处在于:解耦了应用代码与具体数据库的依赖。你可以在MySQL、Oracle、PostgreSQL之间自由切换,只需要更换驱动和连接字符串,而不需要修改核心业务代码。
1.2 JDBC的组件
| 组件 | 职责定位 | 生命周期 | 资源管理 |
|---|---|---|---|
| DriverManager | 驱动管理器,负责加载数据库驱动 | 应用级别,全局单例 | 自动管理,无需手动释放 |
| Connection | 数据库连接对象,代表与数据库的会话 | 请求级别,用完即关 | 必须手动关闭,否则连接泄漏 |
| Statement | SQL语句执行器,用于发送SQL到数据库 | 临时对象,执行完即关 | 必须手动关闭,释放数据库资源 |
| ResultSet | 结果集对象,封装查询返回的数据 | 临时对象,遍历完即关 | 必须手动关闭,释放游标资源 |
1.3 JDBC入门程序
先创建一个Maven项目,在 pom.xml 中引入以下依赖:
xml
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
创建一个test下的测试类来建立JDBC第一个程序:
java
package com.yuki.jdbc;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
@SpringBootTest
class JdbcApplicationTests {
@Test
void jdbc_test() throws ClassNotFoundException, SQLException {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库连接
String url="jdbc:mysql://localhost:3306/scott_data";
String username="root";
String password="Dairenwen1092";
Connection connection= DriverManager.getConnection(url,username,password);
//3.获取sql语句执行对象
Statement statement= connection.createStatement();
//4.执行sql语句
int i=statement.executeUpdate("update emp set EMPNO=7904 where ENAME='FORD'");
System.out.println("SQL语句执行完毕,影响的记录数为:"+i);
//5.释放资源
statement.close();
connection.close();
}
}
上面的executeUpdate为更改语句,常见的是执行查询语句,并使用try-with-resources这样可以省去释放连接的步骤:
java
@Test
public void testSelect() {
String sql = "SELECT id, username, password, name, age FROM user WHERE username = ? AND password = ?";//采用预编译SQL语句
String url="jdbc:mysql://localhost:3306/scott_data";
String username="root";
String password="Dairenwen1092";
// try-with-resources:自动关闭实现AutoCloseable的资源,无需手动finally关闭
try (
Connection connection= DriverManager.getConnection(url,username,password);
PreparedStatement pstmt = connection.prepareStatement(sql)
) {
// 设置查询参数,防SQL注入
pstmt.setString(1, "daqiao");
pstmt.setString(2, "123456");
// 结果集也放入try-with-resources自动管理
try (ResultSet rs = pstmt.executeQuery())//结果集对象
{
while (rs.next()) {//将光标从当前位置向前移动一行,并判断当前是否为有效行,返回boolean
User user = new User(
rs.getInt("id"),
rs.getString("username"),
rs.getString("password"),
rs.getString("name"),
rs.getInt("age")
);
System.out.println(user);
}
}
System.out.println("查询执行完成");
} catch (SQLException e) {
e.printStackTrace();
// 可抛出业务自定义异常,统一异常处理
throw new RuntimeException("数据库查询失败", e);
}
}
@Data
public static class User {
private Integer id;
private String username;
private String password;
private String name;
private Integer age;
public User(Integer id, String username, String password, String name, Integer age) {
this.id = id;
this.username = username;
this.password = password;
this.name = name;
this.age = age;
}
}
为什么预编译的SQL语句比一般的静态SQL语句要好呢?原因如下:
-
性能更高
预编译 SQL 会在第一次执行时被数据库编译、优化并缓存,后续执行相同结构的 SQL 时,可直接复用缓存的执行计划,避免了重复的语法解析和编译开销。在批量执行或频繁执行相似查询的场景下(如循环插入、分页查询),性能提升尤为显著。
-
防止 SQL 注入
这是预编译 SQL 最重要的安全特性。它通过占位符(?) 传递参数,参数值会被数据库当作纯数据处理,而非 SQL 语法的一部分。即使用户输入包含恶意 SQL 片段(如
' OR '1'='1),也不会被执行,从根本上杜绝了 SQL 注入攻击。
二、MyBatis入门
4.1 MyBatis的诞生背景与设计理念
在理解MyBatis之前,需要思考一个问题:为什么有了JDBC还需要MyBatis?
MyBatis解决了JDBC中几个缺点:
- 重复代码泛滥:每次数据库操作都要写连接、关闭、异常处理
- SQL与Java代码耦合:SQL语句硬编码在Java代码中,难以维护
- 结果集映射繁琐:手动从ResultSet中取值并封装到对象
- 参数设置麻烦:需要记住参数索引,容易出错
- 缺乏缓存机制:每次查询都要访问数据库
4.2 MyBatis核心概念
| 核心组件 | 作用说明 | 配置方式 | 生命周期 |
|---|---|---|---|
| SqlSessionFactory | 会话工厂,创建SqlSession | 全局配置文件 | 应用级别,单例 |
| SqlSession | 执行SQL的会话对象 | 通过工厂创建 | 请求级别,用完即关 |
| Mapper接口 | 定义数据库操作方法 | 接口+注解或XML | 无状态,可复用 |
| Mapper XML | 配置SQL语句和映射规则 | XML文件 | 启动时加载 |
| Configuration | 全局配置对象 | mybatis-config.xml | 应用级别 |
4.3 MyBatis入门程序
第一步:添加Maven依赖

xml
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
第二步:修改application.properties配置文件
xml
spring.application.name=Mybatis
# 数据源配置 (DataSource)
# 数据库驱动 (MySQL 8.0+ 用这个,5.x 用 com.mysql.jdbc.Driver)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接地址 (请修改为你的数据库名,这里假设叫 db_test)
spring.datasource.url=jdbc:mysql://localhost:3306/scott_data
# 数据库账号
spring.datasource.username=root
# 数据库密码
spring.datasource.password=Dairenwen1092
#配置mybatis的日志输出
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
第三步:创建实体类(POJO)
java
package com.yuki.mybatis.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private LocalDate hiredate;
private BigDecimal sal;
private BigDecimal comm;
private Integer deptno;
}
第四步:创建Mapper接口
java
package com.yuki.mybatis.mapper;
import com.yuki.mybatis.pojo.Emp;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper // 运行时自动创建接口的代理实现类,交给Spring IOC容器管理
public interface EmpMapper {
/**
* 查询所有员工信息
*/
@Select("select * from emp")
public List<Emp> findAll();//mybatis会自动将查询结果封装成Emp对象
}
第五步:创建Mapper XML映射文件
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">
<!-- UserMapper.xml -->
<!-- namespace必须是Mapper接口的全限定名 -->
<mapper namespace="com.example.mapper.UserMapper">
<!--
resultMap:结果映射配置
作用:定义数据库字段与Java对象属性的映射关系
优势:
1. 可以处理复杂的映射关系
2. 可以复用,避免重复配置
3. 支持关联查询和嵌套查询
-->
<resultMap id="BaseResultMap" type="User">
<!-- id标签:映射主键字段 -->
<id column="id" property="id" jdbcType="BIGINT"/>
<!-- result标签:映射普通字段 -->
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<result column="email" property="email" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR"/>
<result column="status" property="status" jdbcType="INTEGER"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<!--
sql片段:可复用的SQL代码块
使用<include>标签引用
-->
<sql id="Base_Column_List">
id, username, password, email, phone, status, create_time, update_time
</sql>
<!--
select标签:查询语句
id:对应Mapper接口的方法名
resultMap:使用定义好的resultMap
parameterType:参数类型(可省略,MyBatis会自动推断)
-->
<select id="selectById" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM users
WHERE id = #{id}
</select>
<!--
#{参数名}:预编译参数占位符,会被替换为?
优点:防止SQL注入,自动类型转换
-->
<select id="selectByUsername" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM users
WHERE username = #{username}
</select>
<!--
resultType:直接指定返回类型(简单映射)
当字段名与属性名一致(或开启驼峰映射)时可以使用
-->
<select id="selectAll" resultType="User">
SELECT
<include refid="Base_Column_List"/>
FROM users
ORDER BY create_time DESC
</select>
<select id="selectByStatus" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM users
WHERE status = #{status}
ORDER BY create_time DESC
</select>
<!--
动态SQL:根据条件动态生成SQL
where标签:自动处理AND/OR,去除多余的AND/OR
if标签:条件判断
-->
<select id="selectByConditions" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM users
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!--
insert标签:插入语句
useGeneratedKeys:使用数据库生成的主键
keyProperty:将生成的主键值设置到对象的哪个属性
keyColumn:数据库主键列名
-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
INSERT INTO users (
username,
password,
email,
phone,
status,
create_time,
update_time
) VALUES (
#{username},
#{password},
#{email},
#{phone},
#{status},
NOW(),
NOW()
)
</insert>
<!--
update标签:更新语句
set标签:自动处理逗号,去除多余的逗号
动态更新:只更新不为null的字段
-->
<update id="update">
UPDATE users
<set>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="email != null">email = #{email},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="status != null">status = #{status},</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
<!-- delete标签:删除语句 -->
<delete id="deleteById">
DELETE FROM users
WHERE id = #{id}
</delete>
<!-- 统计查询 -->
<select id="count" resultType="int">
SELECT COUNT(*) FROM users
</select>
</mapper>
第六步:测试代码
java
package com.yuki.mybatis;
import com.yuki.mybatis.mapper.EmpMapper;
import com.yuki.mybatis.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest//会在单元测试运行时,加载springboot环境
class MybatisApplicationTests {
@Autowired
private EmpMapper emp;
@Test
void testmapper()
{
List<Emp> emplist=emp.findAll();
emplist.forEach(System.out::println);
}
}
4.4 MyBatis数据库连接池
mybatis底层内置了一个数据库连接池,其工作流程可分为以下几个关键环节:
1. 连接池初始化
应用启动时,连接池会根据配置参数(如 initialSize)预先创建一定数量的数据库连接,并将这些连接放入"空闲连接队列"中等待使用。此时连接并未与数据库断开,而是保持"待命"状态。
2. 连接获取
当应用需要执行数据库操作时,会向连接池"申请"连接:
- 优先复用空闲连接:连接池先检查"空闲连接队列",如果有可用的空闲连接,直接取出并标记为"活跃连接",交给应用使用。
- 创建新连接 :如果没有空闲连接,且当前活跃连接数未达到"最大活跃连接数"(如
maxActive),则创建一个新的物理连接并标记为活跃。 - 等待或超时 :如果活跃连接数已达上限,应用会进入等待状态(可配置
maxWait等待时间),直到有连接被归还或等待超时抛出异常。
3. 连接使用与归还
应用拿到连接后,执行 SQL 操作(查询、更新等)。操作完成后,不会真正关闭物理连接 ,而是调用 connection.close() 将连接"归还"给连接池:
- 连接从"活跃连接队列"移除,重新放回"空闲连接队列"。
- 连接会被重置(如清除事务状态、自动提交设置等),确保下一次复用时的状态一致性。
4. 连接池维护
连接池会定期对连接进行检查和管理:
- 空闲连接清理 :如果空闲连接数超过最大空闲连接数(
maxIdle),或空闲时间超过"最大空闲时间"(minEvictableIdleTimeMillis),连接池会关闭这些多余的物理连接,释放资源。 - 连接有效性检查 :通过"心跳 SQL"(如
SELECT 1)检测空闲连接是否仍然有效,若连接已失效(如数据库重启、网络断开),则丢弃该连接并创建新的连接补充。
第三方连接池
生产环境通常推荐使用性能更优的第三方连接池(如 Hikari 、Druid 、DBCP2 )。MyBatis 可通过替换 dataSource 实现集成。
| 连接池 | 优势 | 适用场景 |
|---|---|---|
| Hikari | 速度极快、轻量级、稳定 | Spring Boot 默认推荐 |
| Druid | 功能强大、监控全面、SQL 防火墙 | 国内常用,适合需要监控的场景 |
| DBCP2 | Apache 出品、稳定可靠 | 传统项目 |
由于Mybatis默认集成的就是Hikari,所以这里讲解集成alibaba旗下的Druid方法:
步骤 1:引入依赖
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.19</version>
</dependency>
步骤 2:修改application.properties配置文件
xml
spring.application.name=Mybatis
# 数据源配置 (DataSource)
# 数据库驱动 (MySQL 8.0+ 用这个,5.x 用 com.mysql.jdbc.Driver)
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource//新增这一行
# 数据库连接地址 (请修改为你的数据库名,这里假设叫 db_test)
spring.datasource.url=jdbc:mysql://localhost:3306/scott_data
# 数据库账号
spring.datasource.username=root
# 数据库密码
spring.datasource.password=Dairenwen1092
#配置mybatis的日志输出
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
三、Mybatis中增删改查
下面是接口中定义的crud语句,通过Autowired定义接口可以直接调用,简单了解一下即可,后续会通过xml配置文件来进一步学习新的增删改查:
java
@Mapper // 运行时自动创建接口的代理实现类,交给Spring IOC容器管理
public interface EmpMapper {
/**
* 查询所有员工信息
*/
//#{}是预编译sql语句,但${}则是直接替换,存在SQL注入问题
@Delete("delete from emp where empno=#{empno}")
public Integer deleteByempno(int empno);
@Insert("insert into emp values(#{empno},#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})")
public Integer insert(Emp emp);
@Update("update emp set empno=#{empno},ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},sal=#{sal},comm=#{comm},deptno=#{deptno})")
public Integer update(Emp emp);
@Select("select * from emp where empno=#{empno} and ename=#{ename}")
public List<Emp> select(@Param("empno")Integer empno,@Param("ename")String ename);//mybatis会自动将查询结果封装成Emp对象
//Param相等于起名字,能准确找到sql中的empno和ename,springboot父工程中包含了相关配置,可以省略,但别的骨架中需要加上!
}
四、Mybatis中xml文件配置
一、全局配置文件(mybatis-config.xml)
全局配置文件用于配置 MyBatis 的核心行为,如数据源、事务、缓存等。其结构和核心标签如下:
1. 顶层结构(按顺序)
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 以下标签按顺序排列 -->
<properties/> <!-- 1. 属性配置 -->
<settings/> <!-- 2. 全局设置 -->
<typeAliases/> <!-- 3. 类型别名 -->
<typeHandlers/> <!-- 4. 类型处理器 -->
<objectFactory/> <!-- 5. 对象工厂 -->
<plugins/> <!-- 6. 插件 -->
<environments/> <!-- 7. 环境配置 -->
<databaseIdProvider/><!-- 8. 数据库厂商标识 -->
<mappers/> <!-- 9. 映射器注册 -->
</configuration>
2. 核心标签详解
(1)<properties>:引入外部属性文件
用于加载外部配置(如 db.properties),可在配置中通过 ${key} 引用。
xml
<properties resource="db.properties">
<property name="username" value="dev_user"/>
</properties>
(2)<settings>:全局行为设置
开启/关闭 MyBatis 的核心功能,如缓存、懒加载、驼峰命名转换等。
xml
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存 -->
<setting name="lazyLoadingEnabled" value="true"/> <!-- 开启懒加载 -->
<setting name="mapUnderscoreToCamelCase" value="true"/><!-- 下划线转驼峰 -->
<setting name="logImpl" value="SLF4J"/> <!-- 日志实现 -->
</settings>
(3)<typeAliases>:类型别名
为 Java 类型设置短别名,减少全限定类名的冗余。
xml
<typeAliases>
<!-- 单个别名 -->
<typeAlias type="com.example.User" alias="User"/>
<!-- 扫描包下所有类,默认别名为类名首字母小写 -->
<package name="com.example.entity"/>
</typeAliases>
(4)<environments>:环境配置
配置数据源和事务管理器,可配置多环境(如开发、测试)。
xml
<environments default="development">
<environment id="development">
<!-- 事务管理器:JDBC/MANAGED -->
<transactionManager type="JDBC"/>
<!-- 数据源:UNPOOLED/POOLED/JNDI -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
(5)<mappers>:注册映射文件
告诉 MyBatis 去哪里找 SQL 映射文件。
xml
<mappers>
<!-- 方式1:引用类路径下的文件 -->
<mapper resource="com/example/mapper/UserMapper.xml"/>
<!-- 方式2:引用接口类(需注解或同包同名XML) -->
<mapper class="com.example.mapper.UserMapper"/>
<!-- 方式3:扫描包下所有Mapper接口 -->
<package name="com.example.mapper"/>
</mappers>
二、SQL 映射文件(Mapper.xml)
映射文件用于定义 SQL 语句、参数映射和结果集映射,是 MyBatis 的核心。
1. 顶层结构
xml
<?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.example.mapper.UserMapper">
<!-- SQL语句、结果映射等 -->
</mapper>
namespace:必须与对应 Mapper 接口的全限定名一致。
2. 核心标签
(1)SQL 语句标签
| 标签 | 用途 | 示例 |
|---|---|---|
<select> |
查询 | <select id="getUser" resultType="User"> |
<insert> |
插入(可配置主键返回) | <insert id="addUser" useGeneratedKeys="true" keyProperty="id"> |
<update> |
更新 | <update id="updateUser"> |
<delete> |
删除 | <delete id="deleteUser"> |
示例:
xml
<select id="getUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="User">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
(2)<resultMap>:结果映射
解决数据库列名与 Java 属性名不一致,或复杂对象映射的问题。
xml
<resultMap id="UserResultMap" type="User">
<!-- 主键映射 -->
<id column="user_id" property="userId"/>
<!-- 普通列映射 -->
<result column="user_name" property="userName"/>
<!-- 关联对象映射(一对一) -->
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
<!-- 集合映射(一对多) -->
<collection property="orders" ofType="Order">
<id column="order_id" property="orderId"/>
</collection>
</resultMap>
<!-- 使用resultMap -->
<select id="getUserDetail" resultMap="UserResultMap">
SELECT * FROM user u LEFT JOIN dept d ON u.dept_id = d.dept_id
</select>
(3)<sql> 和 <include>:SQL 片段复用
定义可重用的 SQL 片段,减少重复代码。
xml
<!-- 定义片段 -->
<sql id="Base_Column_List">
id, name, age, create_time
</sql>
<!-- 引用片段 -->
<select id="getAllUsers" resultType="User">
SELECT <include refid="Base_Column_List"/> FROM user
</select>
(4)动态 SQL 标签
用于根据条件动态拼接 SQL,避免硬编码。
<if>:条件判断<choose>/<when>/<otherwise>:多分支选择<where>/<set>:智能处理 SQL 拼接(如去除多余 AND/逗号)<foreach>:遍历集合(用于 IN 查询或批量插入)
示例:
xml
<select id="getUsersByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
<insert id="batchInsertUsers" parameterType="java.util.List">
INSERT INTO user (name, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>