【问题标题】:Can't delete from two tables with a relation in an @Transaction无法从 @Transaction 中具有关系的两个表中删除
【发布时间】:2017-05-29 11:50:41
【问题描述】:

我一直在使用 Spring 4 的 UserDetailsManager 创建用户,架构是他们的文档为 USERSAUTHORITIES 表建议的架构。

我也一直在使用 Spring Data @Repository 注释接口来管理单独的 REGISTRATIONS 表中的数据,该表被定义为与 USERS 表中的 username 字段有关系。

我一直面临的问题是,当我希望删除用户时,我首先使用注入的 Spring Data 存储库从REGISTRATIONS 表中删除记录,然后使用@987654330 调用deleteUser() @。 (这只是@Service 注释类中@Transactional 方法中的两个连续调用)。

例如

registrationsRepository.delete(uuid);
userDetailsManager.deleteUser(registration.getUsername());

但是,用户删除失败,因为REGISTRATIONS 表(第一行)中的记录尚未删除。随后我收到一个异常(第 2 行)抱怨无法删除用户,因为 REGISTRATIONS 表中有外键约束阻止它被删除。

如果这些更新发生在同一个事务中,为什么会失败?

编辑:

@Repository
public interface RegistrationsRepository extends CrudRepository<Registration, UUID>
{
    // No EntityManager injected - uses Spring Data method queries
    // No additional methods defined
}

注册表定义如下:

CREATE TABLE Registrations (
  username varchar(64) NOT NULL REFERENCES Users (username),
  uuid UUID NOT NULL PRIMARY KEY
);

【问题讨论】:

  • 第一次删除后尝试刷新 EntityManager。
  • 注册实体是否引用了用户实体?还是只是某种映射到 FK 的 id?
  • 补充 Guillaume 的建议:delete() 不会删除实体。它标记要删除的实体。删除行的实际 SQL 查询仅在刷新时执行。
  • @GuillaumeF。我已经更新了我的问题以提供更多信息。我曾考虑过这一点,但这意味着我必须将 EM 单独注入我的服务类并单独调用 flush() - 感觉不对。我也不确定它是否与事务使用的相同,因为我已经阅读了@Transactional 可以从 EM 工厂创建一个新的 EM 并在事务开始时绑定到线程。
  • 这就是为什么你应该调用flush()。如果没有刷新,用户行的删除发生在 注册行的删除之后。因为 delete() 不会删除。它仅将实体标记为删除。 flush() 是强制 JPA 执行删除 SQL 语句的原因。

标签: java spring-mvc jpa spring-data spring-transactions


【解决方案1】:

所以,据我了解,Spring 的 UserDetailsManager 使用 JDBC 调用从各自的表中删除“用户”和“权限”。

我的“注册”实体由EntityManager 管理,它与“用户”记录没有定义关系(在 ORM 级别)。这种关系纯粹是在数据库级别指定的。

EntityManager 将标记要删除的“注册”实体,而 UserDetailsManager 实际上会删除“用户”,这发生在事务结束时 EntityManager 被刷新之前。此操作失败,因为尚未删除“注册”实体,事务仍未完成,但 JDBC 调用已尝试删除“用户和权限”。

为了解决这个问题,我做了以下事情。

class DefaultService implements MyService {

    private final EntityManagerFactory emf;

    // Inject RegistrationRepository and UserDetailsManager...

    @Inject
    public DefaultService(EntityManagerFactory emf, ...) {
        // ...
        this.emf = emf;
    }

    @Override
    @Transactional
    public void serviceMethod(UUID uuid, String username) {
        registrationsRepository.delete(uuid);

        // Flush the entity manager to remove this record from the DB first.
        EntityManagerHolder entityManagerHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf);
        entityManagerHolder.getEntityManager().flush();

        // These will be JDBC calls, 'users' are not managed entities
        userDetailsManager.deleteUser(username);
    }
}

我以这种方式获得了 EntityManager,以确保我为该事务获得绑定到该线程的正确对象。如果这是矫枉过正或有更好的方法,请发表评论!

希望对某人有所帮助。并且是正确的!

【讨论】:

  • 另一种正确的方法是在这些表之间创建一个实体@OneToOne(或其他)链接,以便您的 ORM 知道这种关系。当它进行脏检查时,它将检测关系并自动刷新事务。
  • 啊——我可以这样做吗? Spring 的 UserDetailsManager 似乎没有将它返回的对象视为 ORM 实体。如果我能做到这一点,那将是我的首选方法。
  • 您总是可以将 Class 声明为 @Entity 并在事务上下文中使用它,但这对于 UserDetailsManager 可能有点棘手(而且这并不是它的真正用途)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多