hibernate状态
自然ID是可以唯一标识实体的一个或一组属性。 我们最多可以为一个实体定义一个自然ID。 当Hibernate在实体映射文件中看到natural-id标记时,它会自动在构成natural-id的属性上创建唯一且非空的约束。 首先,让我们看一下简单和复合自然ID的示例。
简单的自然ID:一个人可以通过其选民ID进行唯一标识。 因此,我们可以说这可能来自他的自然身份。
<!-- Version 1 -->
<hibernate-mapping package="com.pramati.model"><class name="Person" table="PERSON"><id name="id" column="ID"><generator class="native"/></id><natural-id><property name="voterId" type="string" column="VOTER_ID"/></natural-id><property name="name" type="string" column="NAME"/><!-- Other properties -->
</class>
</hibernate-mapping>
复合自然ID:电话号码,即标准代码和固定电话号码的组合,可以形成个人实体的自然ID。
<!-- Version 2 -->
<hibernate-mapping package="com.pramati.model"><class name="Person" table="PERSON"><id name="id" column="ID"><generator class="native"/></id><natural-id><property name="stdCode" type="string" column="STD_CODE"/><property name="landlineNumber" type="string" column="LANDLINE_NUMBER"/></natural-id><property name="name" type="string" column="NAME"/><!-- Other properties -->
</class>
</hibernate-mapping>
因此,Hibernate在stdCode和landlineNumber上创建了一个非空约束。 这些属性对于个人实体应该是唯一的。
默认情况下,自然ID是不可变的。 因此,假设您尝试从数据库中加载人员实体并更改构成自然ID的任何属性,则Hibernate将引发异常。 例如,我们已加载Person并尝试在活动会话中修改其landlineNumber / stdcode,这是我们会得到的例外:
org.hibernate.HibernateException:: An immutable natural identifier
of entity com.pramati.model.Person was altered from abc to xyz
Hibernate 4.1提出了通过bean的natural-id加载实体的功能。 到目前为止,会话缓存将缓存通过当前会话中的get / load加载的对象。 现在,默认情况下还将缓存使用natural-id加载的对象。 以下是会话API的最新功能:
public NaturalIdLoadAccess byNaturalId(String entityName);
public NaturalIdLoadAccess byNaturalId(Class entityClass);public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName);
public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass);
我们可以通过自然ID加载类的实例,如下所示:
// In case of version 1 defined above:
Person person = (Person)session.byNaturalId(Person.class ).using( "voterID", "ZAAXDFT435" ).load();// For Version 1, this can be simplified as:
Person person = (Person)session.bySimpleNaturalId(Person.class ).load("ZAAXDFT435");// In case of version 2 defined above:
Person person = (Person)session.byNaturalId(Person.class ).using("stdCode", "040").using("landlineNumber","2345678").load();
请注意,负载返回的实体不仅是代理,而且是实际实体本身。 如果要获取代理,则必须使用getReference()代替load(),如下所示:
session.byNaturalId(Person.class )
.using("stdCode", "040")
.using("landlineNumber","2345678")
.getReference();
为了保持一致性,新方法也可用于基于标识符的加载。
public IdentifierLoadAccess byId(String entityName);
public IdentifierLoadAccess byId(Class entityClass);
因此,我们可以使用session.byId(Person.class).getReference(id)代替session.load(Person.class,id)。 而不是session.get(Person.class,id)我们可以使用session.byId(Person.class).load(id)
当我们使用查询缓存时,自然ID也很有用。 查询缓存通常没有那么有用,因为它经常变得无效。 假设事件序列如下:
方案1:
1.使用实体natural-id中的属性进行HQL查询以加载人员A。 查询也被缓存,即query.setCacheable(true)
2.将另一个人B插入到人表中。
3.现在,使用与步骤1中相同的查询再次加载A。 问题是:在步骤3中,将进行新的数据库调用以从“人”表中获取A。 是还是不是?
答案是肯定的。 发生的事情是Hibernate在内部维护一个时间戳缓存。 这个时间戳缓存记录特定的Hibernate受管表被修改的时间。 现在在步骤(3),Hibernate看到它是一个缓存的查询。 但是在返回存在于缓存中的实体之前,它会验证缓存的结果相对于表修改时间是否较旧。 现在,由于在缓存后修改了表,因此Hibernate再次进行了新查询。
为了进一步了解这一点,让我们考虑以下情形:让我们只在名称为Rama的Person表中进行记录
方案2:
一个。 执行缓存的查询以获取名称与“ Rama”匹配的人员列表:“来自人员名称为“ Rama”的人员”
b。 也将记录插入名称也为“ Rama”的“个人”中。 这不是问题,因为名称未定义为唯一属性
C。 现在,再次执行步骤(a)中的查询。
最初在步骤(a),我们仅获得记录。 但是在步骤(c)中,即使结果被缓存,Hibernate也会再次命中数据库。 这是由于时间戳缓存无效而发生的。 Hibernate只是在从高速缓存返回实体之前检查表是否已被修改。 但是,无论是更新,插入还是后续操作,都不会影响表的更新方式。
但是在我们看过的前一种情况下,此验证检查似乎完全不相关,因为插入的记录与加载的实体无关。 如果我们使用自然ID来获取实体,则可以绕过此检查。 使用natural-id时,可以保证即使修改数据库后结果也不会改变。 早些时候,当我们不支持使用自然ID加载实体时,我们在Criteria API中提供了使用自然ID的规定。 我们可以在方案1的步骤(1)和(3)中使用以下内容
session.createCriteria(Person.class).add(Restrictions.naturalId().set("stdCode", person.getStdCode()).set("landlineNumber", person.getLandlineNumber())).setCacheable(true).uniqueResult();
当使用自然ID来获取实体时,时间戳缓存检查将被绕过。 因此,现在,如果我用此条件而不是查询替换第一种情况的步骤(1)和(3),则数据库只会被命中一次。 如果我们使用Restrictions.eq而不是Restrictions.naturalId,数据库将被命中两次。 另外,如果您使用的是最新版本的Hibernate,我们可以使用新的API代替构建标准。
翻译自: https://www.javacodegeeks.com/2013/10/natural-ids-in-hibernate.html
hibernate状态