创建具有唯一字符串和顺序相应数字

时间:2018-02-11 22:43:55

标签: java mysql hibernate jpa

我是Hibernate的新手,我正在努力创建一个复合主键,它使用一个唯一的字符串和一个自动递增(按顺序,根据唯一字符串)long。我找到了一些资源herehere,但我仍遇到问题。

我正在使用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-1 TICK-2,依此类推。而不是像PROJ-1 PROJ-2 PROJ-3 TICK-4TICK-5这样的条目。

我不知道如何使用hibernate完成此操作。

到目前为止我的代码就是这个。

实体

更新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
}

Embeddable Class

更新根据评论将故障单编号更改为非唯一值。

更新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中看到后者是因为项目必须是唯一的,但我希望它只是一个唯一组合

Error when trying to insert data

更新

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

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

1 个答案:

答案 0 :(得分: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-1CustomGenerator等值。所以我已将列类型更改为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字段。

UPDATE:也可以为sequences表创建一个实体,并使用hibernate会话来管理它:

@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添加到您的属性中,以强制hibernate创建一个新表(如果它不存在)。