没有主键和可为空字段的休眠实体

Posted

技术标签:

【中文标题】没有主键和可为空字段的休眠实体【英文标题】:Hibernate Entity with no Primary Key and nullable fields 【发布时间】:2020-02-05 20:12:12 【问题描述】:

我见过类似的案例,但不是这种困难的确切组合,而且我见过的任何解决方案都没有奏效。我正在将一些库从 Spring-data 1.X 和 Hibernate 4.X 升级到 Spring-data 2.X 和 Hibernate 5.X,我认为这与这两个框架之一有关……可能是 Hibernate,得到了很多对版本之间的 ID 更加挑剔,因为在多个字段上使用 @Id 似乎可以正常工作。我想如果需要,我可以将 Hibernate 4 与 Spring-data 2、QueryDSL 4 一起使用吗?

    我有一个基于具有 2 个字段的表的 Hibernate @Entity。 第一个字段不唯一,因此不能是 PK。 另一个字段可以为空,所以不能是PK。 我在生产中使用 Oracle。 我正在使用带有 sql.syntax_ora=true 的 HSQLDB 进行测试

解决方案 #1:两个字段的复合键。 失败#1:同时使用@EmbeddedId/@Embedded 和@IdClass 解决方案,当可空字段为空时,Hibernate 完全放弃该对象并返回一个简单的空值而不是带有空字段的对象。

解决方案 #2:选择一个伪列,而不是 IE @Column(name = "ROWID") 失败#2:Hibernate 无法识别这不是一个真正的列,并在添加表别名时尝试选择它,但由于找不到该字段而失败。

我也尝试添加括号“ROWID()”,但效果相同。

解决方案 #3:使用 @Formula 尝试注入 sn-p 而不是伪造的 @Column 失败#3:我认为这不是一个允许的组合,只会导致错误提示identifier property [] cannot contain formula []

解决方案 #4:使用自定义方言将“ROWID”添加为关键字,而不是依赖它被注册为函数 失败 #4:与解决方案 #2 没有显着差异

我并没有真正弄乱 JpaRepository 用于此表上的 .findAll() 但在大多数情况下我不确定它应该是什么。如果将 ID 设置为 @IdClass,像 #1 这样的东西会更好吗?

我在这里完全不知所措。它实际上是一个我无法真正搞砸的遗留数据库。我需要覆盖生成的 SQL 以选择对象、具有部分可为空的复合 ID 的函数、具有非唯一 ID 的函数,否则使用非持久字段作为 id...rowid/rownum/某种哈希。 ..还有其他选项我看不到这个@Entity 将在哪里使用其明显的 UN-IDable 字段组合?

在将第二列中的空值用作 ID 的一部分以防止失败 #1 时,是否有某种方法可以将其视为另一个值,但仍允许它在对象本身中为空?

        @Bean
        public EntityManagerFactory entityManagerFactory() 
            final LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
            //bean.setJpaDialect(new HibernateJpaDialect());
            bean.setDataSource(dataSource());
            bean.setJpaVendorAdapter(jpaVendorAdapter());
            bean.setPackagesToScan("com.company.core.domain");

            final Properties jpaProperties = new Properties();
            //jpaProperties.setProperty("javax.persistence.schema-generation.database.action", "create");
            jpaProperties.setProperty("hibernate.show_sql", "true");
            jpaProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");

            bean.setJpaProperties(jpaProperties);
            bean.afterPropertiesSet();

            return bean.getObject();
        
        @Bean
        public DataSource dataSource() 
            final String uuid = UUID.randomUUID().toString();
            return new EmbeddedDatabaseBuilder().addScript("schema.sql").setName(uuid + ";sql.syntax_ora=true;hsqldb.sqllog=3").build();
        

【问题讨论】:

使用JPA最好的方式是关系模型,尽量有一列有主键,表必须有3行。除了条件查询的实现之外,您将来定义它们会更容易。 @JLazar0 我的意思是,理想情况下是的,如果我只有一个 ID 字段,我根本不会有任何问题,但如果我无法更改看起来与使用“ROWID”或“ROWNUM”黑客旨在解决。但是,由于某种原因,它不起作用,这对我来说是个大问题。如果它不起作用,那么经常被接受的答案是什么? 如果您无法更改模型,则 JPA 不是您项目的正确框架。实体需要物理 PK。 【参考方案1】:

我在这里设法做的最好的事情是调整我的@Entity 以包含 ROWID 伪列。不幸的是,虽然 HSQLDB 支持某种类型的 ROWNUM,但它仅在不以 tableAlias 为前缀时才有效,而 Hibernate 将始终这样做。而且它根本不支持 ROWID。但是将在 Oracle 中工作的 ROWID 在 HSQLDB 中不起作用,因此技巧是手动设置我的嵌入式 HSQLDB,其中 ROWID 列设置为自动递增,并附有大量注释,这将在 Oracle 上工作(TM)而不修改模型.

是的,正确的答案是在Oracle中的表中添加一个真正的PK。或者,使用嵌入式 Oracle(通过测试容器的 IE dockerized 实例)也可以正常工作,但许可并不是特别适合......或者我被告知。但是,尽管在测试中故意使用与运行时不同的模式很麻烦,但它确实有效,并且在多个地方都有详细记录,因此没有人会对任何事情感到惊讶。

或者……你知道……只是不要升级 Hibernate。该实体通过旧版本 Hibernate for YEARS 中的一个错误偷偷摸摸,其中 Mapper 接受多个 @Id 字段作为复合键,但加载器没有将 null 字段识别为键的一部分,因此它允许对象重新水化。

【讨论】:

以上是关于没有主键和可为空字段的休眠实体的主要内容,如果未能解决你的问题,请参考以下文章

JPA 可嵌入 PK 和可为空的字段

UML 与泛型和可为空字段的关系

newtonjson序列化空DataTable 没有字段

数据库是重点

主键外键和索引的区别?

主键外键和索引的区别?