Hibernate @SQLDelete语句未添加架构

19
我正在尝试使用Hibernate的@SQLDelete注释实现软删除。当数据库模式是静态的(即在SQL中传递),它运行良好。
不幸的是,似乎SQL像原样传递给EntityPersisters(参见EntityClass的方法CustomSQL createCustomSQL(AnnotationInstance customSqlAnnotation)),所以我找不到一种动态传递模式名称的方法,就像在本机SQL查询中使用{h-schema}一样。
是否有人为此问题找到了一个好的解决方法(我正在使用Hibernate 4.3.5)?
编辑:除非有真正的解决方案,否则我最终修改了org.hibernate.persister.entity.AbstractEntityPersister的代码源,通过在doLateInit方法中设置自定义SQL查询时替换模式占位符来解决。
编辑2:我在Hibernate JIRA中创建了一个问题,针对这种行为。今天稍后我将创建一个拉请求,希望Hibernate团队能够接受它。

JIRA似乎已经在5.2版本中修复了,但是在5.3.7版本中仍然存在此问题,在更新版本的发布说明中没有看到它...但可能是我漏看了。 - James Gawron
1
你在 SQL 查询中是否加入了 {h-schema} 前缀?上次我检查时,该修复已经完成。但说实话,我不确定版本是否有影响。 - Laabidi Raissi
1
在检查源代码后,我添加了 {h-schema},这确实解决了问题。 - James Gawron
3个回答

1

使用Hibernate注解实现软删除。

如下所述的链接作者:

我目前正在开发一个Seam应用程序,需要在数据库中进行软删除。在右侧,您可以看到我的数据库图表的一部分,其中包含CUSTOMERAPP_USER表。这只是一个简单的一对多关系,但需要注意的重要事项是每个表中的“DELETED”字段。这是将用于跟踪软删除的字段。如果该字段包含“1”,则表示记录已被删除,如果包含“0”,则表示记录尚未被删除。

enter image description here

在有像Hibernate这样的ORM之前,我需要使用SQL来跟踪和设置此标志。虽然这并不是很难做到,但谁想写一堆样板代码来跟踪记录是否已被删除呢?这就是Hibernate和注释发挥作用的地方。
下面是由Hibernate使用seamgen生成的2个实体类。为了清晰起见,我省略了部分代码。

Customer.java

//Package name...

//Imports...

@Entity
@Table(name = "CUSTOMER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
@SQLDelete(sql="UPDATE customer SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
@Where(clause="deleted <> '1'")
public class Customer implements java.io.Serializable {
    private long id;
    private Billing billing;
    private String name;
    private String address;
    private String zipCode;
    private String city;
    private String state;
    private String notes;
    private char enabled;
    private char deleted;
    private Set appUsers = new HashSet(0);

    // Constructors...

    // Getters and Setters...

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "customer")
    // Filter added to retrieve only records that have not been soft deleted.
    @Where(clause = "deleted <> '1'")
    public Set getAppUsers() {
        return this.appUsers;
    }

    public void setAppUsers(Set appUsers) {
        this.appUsers = appUsers;
    }
}

AppUser.java

//Package name...

//Imports...

@Entity
@Table(name = "APP_USER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
@SQLDelete(sql="UPDATE app_user SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
@Where(clause="deleted <> '1'")
public class AppUser implements java.io.Serializable {
    private long id;
    private Customer customer;
    private AppRole appRole;
    private char enabled;
    private String username;
    private String appPassword;
    private Date expirationDate;
    private String firstName;
    private String lastName;
    private String email;
    private String phone;
    private String fax;
    private char deleted;
    private Set persons = new HashSet(0);

    // Constructors...

    // Getters and Setters...
}

以下2个步骤就是我实现软删除所需的全部操作。
  1. 添加@SQLDelete注释,覆盖该实体的默认Hibernate删除方式。
  2. 添加@Where注释以过滤查询并仅返回未被软删除的记录。还要注意,在CUSTOMER类中,我为appUsers集合添加了一个@Where。这是为了仅获取该客户的未被软删除的appUsers。

哇!现在,每当您删除这些实体时,它将把“DELETED”字段设置为“1”,并且当您查询这些实体时,它只会返回包含“DELETED”字段中的‘0’的记录。

难以置信,但使用Hibernate注释实现软删除就是这么简单。

注意:

请注意,您可以使用Hibernate过滤器(http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-filters)来全局过滤所有"已删除"实体,而不是使用@Where(clause="deleted ‘1’")语句。我发现定义两个实体管理器(一个用于过滤已删除的项目,另一个用于不过滤已删除的项目,针对罕见情况...)通常非常方便。
使用EntityPersister 您可以创建一个DeleteEventListener,例如:
public class SoftDeleteEventListener extends DefaultDeleteEventListener {

/**
 * 
 */
private static final long serialVersionUID = 1L;

@Override
public void onDelete(DeleteEvent event, Set arg1) throws HibernateException {
    Object o = event.getObject();
    if (o instanceof SoftDeletable) {
        ((SoftDeletable)o).setStatusId(1);
        EntityPersister persister = event.getSession().getEntityPersister( event.getEntityName(), o);
        EntityEntry entityEntry = event.getSession().getPersistenceContext().getEntry(o);
        cascadeBeforeDelete(event.getSession(), persister, o, entityEntry, arg1);

        cascadeAfterDelete(event.getSession(), persister, o, arg1);

    } else {
        super.onDelete(event, arg1);
    }
}
}

将它钩入您的persistence.xml中,就像这样。
<property name = "hibernate.ejb.event.delete" value = "org.something.SoftDeleteEventListener"/> 

另外,不要忘记在注释中更新你的级联。

资源链接:

  1. Hibernate:使用继承覆盖sql-delete
  2. 用于CRUD操作的自定义SQL
  3. 用于创建、更新和删除的自定义SQL

1
谢谢,但抱歉,我的问题不是关于如何使用@SQLDelete,而是如何动态指定SQL查询中的模式。 - Laabidi Raissi
我想,让Hibernate知道表所在的模式所需的全部内容就是为实体定义一个持久化单元。在这种情况下,在执行您的HQL时,您将不需要模式限定符。 - Thomas Mwania

1

使用方法如下

@SQLDelete(sql = "UPDATE {h-schema}LEAVE  SET STATUS = 'DELETED' WHERE id = ?", check = ResultCheckStyle.COUNT)

请注意报告问题的 Hibernate 版本。另外,您可以查看我当时创建的问题。它已在后续版本中修复。 - Laabidi Raissi

0

我认为有两种方法。 第一种是添加:

app.datasource.schema=<schema_name> 

将其添加到您的 application.properties 文件中。
第二种方法是在注释中使用模式来描述您的表模型enter image description here

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接