【问题标题】:Practical issue with MYSQL auto incremented primary keysMYSQL 自动递增主键的实际问题
【发布时间】:2012-05-16 17:35:03
【问题描述】:

我最近为我的 java 应用编写了一个会计模块。

该模块基于 MYSQL 表和 INNODB 引擎。 其中一个要求当然是每张发票的“运行”ID。 我尝试使用auto_increment 方法和使用序列表来生成id,但是在这两种方法中我都有同样的问题:因为id是在我持久化发票实体时生成的,如果在刷新持久化实体期间发生错误到数据库,auto_id 增加,导致我的一系列发票中出现“漏洞”。 我当然可以放弃这种方法,并为新发票分配以前发票的最大 ID + 1,但我认为这是一种不好的做法。我可以使用哪些其他方法来确保我的发票系列中没有漏洞,假设有时发票可能由于某些验证问题而无法保存。

【问题讨论】:

  • 人们一次又一次地尝试将 auto_increment 用于它不适合的用途。使用另一个为您执行顺序编号的查询,不要依赖 auto_increment。依赖 auto_increment 进行任何类型的顺序编号是不好的做法,并且完全错误地使用该类型的列。

标签: java mysql persistence auto-increment


【解决方案1】:

这本身并不是一个坏习惯,但可能值得使用不同的“传统”数字主键字段来保证表的结构完整性,并为发票编号设置另一个字段。

然后,您可以使用不同的逻辑填充该发票编号,或者只是一个简单的 MAX+1,或者可能通过从键表中查找,以便为不同类型的发票提供不同的编号顺序。

例如:

CREATE TABLE `keys` (
    `id` INT NOT NULL auto_increment,
    `type` VARCHAR(10) NOT NULL,
    `prefix` VARCHAR(10) NOT NULL,
    `value` INT(10) NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`)
);

INSERT INTO `keys` (`type`, `prefix`) VALUES
('Sales Receipt', 'SRI'),
('Sales Invoice', 'SIN'),
('Sales Refund', 'SRF');

然后在你的(伪)代码中你可以做

Database.BeginTransaction;
NewInvNum = Database.Query("SELECT `value` FROM `keys` WHERE `type` = 'SIN'");
MyInvoice.InvoiceNumber = NewInvNum;
Database.SaveInvoice(MyInvoice);
Database.Query("UPDATE `keys` SET `value` = {0} WHERE `type` = 'SIN'", NewInvNum+1);
Database.CommitTransaction;

事务(或其他一些并发保护)很重要,因此当创建多个发票时,它们不会得到相同的编号。

【讨论】:

  • 您将必须在创建发票的整个事务期间锁定密钥表(或至少该密钥的行)。否则这不会生成唯一编号
  • 我其实已经有了这样一张表——序列表。只是我没有按照您的建议明确使用它,而是将我的生成类型设置为@TableGeneration。我宁愿不使用单独的表作为键,因为它可能会导致两个表之间存在一些差异(例如在恢复备份之后)
  • 如果你从备份中恢复一些数据库,我认为你会遇到比不同步的键表更大的问题。
【解决方案2】:

如果您的 MySQL 版本 > 5.0.2,那么您可以尝试使用触发器来正确增加表列的值。 但是您应该记住,将发票编号的生成委托给数据库,我认为这与业务逻辑更相关,这不是一个好主意,因为在失败的情况下您可能会遇到一些问题。所以我宁愿建议你在你的代码中以编程方式生成它。

【讨论】:

    【解决方案3】:

    在spring框架中有类似的东西

    @Transactional(rollbackFor=RuntimeException.class)
    

    所以如果服务器调用出现问题,一切都应该回滚。我确信其他框架也有类似的方法。

    【讨论】:

    • 我真的很喜欢你的方法。我试图为我的 toplink 持久性提供找到这个调用,但我似乎没有找到任何...也许你可以帮我在 toplink 中找到等效的东西?
    • 我认为Toplink是一个持久层,如果我没记错的话它相当于hibernate,@Transactional实际上是放在服务层中,这样如果有多个数据库调用,比如在你的在生成序列号的情况下,然后对表进行更新/插入,如果其中一个失败,则两个调用都回滚。你的服务层是什么?
    • 你是对的,Toplink 对我来说和休眠一样......我的业务层是(如果我正确理解这些术语)是“JAX WS 2.2”我实际上只是使用 Netbeans 提供的作为创建新的 RESTFull Web 服务时的模板...顺便说一句,抱歉回复晚了
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-08
    • 2015-11-13
    • 2011-09-02
    • 1970-01-01
    • 2012-01-21
    • 1970-01-01
    相关资源
    最近更新 更多