1. 概述

在这篇文章中,我们将讨论如何在Hibernate中使实体、集合或属性变为不可变(Immutable)。默认情况下,字段是可变的,这意味着我们可以执行改变它们状态的操作。

2. Maven

为了启动项目,我们需要在pom.xml中添加必要的依赖。由于我们使用Hibernate,我们需要添加相应的依赖

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.4.2.Final</version>
</dependency>

同时,因为我们将使用HSQLDB,还需要添加:

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.7.1</version>
</dependency>

3. 实体上的注解

首先,定义一个简单的实体类:

@Entity
@Immutable
@Table(name = "events_generated")
public class EventGeneratedId {

    @Id
    @Column(name = "event_generated_id")
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name = "increment", strategy = "increment")
    private Long id;

    @Column(name = "name")
    private String name;
    @Column(name = "description")
    private String description;

    // standard setters and getters
}

你可能已经注意到,我们在实体上添加了Immutable注解。如果我们尝试保存一个Event

@Test
public void addEvent() {
    Event event = new Event();
    event.setId(2L);
    event.setTitle("Public Event");
    session.save(event);
    session.getTransaction().commit();
    session.close();
}

输出应该是这样的:

Hibernate: insert into events (title, event_id) values (?, ?)

即使我们移除注解,输出也不会改变,这意味着无论是否添加实体注解,都不会影响操作结果。

另外,我们在EventGeneratedId实体中添加了GeneratedValue注解,但这仅在创建实体时起作用。这是因为它指定了ID生成策略,由于Immutable注解的存在,其他操作不会影响Id字段。

3.1. 更新实体

现在,保存实体没有问题,让我们尝试更新它:

@Test
public void updateEvent() {
    Event event = (Event) session.createQuery(
      "FROM Event WHERE title='New Event'").list().get(0);
    event.setTitle("Private Event");
    session.update(event);
    session.getTransaction().commit();
}

Hibernate会简单地忽略update操作,而不会抛出异常。然而,如果移除Immutable注解,我们会得到不同的结果:

Hibernate: select ... from events where title='New Event'
Hibernate: update events set title=? where event_id=?

这告诉我们,对象现在是可变的(如果未包含注解,默认值),并且允许更新操作进行。

3.2. 删除实体

对于删除实体:

@Test
public void deleteEvent() {
    Event event = (Event) session.createQuery(
      "FROM Event WHERE title='New Event'").list().get(0);
    session.delete(event);
    session.getTransaction().commit();
    session.close();
}

无论实体是否可变,我们都可以执行删除操作:

Hibernate: select ... from events where title='New Event'
Hibernate: delete from events where event_id=?

4. 注解在集合上

到目前为止,我们已经了解了注解对实体的影响,但正如开头所说,它也可以应用于集合。

首先,向Event类添加一个集合:

@Immutable
public Set<String> getGuestList() {
    return guestList;
}

同样,我们提前添加了注解。如果我们尝试向集合中添加元素:

org.hibernate.HibernateException: 
  changed an immutable collection instance: [com.baeldung.entities.Event.guestList#1]

这次我们收到异常,因为不允许添加或删除集合。

4.1. 删除集合

当集合不可变且设置了*@Cascade*注解时,另一个会抛出异常的情况是试图删除它。

所以,当Immutable存在且尝试删除时:

@Test
public void deleteCascade() {
    Event event = (Event) session.createQuery(
      "FROM Event WHERE title='New Event'").list().get(0);
    String guest = event.getGuestList().iterator().next();
    event.getGuestList().remove(guest);
    
    exception.except(PersistenceException.class);
    session.saveOrUpdate(event);
    session.getTransaction().commit();
}

输出:

org.hibernate.HibernateException: 
  changed an immutable collection instance:
  [com.baeldung.entities.Event.guestList#1]

5. XML 注解说明

最后,配置也可以通过XML的mutable=false属性来完成:

<hibernate-mapping>
    <class name="com.baeldung.entities.Event" mutable="false">
        <id name="id" column="event_id">
            <generator class="increment"/>
        </id>
        <property name="title"/>
    </class>
</hibernate-mapping>

但由于我们主要使用注解方法实现示例,这里不会详细讲解XML配置。

6. 总结

在这篇简短的文章中,我们探讨了Hibernate中的有用Immutable注解,以及它如何帮助我们在数据上定义更好的语义和约束。

如往常一样,所有这些示例和片段的实现可以在GitHub项目中找到。这是一个基于Maven的项目,导入和运行起来应该很容易。