序
自增 ID , 是一种常见的 ID 生成方式
以 pgsql 为例:
CREATE TABLE COMPANY(
ID serial PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
);
其中 ID 为自增字段;
但可能,我们会在一些边界情况(例如,数据库迁移)遇到这样的报错:
could not execute statement [
错误: 重复键违反唯一约束"company_pkey"
详细:键值"(id)=(1)" 已经存在
] [insert into public.company (name) values (?)]
数据在插入时,没有按照 max + 1 的预期添加
问题复现
-
初始化数据库,添加默认数据
CREATE TABLE COMPANY( ID serial PRIMARY KEY NOT NULL, NAME TEXT NOT NULL, AGE INT NOT NULL, ADDRESS CHAR(50), SALARY REAL ); INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1, 'Paul', 32, 'California', 20000.00 ); INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (2, 'Allen', 25, 'Texas', 15000.00 ); INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (3, 'Teddy', 23, 'Norway', 20000.00 ); INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 ); INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (5, 'David', 27, 'Texas', 85000.00 ); INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (6, 'Kim', 22, 'South-Hall', 45000.00 ); INSERT INTO COMPANY VALUES (7, 'James', 24, 'Houston', 10000.00 ); -
java实体类
@Entity
@Table(name = "company", schema = "public")
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Company() {
}
// getter/setter
}
Repository和Service
@Repository
public interface CompanyRepository extends JpaRepository<Company, Long> {}
@Service
public class CompanyService {
private final CompanyRepository companyRepository;
@Autowired
public CompanyService(CompanyRepository companyRepository) {
this.companyRepository = companyRepository;
}
public Company addCompany(Company company) {
return companyRepository.save(company);
}
}
-
运行
Company company = new Company(); company.setName("Company 2"); companyService.addCompany(company); -
报错
could not execute statement [ 错误: 重复键违反唯一约束"company_pkey" 详细:键值"(id)=(1)" 已经存在 ] [insert into public.company (name) values (?)]
问题分析
以 PostgreSQ L数据库为例。
# ...
INSERT INTO COMPANY VALUES (7, 'James', 24, 'Houston', 10000.00 );
数据库初始化的时候,已经预先插入了 7 条数据。
但是在代码运行的时候,生成的 ID 似乎是重新还是计数的。
解决方案
SEQUENCE
一个序列是一个独立于表的对象,它用于生成唯一的整数。
当使用 SERIAL 数据类型为一个表列创建一个自增字段时,PostgreSQL会自动创建一个序列。
但是,在手动插入数据时,这个序列并不会自动更新。
在 pgAdmin 中查看这个序列的属性,会有更直观的体会
注意 currentValue 这个值, 在初始化插入的时候并不会自动更新到 8
解决这个问题,我们可以使用 ALTER SEQUENCE 语句设置序列的字段的值。
ALTER SEQUENCE company_id_seq RESTART WITH 8;
或者在可视化界面中直接修改 .
其他
有的开发人员,可能会考虑,在代码层解决这一问题.
@Entity
@Table(name = "company", schema = "public")
public class Company {
@Id
private Long id;
}
@Repository
public interface CompanyRepository extends JpaRepository<Company, Long> {
@Query("SELECT MAX(c.id) FROM Company c") // JPA Query to find the max ID
Long findMaxId();
}
@Service
public class CompanyService {
// ...
public Company addCompany(Company company) {
Long maxId = companyRepository.findMaxId(); // New method to find max ID
maxId = (maxId == null) ? 1 : maxId + 1; // If maxId is null, start from 1
company.setId(maxId); // Set the new ID
return companyRepository.save(company); // Save the new company object
}
}
:warning: 请注意,这种方法绕过了数据库的自增机制,如果有多个应用实例或者多个线程同时插入数据,可能会导致ID冲突。因此,在高并发的生产环境中,这可能不是最佳实践。
结
在对数据库进行直接的操作时,
除了表以外, 多注意一些独立于表的对象,
可以避免一些意料之外的错误发生