MySQL 上的 @GeneratedValue 多态抽象超类
Posted
技术标签:
【中文标题】MySQL 上的 @GeneratedValue 多态抽象超类【英文标题】:@GeneratedValue polymorphic abstract superclass over MySQL 【发布时间】:2014-10-04 19:52:47 【问题描述】:在使用 Hibernate 和 mysql 的 Spring MVC 应用程序中,我有一个抽象超类 BaseEntity
,它管理模型中所有其他实体的 ID 值。 id
字段使用 @GeneratedValue
。每当我的代码尝试保存扩展BaseEntity
的任何子类时,我都会遇到问题。问题在于为@GeneratedValue
选择GenerationType
。
在我的代码中BaseEntity
的子类尝试保存到底层 MySQL 数据库的每个地方,我都会收到以下错误:
ERROR SqlExceptionHelper - Table 'docbd.hibernate_sequences' doesn't exist
我在 SO 和 google 上阅读了很多关于此的帖子,但它们要么处理其他数据库(不是 MySQL),要么不处理抽象超类。我无法通过使用GenerationType.IDENTITY
来解决问题,因为我正在使用抽象超类来管理模型中所有实体的id
字段。同样,我不能使用GenerationType.SEQUENCE
,因为 MySQL 不支持序列。
那么我该如何解决这个问题呢?
这是BaseEntity.java
的代码:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
protected Integer id;
public void setId(Integer id) this.id = id;
public Integer getId() return id;
public boolean isNew() return (this.id == null);
以下是扩展BaseEntity
的实体之一的代码示例:
@Entity
@Table(name = "ccd")
public class CCD extends BaseEntity
//other stuff
这里是 DDL:
CREATE TABLE IF NOT EXISTS ccd(
id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
#other stuff
)engine=InnoDB;SHOW WARNINGS;
这是 DAO 中的 JPQL 代码:
@Override
@Transactional
public void saveCCD(CCD ccd)
if (ccd.getId() == null)
System.out.println("[[[[[[[[[[[[ about to persist CCD ]]]]]]]]]]]]]]]]]]]]");
this.em.persist(ccd);
this.em.flush();
else
System.out.println("]]]]]]]]]]]]]]]]]] about to merge CCD [[[[[[[[[[[[[[[[[[[[[");
this.em.merge(ccd);
this.em.flush();
编辑:
在这种情况下我不能使用@MappedSuperClass
的原因是我需要有ManyToOne
关系,允许多个子类型可以互换使用。以下面的AccessLog
类为例。它有一个actor_entity
和一个target_entity
。可以有多种类型的参与者实体和多种类型的目标实体,但它们都继承自BaseEntity
。这种继承使得 MySQL 中的底层 accesslogs
数据表只有一个 actor_entity_id
字段和一个 target_entity_id
字段,而不是每个字段都必须有几个字段。当我将BaseEntity
上方的@Entity
更改为@MappedSuperClass
时,会抛出一个不同的错误,表明AccessLog
找不到BaseEntity
。 BaseEntity
需要 @Entity
注释才能使 AccessLog
具有多态属性。
@Entity
@Table(name = "accesslogs")
public class AccessLog extends BaseEntity
@ManyToOne
@JoinColumn(name = "actorentity_id")
private BaseEntity actor_entity;
@ManyToOne
@JoinColumn(name = "targetentity_id")
private BaseEntity target_entity;
@Column(name="action_code")
private String action;
//getters, setters, & other stuff
第二次编辑:
根据 JBNizet 的建议,我创建了一个 hibernate_sequences 表,如下所示:
CREATE TABLE IF NOT EXISTS hibernate_sequences(
sequence_next_hi_value int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
)engine=InnoDB;SHOW WARNINGS;
但现在我收到以下错误:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'sequence_name' in 'where clause'
这是导致错误的休眠 sql,后面是堆栈跟踪的下 2 行:
Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'BaseEntity' for update
ERROR MultipleHiLoPerTableGenerator - HHH000351: Could not read or init a hi value
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'sequence_name' in 'where clause'
我该如何解决这个问题?
【问题讨论】:
你真的不应该使用实体继承来做到这一点。 BaseEntity 应使用@Mappedsuperclass
注释,而不是@Entity
。
是的,您正在寻找映射的超类。经验法则:当您需要对基类型执行查询时使用继承,但不知道将返回哪些具体实例。
为什么你有一个 BaseEntity
来管理模型中所有其他实体的 ID 值?
@JBNizet 我刚刚在上面的原始帖子末尾添加了一个编辑,解释了为什么在这种情况下我不能使用@MappedSuperClass
。这是否有助于您了解如何解决我的问题?
@Henning 我正在使用继承,因为我有像AccessLog
这样的类需要具有多态属性,所有这些都继承自BaseEntity
。我在上面的原始帖子中添加了一个编辑来解释这一点。这些额外信息是否有助于您了解如何解决我的问题?
【参考方案1】:
真是一团糟……AUTO_INCREMENT
是 MySQL 的隐藏序列。根本的问题是MySQL
不能同时插入和返回PK,但是Hibernate在INSERT
创建一个新实体时需要这个。
你遇到的问题:
-
如果 Hibernate 保存一个新实体,他会尝试将 id 设置为新的 EntityBean。因此,在休眠将新元组保存到表之前,休眠必须读取数据库将使用的 ID。
如果你有多个服务器访问数据库,你应该让hibernate的会话工厂决定使用内置序列(AUTO-INCREMENT)或者让hibernate决定(
GenerationType.AUTO
/GenerationType.IDENTITY
)打开多大保留 PK 的范围是(数据库架构师的工作)。 (我们有大约 20 台服务器到一个数据库,所以在一个使用良好的表上,我们使用 +100 的 PK 距离)。如果只有一台服务器可以访问数据库GenerationType.TABLE
应该是正确的。
Hibernate 必须使用 max(*)+1
自己计算下一个 id 但是:
max(*)+1
/结果相同怎么办?右:对insert
的最后一次尝试将失败。
所以你需要在数据库中有一个表LAST_IDS
来存储最后一个表-PK。如果您想添加一个,您必须执行以下步骤:
-
开始读取乐观事务。
从 LAST_IDS 中选择 MAX(address_id)
将最大值存储在 java 变量中,即:$OldID。
$NewID = $OldID + 1。(悲观锁定+100)
UPDATE LAST_IDS SET address_id=
$newID
WHERE address_id= $oldID
?
提交读取乐观事务。
如果提交成功,将$newID
到setID()
存储在您要保存的HibernateBean 中。
最后让 Hibernate 调用插入。
这是我知道的唯一方法。
顺便说一句:如果数据库支持表之间的继承,如 PostgreSQL
或 Oracle
,Hibernate-Entitys 应仅使用继承。
【讨论】:
这取决于很多问题,比如你使用事务,什么锁定,有多少服务器并发请求,pk-fields是什么类型,你使用innodb ... +1 感谢您抽出宝贵时间思考我的问题。我通过简单地创建错误消息中指定的表和字段来解决错误。【参考方案2】:因为您使用 TABLE 标识符生成器,所以您需要创建该表。如果您不使用enhanced identifier generators,您可能会使用MultipleHiLoPerTableGenerator。
MultipleHiLoPerTableGenerator 可以为所有表标识符生成器使用一个表。
我的建议是从集成测试中获取表 ddl,以防您使用 hbmddl 构建测试模式。如果使用flyway或liquibase进行测试,可以添加maven plugin生成ddl schema。
一旦你有了模式,你需要使用确切的创建表命令并将它添加到你的 MySQL 数据库中。
【讨论】:
+1 感谢您抽出宝贵时间回答我的问题。在看到您的回复之前,我能够解决错误。我刚刚创建了一个带有sequence_next_hi_value
字段和sequence_name
字段的hibernate_sequences
表。 Hibernate 似乎负责其余的工作。我想知道它是否在幕后做你在答案和博客中描述的事情。
是的,应该。您应该知道 TABLE 生成器使用锁(选择更新)查询和一个额外的连接来提交与当前 tx 隔离的 id 生成。因此,虽然它是可移植的,但您可能会付出性能成本。以上是关于MySQL 上的 @GeneratedValue 多态抽象超类的主要内容,如果未能解决你的问题,请参考以下文章
@EmbeddedId 和 @Embeddable 中的 @GeneratedValue
HSQL 中的@GeneratedValue(strategy=GenerationType.IDENTITY) 不会自动生成主键
Springboot集成Mybatis ID生成策略注解 @GeneratedValue