spring + hibernate 设置更新指定字段

本文介绍如何在Spring和Hibernate框架中实现仅更新实体的部分字段,而非全部字段。通过自定义拦截器dirtyFindInterceptor,结合dynamic-update配置,实现了根据实体状态智能选择更新策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

spring + hibernate 设置更新指定字段
hibernate的update方式默认的是更新所有字段的,这导致一个很大的问题,当想更新部分字段时必须要把整个entity先load一遍,然后set相应的更新字段再保存
这一步load数据势必将数据库的操作增加一倍,而且基本上是多余的,最近看了下hibernate源码,发现hibernate有设置指定更新的方式,具体方式如下

org.hibernate.persister.entity.AbstractEntityPersister中定义了基类的更新方法
public void update(
final Serializable id,
final Object[] fields,
final int[] dirtyFields,
final boolean hasDirtyCollection,
final Object[] oldFields,
final Object oldVersion,
final Object object,
final Object rowId,
final SessionImplementor session) throws HibernateException {

其中的一段代码:
if ( ( entityMetamodel.isDynamicUpdate() && dirtyFields != null ) ) {
// We need to generate the UPDATE SQL when dynamic-update="true"
propsToUpdate = getPropertiesToUpdate( dirtyFields, hasDirtyCollection );
// don't need to check laziness (dirty checking algorithm handles that)
updateStrings = new String[span];
for ( int j = 0; j < span; j++ ) {
updateStrings[j] = tableUpdateNeeded[j] ?
generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null ) :
null;
}
}
else if ( ! isModifiableEntity( entry ) ) {
// We need to generate UPDATE SQL when a non-modifiable entity (e.g., read-only or immutable)
// needs:
// - to have references to transient entities set to null before being deleted
// - to have version incremented do to a "dirty" association
// If dirtyFields == null, then that means that there are no dirty properties to
// to be updated; an empty array for the dirty fields needs to be passed to
// getPropertiesToUpdate() instead of null.
propsToUpdate = getPropertiesToUpdate(
( dirtyFields == null ? ArrayHelper.EMPTY_INT_ARRAY : dirtyFields ),
hasDirtyCollection
);
// don't need to check laziness (dirty checking algorithm handles that)
updateStrings = new String[span];
for ( int j = 0; j < span; j++ ) {
updateStrings[j] = tableUpdateNeeded[j] ?
generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null ) :
null;
}
}
else {
// For the case of dynamic-update="false", or no snapshot, we use the static SQL
updateStrings = getUpdateStrings(
rowId != null,
hasUninitializedLazyProperties( object )
);
propsToUpdate = getPropertyUpdateability( object );
}

可以看到hibernate提供了3种的获得更新字段方式,默认情况下会进入最后的else中获得所有的字段,中间else if 的方式可以看到注解里的说明,应该是为read-only的对象准备,此处没有详细深入,而我们需要的是第一种方式
当对象的mapping文件配置了dynamic-update="true" 且dirtyFields存在时hibernate便会根据dirtyFields获得需要更新的字段
propsToUpdate = getPropertiesToUpdate( dirtyFields, hasDirtyCollection );
这里的dirtyFields便是entity对应的字段下标
一直向前追溯dirtyFields的来源可以在org.hibernate.event.internal.DefaultFlushEntityEventListener中找到protected void dirtyCheck(final FlushEntityEvent event) throws HibernateException函数
关键代码:
int[] dirtyProperties = session.getInterceptor().findDirty(
entity,
id,
values,
loadedState,
persister.getPropertyNames(),
persister.getPropertyTypes()
);

hibernate在session中做了一个拦截器,可以在操作前做一些处理。
session由sessionFactory创建,而interceptor则由从Configuration注入
这样就很清楚了,我们要做的操作只要在Configuration中注入我们自定义的interceptor就行了

先写一个interceptor继承与emtpyInterceptor
package net.esj.basic.dao.hibernate.sessioninterceptor;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import net.esj.basic.pojo.AbstractPojo;
import net.esj.basic.pojo.UpdateType;
import net.esj.basic.pojo.Updater;
import net.esj.basic.utils.JavaProtoTypeHelper;

import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("dirtyFindInterceptor")
@Scope("prototype")
public class DirtyFindInterceptor extends EmptyInterceptor {

@Override
public int[] findDirty(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) {
if(entity instanceof AbstractPojo){
Updater updater = ((AbstractPojo)entity).getUpdater();
UpdateType type =((AbstractPojo)entity).getUpdateType();
if(type==UpdateType.AUTO){
type = checkUpdateType(entity, id, currentState, previousState, propertyNames, types);
}
switch(type){
case BY_DIFF:
return findDirtyByDiff(entity, id, currentState, previousState, propertyNames, types);
case BY_UPDATER:
return findDirtyByUpdater(entity, id, currentState, previousState, propertyNames, types, ((AbstractPojo)entity));
case NONE:
return super.findDirty(entity, id, currentState, previousState, propertyNames,
types);
}
}
return super.findDirty(entity, id, currentState, previousState, propertyNames,
types);

}

protected UpdateType checkUpdateType(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types){
if(! (entity instanceof AbstractPojo)){
return UpdateType.NONE;
}
Updater updater = ((AbstractPojo)entity).getUpdater();
if(updater.hasProperty()){
return UpdateType.BY_UPDATER;
}
return UpdateType.NONE;
}

private int[] findDirtyByUpdater(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types,AbstractPojo pojo){
Updater updater = pojo.getUpdater();
Set<Integer> tmp = new HashSet<Integer>();
//int[] reval = new int[propertyNames.length];
for(int i=0;i<propertyNames.length;i++){
String propertyName = propertyNames[i];
if(updater.getProperties().contains(propertyName)){
tmp.add(i);
}
}
int[] reval = new int[tmp.size()];
int i=0;
for(Integer t:tmp){
reval[i] = t;
i++;
}
return reval;
}

private int[] findDirtyByDiff(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types){
if(currentState==null
|| previousState ==null){
return null;
}

Set<Integer> tmp = new HashSet<Integer>();
for(int i=0;i<currentState.length;i++){
Object cur = currentState[i];
Object pre = previousState[i];
if(!JavaProtoTypeHelper.equal(cur, pre)){
tmp.add(i);
}
}
int[] reval = new int[tmp.size()];
int i=0;
for(Integer t:tmp){
reval[i] = t;
i++;
}
return reval;
}

}


这里我做了3种更新方式,一种为基本的全字段更新,一种为根据程序员自定义的字段更新,一种为核查原始数据和新数据是否相等,更新不相等的字段。


基于spring的配置,我们的hibernate的Configuration是由LocalSessionFactoryBean创建的。
具体配置为:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="mappingDirectoryLocations">
<list>
<value>classpath*:/net/esj/test/pojo/</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.connection.release_mode">after_transaction</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
</props>
</property>
<property name="entityInterceptor" ref="dirtyFindInterceptor" />
</bean>

具体的过程可以在spring的源码中找到,此处我们注入上面的dirtyFindInterceptor类。


测试:

<hibernate-mapping package="net.esj.test.pojo">
<class
name="Foo"
table="test_foo" dynamic-update="true" //dynamic-update="true"此须增加
>
<meta attribute="sync-DAO">true</meta>
<id
name="id"
type="string"
column="id"
>
<generator class="uuid"/>
</id>

<property
name="name"
column="name"
type="string"
not-null="false"
/>
<property
name="code"
column="code"
type="integer"
not-null="false"
/>


</class>
</hibernate-mapping>

public void testUpdate(){
Foo foo = new Foo();
foo.setId("402881973aa11bb7013aa11bb8340000");
foo.setName("asdas");
foo.notifyUpdater("name");//只更新name字段
baseDao.update(foo);
}


hibernate打印的sql:
Hibernate: update test_foo set name=? where id=?
可以看到现在只更新了name字段
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值