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 找不到BaseEntityBaseEntity 需要 @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? 提交读取乐观事务。 如果提交成功,将$newIDsetID() 存储在您要保存的HibernateBean 中。 最后让 Hibernate 调用插入。

这是我知道的唯一方法。

顺便说一句:如果数据库支持表之间的继承,如 PostgreSQLOracle,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

hibernate - 如何在 mysql 和 oracle 数据库中设置自动增量?

@GeneratedValue

hibernate注解note