在 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 子句中包含附加列的主要内容,如果未能解决你的问题,请参考以下文章

直接加入 JPA 或 HIBERNATE

hibernate jpa 报错

在运行时更新 Springboot 属性 Hibernate/JPA

ORM 解决方案(JPA;Hibernate)与 JDBC

JPA/Hibernate 无法创建名为 Order 的实体

Spring Hibernate JPA 联表查询