在Spring Boot的业务开发中,记录存在性检查与按需创建是非常常见的需求,比如用户注册时判断账号是否已存在、订单创建时判断对应商品库存记录是否存在等,核心逻辑是先查询目标记录是否存在,不存在则执行新增操作。

核心实现思路
整个策略的执行流程可以分为三步:首先根据唯一标识查询对应记录,然后判断查询结果是否为空,最后根据判断结果执行新增或者跳过操作。需要注意的是,在高并发场景下,简单的先查后插可能会出现重复创建的问题,需要额外做并发控制。
基于JPA的实现方案
JPA是Spring Boot默认的持久层框架,提供了丰富的查询和保存方法,实现该策略非常便捷。
1. 定义实体类
首先创建对应的实体类,假设我们需要操作用户表,实体类定义如下:
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
private String password;
private LocalDateTime createTime;
// 省略getter、setter方法
}
2. 定义Repository接口
在Repository中定义根据用户名查询的方法,JPA会根据方法名自动生成查询语句:
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
// 根据用户名查询用户,返回Optional避免空指针
Optional<User> findByUsername(String username);
}
3. 业务逻辑实现
在Service层编写存在性检查与按需创建的逻辑:
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User createUserIfNotExists(String username, String password) {
// 第一步:检查记录是否存在
Optional<User> existUser = userRepository.findByUsername(username);
if (existUser.isPresent()) {
// 已存在则返回已有记录
return existUser.get();
}
// 第二步:不存在则创建新记录
User newUser = new User();
newUser.setUsername(username);
newUser.setPassword(password);
newUser.setCreateTime(LocalDateTime.now());
return userRepository.save(newUser);
}
}
基于MyBatis的实现方案
如果项目使用MyBatis作为持久层框架,实现逻辑类似,只是查询和插入的操作需要通过Mapper来完成。
1. 定义Mapper接口
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
// 根据用户名查询用户
List<User> selectByUsername(@Param("username") String username);
// 插入新用户
int insertUser(User user);
}
2. 编写XML映射文件
对应的Mapper XML文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://ipipp.com/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectByUsername" resultType="com.example.entity.User">
SELECT id, username, password, create_time AS createTime
FROM t_user
WHERE username = #{username}
</select>
<insert id="insertUser" parameterType="com.example.entity.User">
INSERT INTO t_user (username, password, create_time)
VALUES (#{username}, #{password}, #{createTime})
</insert>
</mapper>
3. 业务逻辑实现
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User createUserIfNotExists(String username, String password) {
// 检查记录是否存在
List<User> userList = userMapper.selectByUsername(username);
if (!userList.isEmpty()) {
return userList.get(0);
}
// 不存在则创建
User newUser = new User();
newUser.setUsername(username);
newUser.setPassword(password);
newUser.setCreateTime(LocalDateTime.now());
userMapper.insertUser(newUser);
return newUser;
}
}
并发场景的问题与解决
上述方案在单线程场景下没有问题,但在高并发场景中,比如多个请求同时判断某个用户名不存在,然后同时执行插入操作,就会出现重复记录。常见的解决方式有两种:
- 数据库唯一约束:在数据库表中给需要唯一判断的字段(比如username)添加唯一索引,这样即使并发插入,数据库会抛出唯一约束异常,我们捕获异常后返回已有记录即可。这种方式可靠性最高,建议优先使用。
- 分布式锁:在查询和插入操作之前加分布式锁,保证同一时间只有一个请求能执行这个流程,适合分布式部署的场景。
基于唯一约束的优化实现
以JPA方案为例,添加唯一约束后,优化Service逻辑:
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User createUserIfNotExists(String username, String password) {
// 先尝试查询
Optional<User> existUser = userRepository.findByUsername(username);
if (existUser.isPresent()) {
return existUser.get();
}
// 查询不存在则尝试插入
User newUser = new User();
newUser.setUsername(username);
newUser.setPassword(password);
newUser.setCreateTime(LocalDateTime.now());
try {
return userRepository.save(newUser);
} catch (DataIntegrityViolationException e) {
// 插入失败说明已经被其他线程插入,重新查询返回
return userRepository.findByUsername(username).orElseThrow(() -> new RuntimeException("用户不存在且插入失败"));
}
}
}
方案对比
不同实现方案的优缺点对比如下:
| 实现方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JPA先查后插 | 代码简洁,框架自带支持 | 高并发下有重复风险 | 低并发、内部系统场景 |
| MyBatis先查后插 | SQL可控,灵活性高 | 高并发下有重复风险 | 低并发、需要自定义SQL的场景 |
| 唯一约束+异常捕获 | 并发安全,可靠性高 | 需要处理异常逻辑 | 高并发、对外服务场景 |
在实际开发中,建议优先给数据库对应字段添加唯一约束,再结合先查后插的逻辑,既能减少不必要的插入操作,又能保证并发场景下的数据正确性。
Spring_Boot记录存在性检查按需创建策略数据库操作修改时间:2026-06-13 16:49:02