检查对象是否被引用以防止在不修改数据库的情况下进行软删除
Check if object is referenced to prevent soft-deleting without modifying database
如您所见,我正在我的系统上使用 soft/logical 删除:
我的实体:
@Where(clause = "deleted='false'")
public class Person {
//...
}
我的服务:
@Override
public ServiceResponse DeletePerson (Person entity) {
ServiceResponse sr = new ServiceResponse<>();
try {
sr = ValidateDeletePerson(entity); //Business logic treatment
if (sr.hasError())
return sr;
Person dbEntity = GetPerson(entity.getPersonID());
dbEntity.setDeleted(true);
_repository.save(dbEntity);
} catch (Exception ex) {
sr.getErrors().add(new ServiceError(ex));
}
return sr;
}
当用户尝试删除对象时 - 实际上只有布尔值 deleted 在数据库上切换为 true 正如我在上面演示的-,我想检查对象是否被引用到另一个然后执行此逻辑删除。
因此,如果该对象正被其他人 used/referenced,我想将此操作视为错误,或进行其他类似处理以防止删除。
例如,如果 Person with ID 5
附加到 Student
,则用户无法删除此人。
什么是"best practice"来防止?
重要编辑:
1) Person 在 N 个表中被引用,不仅在 Student 上,所以我正在研究实现它的通用方法(也许你可以让 hibernate 检查它?)
2) 想法是以编程方式进行,避免对数据库进行修改。
您可以在数据库级别使用检查约束。细节是特定于 DBMS 的,但约束的一般想法是
CHECK ((select count(*) from studens
where studens.id= person.id and person.deleted=false) = 0)
其他解决方案-存档table
您可以通过删除记录并将删除的记录移动到 person_archive table 来避免此问题(数据库触发器可以为您完成)。使用此解决方案,外键将能够保护数据完整性。如果你的 person
table 很大,这个解决方案也可能更有效,因为数据库将不得不读取更少的数据。
我会使用 person_archive table,除非你需要轻松地从 UI 中恢复已删除的记录 使用 deleted 标志,恢复只是翻转标志。使用存档 table,恢复工作更多:
- select 来自存档
- 插入数据table
- 从存档中删除
如果不能修改数据库
如果无法更改数据库,则必须在您的 DAO 类 中完成这些检查(您需要为所有相关实体调用查询)。最好确保所有数据库访问都通过那些 类,否则(如果有人使用直接 SQL)你最终可能会得到数据库不变量不成立的结果。
您应该通过数据库查询来检查此引用。
例如如果 Person 和 Student table 在那里。如果您要删除 ID=5 的人 table 的条目,您可以使用
之类的查询来检查学生 table 中的第一个引用
select count(1) from student where person_id = 5
如果结果 > 0 则在您的情况下是错误的,否则继续删除
最佳做法是在数据库中执行此操作,无需争论。应用程序代码中处理的引用完整性总是在某个时候中断,即使只是因为人们倾向于从多个应用程序手动访问表。
以编程方式,您可以通过构建查询来检查是否存在引用人的内容。我会使用 JPA QL 或 HQL 甚至 Criteria,无需下降到 SQL。一个查询就足够了(结合 OR)。诀窍是找到要包含在查询中的所有实体和字段。对于一种情况,它很简单,手工查询。您可能知道引用 Person 的内容,如果您不知道,您可以在 IDE 中搜索或检查数据库中的约束。在一般情况下,您希望生成此代码或即时构建它。
对于即时构建,EntityManager.getMetamodel()
将为您提供所有实体,因此您可以浏览它们的属性并查看是否存在对您正在使用的实体的潜在引用。很麻烦。
根据带注释的 类 生成代码可能很棘手,但如果您知道 类 要检查的内容(并且如果您将它们列在您的持久性单元中),事情就会变得容易得多。浏览列出的 类,阅读注释并生成验证码。
如果您真的决定以编程方式以一般方式执行此操作,我将从手工制作前两个实现开始。然后我会用它作为生成代码的基础。
如您所见,我正在我的系统上使用 soft/logical 删除:
我的实体:
@Where(clause = "deleted='false'")
public class Person {
//...
}
我的服务:
@Override
public ServiceResponse DeletePerson (Person entity) {
ServiceResponse sr = new ServiceResponse<>();
try {
sr = ValidateDeletePerson(entity); //Business logic treatment
if (sr.hasError())
return sr;
Person dbEntity = GetPerson(entity.getPersonID());
dbEntity.setDeleted(true);
_repository.save(dbEntity);
} catch (Exception ex) {
sr.getErrors().add(new ServiceError(ex));
}
return sr;
}
当用户尝试删除对象时 - 实际上只有布尔值 deleted 在数据库上切换为 true 正如我在上面演示的-,我想检查对象是否被引用到另一个然后执行此逻辑删除。
因此,如果该对象正被其他人 used/referenced,我想将此操作视为错误,或进行其他类似处理以防止删除。
例如,如果 Person with ID 5
附加到 Student
,则用户无法删除此人。
什么是"best practice"来防止?
重要编辑:
1) Person 在 N 个表中被引用,不仅在 Student 上,所以我正在研究实现它的通用方法(也许你可以让 hibernate 检查它?)
2) 想法是以编程方式进行,避免对数据库进行修改。
您可以在数据库级别使用检查约束。细节是特定于 DBMS 的,但约束的一般想法是
CHECK ((select count(*) from studens
where studens.id= person.id and person.deleted=false) = 0)
其他解决方案-存档table
您可以通过删除记录并将删除的记录移动到 person_archive table 来避免此问题(数据库触发器可以为您完成)。使用此解决方案,外键将能够保护数据完整性。如果你的 person
table 很大,这个解决方案也可能更有效,因为数据库将不得不读取更少的数据。
我会使用 person_archive table,除非你需要轻松地从 UI 中恢复已删除的记录 使用 deleted 标志,恢复只是翻转标志。使用存档 table,恢复工作更多:
- select 来自存档
- 插入数据table
- 从存档中删除
如果不能修改数据库
如果无法更改数据库,则必须在您的 DAO 类 中完成这些检查(您需要为所有相关实体调用查询)。最好确保所有数据库访问都通过那些 类,否则(如果有人使用直接 SQL)你最终可能会得到数据库不变量不成立的结果。
您应该通过数据库查询来检查此引用。
例如如果 Person 和 Student table 在那里。如果您要删除 ID=5 的人 table 的条目,您可以使用
之类的查询来检查学生 table 中的第一个引用select count(1) from student where person_id = 5
如果结果 > 0 则在您的情况下是错误的,否则继续删除
最佳做法是在数据库中执行此操作,无需争论。应用程序代码中处理的引用完整性总是在某个时候中断,即使只是因为人们倾向于从多个应用程序手动访问表。
以编程方式,您可以通过构建查询来检查是否存在引用人的内容。我会使用 JPA QL 或 HQL 甚至 Criteria,无需下降到 SQL。一个查询就足够了(结合 OR)。诀窍是找到要包含在查询中的所有实体和字段。对于一种情况,它很简单,手工查询。您可能知道引用 Person 的内容,如果您不知道,您可以在 IDE 中搜索或检查数据库中的约束。在一般情况下,您希望生成此代码或即时构建它。
对于即时构建,EntityManager.getMetamodel()
将为您提供所有实体,因此您可以浏览它们的属性并查看是否存在对您正在使用的实体的潜在引用。很麻烦。
根据带注释的 类 生成代码可能很棘手,但如果您知道 类 要检查的内容(并且如果您将它们列在您的持久性单元中),事情就会变得容易得多。浏览列出的 类,阅读注释并生成验证码。
如果您真的决定以编程方式以一般方式执行此操作,我将从手工制作前两个实现开始。然后我会用它作为生成代码的基础。