摘要(详情如下):
我希望在使用Spring / JPA堆栈保存/更新/删除任何实体之前进行存储过程调用。
无聊的细节:
我们有一个Oracle / JPA(Hibernate)/ Spring MVC(带有Spring Data repos)应用程序,它被设置为使用触发器将一些表的历史记录到一组历史表中(每个表我们想要审计一个历史表) )。这些实体中的每一个都通过在更新或插入时扩展modifiedByUser
的类设置EmptyInterceptor
。当触发器归档任何插入或更新时,可以使用此列轻松查看谁进行了更改(我们对哪个应用程序用户感兴趣,而不是对数据库用户感兴趣)。问题是,对于删除,我们不会从执行的SQL中获取最后修改的信息,因为它只是一个普通的delete from x where y
。
为了解决这个问题,我们希望在执行任何操作之前执行存储过程以告知数据库哪个应用程序用户已登录。然后,审计触发器会在发生删除时查看此值,并使用它来记录执行删除的人员。
有没有办法拦截begin事务或其他一些方法来执行SQL或存储过程来告诉db什么用户正在执行插入/更新/删除操作,这些插入/更新/删除将在事务发生之前发生。操作发生了吗?
我详细介绍了数据库方面的工作方式,但必要时可以获得更多。要点是存储的proc将创建一个将保存会话变量的上下文,触发器将在delete上查询该上下文以获取用户ID。
答案 0 :(得分:2)
从数据库端,这里有一些讨论:
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;
}
}
通过Configuration类启用AspectJ:
@Configuration
@EnableAspectJAutoProxy
public class SomeConfigurationClass
{
}
请注意,虽然这是在特定于Spring的Oracle扩展的部分中隐藏的,但在我看来,8.2节(与8.1不同)中没有任何特定于Oracle的内容(除了执行的Statement之外)和只需指定相关的过程调用或SQL,通用方法对任何数据库都是可行的:
Postgres例如如下所示我不明白为什么使用Postgres的人不能使用以下方法:
https://www.postgresql.org/docs/8.4/static/sql-set-role.html
答案 1 :(得分:1)
除非您的存储过程比您描述的更多,否则更清晰的解决方案是使用Envers (Entity Versioning)。 Hibernate可以自动将实体的版本存储在一个单独的表中,并为您跟踪所有CRUD操作,您不必担心失败的事务,因为这将在同一个会话中发生。
至于跟踪谁进行了更改,添加新的colulmn(updatedBy)并从Security Principal(例如Spring Security用户)获取用户的登录ID
答案 2 :(得分:0)
我认为你要找的是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
}
}
如果你有要求,这也可以在trasaction的边界之外工作:
如果没有正在运行的事务,则从那时起根本不会调用侦听器 我们无法尊重所需的语义。但是有可能 通过设置fallbackExecution属性来覆盖该行为 注释为真。