挂钩JDBC事务的开始

时间:2018-01-05 11:28:32

标签: java spring postgresql jdbc spring-jdbc

我有一个Spring Boot webapp连接到Postgres 9.6数据库 我使用Spring的JdbcTemplate来执行SQL语句。 我的数据库中的每个表都有INSERT,CREATE和DELETE语句的触发器。这些触发器将受影响的行复制到历史记录表中。

我希望触发器还保存进行更改的用户的应用程序用户ID。

根据https://stackoverflow.com/a/13172964/2591231我可以通过让应用程序将当前用户ID插入每个事务开始时的临时表 并从临时表中读取触发器来实现我的目标。
在其他几个地方提到的类似方法正在执行: SET LOCAL application_name = "my_application_user",然后在触发器内阅读application_name。同样,这必须在每笔交易开始时完成。

我正在寻找方法,它与业务代码正交(我不希望每个DAO显式设置用户ID),挂钩到每个事务的开始以便运行特定的SQL语句同一交易中的任何其他陈述之前。

我需要这个用于隐式事务(JdbcTemplate的单个调用)和使用Spring的@Transactional注释以声明方式划分的事务。

4 个答案:

答案 0 :(得分:2)

首先,JdbcTemplate不提供开箱即用的交易支持(请参阅here)。因此,为了拦截所有@Transaction带注释的代码 AND ,每次调用JdbcTemplate,都可以在DataSource级别完成,正如Serge Bogatyrev先前评论的那样。

我有一个Spring Web项目,我测试了这种方法。我定义了一个名为DataSource的替换@Bean MyDataSource,它扩展了BasicDataSource,并覆盖了它的getConnection()方法,以便创建临时表并在返回之前插入user_id连接。

适用于@Transaction来电和纯JdbcTemplate来电。

如果您想在每次交易开始时严格绑定此临时表更新 ,请执行相同的策略来定义PlatformTransactionManager @Bean。您只需要覆盖doBegin()方法。并且不要忘记使用@Transaction所有调用JdbcTemplate的方法进行注释。

PS1 :确保在创建临时表之前调用DROP TABLE IF EXISTS temp_table_name,以便替换返回池的连接上的DISCARD ALL,如上所述here

PS2 :创建临时表的整个解决方案闻起来不太好。我不会自己实现它。我更愿意深呼吸,并将created_by和updated_by列添加到我的所有表格中。

答案 1 :(得分:2)

您可以利用Spring AOP来设置用户。该方面将调用数据库来设置应用程序用户。

在我的示例中,存储过程用于设置负责创建,修改或删除记录的应用程序用户。您可以根据自己的要求进行自定义。以下是从HTTP请求中检索user然后调用存储过程的示例方面,

@Component
@Aspect
@Slf4j
public class SetUserAspect {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public SetUserAspect(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Before("execution(* com.basaki.example.service.BookService.*(..) )")
    public void setUser(JoinPoint jp) {
        log.info("In class: " + jp.getSignature().getDeclaringTypeName() +
                " - before method: " + jp.getSignature().getName());

        HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        if (request != null) {
            String user = request.getHeader("user");
            if (user != null) {
                log.info("Setting user " + user);
                SimpleJdbcCall
                        jdbcCall = new SimpleJdbcCall(jdbcTemplate)
                        .withSchemaName("example_book_schema")
                        .withFunctionName("set_user");
                SqlParameterSource
                        in =
                        new MapSqlParameterSource().addValue("audit_user",
                                user);
                jdbcCall.executeFunction(String.class, in);
            }
        }
    }
}

所有CRUD操作都在BookService中执行,基本上是DAO。

以下是用于设置用户的存储过程

CREATE OR REPLACE FUNCTION example_book_schema.set_user(
    audit_user TEXT
) RETURNS BOOLEAN STABLE LANGUAGE SQL AS $$
    SELECT set_config('book.audit_user', audit_user, true);
    SELECT TRUE;

将切入点仅限于交易方法

您可以通过在BookService建议中添加附加条款,将点数限制仅限于Before中的交易方法。

@Before("execution(* com.basaki.example.service.BookService.*(..) ) " +
            "&& @annotation(annotation)")
    public void setUser(final JoinPoint jp, final Transactional annotation) {
    ...
}

答案 2 :(得分:0)

您可以使用@EntityListeners侦听应用程序上下文中的实体更改,然后收集任何信息(实体值,身份验证用户等...),然后插入到历史记录表中。您可以按照此处的示例进行操作:http://www.baeldung.com/database-auditing-jpa

答案 3 :(得分:0)

您可以创建视图,添加用户ID列,并使用触发器来处理更新。这是在DB端编码的另一种方法。这样你每次都应该通过它,所以不需要其他任何改变。

转到Java / Spring端。

有点古老的风格:TransactionTemplate - 这样你可以完全控制,但你的dao需要更多的代码,因为事务管理需要在那里完成。

其他选项是创建代理 org.springframework.jdbc.datasource.DataSourceTransactionManager并在doBegin处完成工作,然后您的代理需要传递给事务管理器。这就是我的方式。