【问题标题】:Creating a composite Primary Key with a unique string and sequential, corresponding number创建具有唯一字符串和顺序对应数字的复合主键
【发布时间】:2018-07-22 01:56:12
【问题描述】:

我是 Hibernate 的新手,正在努力寻找一种方法来创建复合主键,该主键使用唯一字符串和自动递增(按唯一字符串顺序)长。我找到了一些资源here 和here,但仍然遇到问题。

我正在使用 Java 8、Hibernate 5、Spring Boot 1.5、Spring JDBC 5 和 MySQL 连接器 6,以及 MySQL 5.7

问题

我正在尝试为映射到票务表的实体创建主键。我希望主键类似于PROJ-1,我在很多地方都看到过,我喜欢这种约定。

理想情况下,我希望 PK 有两个部分。我想要一个唯一的项目代码,如 PROJ 和一个随该项目代码递增的票号,每张票应该是连续的:PROJ-1 PROJ-2 PROJ-3 但如果我制作另一个项目代码,如 TICK 然后它应该从一个开始:TICK-1TICK-2等等。而不是像PROJ-1PROJ-2PROJ-3TICK-4和TICK-5这样的条目。

我不确定如何使用休眠来完成此操作。

我目前的代码是这样的。

实体

更新 2 使生成的 TicketIdentifier 独一无二。

@Entity
@Table(name = "support_ticket")
public class SupportTicket implements Serializable {
    private TicketIdentifier id;
    ... other irrelevant properties

    @EmbeddedId
    @AttributeOverride(name = "id", column = @Column(unique = true, nullable = false))
    public TicketIdentifier getId() {
        return id;
    }

    public void setId(TicketIdentifier id) {
      this.id = id;
    }

    ... other irrelevant getters and setters
}

可嵌入类

更新根据 cmets 将票号更改为非唯一值。

更新 2 使项目非唯一,并从票证方面做到这一点

@Embeddable
public class TicketIdentifier implements Serializable {
    String projectId;
    Long ticketNum;

    public TicketIdentifier() {}

    public TicketIdentifier(String projectId) {
        this.projectId = projectId;
    }

    @Column(name = "project", nullable = false, length = 10)
    public String getProjectId() {
        return projectId;
    }

    public void setProjectId(String projectId) {
        this.projectId = projectId;
    }

    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "ticket", nullable = false)
    public Long getTicketNum() {
        return ticketNum;
    }

    public void setTicketNum(Long ticketNum) {
        this.ticketNum = ticketNum;
    }
    ... implementing of hashcode and equals
}

表 DDL

CREATE TABLE support_ticket
(
  project         VARCHAR(10)  NOT NULL,
  ticket          BIGINT       NOT NULL,
  ... irrelevant stuff
  PRIMARY KEY (project, ticket),
  CONSTRAINT UK_6qwbkx66syjgp0jcxn16vkqkd
)

但正如您所见,票只是 bigint 而不是看起来更像 bigint auto_increment 不确定我在这里做错了什么。这非常接近我想我想要的。除了票证应该自动递增(如“问题”部分所述),我不知道如何(或者如果这是正确的地方)在项目和票证列组合之间放置破折号。

此外,在下面的屏幕截图中,我也无法做两件事:自动增加 ticket 的值,以及重复使用相同的项目代码。我可以在 DDL 中看到后者是因为项目必须是唯一的,但我希望它只是 唯一的组合。

更新

我能够获得唯一的组合(我认为),但仍然无法按照我想要的方式自动增加 ticket 的值。

任何关于如何在 Java 和/或 Hibernate 组合中执行此操作的建议将不胜感激。提前谢谢你!

【问题讨论】:

标签: java mysql hibernate jpa


【解决方案1】:

实现此目的的一种方法是为您的主键使用自定义生成策略。 (注意@GeneratedValue只能和@Id注解一起使用)

要实现自定义生成策略,您需要创建一个实现IdentifierGenerator 接口或扩展现有生成器之一的类,例如SequenceStyleGenerator。然后你可以使用@GenericGenerator注解来定义它:

@Id
@Column(name = "ticket", nullable = false, insertable = false, updatable = false)
@GenericGenerator(
    name = "custom-sequence",
    strategy = "your.package.CustomGenerator"
)
@GeneratedValue(generator = "custom-sequence")
public String getTicketNum() {
    return ticketNum;
}

为了通过示例进行演示,我已通过以下方式修改了您的 SupportTicket 实体:

@Entity
@Table(name = "support_ticket")
public class SupportTicket implements Serializable {

    private String projectId;
    private String ticketNum;

    public SupportTicket() {
    }

    public SupportTicket(String projectId) {
        this.projectId = projectId;
    }

    @Column(name = "project", unique = true, nullable = false, length = 10)
    public String getProjectId() {
        return projectId;
    }

    public void setProjectId(String projectId) {
        this.projectId = projectId;
    }

    @Id
    @Column(name = "ticket", nullable = false, insertable = false, updatable = false)
    @GenericGenerator(
            name = "custom-sequence",
            strategy = "your.package.CustomGenerator"
    )
    @GeneratedValue(generator = "custom-sequence")
    public String getTicketNum() {
        return ticketNum;
    }

    public void setTicketNum(String ticketNum) {
        this.ticketNum = ticketNum;
    }
}

ticket 列现在是一个主键,将保存 PROJ-1 和 TICK-1 等由 CustomGenerator 生成的值。所以我将列类型更改为VARCHAR。

最后一步是实现CustomGenerator。由于您使用的是 MySQL,并且它没有序列,因此您必须模拟它们。

public class CustomGenerator implements IdentifierGenerator {

    @Override
    public synchronized Serializable generate(SessionImplementor session, Object obj) {
        if (obj instanceof SupportTicket) {
            String sequenceName = ((SupportTicket) obj).getProjectId();
            Serializable result = null;
            try {
                Connection c = session.connection();
                Statement s = c.createStatement();
                s.execute("CREATE TABLE IF NOT EXISTS sequences\n" +
                        "     (\n" +
                        "         name VARCHAR(70) NOT NULL UNIQUE,\n" +
                        "         next INT NOT NULL\n" +
                        "     );");
                s.execute("INSERT INTO sequences (name, next)\n" +
                        "VALUES ('" + sequenceName + "', @current := 1)\n" +
                        "ON DUPLICATE KEY UPDATE \n" +
                        "next = @current := next + 1");
                ResultSet resultSet = s.executeQuery("SELECT @current");
                if (resultSet.next()) {
                    int nextValue = resultSet.getInt(1);
                    result = sequenceName + "-" + nextValue;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return result;
        } else {
            return null;
        }
    }
}

此自定义生成器将创建一个sequences 表,其中每一行对应一个特定的projectId,并使用它来增加ticketNum 字段。

更新:还可以为sequences 表创建一个实体并使用休眠会话来管理它:

@Override
public synchronized Serializable generate(SessionImplementor session, Object obj) {
    if (obj instanceof SupportTicket) {
        Session s = (Session) session;
        String sequenceName = ((SupportTicket) obj).getProjectId();
        Sequences sequences = (Sequences) s.get(Sequences.class, sequenceName);
        long next = 1;
        if (sequences == null) {
            sequences = new Sequences(sequenceName, next);
        } else {
            next = sequences.getNext() + 1;
            sequences.setNext(next);
        }
        s.saveOrUpdate(sequences);
        return sequenceName + "-" + next;
    } else {
        return null;
    }
}

不要忘记将spring.jpa.properties.hibernate.hbm2ddl.auto=update 添加到您的属性中,以强制休眠在新表不存在时创建它。

【讨论】:

  • 这让我非常接近,但我使用的是 MySQL 而不是 Postgres。您能否更新有关如何为 MySQL 创建序列的答案?此外,SequenceGenerator 似乎已被弃用,所以我使用了 SequenceStyleGenerator,它的 generate 方法与您展示的完全一样,但您也可以更新它。
  • @mrClean 感谢您的反馈!我已经根据 MySQL 语法更新了我的答案。 MySQL 没有序列,但这不是问题 - 您可以创建一个表并将所有递增的值存储在其中。
  • 非常感谢,这就是我要找的!没有办法将该表创建语句移动到实体并让休眠管理它,是吗?那将是清洁的奖励,但如果那不可能,我可能会理解。不过,我会继续并将其标记为完成,因为它解决了我的问题。
  • @mrClean 是的,这是可能的。检查我的更新。但是你应该测试一下
  • 完美,真的很干净。感谢您的帮助,我一定会进行测试。对于这样一个干净的解决方案,我已为您的答案 +1 投票。
猜你喜欢
  • 2011-03-26
  • 2020-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-10
  • 2016-02-15
  • 2015-06-14
  • 1970-01-01
相关资源
最近更新 更多