如何在Spring Boot JPA查询中自定义数据库会话变量encrypt.key?
问题描述
我需要在数据库连接/查询时设置SET SESSION encrypt.key='some_key',模型类中的列定义如下:
@ColumnTransformer(forColumn = "first_name", read = "pgp_sym_decrypt(first_name, current_setting('encrypt.key'))", write = "pgp_sym_encrypt(?, current_setting('encrypt.key'))") @Column(name = "first_name", columnDefinition = "bytea") private String firstName;
直接在postgres.conf中设置encrypt.key时代码可正常运行,但现在需要从Spring配置文件中配置该密钥。我尝试过以下方法,但都存在问题:
- 使用
AttributeConverter注解和自定义转换器:仅适用于JPA操作,不支持LIKE查询。 - 使用
ContextEventListener在应用启动时执行SET SESSION语句:仅对部分请求生效。 - 自定义继承
JpaTransactionManager的CustomTransactionManager,重写prepareSynchronization方法:
@Override protected void prepareSynchronization(DefaultTransactionStatus status,TransactionDefinition definition) { super.prepareSynchronization(status, definition); if (status.isNewTransaction()) { final String query = "SET encrypt.key='" + encryptKey + "'"; entityManager.createNativeQuery(query).executeUpdate(); } log.info("Encrypt Key : {}", entityManager.createNativeQuery("SElECT current_setting('encrypt.key')").getSingleResult()); } }
但调用普通JPA Repository方法时该类未被触发,encrypt.key设置失败。需要正确的实现方向指导。
可行解决方案
方案1:配置连接池初始化SQL(推荐)
如果使用HikariCP等主流连接池,可以直接配置连接初始化SQL,让每个从连接池获取的数据库连接自动执行密钥设置语句,覆盖所有数据库操作场景。
以HikariCP为例,在application.properties中添加:
spring.datasource.hikari.connection-init-sql=SET SESSION encrypt.key='${encrypt.key}'
或YAML格式:
spring: datasource: hikari: connection-init-sql: "SET SESSION encrypt.key='${encrypt.key}'"
这种方式无需修改代码,每个新创建的数据库连接都会预先执行该语句,会话级别的密钥配置自动生效。
方案2:使用Hibernate SessionEventListener绑定会话初始化
通过Hibernate的会话监听器,在每个Hibernate Session创建时执行密钥设置,确保所有基于该会话的JPA操作都能获取到密钥。
- 自定义会话监听器:
public class EncryptionKeySessionListener implements SessionEventListener { private final String encryptKey; public EncryptionKeySessionListener(String encryptKey) { this.encryptKey = encryptKey; } @Override public void sessionCreated(SessionCreatedEvent event) { event.getSession().doWork(connection -> { try (Statement statement = connection.createStatement()) { // 避免SQL注入,使用参数绑定 String sql = "SET SESSION encrypt.key = ?"; try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setString(1, encryptKey); pstmt.execute(); } } }); } }
- 注册监听器到Hibernate配置:
@Configuration public class HibernateConfig { @Value("${encrypt.key}") private String encryptKey; @Bean public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() { return properties -> { properties.put("hibernate.session_event_listeners", Collections.singletonList(new EncryptionKeySessionListener(encryptKey))); }; } }
方案3:修复自定义JpaTransactionManager的配置问题
之前的自定义事务管理器未被调用,是因为Spring容器没有使用该Bean。需要通过@Primary标记它为优先使用的事务管理器:
- 配置自定义事务管理器Bean:
@Configuration @EnableTransactionManagement public class TransactionConfig { @Value("${encrypt.key}") private String encryptKey; @Autowired private EntityManagerFactory entityManagerFactory; @Primary @Bean public PlatformTransactionManager transactionManager() { CustomTransactionManager transactionManager = new CustomTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory); transactionManager.setEncryptKey(encryptKey); return transactionManager; } }
- 修改自定义事务管理器,修复SQL注入问题并正确获取EntityManager:
public class CustomTransactionManager extends JpaTransactionManager { private String encryptKey; public void setEncryptKey(String encryptKey) { this.encryptKey = encryptKey; } @Override protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) { super.prepareSynchronization(status, definition); if (status.isNewTransaction()) { EntityManager entityManager = getEntityManagerFactory().createEntityManager(); try { // 使用参数绑定避免SQL注入 entityManager.createNativeQuery("SET encrypt.key = ?1") .setParameter(1, encryptKey) .executeUpdate(); } finally { entityManager.close(); } } } }
内容的提问来源于stack exchange,提问作者Aditya T
相关产品推荐
相关产品推荐

