1、创建表user_info实体类
2、创建表user_login_log实体类
3、创建表user_receive_address实体类
4、创建通用DAO接口IBaseMapper
5、创建表user_info的DAO接口
6、创建表user_login_log的DAO接口
7、创建表user_receive_address的DAO接口
8、创建表user_info的接口实现Mapper文件
9、创建表user_login_log的接口实现Mapper文件
10、创建表user_receive_address的接口实现Mapper文件
11、表user_info的接口方法测试
12、表user_login_log的接口方法测试
13、表user_receive_address的接口方法测试
数据库(MySQL)←→ 实体类(POJO)←→ DAO 接口(泛型)←→ Mapper XML ←→ MyBatis + 测试类
开一个新的Maven项目

你写一个 Java 项目时,如果不用 Maven:
-
你可能手动下载 jar 包、配置 classpath、写复杂的编译脚本
用了 Maven:
-
你只需要在
pom.xml
文件中写上你想用的库(如 MyBatis、JUnit) -
Maven 会自动帮你:
下载依赖,组织代码结构,编译/打包/测试,生成.jar
或.war
文件
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
</dependencies>
Maven 看到它,就知道要下载 MyBatis 并放入项目中。尤其适合 Java 生态,比如 Spring、MyBatis 项目等
Maven 内置了对依赖库(如 MyBatis)的自动链接机制,它会从中央仓库自动下载这些 jar 包。
pom.xml
中声明依赖,Maven 会:
-
自动联网
-
找到对应版本的 jar 包
-
下载下来(连带依赖项)
-
放到你项目的
target
或lib
路径里供使用
测试增删查改、写 Mapper 映射、运行 SQL,都需要一个真实的数据库环境。
简单了解,介绍完之后
1、创建表user_info实体类
2、创建表user_login_log实体类
3、创建表user_receive_address实体类
三个类的代码
1
package com.example.entity;
import java.time.LocalDateTime;
public class UserInfo {
private Integer id; // 用户 ID(主键)
private String username; // 用户名
private String email; // 邮箱
private Integer age; // 年龄
private String phone; // 手机号
private String status; // 用户状态
private LocalDateTime createTime; // 创建时间
// 无参构造方法(MyBatis 必须要有)
public UserInfo() {}
// 全参构造(可选)
public UserInfo(Integer id, String username, String email, Integer age,
String phone, String status, LocalDateTime createTime) {
this.id = id;
this.username = username;
this.email = email;
this.age = age;
this.phone = phone;
this.status = status;
this.createTime = createTime;
}
// Getter 和 Setter 方法
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 getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "UserInfo{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
", status='" + status + '\'' +
", createTime=" + createTime +
'}';
}
}
2
package com.example.entity;
import java.time.LocalDateTime;
public class UserLoginLog {
private Integer id; // 日志主键
private Integer userId; // 登录的用户 ID
private LocalDateTime loginTime; // 登录时间
private String ipAddress; // 登录 IP
private String device; // 登录设备
// 无参构造
public UserLoginLog() {}
// 全参构造
public UserLoginLog(Integer id, Integer userId, LocalDateTime loginTime,
String ipAddress, String device) {
this.id = id;
this.userId = userId;
this.loginTime = loginTime;
this.ipAddress = ipAddress;
this.device = device;
}
// Getter / Setter
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public LocalDateTime getLoginTime() {
return loginTime;
}
public void setLoginTime(LocalDateTime loginTime) {
this.loginTime = loginTime;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getDevice() {
return device;
}
public void setDevice(String device) {
this.device = device;
}
@Override
public String toString() {
return "UserLoginLog{" +
"id=" + id +
", userId=" + userId +
", loginTime=" + loginTime +
", ipAddress='" + ipAddress + '\'' +
", device='" + device + '\'' +
'}';
}
}
3
package com.example.entity;
public class UserReceiveAddress {
private Integer id; // 地址ID(主键)
private Integer userId; // 用户ID(外键)
private String address; // 详细地址
private String city; // 城市
private String province; // 省份
private String postalCode; // 邮编
private String phone; // 联系电话
// 无参构造
public UserReceiveAddress() {}
// 全参构造
public UserReceiveAddress(Integer id, Integer userId, String address,
String city, String province, String postalCode, String phone) {
this.id = id;
this.userId = userId;
this.address = address;
this.city = city;
this.province = province;
this.postalCode = postalCode;
this.phone = phone;
}
// Getter / Setter
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "UserReceiveAddress{" +
"id=" + id +
", userId=" + userId +
", address='" + address + '\'' +
", city='" + city + '\'' +
", province='" + province + '\'' +
", postalCode='" + postalCode + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
都处理好之后,就是建立和数据库的连接的,但这需要---提供连接的各种工具,---数据库本身真实存在
所以先构建数据库的连接
第一个遇到的需求
而/jdbc.properties
文件还没有
没有jdbc.properties
这是一个数据库连接配置文件,它的作用是:
把数据库连接信息(URL、用户名、密码等)单独存放在一个配置文件中,方便统一维护,供 MyBatis 使用。
目前就直接创建一个,接着
在 jdbc.properties
文件中写入以下内容:
第三步:确保 MyBatis 主配置文件中引用了它(mybatis-config.xml
)
没有mybatis-config.xml
但是目前没有这个文件----
mybatis-config.xml
是 MyBatis 的主配置文件,用于告诉 MyBatis 如何加载数据库配置、注册实体类、加载 Mapper 等。
要做的是:
📁 在 resources
文件夹下:
-
右键
resources
→New → File
-
命名为:
mybatis-config.xml
总是纯粹的创建新文件,不用引用什么什么让这两个文件有效?
需要,但是这--Maven解决
不需要额外去下载 MyBatis / MySQL 的 jar 包,只要你是用的 Maven 项目,只要你添加了依赖,IDEA 会帮你自动下载和管理这些库。
但依赖是需要添加的,
项目的 pom.xml
中,加入这几段依赖
(这里的mysql connector-j是错的,要-java)
<dependencies>
<!-- MyBatis 核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MySQL 驱动(8.x版本适用于现代数据库) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- JUnit 测试框架(测试用,不写测试类可忽略) -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
粘贴进去之后。
同步 Maven 依赖
-
点击 IDEA 右上角提示框上的:
Load Maven Changes
-
或者右键项目 → Maven → Reload Project
-
IDEA 会自动帮你下载依赖(jar 包)
如果网络慢,下载会花几分钟(或者你可以改为阿里云镜像)。
在这里报错了,接下来
同步成功
同步成功意味着什么?
-
你刚才配置的两个核心文件:
jdbc.properties
(数据库连接信息)
mybatis-config.xml
(MyBatis 主配置文件)
✔️ 现在终于变成「可执行、可使用」的有效配置,因为相关依赖(MyBatis、MySQL驱动)都下载到了本地。
停顿整理
🧱 第一部分:实体类(ORM 映射)
-
创建表
user_info
的实体类 -
创建表
user_login_log
的实体类 -
创建表
user_receive_address
的实体类
🧩 第二部分:DAO 接口设计
-
创建通用 DAO 接口
IBaseMapper
-
创建
user_info
的 DAO 接口 -
创建
user_login_log
的 DAO 接口 -
创建
user_receive_address
的 DAO 接口
⚙️ 第三部分:Mapper 映射文件(MyBatis)
-
创建
user_info
的 Mapper XML 文件 -
创建
user_login_log
的 Mapper XML 文件 -
创建
user_receive_address
的 Mapper XML 文件
✅ 第四部分:功能测试
-
测试
user_info
的接口方法 -
测试
user_login_log
的接口方法 -
测试
user_receive_address
的接口方法
目前整体进度(🟢 完成 / 🟡 准备中 / 🔲 未做)
步骤 | 内容 | 状态 | 说明 |
---|---|---|---|
1 | UserInfo 实体类 | 🟢 已完成 | 并优化字段结构 |
2 | UserLoginLog 实体类 | 🟢 已完成 | |
3 | UserReceiveAddress 实体类 | 🔲 待写 | 可以继续按模板生成 |
4 | IBaseMapper<T> 通用接口 | 🔲 待写 | 可以马上生成 |
5-7 | 每张表的 DAO 接口 | 🔲 待写 | 每个接口继承通用接口 |
8-10 | 每张表的 Mapper XML 文件 | 🔲 待写 | resources/mapper/ 中创建 XML |
11-13 | 接口测试 | 🔲 待做 | 写 TestUserInfo , TestUserLoginLog 类 |
项目环境配置方面,已经完成:
项目内容 | 状态 |
---|---|
Maven 项目结构 | 🟢 |
依赖配置(MyBatis + MySQL) | 🟢 |
jdbc.properties | 🟢 |
mybatis-config.xml | 🟢 |
成功下载依赖 | 🟢 |
IDEA 同步无错误 | 🟢 |
“数据库表结构(SQL 表)是不是需要现在就建?是不是要立即绑定到 MyBatis?”
实验项目中的数据库结构是必要的。一定要建数据库和三张表。
因为你后面第 11~13 步是:
“表
user_info
/user_login_log
/user_receive_address
的接口方法测试”
如果数据库没有表,你的 insert / select / update / delete 全部会失败,MyBatis 也没法正常工作。
不需要马上绑定到 MyBatis(写 XML)优先建好数据库和表结构,确保字段与实体类一一对应。
然后写 MyBatis 的 Mapper XML 映射文件时才能:
-
保证 SQL 语句是对的
-
字段名不会拼错
-
parameterType
/resultType
能正确匹配实体类
创建mysql数据库结构(sqlyog)
CREATE DATABASE mybatis_demo DEFAULT CHARACTER SET utf8mb4;
CREATE TABLE user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
phone VARCHAR(20),
STATUS VARCHAR(20),
create_time DATETIME
);
CREATE TABLE user_login_log (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
login_time DATETIME,
ip_address VARCHAR(50),
device VARCHAR(100)
);
CREATE TABLE user_receive_address (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
address VARCHAR(255),
city VARCHAR(50),
province VARCHAR(50),
postal_code VARCHAR(20),
phone VARCHAR(20)
);
通用 DAO 接口 IBaseMapper<T>
Data Access Object
(数据访问对象)
DAO 是一种用于 封装数据库访问逻辑 的设计模式。
它将对数据库的增删查改操作从业务逻辑中分离出来,形成一个“中介对象”。
Controller / Service 层 ←→ DAO ←→ 数据库
业务逻辑 → 不直接写 SQL → 都通过 DAO 去调用数据库接口
这样可以让你的程序更清晰、更易于维护、更方便测试。
4、创建通用DAO接口IBaseMapper
5、创建表user_info的DAO接口
6、创建表user_login_log的DAO接口
7、创建表user_receive_address的DAO接口
创建 IBaseMapper 通用 DAO 接口 | 为后续代码复用打基础 |
创建每个实体类的 Mapper 接口 | 继承通用接口 |
这个接口是整个数据访问逻辑的核心,它的作用是:
把所有表的基本操作(增、删、改、查)提取为通用方法,其他表的 DAO 接口只需要继承它,就能自动获得这些方法定义。
第一步:在 org.example.mapper
包中新建接口
-
在
src/main/java/com.example/
下 -
右键 →
New → Package
,命名为mapper
-
然后右键该包 →
New → Java Class
→ 选择Interface
→ 命名为:IBaseMapper
package org.example.mapper;
import java.util.List;
// 通用 Mapper 接口,T 是泛型,代表某个实体类(UserInfo, UserLoginLog 等)
public interface IBaseMapper<T> {
// 插入一条数据
int insert(T entity);
// 根据主键删除
int deleteById(int id);
// 更新一条数据(一般要求 entity.id 不为空)
int update(T entity);
// 根据主键查询一条数据
T selectById(int id);
// 查询所有数据
List<T> selectAll();
}
之后会这样使用它:
// user_info 表对应的 Mapper 接口
public interface UserInfoMapper extends IBaseMapper<UserInfo> {}
// user_login_log 表对应的 Mapper 接口
public interface UserLoginLogMapper extends IBaseMapper<UserLoginLog> {}
// user_receive_address 表对应的 Mapper 接口
public interface UserReceiveAddressMapper extends IBaseMapper<UserReceiveAddress> {}
这样就能实现代码复用,你写一个 XML 就能映射多个方法。
每个表创建自己的 Mapper 接口(继承 IBaseMapper
)
在 org.example.mapper
包下:
-
右键 → New → Java Class
-
类名分别是:
-
UserInfoMapper
-
UserLoginLogMapper
-
UserReceiveAddressMapper
-
创建每张表的 Mapper 映射 XML 文件(写 SQL)
8、创建表user_info的接口实现Mapper文件
9、创建表user_login_log的接口实现Mapper文件
10、创建表user_receive_address的接口实现Mapper文件
创建 user_info
表的 Mapper XML 文件,实现接口方法与 SQL 的映射
现在已经有了这个接口:
public interface UserInfoMapper extends IBaseMapper<UserInfo> {}
接下来要做的,是给它配上一个对应的 XML 文件(UserInfoMapper.xml
),在里面实现增删改查操作。
第一步:在 resources/mapper/
下新建 XML 文件
-
打开项目的
src/main/resources
目录 -
新建一个子文件夹:
mapper
-
在
mapper
目录下右键 →New → File
-
命名为:
UserInfoMapper.xml
第二步:写入标准 MyBatis 映射内容
<?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="org.example.mapper.UserInfoMapper">
<!-- 插入一条用户信息 -->
<insert id="insert" parameterType="UserInfo">
INSERT INTO user_info (username, email, age, phone, status, create_time)
VALUES (#{username}, #{email}, #{age}, #{phone}, #{status}, #{createTime})
</insert>
<!-- 根据 ID 删除 -->
<delete id="deleteById" parameterType="int">
DELETE FROM user_info WHERE id = #{id}
</delete>
<!-- 更新用户信息 -->
<update id="update" parameterType="UserInfo">
UPDATE user_info
SET username = #{username},
email = #{email},
age = #{age},
phone = #{phone},
status = #{status},
create_time = #{createTime}
WHERE id = #{id}
</update>
<!-- 根据 ID 查询 -->
<select id="selectById" parameterType="int" resultType="UserInfo">
SELECT * FROM user_info WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="selectAll" resultType="UserInfo">
SELECT * FROM user_info
</select>
</mapper>
第三步:在 mybatis-config.xml
中注册这个 XML 文件
打开mybatis-config.xml
,找到 <mappers>
部分,在其中添加:
<mapper resource="mapper/UserInfoMapper.xml"/>
创建 UserLoginLogMapper.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="org.example.mapper.UserLoginLogMapper">
<insert id="insert" parameterType="UserLoginLog">
INSERT INTO user_login_log (user_id, login_time, ip_address, device)
VALUES (#{userId}, #{loginTime}, #{ipAddress}, #{device})
</insert>
<delete id="deleteById" parameterType="int">
DELETE FROM user_login_log WHERE id = #{id}
</delete>
<update id="update" parameterType="UserLoginLog">
UPDATE user_login_log
SET user_id = #{userId},
login_time = #{loginTime},
ip_address = #{ipAddress},
device = #{device}
WHERE id = #{id}
</update>
<select id="selectById" parameterType="int" resultType="UserLoginLog">
SELECT * FROM user_login_log WHERE id = #{id}
</select>
<select id="selectAll" resultType="UserLoginLog">
SELECT * FROM user_login_log
</select>
</mapper>
创建 UserReceiveAddressMapper.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="org.example.mapper.UserReceiveAddressMapper">
<insert id="insert" parameterType="UserReceiveAddress">
INSERT INTO user_receive_address (user_id, address, city, province, postal_code, phone)
VALUES (#{userId}, #{address}, #{city}, #{province}, #{postalCode}, #{phone})
</insert>
<delete id="deleteById" parameterType="int">
DELETE FROM user_receive_address WHERE id = #{id}
</delete>
<update id="update" parameterType="UserReceiveAddress">
UPDATE user_receive_address
SET user_id = #{userId},
address = #{address},
city = #{city},
province = #{province},
postal_code = #{postalCode},
phone = #{phone}
WHERE id = #{id}
</update>
<select id="selectById" parameterType="int" resultType="UserReceiveAddress">
SELECT * FROM user_receive_address WHERE id = #{id}
</select>
<select id="selectAll" resultType="UserReceiveAddress">
SELECT * FROM user_receive_address
</select>
</mapper>
Final--测试user_info表的 DAO 接口
11、表user_info的接口方法测试
12、表user_login_log的接口方法测试
13、表user_receive_address的接口方法测试
第一步:创建 MyBatis 工具类(用于获取 SqlSession
)
这个类统一管理 MyBatis 的初始化和会话获取,适用于所有测试类。
📁 路径建议:com.example.util.MyBatisUtil
package com.example.util;
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;
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 读取 mybatis-config.xml 配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取 SqlSession
public static SqlSession getSession() {
return sqlSessionFactory.openSession(); // 默认不自动提交
}
}
第二步:写一个主类进行测试(UserInfo)
📁 路径建议:com.example.test.TestUserInfo
package com.example.test;
import com.example.entity.UserInfo;
import org.example.mapper.UserInfoMapper;
import com.example.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.time.LocalDateTime;
public class TestUserInfo {
public static void main(String[] args) {
// 获取 SqlSession
SqlSession session = MyBatisUtil.getSession();
try {
// 获取 Mapper 接口的实现(MyBatis 会自动生成代理对象)
UserInfoMapper mapper = session.getMapper(UserInfoMapper.class);
// 新建一个用户对象
UserInfo user = new UserInfo(null, "张三", "zhangsan@example.com", 25,
"13900000000", "active", LocalDateTime.now());
// 插入操作
int result = mapper.insert(user);
System.out.println("插入结果: " + result);
// 查询所有用户
for (UserInfo u : mapper.selectAll()) {
System.out.println(u);
}
// 提交事务
session.commit();
} catch (Exception e) {
session.rollback(); // 出错就回滚
e.printStackTrace();
} finally {
session.close();
}
}
}
TestUserInfo
是你写的主函数测试类,它的任务是:
-
手动获取 MyBatis 的
SqlSession
-
从中获取
UserInfoMapper
接口的实现 -
调用
.insert(...)
、.selectAll()
方法 -
打印数据库交互结果 ✅
如果你运行了 TestUserInfo.java
,会发生什么?
控制台会打印类似下面的输出:
插入结果: 1
UserInfo{id=1, username='张三', email='zhangsan@example.com', age=25, phone='13900000000', status='active', createTime=2025-03-29T20:17:43.123}
这说明:
-
成功向数据库插入了一条用户记录 ✔️
-
成功执行了
selectAll()
查询 ✔️ -
MyBatis 映射和连接都已经打通 ✔️
整体流程
------------------------------------------
TestUserInfo.java
↓
MyBatisUtil → SqlSessionFactory → SqlSession
↓
UserInfoMapper ← 自动代理 ← UserInfoMapper.xml
↓
调用 insert() / selectAll() 方法
↓
数据库执行 SQL,返回 Java 对象
↓
控制台打印结果
运行
数据库里没有变化
报错摘要:Could not find resource mapper/UserInfoMapper.xml
是文件路径的问题
重新运行,,再次报错
报错核心:
Cannot invoke "org.apache.ibatis.mapping.Environment.getDataSource()" because "environment" is null
MyBatis 启动时找不到数据库连接环境(Environment),也就是你没有配置 <environments>
标签,或者没写 dataSource
!
解决方式:你需要在 mybatis-config.xml
中补上
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
关于里面的“占位符”语法,代表:
这些值 不是手动写在这里,而是从你单独配置的文件
jdbc.properties
中自动读取的。
再次运行,成功,但是实际数据库里并没有发生变化
根本原因:
你没有调用
session.commit()
,或者插入数据后就退出了事务,没有提交!
但你截图中已经 打印出插入成功并返回了 id=1 的记录,说明这条数据确实插入到了内存中的事务里,但 没有提交到数据库中,所以外部看不到。
只需要在插入后手动调用:session.commit(); // ✅ 提交事务
补充说明:为什么 MyBatis 不默认自动提交?
-
默认 MyBatis 使用
openSession()
是不自动提交的(MyBatis 认为开发者要手动控制事务) -
如果你想自动提交,可以改为:
sqlSessionFactory.openSession(true); // true 表示自动提交事务
但为了更清晰地控制事务,一般推荐使用:
SqlSession session = MyBatisUtil.getSession();
...
session.commit(); // ✔️ 显式提交
看不到数据的原因 | 解决方案 |
---|---|
没有 session.commit() | ➤ 添加这一句,提交事务 ✅ |
insert 成功但未提交 | ➤ 默认事务在内存中,不持久化 |
select 能查出但客户端无数据 | ➤ 插入只存在于当前事务作用域中 |
意思是对当前数据库的一大堆增删查改操作捆成一团,然后在内存里存着,session.commit()才能把这一连串的操作一并发给数据库
事务是一组操作的最小执行单元,具有四大特性(ACID):
特性 | 含义 |
---|---|
A 原子性 | 一系列操作要么全部执行,要么全部不执行 |
C 一致性 | 执行前后,数据保持一致(比如主外键、约束) |
I 隔离性 | 多个事务互不影响(如并发插入) |
D 持久性 | 一旦提交,数据就永久保存在数据库中 |
一致性(Consistency) vs 原子性(Atomicity)
对比点 | 原子性(A) | 一致性(C) |
---|---|---|
🌱 核心含义 | 一件事要么全做,要么全不做(不留中间状态) | 执行事务前后,数据必须满足业务逻辑规则 / 约束完整性 |
💡 类比 | “不成功便成仁”,不能只做一半 | 做完以后,世界规则不能被破坏(数据不能处于“错误状态”) |
🎯 场景 | 用户转账:减账成功但加账失败 → 回滚(不能部分成功) | 用户转账后,两人账户总和不变、不能负数、不能跨表不匹配等 |
🔧 控制方式 | 数据库自动回滚 / 编程时 try-catch rollback | 通过主键、外键、唯一性、校验逻辑等保持一致性 |
-
原子性保证:① 和 ② 要么一起成功,要么一起失败
-
一致性保证:转账前后 A+B 的总金额必须保持不变,比如 2000 + 1000 = 3000
如果没有隔离性 → | 用户 B 可能会查到用户 A 那条“还没提交”的数据(脏读) |
---|---|
有隔离性 → | 用户 B 查询不到用户 A 的临时数据,直到 A commit |
“你做你的事,我看不到中间过程”
“数据库会为并发插入备份多份结果吗?”
✔️ 并不是备份多份,而是:
-
数据库内部维护每个事务自己的快照视图(版本)
-
并发时,每个事务看见的数据库世界是“自己的”
-
等你提交时,再做“冲突检测” → 如果有写冲突,会回滚 / 报错
这背后就是**MVCC(多版本并发控制)**的机制。
四大隔离级别(了解一下)
隔离级别 | 能防止什么问题 | 常见数据库默认 |
---|---|---|
READ UNCOMMITTED | 最弱,能读到别人的未提交数据(脏读) | ❌ 很少用 |
READ COMMITTED | 只能读到已提交的数据(避免脏读) | ✅ MySQL 默认 |
REPEATABLE READ | 多次查询结果一样(避免不可重复读) | ✅ InnoDB 默认 |
SERIALIZABLE | 最强,完全串行执行(防幻读) | ❌ 性能差,一般不设为默认 |
“如果a提交的前一刻,b提交了修改,a此刻改的就会是b修改过的版本?”
要看你设置的隔离级别,也就是是否启用了 MVCC(多版本并发控制)
如果数据库使用 MVCC + REPEATABLE READ(MySQL 默认):
1. A 是在自己事务刚开启时的快照版本上操作的
-
当 A 启动事务时,它读取的是当时的“数据快照”
-
就算 B 提交了修改,A 也不会受到 B 的改动影响
A 修改的其实是“旧版本”
当 A 提交时,数据库会检查:
当前这条数据在你读的之后,是否被别人改过?
如果是:
-
✅ 触发 冲突检测,会导致:
-
❌ A 提交失败(回滚)
-
❌ 报错:“更新失败,数据已被修改”
-
👉 所以并不会悄悄覆盖,而是会阻止你提交旧版本
如果你使用的是低隔离级别(如 READ COMMITTED)
那 A 的事务在读取时就能看到 B 的最新版本
→ 那你说的情况就成立了:
✔️ A 可能会基于 B 的提交版本继续修改
这就叫 不可重复读(Non-repeatable Read)
→ 这时不会报错,但可能出现“最后写入者覆盖”的问题
额外两个表的接口测试
package com.example.test;
import com.example.entity.UserLoginLog;
import org.example.mapper.UserLoginLogMapper;
import com.example.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.time.LocalDateTime;
public class TestUserLoginLog {
public static void main(String[] args) {
SqlSession session = MyBatisUtil.getSession();
try {
UserLoginLogMapper mapper = session.getMapper(UserLoginLogMapper.class);
// 创建一条登录日志(userId 需要与 user_info 表中已有用户匹配)
UserLoginLog log = new UserLoginLog(null, 1, LocalDateTime.now(), "127.0.0.1", "Windows-PC");
// 插入
int result = mapper.insert(log);
System.out.println("插入结果: " + result);
// 查询所有
for (UserLoginLog l : mapper.selectAll()) {
System.out.println(l);
}
session.commit();
} catch (Exception e) {
session.rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
package com.example.test;
import com.example.entity.UserReceiveAddress;
import org.example.mapper.UserReceiveAddressMapper;
import com.example.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
public class TestUserReceiveAddress {
public static void main(String[] args) {
SqlSession session = MyBatisUtil.getSession();
try {
UserReceiveAddressMapper mapper = session.getMapper(UserReceiveAddressMapper.class);
// 创建一个收货地址(userId 也需为已有用户)
UserReceiveAddress address = new UserReceiveAddress(
null, 1, "123 中山路", "广州", "广东", "510000", "13988889999"
);
// 插入
int result = mapper.insert(address);
System.out.println("插入结果: " + result);
// 查询所有
for (UserReceiveAddress addr : mapper.selectAll()) {
System.out.println(addr);
}
session.commit();
} catch (Exception e) {
session.rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
package com.example.test;
import com.example.entity.UserReceiveAddress;
import org.example.mapper.UserReceiveAddressMapper;
import com.example.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
public class TestUserReceiveAddress {
public static void main(String[] args) {
SqlSession session = MyBatisUtil.getSession();
try {
UserReceiveAddressMapper mapper = session.getMapper(UserReceiveAddressMapper.class);
// 创建一个收货地址(userId 也需为已有用户)
UserReceiveAddress address = new UserReceiveAddress(
null, 1, "123 中山路", "广州", "广东", "510000", "13988889999"
);
// 插入
int result = mapper.insert(address);
System.out.println("插入结果: " + result);
// 查询所有
for (UserReceiveAddress addr : mapper.selectAll()) {
System.out.println(addr);
}
session.commit();
} catch (Exception e) {
session.rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
结束的整体结构
ORM 是什么的缩写?
Object-Relational Mapping
中文叫:对象-关系 映射
一句话定义 ORM:
它是把面向对象的编程语言中的“类”与数据库中的“表”一一对应起来的机制。
这么一看,数据库的结构好像都没排上用场(也可能是绑定之后已经自动检测过结果相同,才得到最终可以执行的结果)