You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Liferay Service Builder未获取最新自增ID致主键冲突问题

解决Service Builder与原生SQL插入的主键冲突问题

这个问题的核心原因是Service Builder的id-type="increment"是在应用层面(通过Liferay的Counter表)维护自增序列,而非依赖数据库的AUTO_INCREMENT机制。当Module2直接用原生SQL插入数据时,数据库的AUTO_INCREMENT值会自动增长,但Service Builder缓存的序列值(存在Counter表)并没有同步更新,导致两边的主键序列脱节,最终触发重复主键异常。

下面是几种可行的解决方案,按推荐优先级排序:

1. 修改Service Builder的主键类型为identity(推荐)

这是最彻底的解决方式,让Service Builder完全依赖数据库的自增主键机制,和Module2的原生SQL插入行为保持一致。

修改service.xml中xyzId列的配置:

<entity name="XYZ" local-service="true" remote-service="false" cache-enabled="false" >
    <column name="xyzId" type="long" primary="true" id-type="identity"></column>
    <column name="x" type="long"></column>
    <column name="y" type="long"></column>
    <column name="z" type="long"></column>
</entity>

修改完成后,重新执行Service Builder的构建命令(比如ant build-service或Maven对应构建目标),生成新的Service层代码。

为什么这能解决问题?

当id-type="identity"时,Service Builder在插入数据时不会提前生成主键,而是让数据库通过AUTO_INCREMENT自动生成主键,插入后再将生成的ID返回给实体对象。这样不管是Module1通过Service API插入,还是Module2通过原生SQL插入,都共用数据库的自增序列,不会出现冲突。

2. 手动同步Service Builder的Counter缓存(兼容旧代码场景)

如果因为历史原因无法修改id-type,可以在Module1插入数据前,手动将Service Builder的Counter值同步到数据库最新的AUTO_INCREMENT值。

实现步骤:

  1. 先查询数据库中XYZ表当前的AUTO_INCREMENT值:
private long getCurrentAutoIncrement() throws SQLException {
    String sql = "SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'myDatabase' AND TABLE_NAME = 'XYZ'";
    try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass)) {
        try (Statement stmt = conn.createStatement()) {
            ResultSet rs = stmt.executeQuery(sql);
            if (rs.next()) {
                return rs.getLong("AUTO_INCREMENT");
            }
        }
    }
    return 0;
}
  1. 更新Liferay的Counter值,同步到数据库的自增值:
// 获取数据库最新的自增值
long currentAutoInc = getCurrentAutoIncrement();
// 更新Counter表中对应实体的序列值
CounterLocalServiceUtil.setCounter(XYZ.class.getName(), currentAutoInc);

// 然后执行插入操作
XYZ xyz = XYZLocalServiceUtil.createXYZ(0);
xyz.setX(x);
xyz.setY(y);
xyz.setZ(z);
xyz = XYZLocalServiceUtil.addXYZ(xyz);

注意事项:

  • 这种方式存在并发风险,如果在查询和更新Counter之间有其他操作插入数据,可能还是会出现冲突。
  • 需要确保Module1有数据库查询的权限,并且处理好JDBC连接的资源释放。

3. 统一数据插入入口(理想场景)

如果Module2可以调整,建议统一通过Module1的Service Builder API来插入数据,避免直接操作数据库。这样所有的插入操作都走同一个序列维护逻辑,从根源上杜绝冲突。但如果Module2无法依赖Liferay,这个方案就不可行。


内容的提问来源于stack exchange,提问作者Navankur Chauhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:37:31