在休眠 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 日志中看到更新查询。
但是我观察到的行为如下 -
- 给定代码 - select 然后更新
- 评论 t.setAction("abc");行 - 无 select 且无更新
- 用 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);
}
}
}
我有以下方法-
@Transactional
public void savethis(){
EntityObject t = entityManagerTreasury.getReference(EntityObject.class, 1);
t.setAction("abc");
}
现在,与以下答案一致 -
我应该只在 sql 日志中看到更新查询。
但是我观察到的行为如下 -
- 给定代码 - select 然后更新
- 评论 t.setAction("abc");行 - 无 select 且无更新
- 用 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);
}
}
}