You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

MyBatis事务回滚时序列未回滚问题求助

问题分析与解决方案

这事儿其实不是Spring或MyBatis的锅,而是数据库序列的固有特性导致的——序列的nextval调用是原子且非事务性的,一旦你调用了它,不管后续事务是提交还是回滚,这个序列值都被“消耗”了,数据库不会因为事务回滚而把序列值退回去。所以你的单元测试每次跑,哪怕事务回滚了,序列还是会一直递增。

针对测试场景的解决办法

1. 测试后手动重置序列

既然序列不会自动回滚,那我们就在每次测试结束后手动把序列重置到初始值,保证下一次测试的环境是干净的。

比如你的测试类可以加个@AfterEach方法,用JdbcTemplate执行序列重置的SQL:

@RunWith(SpringRunner.class)
@ActiveProfiles(profiles = "local")
@SpringBootTest
@Slf4j
@Transactional
public class MultimediaMapperTest {

    @Autowired
    private MultimediaMapper multimediaMapper;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 你的测试方法...

    @AfterEach
    void resetSequence() {
        // 根据你的数据库类型调整SQL:
        // Oracle 语法
        jdbcTemplate.execute("ALTER SEQUENCE multimedia_seq RESTART WITH 1");
        // PostgreSQL 语法
        // jdbcTemplate.execute("ALTER SEQUENCE multimedia_seq RESTART WITH 1");
        // 如果是MySQL自增主键(非序列)
        // jdbcTemplate.execute("ALTER TABLE multimedia AUTO_INCREMENT = 1");
    }
}

2. 改用MyBatis的useGeneratedKeys(如果数据库支持)

如果你的数据库支持JDBC的getGeneratedKeys机制(比如Oracle配合触发器、PostgreSQL的序列自增列),可以替换掉@SelectKey,让MyBatis自动处理主键生成,这样虽然序列还是会递增,但写法更规范,而且某些场景下可能减少手动调用nextval的次数:

比如映射器接口的方法可以改成:

@Insert("INSERT INTO multimedia (name) VALUES (#{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Multimedia multimedia);

前提是你要在数据库端配置好序列和触发器(比如Oracle),或者把id列设为SERIAL/IDENTITY类型(PostgreSQL/MySQL),让数据库自动为列赋值,MyBatis通过JDBC获取生成的主键。

3. 测试使用嵌入式数据库(可选)

如果你的测试允许用嵌入式数据库(比如H2),可以在测试配置里用H2模拟生产数据库的序列行为,不过H2的序列在事务回滚时会回滚,刚好能满足你的测试需求。但要注意,嵌入式数据库和生产数据库的行为可能有差异,适合简单的单元测试场景。

总结

序列的递增不随事务回滚是数据库的设计决定,目的是保证序列生成的性能和唯一性。在测试场景下,手动重置序列是最直接且贴近生产环境的解决方案,能保证每次测试的独立性。

内容的提问来源于stack exchange,提问作者Jose A. Matarán

火山引擎 最新活动