我有適用于 PostgreSQL 資料庫的 Spring Boot 應用程式。
我只有一個用于 Spring Data JPA 操作和 Liquibase 遷移的資料源(我猜)。
spring:
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://*************/*****
username: *******
password: *******
type: com.zaxxer.hikari.HikariDataSource
connection-test-query: SELECT 1;
idle-timeout: 30000
maximum-pool-size: 100
minimum-idle: 7
jpa:
hibernate:
ddl-auto: validate
database-platform: org.hibernate.dialect.PostgreSQLDialect
show-sql: false
properties:
hibernate:
default_schema: public
format_sql: true
enable_lazy_load_no_trans: true
generate_statistics: false
jdbc.batch_size: 100
order_inserts: true
order_updates: true
jdbc.batch_versioned_data: true
query.fail_on_pagination_over_collection_fetch: true
liquibase:
liquibase-schema: public
default-schema: public
change-log: classpath:db/changelog/db.changelog-master.xml
我也有 Spring Boot 配置類,它指定我想在我的應用程式中使用哪個事務管理器。
@Configuration
public class TransactionManagerConfig {
@Primary
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(sessionFactory);
return transactionManager;
}
}
在使用我的資料庫時,我必須以不同的方式與我的資料進行通信。第一個是使用帶有@Transactional 注釋的服務方法。例如,對于簡單的閱讀,我使用下一種方法:
@Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED)
public List<Dto> fetchAll() {...
另一種方法稍微復雜一些,但仍然很常見。例如,對于更新,我手動與事務互動:
@Service
public class EntityService {
@Autowired
private DocumentRepository documentRepository;
@Autowired
private PlatformTransactionManager transactionManager;
public Long update(Long id, Entity entity) {
DefaultTransactionDefinition transForCursor = new DefaultTransactionDefinition();
transForCursor.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transForCursor.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus cursorTransactionStatus = transactionManager.getTransaction(transForCursor);
try {
//...
//here I am interactiong with S3 File storage
//...
savedEntity = entityRepository.save(entity);
} catch (Exception e) {
//...
//if I catch any exception I should delete files from S3 File storage
//which I just uploaded and only then I should rollback database transaction
//...
if (!(e instanceof RuntimeException)) {
transactionManager.rollback(cursorTransactionStatus);
}
throw e;
}
try {
transactionManager.commit(cursorTransactionStatus);
} catch (Exception e) {
//...
//if any exception happens when I was trying to commit I still should delete files
//...
throw new TransactionException();
}
return savedEntity.getId();
}
}
問題是有時我在呼叫讀取或更新方法時會遇到此例外:
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! JpaTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single JpaTransactionManager for all transactions on a single DataSource, no matter whether JPA or JDBC access.
但有時它作業正常,沒有任何錯誤。最令人難以置信的有趣的事情是,上次我遇到這個問題時,我每呼叫一次read 方法就會出現這個例外。
有誰知道它為什么會發生以及如何解決它?非常感謝您的時間和回答!
我試圖通過在我的讀取方法中添加@Transactional 注釋來修復它。我在定義事務管理器的配置類中添加了@Primay 注釋。但不幸的是,它沒有幫助我。我試圖在這里找到同樣的問題,但我沒有滿足同樣的條件。
uj5u.com熱心網友回復:
那個模式:
TransactionStatus tx = transactionManager.getTransaction(...);
try {
...
} catch (Exception e) {
if (!(e instanceof RuntimeException)) {
transactionManager.rollback(tx);
}
throw e;
}
try {
transactionManager.commit(tx);
} catch (Exception e) {
...
throw new TransactionException();
}
return savedEntity.getId();
絕對不正確,經驗法則是:您必須在啟動它的同一位置完成(提交或回滾)事務。但是,在您的情況下,情況并非如此,因此您會遇到資源泄漏和其他相關后果。
如果您需要在事務回滾時清理一些外部資源,您有以下選項:
- spring-tx 方式:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
if (STATUS_ROLLED_BACK== status) {
// do something
}
}
});
- 休眠方式:
SessionImplementor session = entityManager.unwrap(SessionImplementor.class);
session.addEventListeners(new BaseSessionEventListener() {
@Override
public void transactionCompletion(boolean success) {
if (!success) {
// do something
}
}
});
- 另一種休眠方式:
SessionImplementor session = entityManager.unwrap(SessionImplementor.class);
session.getActionQueue().registerProcess((success, sess) -> {
if (!success) {
// do something
}
});
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/525130.html
