我有一个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
注释以声明方式划分的事务。
答案 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
处完成工作,然后您的代理需要传递给事务管理器。这就是我的方式。