在休眠 JPA 的 getReference() 之后使用 setter 时如果不发出 select 就无法更新

cannot update without issuing select on using setter after getReference() of hibernate JPA

我有以下方法-

 @Transactional
 public void savethis(){
    EntityObject t = entityManagerTreasury.getReference(EntityObject.class, 1);
    t.setAction("abc");
 }

现在,与以下答案一致 -

我应该只在 sql 日志中看到更新查询。

但是我观察到的行为如下 -

  1. 给定代码 - select 然后更新
  2. 评论 t.setAction("abc");行 - 无 select 且无更新
  3. 用 find() 替换 getReference() - select 然后更新

我期望的行为是,如果我在代理上使用任何 getter,那么应该发出 select,但是当只使用 setter 时,我想要更改将在方法结束时提交并进行更新,并且不会发出 select。

事实证明,无论我如何处理代理对象,getter 或 setter,它都会发出 select.

我想为给定 ID 更新实体的 selected 字段。 如果有任何方法可以在不编写 jpql 或本机查询的情况下更新我想要的任何字段,我将非常感激。

提前致谢!

来自 EntityManager.getReference() 文档:

Get an instance, whose state may be lazily fetched.

因此,在entityManagerTreasury.getReference之后没有发出select。

仅在 t.setAction("abc") 之后,如果尚未获取实体状态,则会发出 select 以获取状态。

重点是:实体管理器无法保存实体状态,除非获取实体状态。因此你不能跳过之前的select,除非你使用JPQL。

如果 JPA getReference() 代理不提供该功能怎么办。我可以自己写代理。

现在,我们都可以争辩说,对主键的选择与查询所能达到的速度一样快,而且这并不是需要竭尽全力避免的事情。但是对于我们这些由于某种原因无法处理它的人来说,下面是这种代理的实现。但在我看到实现之前,请先了解它的用法以及使用起来有多简单。

用法

Order example = ProxyHandler.getReference(Order.class, 3);
example.setType("ABCD");
example.setCost(10);
PersistenceService.save(example);

这将触发以下查询 -

UPDATE Order SET type = 'ABCD' and cost = 10 WHERE id = 3;

即使你想插入,你仍然可以做 PersistenceService.save(new Order("a", 2));它会按预期触发插入。

实施

将此添加到您的 pom.xml -

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

使这个class创建动态代理-

@SuppressWarnings("unchecked")
public class ProxyHandler {

public static <T> T getReference(Class<T> classType, Object id) {
    if (!classType.isAnnotationPresent(Entity.class)) {
        throw new ProxyInstantiationException("This is not an entity!");
    }

    try {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classType);
        enhancer.setCallback(new ProxyMethodInterceptor(classType, id));
        enhancer.setInterfaces((new Class<?>[]{EnhancedProxy.class}));
        return (T) enhancer.create();
    } catch (Exception e) {
        throw new ProxyInstantiationException("Error creating proxy, cause :" + e.getCause());
    }
}

使用所有方法创建接口 -

public interface EnhancedProxy {
    public String getJPQLUpdate();
    public HashMap<String, Object> getModifiedFields();
}

现在,创建一个拦截器,允许您在代理上实现这些方法 -

import com.anil.app.exception.ProxyInstantiationException;
import javafx.util.Pair;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import javax.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author Anil Kumar
*/
public class ProxyMethodInterceptor implements MethodInterceptor, EnhancedProxy {

private Object target;
private Object proxy;
private Class classType;
private Pair<String, Object> primaryKey;
private static HashSet<String> enhancedMethods;

ProxyMethodInterceptor(Class classType, Object id) throws IllegalAccessException, InstantiationException {
    this.classType = classType;
    this.target = classType.newInstance();
    this.primaryKey = new Pair<>(getPrimaryKeyField().getName(), id);
}

static {
    enhancedMethods = new HashSet<>();
    for (Method method : EnhancedProxy.class.getDeclaredMethods()) {
        enhancedMethods.add(method.getName());
    }
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    //intercept enhanced methods
    if (enhancedMethods.contains(method.getName())) {
        this.proxy = obj;
        return method.invoke(this, args);
    }
    //else invoke super class method
    else
        return proxy.invokeSuper(obj, args);
}

@Override
public HashMap<String, Object> getModifiedFields() {
    HashMap<String, Object> modifiedFields = new HashMap<>();
    try {
        for (Field field : classType.getDeclaredFields()) {

            field.setAccessible(true);

            Object initialValue = field.get(target);
            Object finalValue = field.get(proxy);

            //put if modified
            if (!Objects.equals(initialValue, finalValue)) {
                modifiedFields.put(field.getName(), finalValue);
            }
        }
    } catch (Exception e) {
        return null;
    }
    return modifiedFields;
}

@Override
public String getJPQLUpdate() {
    HashMap<String, Object> modifiedFields = getModifiedFields();
    if (modifiedFields == null || modifiedFields.isEmpty()) {
        return null;
    }
    StringBuilder fieldsToSet = new StringBuilder();
    for (String field : modifiedFields.keySet()) {
        fieldsToSet.append(field).append(" = :").append(field).append(" and ");
    }
    fieldsToSet.setLength(fieldsToSet.length() - 4);
    return "UPDATE "
            + classType.getSimpleName()
            + " SET "
            + fieldsToSet
            + "WHERE "
            + primaryKey.getKey() + " = " + primaryKey.getValue();
}

private Field getPrimaryKeyField() throws ProxyInstantiationException {
    for (Field field : classType.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(Id.class))
            return field;
    }
    throw new ProxyInstantiationException("Entity class doesn't have a primary key!");
}
}

和异常 class -

public class ProxyInstantiationException extends RuntimeException {
public ProxyInstantiationException(String message) {
    super(message);
}

使用此代理保存的服务 -

@Service
public class PersistenceService {

@PersistenceContext
private EntityManager em;

@Transactional
private void save(Object entity) {
    // update entity for proxies
    if (entity instanceof EnhancedProxy) {
        EnhancedProxy proxy = (EnhancedProxy) entity;
        Query updateQuery = em.createQuery(proxy.getJPQLUpdate());
        for (Entry<String, Object> entry : proxy.getModifiedFields().entrySet()) {
            updateQuery.setParameter(entry.getKey(), entry.getValue());
        }
        updateQuery.executeUpdate();
    // insert otherwise
    } else {
        em.persist(entity);
    }

}
}