【问题标题】:How can I use Hibernate/JPA to tell the DB who the user is before inserts/updates/deletes?如何在插入/更新/删除之前使用 Hibernate/JPA 告诉数据库用户是谁?
【发布时间】:2017-06-13 08:53:09
【问题描述】:

总结(详情如下):

我想在使用 Spring/JPA 堆栈保存/更新/删除任何实体之前进行存储过程调用。

无聊的细节:

我们有一个 Oracle/JPA(Hibernate)/Spring MVC(带有 Spring Data repos)应用程序,该应用程序设置为使用触发器将某些表的历史记录到一组历史表中(我们要审计的每个表一个历史表)。这些实体中的每一个都有一个modifiedByUser,通过一个在更新或插入时扩展EmptyInterceptor 的类来设置。当触发器归档任何插入或更新时,它可以使用此列轻松查看谁进行了更改(我们感兴趣的是哪个应用程序用户,而不是数据库用户)。问题是对于删除,我们不会从执行的 SQL 中获取最后修改的信息,因为它只是一个普通的delete from x where y

为了解决这个问题,我们想在执行任何操作之前执行一个存储过程来告诉数据库哪个应用用户登录了。然后,审计触发器会在删除发生时查看此值,并使用它来记录执行删除的人员。

是否有任何方法可以拦截开始事务或执行 SQL 或存储过程的其他方式来告诉 db 哪些用户正在执行将在事务中发生的插入/更新/删除,然后再执行其他操作操作发生了吗?

我对数据库端如何工作的细节了解不多,但如有必要,可以了解更多。要点是存储过程将创建一个包含会话变量的上下文,触发器将在删除时查询该上下文以获取用户 ID。

【问题讨论】:

  • 在此处查看我的答案和相关链接,这些链接显示了如何通过使用 Oracle 的 CLIENT_IDENTIFIER 属性'{ call DBMS_SESSION.SET_IDENTIFIER('MY APP USER') } 的连接检出上的 AOP 切入点来完成此操作: stackoverflow.com/questions/39884243/…
  • @AlanHay - 你能发布答案吗?我最终使用了你的解决方案,你应该得到认可。
  • 嗨@Chris Williams。我确实发布了答案

标签: java spring oracle hibernate jpa


【解决方案1】:

从数据库端,这里有一些讨论:

https://docs.oracle.com/cd/B19306_01/network.102/b14266/apdvprxy.htm#i1010372

许多应用程序使用会话池来设置多个会话 供多个应用程序用户重用。用户认证 他们自己到一个中间层应用程序,它使用一个单一的身份 登录到数据库并维护所有用户连接。在 在此模型中,应用程序用户是经过身份验证的用户 应用程序的中间层,但谁不知道 数据库.....在这些情况下,应用程序通常连接 作为单个数据库用户,所有操作都作为该用户执行。 因为所有用户会话都是作为同一用户创建的,所以这种安全性 模型使得每个人都很难实现数据分离 用户。这些应用程序可以使用 CLIENT_IDENTIFIER 属性来 将真实的应用程序用户身份保存到数据库中。

从 Spring/JPA 方面来看,请参阅下面的第 8.2 节:

http://docs.spring.io/spring-data/jdbc/docs/current/reference/html/orcl.connection.html

有时您想在 使用标准连接不容易支持的某些方式 特性。一个例子是在 SYS_CONTEXT,例如 MODULE 或 CLIENT_IDENTIFIER。本章 解释了如何使用 ConnectionPreparer 来完成此操作。这 示例将设置 CLIENT_IDENTIFIER。

Spring 文档中给出的示例使用 XML 配置。如果您使用的是 Java 配置,那么它看起来像:

@Component
@Aspect
public class ClientIdentifierConnectionPreparer implements ConnectionPreparer
{
  @AfterReturning(pointcut = "execution(* *.getConnection(..))", returning = "connection")
  public Connection prepare(Connection connection) throws SQLException
  {
    String webAppUser = //from Spring Security Context or wherever;

    CallableStatement cs = connection.prepareCall(
                 "{ call DBMS_SESSION.SET_IDENTIFIER(?) }");
    cs.setString(1, webAppUser);
    cs.execute();
    cs.close();

    return connection;
  }
}

通过配置类启用 AspectJ:

@Configuration
@EnableAspectJAutoProxy
public class SomeConfigurationClass
{

}

请注意,虽然这隐藏在特定于 Spring 的 Oracle 扩展的部分中,但在我看来,第 8.2 节(与 8.1 不同)中没有任何内容是 Oracle 特定的(除了执行的语句),一般方法应该是只需指定相关的过程调用或 SQL 即可用于任何数据库:

以 Postgres 为例,如下所示,所以我不明白为什么使用 Postgres 的人不能通过以下方式使用这种方法:

https://www.postgresql.org/docs/8.4/static/sql-set-role.html

【讨论】:

    【解决方案2】:

    除非您的存储过程比您描述的更多,否则更简洁的解决方案是使用Envers (Entity Versioning)。 Hibernate 可以自动将实体的版本存储在单独的表中,并为您跟踪所有 CRUD 操作,您不必担心失败的事务,因为这一切都会发生在同一个会话中。

    至于跟踪谁进行了更改,添加一个新列(updatedBy)并从安全主体(例如 Spring Security 用户)获取用户的登录 ID

    还可以查看@CreationTimestamp@UpdateTimestamp

    【讨论】:

      【解决方案3】:

      我认为您正在寻找的是 TransactionalEvent:

      @Service
      public class TransactionalListenerService{
      
         @Autowired
         SessionFactory sessionFactory;
      
         @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
          public void handleEntityCreationEvent(CreationEvent<Entity> creationEvent) {
                // use sessionFactory to run a stored procedure
          }
      }
      

      通过@EventListener 注册一个常规事件监听器 注解。如果您需要将其绑定到事务使用 @TransactionalEventListener。当你这样做时,听众将 默认绑定到事务的提交阶段。

      然后在您的事务服务中,您在必要时注册该事件:

      @Service
      public class MyTransactionalService{
      
           @Autowired
           private ApplicationEventPublisher applicationEventPublisher;
      
           @Transactional
           public void insertEntityMethod(Entity entity){
               // insert 
      
                // Publish event after insert operation
                applicationEventPublisher.publishEvent(new CreationEvent(this, entity));
      
               // more processing
           }
      
      }
      

      如果您有要求,这也可以在事务的边界之外工作:

      如果没有事务正在运行,则根本不会调用侦听器,因为 我们不能尊重所需的语义。然而,有可能 通过设置 fallbackExecution 属性来覆盖该行为 注释为 true。

      【讨论】:

        猜你喜欢
        • 2021-04-02
        • 1970-01-01
        • 1970-01-01
        • 2021-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多