事务

事务特性

1、数据库事务(ACID 特性)是保证多步数据库操作「原子性、一致性、隔离性、持久性」的核心机制:

原子性:一组操作要么全成,要么全败; 一致性:操作前后数据符合业务规则(如转账后双方余额总和不变); 持久性:提交后数据永久保存到数据库; 隔离性:多事务并发时互不干扰。

Mybatis事务默认是「自动提交关闭」状态

1、MyBatis 的 SqlSession 底层封装了 JDBC 的 Connection,事务默认是「自动提交关闭」状态(autoCommit=false); 所有通过 SqlSession 执行的增删改操作,默认都在一个未提交的事务中,必须手动提交才能生效。

自动提交状态 行为逻辑
开启(ON) 每执行一条 SQL 语句,数据库会自动将该语句封装为一个独立事务并立即提交 —— 这就是 “语句级生效” 的本质;
关闭(OFF) 所有后续 SQL 语句都归入同一个未提交的事务,直到显式执行 COMMIT 才提交,或 ROLLBACK 回滚;

====》

2、MyBatis 执行 insert/update/delete 后,若不提交事务: 操作仅停留在数据库的「事务缓冲区」,未写入磁盘; 数据库连接关闭后,事务会自动回滚,所有操作全部失效; 即使查询当前 SqlSession 能看到未提交的数据(隔离级别问题),但其他会话 / 连接完全不可见,等同于操作白做。

// 获取SqlSession(默认autoCommit=false)
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
​
// 执行更新操作
mapper.updateUserStatus(1, "ACTIVE"); 
​
// 未调用session.commit(),直接关闭会话
session.close(); 
​
// 结果:数据库中user_id=1的状态未更新,操作全部回滚

3、实际开发中: 非 Spring 环境:手动控制 SqlSession 的 commit()/rollback(); Spring 环境:优先使用 @Transactional 声明式事务,让框架自动管理提交 / 回滚,减少手动操作的失误。

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
​
    // Spring 事务注解:自动管理提交/回滚
    @Transactional(rollbackFor = Exception.class)
    public void batchUpdate(User user1, User user2) {
        // 无需手动commit/rollback,Spring自动处理
        userMapper.insertUser(user1);
        userMapper.updateUser(user2);
    }
}

数据库客户端默认开启自动提交

相比之下, 数据库客户端默认开启自动提交,是为了「简化交互式操作」—— 用户在客户端执行 SQL 时,大多是单条查询 / 修改,需要 “写一条生效一条”,提升易用性;

而 上面介绍了,JDBC/MyBatis 作为 “程序级操作接口”,首要目标是「支持业务事务的原子性」,因此默认关闭自动提交,优先保证数据一致性。

场景 自动提交状态 事务行为
数据库客户端(Navicat / 命令行) ON(默认) 每条 SQL 自动封装为独立事务,执行后立即 COMMIT,看似 “语句级生效”;
JDBC/MyBatis(默认) OFF(默认) 多条 SQL 归入同一个事务,需显式 COMMIT 才提交,支持原子性;
JDBC/MyBatis(手动开启) ON 与客户端行为一致,单条 SQL 自动提交,失去多语句原子性;

mapper层

1、对于mapper层的理解

在springboot中,如果使用mybatis框架,与数据库打交道的层我们叫mapper层。(也就是我们没使用mybatis框架时叫的dao层);既然是框架就有框架他的设计规范,只要我们遵循mybatis的规范去完成一些工作,在某些实现上去自定义我们的逻辑,就能满足上层(service,controller)对于数据库数据的使用。

2、mapper层和dao层的叫法

传统 JDBC/原生开发:我们通常称为 DAO (Data Access Object) 层。这是一个设计模式的概念,强调对象化地访问数据。

MyBatis 语境:虽然本质依然是做数据访问(所以叫它 DAO 层在逻辑上没错),但 MyBatis 官方和社区更倾向于称之为 Mapper。

  • 原因:MyBatis 的核心机制是“映射”(Mapping)。它将 Java 接口方法映射到 SQL 语句,将数据库结果集映射到 Java 对象。因此,UserMapperUserDao 更能体现其“SQL 映射器”的本质。

  • 结论:在 MyBatis 项目中,叫 Mapper 层 是行业标准,叫 DAO 层大家也能听懂,但 Mapper 更专业。

SqlSession及Springboot对它的封装

SqlSession是mybatis提供的操作数据库的会话对象,从而我们在测试某个mapper接口的方法的一般步骤都是:

1、加载核心配置文件

2、获取SqlSessionFactoryBuilder

3、获取SqlSessionFactory

4、获取SqlSession 从而用SqlSession.getMapper(User.class)来获取UserMapper对象 从而再来进行增删改查;

//读取MyBatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
SqlSessionFactoryBuilder();
//通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
//SqlSession sqlSession = sqlSessionFactory.openSession();
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//通过代理模式创建UserMapper接口的代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,通过调用的方法名匹配映射文件中的SQL标签,并执行标签中的SQL语句
int result = userMapper.insertUser();
//sqlSession.commit();

可是在上述springboot+mybatis中怎么没看到SqlSession啊?

===> 在 Spring Boot + MyBatis 的整合中,SqlSession 依然存在,但被 Spring 框架「自动管理」了—— 它不再需要你手动创建 / 关闭,而是通过 Spring 的依赖注入和自动配置,隐藏在底层逻辑中。

Spring Boot 整合 MyBatis 的核心魔法:“约定优于配置” + “依赖注入” + “动态代理”。

第一步:启动时的“扫描与注册”

  • 动作:Spring Boot 启动,读取 @MapperScan("com.example.mapper")

  • 内部机制:MyBatis-Spring 的 MapperScannerConfigurer 会扫描指定包下的接口。

  • 关键点:它不会尝试实例化这些接口(因为接口不能实例化),而是将这些接口定义注册为 Spring 容器中的 BeanDefinition。此时,Bean 的具体类是 MapperFactoryBean

第二步:运行时的“动态代理” (最关键的一步)

  • 动作:当你的 Service 层需要 UserMapper 时,向 Spring 容器请求这个 Bean。

  • 内部机制

    1. Spring 调用 MapperFactoryBean.getObject()

    2. MyBatis 使用 JDK 动态代理(或 CGLIB)为 UserMapper 接口生成一个代理对象(MapperProxy)。

    3. 这个代理对象被放入 Spring 容器,等待注入。

  • 结果:你的 @Autowired UserMapper userMapper 拿到的就是这个 代理对象,而不是真正的实现类(因为本来就没有实现类)。

第三步:调用时的“借还会话”

  • 动作:你调用 userMapper.selectById(1)

  • 内部机制

    1. 请求被拦截器 MapperProxy.invoke() 捕获。

    2. 获取 SqlSession

      代理对象不会 new 一个 Session,而是向 Spring 的 SqlSessionTemplate 请求。

      • 如果有事务 (@Transactional):SqlSessionTemplate 会从当前线程绑定的事务上下文中获取已经存在的 SqlSession(保证同一个事务内多次数据库操作共用一个连接)。

      • 如果没有事务:它会临时打开一个新的 SqlSession

    3. 执行 SQL:通过 SqlSession 找到对应的 XML 或注解 SQL,执行查询,映射结果。

    4. 归还/关闭

      • 如果有事务:什么都不做,等事务提交/回滚时统一由事务管理器关闭。

      • 如果无事务:立即关闭 SqlSession,释放数据库连接。

Spring Boot + MyBatis案例

接下来我们会以对一张user表进行操作的例子为例,介绍SpringBoot+Mybatis的步骤。

一、先创建 user 表(以 MySQL 为例)

CREATE TABLE `user` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` VARCHAR(50) NOT NULL COMMENT '用户名',
  `age` INT(11) DEFAULT NULL COMMENT '年龄',
  `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
​
-- 插入测试数据
INSERT INTO `user` (`username`, `age`, `email`) VALUES 
('zhangsan', 20, 'zhangsan@test.com'),
('lisi', 25, 'lisi@test.com');

二、Spring Boot 集成 MyBatis 完整步骤

步骤 1:创建 Spring Boot 项目并引入依赖

步骤 2:yml配置

配置数据库连接替代原生 MyBatis 的 mybatis-config.xml 环境配置)

Spring Boot 中无需编写 mybatis-config.xml, 直接在 application.yml(或 application.properties)中配置数据库和 MyBatis 核心参数:

# 服务器配置(可选,默认8080)
server:
  port: 8080
​
# 数据库连接配置(Spring Boot 4.0 兼容MySQL 8.x)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root # 替换为你的MySQL用户名
    password: 123456 # 替换为你的MySQL密码
    driver-class-name: com.mysql.cj.jdbc.Driver
    # Spring Boot 4.0 默认使用HikariCP连接池,无需额外配置
​
# MyBatis 核心配置(适配4.0)
mybatis:
  # 映射文件位置(XML方式SQL存放路径)
  mapper-locations: classpath:mapper/*.xml
  # 实体类别名包(简化XML中全类名)
  type-aliases-package: org.example.mybatisdemo.entity
  # MyBatis 配置项(4.0 兼容)
  configuration:
    map-underscore-to-camel-case: true # 驼峰命名转换
    cache-enabled: false # 关闭二级缓存(默认)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(调试用)

步骤 3:创建实体类

1、对应 user 表,创建 User 实体类

可以使用Alt + Insert来自动生成所有属性的 Getter(getXxx())和 Setter(setXxx());构造方法,生成 toString () 方法;

亦可以使用 Lombok 一键替代所有方法生成(推荐) 手动生成 Getter/Setter 等方法会让实体类代码臃肿,Spring Boot 项目中更推荐用 Lombok 插件,通过注解一键生成所有方法,无需手动操作。

1)引入lombok依赖

2)安装lombok插件

2、注意:MyBatis 兼容性

  • 必须保证实体类有无参构造@NoArgsConstructor 或手动生成),否则 MyBatis 反射创建对象时会报错;

  • Lombok 注解在编译期生效,运行时与手动生成的方法完全一致,不影响 MyBatis 映射。

注解 作用 替代手动生成的方法
@Data 综合注解:包含 Getter/Setter/toString/equals/hashCode Getter + Setter + toString 等
@NoArgsConstructor 生成无参构造 无参构造方法
@AllArgsConstructor 生成全参构造 全参构造方法
@Getter 仅生成 Getter(按需) Getter 方法
@Setter 仅生成 Setter(按需) Setter 方法
@ToString 仅生成 toString(按需) toString 方法

下面给出使用了注解之后的实体类的形态;

// 核心注解:自动生成Getter/Setter/toString/equals/hashCode
@Data
// 生成无参构造(MyBatis 必须)
@NoArgsConstructor
// 生成全参构造
@AllArgsConstructor
public class User {
    private Long id;
    private String username;
    private Integer age;
    private String email;
}

步骤 4:创建 Mapper 接口

Spring Boot 中 Mapper 接口只需添加 @Mapper 注解(或全局扫描),无需手动实现类

package org.example.mybatisdemo.mapper;
​
import org.example.mybatisdemo.entity.User;
import org.apache.ibatis.annotations.*;
​
import java.util.List;
​
/**
 * UserMapper接口(MyBatis 核心)
 * 无需加@Mapper注解(启动类已用@MapperScan扫描)
 */
public interface UserMapper {
​
    // 注解式SQL:根据ID查询
    @Select("SELECT * FROM user WHERE id = #{id}")
    User getUserById(Long id);
​
    // 注解式SQL:查询所有
    @Select("SELECT * FROM user")
    List<User> listAllUsers();
​
  
    @Insert("INSERT INTO user (username, age, email) VALUES (#{username}, #{age}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id") // 回填自增ID
    int addUser(User user);
​
    // 注解式SQL:修改
    @Update("UPDATE user SET username = #{username}, age = #{age}, email = #{email} WHERE id = #{id}")
    int updateUser(User user);
​
    // 注解式SQL:删除
    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteUser(Long id);
​
    // XML式SQL:根据年龄查询(复杂SQL推荐XML)
    List<User> listUsersByAge(Integer age);
}
注解式Sql和XML映射式Sql
  1. 早期 MyBatis (iBatis 时代):只有 XML 模式。这是为了彻底将 SQL 从 Java 代码中剥离,让 DBA(数据库管理员)可以独立优化 SQL 而不触碰 Java 代码。

  2. MyBatis 3+ 引入注解:随着敏捷开发和简单微服务的流行,很多场景只需要简单的单表查询。每次都去写一个 XML 文件太麻烦了。于是引入了注解,让简单场景变得极度轻量。

  3. 现状

    • 小型项目/简单模块:倾向于全注解,甚至配合 MyBatis-Plus(它连注解都不用写,直接调用方法)。

    • 大型项目/核心业务:倾向于 XML 为主,注解为辅。因为核心业务往往涉及复杂的联表查询和动态条件,XML 的可维护性和 SQL 调优便利性是注解无法比拟的。

步骤 5:(可选)创建 XML 映射文件(复杂 SQL 场景)

若需编写复杂 SQL(联表、分页、动态 SQL),创建 XML 映射文件 UserMapper.xml,放在 resources/mapper/ 目录下(与 application.yml 中 mapper-locations 配置一致)

在 src/main/resources/mapper 下创建

<?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">
​
<!-- namespace 必须与Mapper接口全类名一致 -->
<mapper namespace="org.example.mybatisdemo.mapper.UserMapper">
    <!-- id = 接口方法名,resultType = 实体类(已配置别名包,直接写User) -->
    <select id="listUsersByAge" parameterType="Integer" resultType="User">
        SELECT * FROM user WHERE age > #{age}
    </select>
​
    <!-- 示例:动态SQL(Spring Boot 4.0 兼容) -->
    <select id="listUsersByCondition" parameterType="User" resultType="User">
        SELECT * FROM user
        <where>
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="age != null">
                AND age = #{age}
            </if>
        </where>
    </select>
</mapper>

步骤 6:创建 Service 层

需手动管理 SqlSession 的创建 / 提交 / 回滚,@Transactional 注解自动处理事务。

package org.example.mybatisdemo.service;
​
import org.example.mybatisdemo.entity.User;
​
import java.util.List;
​
public interface UserService {
    User getUserById(Long id);
    List<User> listAllUsers();
    int addUser(User user);
    int updateUser(User user);
    int deleteUser(Long id);
    List<User> listUsersByAge(Integer age);
}

实现类(UserServiceImpl.java)

/**
 * Service实现类(Spring Boot 4.0 事务注解兼容)
 */
@Service
public class UserServiceImpl implements UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    @Override
    public User getUserById(Long id) {
        return userMapper.getUserById(id);
    }
​
    @Override
    public List<User> listAllUsers() {
        return userMapper.listAllUsers();
    }
​
    // 事务注解:Spring Boot 4.0 支持rollbackFor = Exception.class
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addUser(User user) {
        return userMapper.addUser(user);
    }
​
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateUser(User user) {
        return userMapper.updateUser(user);
    }
​
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteUser(Long id) {
        return userMapper.deleteUser(id);
    }
​
    @Override
    public List<User> listUsersByAge(Integer age) {
        return userMapper.listUsersByAge(age);
    }
}

步骤 7:创建 Controller 层

(接口测试,可选)

步骤 8:启动类(核心入口)

package org.example.mybatisdemo;
​
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
/**
 * Spring Boot 4.0 启动类
 * 注意:@MapperScan 扫描Mapper接口包,替代每个Mapper的@Mapper注解
 */
@SpringBootApplication
@MapperScan("org.example.mybatisdemo.mapper") // 扫描Mapper接口包
public class MybatisDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisDemoApplication.class, args);
    }
}

总结:

1、本文介绍了数据库事务(ACID 特性)原子性,一致性,持久性,隔离性的定义;并指出MyBatis 的 SqlSession 底层封装了 JDBC 的 Connection,事务默认是「自动提交关闭」状态。建议在springboot项目中使用 @Transactional 声明式事务,让框架自动管理提交 / 回滚,减少手动操作的失误。又对比指出了数据库客户端事务默认是「自动提交开启」状态。

2、分享了对mapper层的理解以及mapper层和dao层两种叫法的原因。

3、介绍了SqlSession这一mybatis提供的操作数据库的会话对象,指出我们在操作某个mapper接口的一般操作时 (读取MyBatis的核心配置文件,获取SqlSessionFactoryBuilder ,获取SqlSessionFactory ,获取SqlSession 从而用SqlSession.getMapper(User.class)来获取UserMapper对象 从而再来进行增删改查;)

重点解释了为什么在spring中看不到这个SqlSession对象的原因(被框架封装管理了)。

4、介绍了Spingboot+MYBatis的实践:创建user表;创建springboot项目,引入依赖,在yml文件中配置(数据库连接信息,mybatis相关配置);创建实体类,创建mapper接口;可选的:创建XML 映射文件(复杂 SQL 场景)创建Service,Controller等上层逻辑,在启动类上使用@MapperScan接口以扫描Mapper接口包

5、在创建User类实体类逻辑中,指出可以使用Alt + Insert来自动生成所有属性的 Getter(getXxx())和 Setter(setXxx());构造方法,生成 toString () 方法;又提出可以使用Lombok插件键替代所有方法生成;并介绍了@Data,@NoArfConstructor,@AllArgsConstructor,@Getter,@Setter,@ToString等注解的作用,并给出了给出使用了注解之后的实体类的形态;

6、在创建mapper逻辑中:又介绍了创建mapper接口只需添加 @Mapper 注解(或全局扫描),无需手动实现类;且我们可以使用注解式SQL(用@Select, @Insert, @Update,@Delete);或少数情况下在resources/mapper/ 目录下创建 XML 映射文件 UserMapper.xml,进行映射式sql的书写(原生框架的逻辑),这便是 XML 映射式 SQL (XML-based Mapping) 。进而又再横向对比了这两种模式且目前的应用场景。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐