Java + MyBatis + 泛型 DAO

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 包

  • 下载下来(连带依赖项)

  • 放到你项目的 targetlib 路径里供使用

测试增删查改、写 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 文件夹下:

  1. 右键 resourcesNew → File

  2. 命名为: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 映射)

  1. 创建表 user_info 的实体类

  2. 创建表 user_login_log 的实体类

  3. 创建表 user_receive_address 的实体类


🧩 第二部分:DAO 接口设计

  1. 创建通用 DAO 接口 IBaseMapper

  2. 创建 user_info 的 DAO 接口

  3. 创建 user_login_log 的 DAO 接口

  4. 创建 user_receive_address 的 DAO 接口


⚙️ 第三部分:Mapper 映射文件(MyBatis)

  1. 创建 user_info 的 Mapper XML 文件

  2. 创建 user_login_log 的 Mapper XML 文件

  3. 创建 user_receive_address 的 Mapper XML 文件


✅ 第四部分:功能测试

  1. 测试 user_info 的接口方法

  2. 测试 user_login_log 的接口方法

  3. 测试 user_receive_address 的接口方法

目前整体进度(🟢 完成 / 🟡 准备中 / 🔲 未做)

步骤内容状态说明
1UserInfo 实体类🟢 已完成并优化字段结构
2UserLoginLog 实体类🟢 已完成
3UserReceiveAddress 实体类🔲 待写可以继续按模板生成
4IBaseMapper<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 包中新建接口

  1. src/main/java/com.example/

  2. 右键 → New → Package,命名为 mapper

  3. 然后右键该包 → 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:

它是把面向对象的编程语言中的“类”与数据库中的“表”一一对应起来的机制。

这么一看,数据库的结构好像都没排上用场(也可能是绑定之后已经自动检测过结果相同,才得到最终可以执行的结果) 

为什么我们要使用通用DAO接口呢,因为我们的数据库操作无非是增删改查,CRUD操作,我们不需要为每个实体去编写一个dao接口,对于相似的实体操作可以只编写一个通用接口,然后采用不同的实现! DAO已经成为持久层的标准模式,DAO使结构清晰,面向接口编程为代码提供了规范。而DAO是一个类安全的,代码精简的设计模式(相对于传统DAO),尤其在DAO组件数量庞大的时候,代码量的减少更加明显。 DAO的核心是定义一个GenericDao接口,声明基本的CRUD操作: 用hibernate作为持久化解决方案的GenericHibernateDao实现类,被定义为抽象类,它提取了CRUD操作,这就是简化代码的关键,以便于更好的重用,这个就不给例子了,增删改都好写,查就需要各种条件了。 然后是各个领域对象的dao接口,这些dao接口都继承GenericDao接口,这样各个领域对象的dao接口就和传统dao接口具有一样的功能了。 下一步是实现类了,个自领域对象去实现各自的接口,还要集成上面的抽象类,这样就实现了代码复用的最大化,实现类中只需要写出额外的查询操作就可以了。当然还要获得域对象的Class实例,这就要在构造方法中传入Class实例。用spring提供的HibernateTemplate注入到GenericHibernateDao中,这样在各个实现类就可以直接调用HibernateTemplate来实现额外的查询操作了。 如果在实现类中不想调用某个方法(例如:update()),就可以覆盖它,方法中抛出UnsupportedOperationException()异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值