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

30
在我们的项目中,有一个名为“餐厅”的实体,它有近30个字段(其中一些与其他实体有关系)。因此,每次我们需要一个“餐厅”对象,即使只需要几个字段,所有其他字段也会被检索,这影响了性能。因此,在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'并返回餐厅列表,如下所示。

在DAO的一种实现中,我们使用了条件查询(Criteria),该查询接受'RestaurantLite'参数并返回一组餐厅,具体见下文。

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

   // criteria related stuff

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

现在我们希望删除所有的hbm文件并使用注释。那么如何使用注释来处理实体?我们需要创建一个额外的类'RestaurantLite'吗?如果是这样,那么如何满足上述标准返回'Restaurant'对象?


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

61

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

@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.");
    });
}
  1. PostSummary只是对原始实体的只读视图,因此我使用了@Immutable进行注释。

  2. UpdatablePostSummary标记为@DynamicUpdate,因此您也可以从这个视图实体中传播更改。

这个测试也可以在GitHub上找到。


1
如果您更新了一个UpdatablePostSummary,那么会发生什么?Post实体也会被更新吗?(我的意思是在Hibernate内部,在数据库中显然是同一张表)。 - Gaël J
6
在Hibernate中,Post实体必须手动刷新,否则Hibernate不会自动刷新它。 - Vlad Mihalcea

0

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

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

DocumentContentWithData extends DocumentContent {}

我为这两个类定义了一个Hibernate映射,对于类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类并复制所有属性时,它可以工作。但是这不是一个很好的解决方案。


1
这是一个问题,但你把它发成了回答。请重新发一个新的问题。 - Task

-1

那适用于“餐厅”类。那“RestaurantLite”呢? - Raj44

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接