在 Hibernate/JPA 生成的 UPDATE 查询的 Where 子句中包含其他列
Include additional columns in Where clause of Hibernate/JPA Generated UPDATE Query
我正在使用 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()
即可实现。
如果我理解正确的话,基本上你所描述的是你希望休眠就像你有一个复合主键一样,即使你的数据库有一个 single-column 主键(你也有一个 @执行乐观锁定的版本列)。
严格来说,您的休眠模型不需要与您的 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。
以下解决方案有效,但我建议您以一种最终使用更自然的方法的方式包装您的 saveOrUpdate
方法。我的很好...但是有点老套。
解决方案:
你可以create your own annotation and inject your extra condition to hibernate save method using a hibernate interceptor。步骤如下:
1。创建class级注解:
@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);
}
}
毕竟,每次您在用 @ForcedCondition
注释的实体上调用 entity.save()
或 session.update(entity)
时,SQL 将被注入您想要的额外条件。
顺便说一句:我没有测试这段代码,但它应该可以帮助你。如果我有任何错误,请告诉我,以便我改正。
我正在使用 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()
即可实现。
如果我理解正确的话,基本上你所描述的是你希望休眠就像你有一个复合主键一样,即使你的数据库有一个 single-column 主键(你也有一个 @执行乐观锁定的版本列)。
严格来说,您的休眠模型不需要与您的 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。
以下解决方案有效,但我建议您以一种最终使用更自然的方法的方式包装您的 saveOrUpdate
方法。我的很好...但是有点老套。
解决方案:
你可以create your own annotation and inject your extra condition to hibernate save method using a hibernate interceptor。步骤如下:
1。创建class级注解:
@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);
}
}
毕竟,每次您在用 @ForcedCondition
注释的实体上调用 entity.save()
或 session.update(entity)
时,SQL 将被注入您想要的额外条件。
顺便说一句:我没有测试这段代码,但它应该可以帮助你。如果我有任何错误,请告诉我,以便我改正。