我是Hibernate的新手,我正在努力创建一个复合主键,它使用一个唯一的字符串和一个自动递增(按顺序,根据唯一字符串)long。我找到了一些资源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-1
TICK-2
,依此类推。而不是像PROJ-1
PROJ-2
PROJ-3
TICK-4
和TICK-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
}
更新根据评论将故障单编号更改为非唯一值。
更新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
}
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组合中执行此操作的任何建议将非常感激。提前谢谢!
答案 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-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
字段。
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创建一个新表(如果它不存在)。