如何在同一个数据库表上映射两个 JPA 或 Hibernate 实体

Posted

技术标签:

【中文标题】如何在同一个数据库表上映射两个 JPA 或 Hibernate 实体【英文标题】:How to map two JPA or Hibernate entities on the same database table 【发布时间】:2015-05-14 11:39:15 【问题描述】:

在我们的项目中,我们有一个实体“餐厅”,其中包含近 30 个字段(有些与其他实体有关系)。因此,每次我们需要一个“Restaurant”对象时,即使是几个字段,也会检索所有其他字段。这会影响性能。所以,在HBM文件中,我们写了两个类,都指向同一个物理类和同一个数据库表,如下所示。

=== restaurant.hbm.xml ===
<!-- Light Weight Version -->
<class name="com.raj.model.Restaurant" table="RESTAURANTS" entity-name="RestaurantLite" 
                dynamic-update="false" dynamic-insert="false">
<cache usage="read-only"/>
     <!-- few basic properties and relationships -->
</class>

<!-- Restaurant -->
<class name="com.raj.model.Restaurant" table="RESTAURANTS" entity-name="Restaurant">
     <!-- all properties and relationships -->
</class>

在其中一个 DAO 实现中,我们使用的 Criteria 采用“RestaurantLite”并返回如下所示的餐厅列表。

Criteria criteria = session.createCriteria("RestaurantLite");

   // criteria related stuff

return new LinkedHashSet<Restaurant>(criteria.list());

现在我们要删除所有 hbm 文件并使用注释。那么如何使用实体的注释来完成相同的操作呢?我们是否需要创建一个额外的类“RestaurantLite”?如果是这样,上述条件如何返回 'Restaurant' 对象??

【问题讨论】:

这会影响性能。到什么程度?你量过吗?正如 Hibernate 文档所指出的:优化行读取比优化列读取重要得多。但是,仅加载类的某些属性在极端 情况下可能有用。例如,当旧表具有 数百 列并且无法改进数据模型时。 docs.jboss.org/hibernate/orm/3.3/reference/en/html/… @Alan Hay 我们有一个数据库,每个表都有数千条记录。大多数操作都需要上面提到的类。因此,每次检索每家餐厅的所有属性肯定会影响性能。因此,我们在 hbm 文件中提出了该解决方案。 所有数据库都有数千、几万、几百万行。正如 Hibernate 文档指出的那样,列优化很少值得。 en.wikipedia.org/wiki/Program_optimization#When_to_optimize @AlanHay 是的。但是我们不能更改遗留代码。我们必须使用注释并删除 hbm 文件.. 【参考方案1】:

总而言之,以下映射将演示如何将多个实体映射到同一个数据库表:

@Entity(name = "Post")
public class Post 

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    private String name;

    private String description;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public String getDescription() 
        return description;
    

    public void setDescription(String description) 
        this.description = description;
    


@Entity(name = "PostSummary")
@Table(name = "Post")
@Immutable
public class PostSummary 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    


@Entity(name = "UpdatablePostSummary")
@Table(name = "Post")
@DynamicUpdate
public class UpdatablePostSummary 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

Hibernate 可以正常工作:

@Test
public void testOneTableMultipleEntities() 
    doInTransaction(session -> 
        Post post = (Post) session.get(Post.class, 1L);
        PostSummary postSummary = (PostSummary) session.get(PostSummary.class, 1L);
        UpdatablePostSummary updatablePostSummary = (UpdatablePostSummary) session.get(UpdatablePostSummary.class, 1L);
        assertEquals(post.getName(), postSummary.getName());
        assertEquals(post.getName(), updatablePostSummary.getName());
        updatablePostSummary.setName("Hibernate Master Class Tutorial.");
    );

    PostSummary 只是原始实体的只读视图,因此我用@Immutable 对其进行了注释。

    UpdatablePostSummary 标有@DynamicUpdate,因此您也可以从该视图实体传播更改。

GitHub 也提供此测试。

【讨论】:

如果更新 UpdatablePostSummary 会发生什么? Post 实体是否会更新为? (我的意思是在 Hibernate 内部,显然在数据库中这是同一张表)。 在 Hibernate 中,Post 实体必须手动刷新,否则 Hibernate 不会自动刷新。【参考方案2】:

我有一个类似的问题。我有两个类,基类 DocumentContent 和派生类 DocumentContentWithData

DocumentContent 
    private Long documentContentId;
    private InputStream contentStream;
    private File importFile;
    ... and several other attributes


DocumentContentWithData extends DocumentContent 

我为这两个类定义了一个休眠映射,DocumentContent 类定义了一个没有 contentStream 的映射,DocumentContentWithData 类定义了一个映射使用 contentStream

使用类 DocumentContentWithData 保存数据有效,并且还可以使用方法 Session.get(Class, ID) 获取两个类的实例。

但是,以下代码中的查询返回两个实体,一个作为 DocumentContent 的实例,另一个作为 DocumentContentWithData 的实例,尽管只有一个带有 importFile 在数据库中:

CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<DocumentContent> criteriaQuery = criteriaBuilder.createQuery(DocumentContent.class);
Root<DocumentContent> root = criteriaQuery.from(DocumentContent.class);
criteriaQuery.select(root).where(criteriaBuilder.equal(root.get("importFile"), importFile));
Query<DocumentContent> query = session.createQuery(criteriaQuery);
List<DocumentContent> contentList = query.getResultList();

谁能解释一下这种行为。当我没有从 DocumentContent 派生类 DocumentContentWithData 并复制它的所有属性时。然而,这不是一个很好的解决方案。

【讨论】:

这是一个问题,但您已将其作为答案发布。而是将其作为新问题发布。【参考方案3】:

您必须在您的班级上添加注释@Entity,@Table(name="RESTAURANT"),添加注释并将hbm文件中的详细映射替换为 .

这里有一个完整的例子:http://viralpatel.net/blogs/hibernate-many-to-many-annotation-mapping-tutorial/

【讨论】:

这适用于“餐厅”类。 'RestaurantLite'怎么样??

以上是关于如何在同一个数据库表上映射两个 JPA 或 Hibernate 实体的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 JPA @OneToOne 映射中的“孤立”行

JPA:HellWord工程

在 JPA 中映射为实体的关系表

Spring数据JPA @Query映射与命名列

JPA:JPA基本注解

2JPA-Annotation