在Hibernate / JPA生成的UPDATE查询的Where子句中包括其他列

问题描述

我正在使用Hibernate / JPA。

当我执行entity.save()session.update(entity)时,休眠会生成如下查询:-

update TABLE1 set COL_1=?,COL_2=?,COL_3=? where COL_PK=?

我可以通过实体中的任何注释在WHERE子句中包括附加的列,这样它可以导致类似:-

查询
update TABLE1 set COL_1=?,COL_3=? where COL_PK=? **AND COL_3=?**

这是因为我们的数据库基于COL_3进行分片,并且需要在where子句中出现

我希望仅使用session.update(entity)entity.save()来实现这一目标。

解决方法

如果我对事情的理解正确,本质上讲,您所描述的是即使数据库具有单列主键,您也希望休眠状态像具有复合主键一样(其中您还具有一个@Version列来执行)乐观锁定)。

严格来说,您的休眠模型无需完全匹配您的数据库模式。您可以将实体定义为具有复合主键,以确保所有更新都基于两个值的组合进行。这样做的缺点是您的加载操作会稍微复杂些。

请考虑以下实体:

@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列。

,

以下解决方案有效,但是我建议您仅使用一种更自然的方法来包装saveOrUpdate方法。我的很好...但是有点笨拙,这只是做到这一点的一种方法:

解决方案:

您可以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",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,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注入您想要的额外条件。

顺便说一句::我没有测试这段代码,但是它可以帮助您。如果我有任何错误,请告诉我,以便我纠正。