在 Hibernate/JPA Generated UPDATE Query 的 Where 子句中包含附加列
Posted
技术标签:
【中文标题】在 Hibernate/JPA Generated UPDATE Query 的 Where 子句中包含附加列【英文标题】:Include additional columns in Where clause of Hibernate/JPA Generated UPDATE Query 【发布时间】:2021-01-21 05:27:59 【问题描述】:我正在使用 Hibernate/JPA。
当我执行entity.save()
或session.update(entity)
时,hibernate 会生成这样的查询:-
update TABLE1 set COL_1=? , COL_2=? , COL_3=? where COL_PK=?
我可以通过实体中的任何注释在 WHERE 子句中包含一个附加列,因此它可以导致如下查询:-
update TABLE1 set COL_1=? , COL_2=? , COL_3=? where COL_PK=? **AND COL_3=?**
这是因为我们的数据库是基于COL_3
分片的,这需要出现在 where 子句中
我希望能够仅使用session.update(entity)
或entity.save()
来实现此目的。
【问题讨论】:
你在使用 spring 存储库吗? @silentsudo 是的,我正在使用 Spring Data JPA 存储库 您是否考虑过为此编写条件查询以及您需要的 extar where 子句,因为保存/更新或多或少在存储库中工作相同,覆盖默认保存行为我不会建议由于存储库在不同的地方使用,事情可能会开始破裂 该表很大,有多个列,编写自己的查询可能很乏味且难以管理。此外,这个实体也有一个@Version。使用自定义查询,这将很难手动管理。 您的实体标识符是只有 COL_PK 还是 (COL_PK AND COL_3)?事情并不总是如你所愿。我也希望很多事情都能轻松完成,但这不是其中之一。您应该根据您想要的条件选择实体,然后更新相应的实体。 【参考方案1】:如果我理解正确,基本上您所描述的是,即使您的数据库有一个单列主键(您也有一个 @Version 列来执行),您希望休眠的行为就像您有一个复合主键一样乐观锁定)。
严格来说,您的休眠模型不需要完全匹配您的 db-schema。您可以将实体定义为具有复合主键,确保所有更新都基于这两个值的组合。这里的缺点是您的加载操作稍微复杂一些。
考虑以下实体:
@Entity
@Table(name="test_entity", uniqueConstraints = @UniqueConstraint(columnNames = "id") )
public class TestEntity implements Serializable
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false, unique = true)
private Long id;
@Id
@Column(name = "col_3", nullable = false)
private String col_3;
@Column(name = "value", nullable = true)
private String value;
@Version
@Column(nullable = false)
private Integer version;
... getters & setters
那么你就可以有下面的方法(在我的例子中,我创建了一个简单的JUnit测试)
@Test
public void test()
TestEntity test = new TestEntity();
test.setCol_3("col_3_value");
test.setValue("first-value");
session.persist(test);
long id = test.getId();
session.flush();
session.clear();
TestEntity loadedTest = (TestEntity) session
.createCriteria(TestEntity.class)
.add(Restrictions.eq("id", id))
.uniqueResult();
loadedTest.setValue("new-value");
session.saveOrUpdate(loadedTest);
session.flush();
这会生成以下 SQL 语句(启用 Hibernate 日志记录)
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
test_entity
(value, version, id, col_3)
values
(?, ?, ?, ?)
Hibernate:
select
this_.id as id1_402_0_,
this_.col_3 as col_2_402_0_,
this_.value as value3_402_0_,
this_.version as version4_402_0_
from
test_entity this_
where
this_.id=?
Hibernate:
update
test_entity
set
value=?,
version=?
where
id=?
and col_3=?
and version=?
如您所见,这会使加载稍微复杂一些 - 我在这里使用了一个条件,但它满足您的条件,即您的更新语句始终在“where”子句中包含 col_3 列。
【讨论】:
看起来很合理。不过要记住两个注意事项:(1) 无法使用Session.get()/EntityManager.find()
意味着对已加载实体的每次重新查询仍会命中数据库,并且 (2) 我认为 Hibernate 现在希望您使用这两列作为实体关联的连接列,这意味着您现在需要一个额外的列来存储每个一对一的关联(而且,我想某些查询计划可能会受到不利影响)
是的。如果您对表格有 FK,则会产生问题。
我可以使用 IdClass 注释来做到这一点。使用 IdClass 定义主键和你建议的有区别吗?
你所说的“加载操作”更复杂是什么意思?什么是“加载操作”?这是选择语句吗?
使用 IdClass 是相同的基本概念。 “加载操作”是“选择”语句。如果您使用 IdClass 或复合主键,则无法使用“当前 ID”执行“按 ID 加载”。您要么需要使用您定义的 id-class,要么执行查询或选择。【参考方案2】:
以下解决方案有效,但是我建议您将 saveOrUpdate
方法包装起来,最终使用更自然的方法。我的很好......但有点hacky。
解决方案:
您可以create your own annotation 并使用hibernate interceptor 将您的额外条件注入休眠保存方法。具体步骤如下:
1.创建类级别注释:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ForcedCondition
String columnName() default "";
String attributeName() default ""; // <-- this one is just in case your DB column differs from your attribute's name
2。注释您的实体,指定您的列数据库名称和您的实体属性名称
@ForcedCondition(columnName = "col_3", attributeName= "col_3")
@Entity
@Table(name="test_entity")
public class TestEntity implements Serializable
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false, unique = true)
private Long id;
@Column(name = "col_3", nullable = false)
private String col_3;
public String getCol_3()
return col_3;
... getters & setters
3.添加一个 Hibernate 拦截器并注入额外的条件:
public class ForcedConditionInterceptor extends EmptyInterceptor
private boolean forceCondition = false;
private String columnName;
private String attributeValue;
@Override
public boolean onSave(
Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types)
// If your annotation is present, backup attribute name and value
if (entity.getClass().isAnnotationPresent(ForcedCondition.class))
// Turn on the flag, so later you'll inject the condition
forceCondition = true;
// Extract the values from the annotation
columnName = entity.getClass().getAnnotation(ForcedCondition.class)).columnName();
String attributeName = entity.getClass().getAnnotation(ForcedCondition.class)).attributeName();
// Use Reflection to get the value
// org.apache.commons.beanutils.PropertyUtils
attributeValue = PropertyUtils.getProperty(entity, attributeName);
return super.onSave(entity, id, state, propertyNames, types);
@Override
public String onPrepareStatement(String sql)
if (forceCondition)
// inject your extra condition, for better performance try java.util.regex.Pattern
sql = sql.replace(" where ", " where " + columnName + " = '" + attributeValue.replaceAll("'", "''") + "' AND ");
return super.onPrepareStatement(sql);
毕竟每次你调用entity.save()
或session.update(entity)
对一个带有@ForcedCondition
注释的实体,SQL 将被注入你想要的额外条件。
顺便说一句:我没有测试过这段代码,但它应该可以帮助你。如果我做错了,请告诉我,以便我更正。
【讨论】:
以上是关于在 Hibernate/JPA Generated UPDATE Query 的 Where 子句中包含附加列的主要内容,如果未能解决你的问题,请参考以下文章
在运行时更新 Springboot 属性 Hibernate/JPA