#jpa #spring-data #eclipselink
#jpa #весна-данные #eclipselink
Вопрос:
У меня есть следующая служба, которая просто регистрирует сообщение об ошибке в базе данных. Когда у меня нет @Transactional
метода, соединение остается активным до тех пор, пока не будут израсходованы все соединения. Все StoredProcedureQuery
вызовы, возвращающие результат, не имеют этой проблемы.
Почему мне нужно пометить метод, @Transactional
чтобы он освобождал соединение?
@Service
public class SSOErrorLogServiceImpl implements SSOErrorLogService {
@PersistenceContext
private EntityManager em;
@Override
@Transactional
public void logError(String errorMessage, String request){
StoredProcedureQuery proc = em.createNamedStoredProcedureQuery("dbo.spSSOLogError");
proc.setParameter("errorMessage", errorMessage);
proc.setParameter("request", request);
proc.execute();
}
}
Настройка данных
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory
= new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(reflexDataSource());
entityManagerFactory.setPackagesToScan("com.company.platform.jpa");
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
Properties additionalProperties = new Properties();
additionalProperties.put("eclipselink.weaving", "false");
entityManagerFactory.setJpaProperties(additionalProperties);
return entityManagerFactory;
}
@Bean
public HikariDataSource reflexDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setLeakDetectionThreshold(20000);
dataSource.setMaximumPoolSize(20);
dataSource.setConnectionTimeout(5000);
dataSource.setRegisterMbeans(false);
dataSource.setInitializationFailFast(false);
dataSource.setDriverClassName(driver);
dataSource.setJdbcUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(password);
return dataSource;
}
Журналы без транзакции (выдержка)
[EL Finer]: connection: 2016-09-30 14:33:16.955--ServerSession(317574415)--Thread(Thread[Test worker,5,main])--client acquired: 1151058631
[EL Finer]: transaction: 2016-09-30 14:33:16.962--ClientSession(1151058631)--Thread(Thread[Test worker,5,main])--acquire unit of work: 544160013
[EL Finest]: query: 2016-09-30 14:33:16.962--UnitOfWork(544160013)--Thread(Thread[Test worker,5,main])--Execute query ResultSetMappingQuery(name="dbo.spSSOLogError" )
[EL Finest]: connection: 2016-09-30 14:33:16.963--ServerSession(317574415)--Connection(1107704332)--Thread(Thread[Test worker,5,main])--Connection acquired from connection pool [read].
[EL Finest]: connection: 2016-09-30 14:33:16.963--ServerSession(317574415)--Thread(Thread[Test worker,5,main])--reconnecting to external connection pool
[EL Fine]: sql: 2016-09-30 14:33:16.963--ServerSession(317574415)--Connection(539538496)--Thread(Thread[Test worker,5,main])--EXECUTE dbo.spSSOLogError @errorMessage = ?, @request = ?
bind => [Message, Request]
2016-09-30 14:33:16,984 | TRACE | org.springframework.test.context.TestContextManager:394 - afterTestMethod(): instance [com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest@314c32e8], method [public void com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest.test_logError()], exception [null]
2016-09-30 14:33:16,985 | DEBUG | org.springframework.test.context.support.DirtiesContextTestExecutionListener:94 - After test method: context [DefaultTestContext@6cce3af3 testClass = SSOErrorLogServiceTest, testInstance = com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest@314c32e8, testMethod = test_logError@SSOErrorLogServiceTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@37770c96 testClass = SSOErrorLogServiceTest, locations = '{}', classes = '{class com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], class dirties context [false], class mode [null], method dirties context [false].
2016-09-30 14:33:16,985 | TRACE | org.springframework.test.context.TestContextManager:437 - afterTestClass(): class [class com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest]
2016-09-30 14:33:16,986 | DEBUG | org.springframework.test.context.support.DirtiesContextTestExecutionListener:126 - After test class: context [DefaultTestContext@6cce3af3 testClass = SSOErrorLogServiceTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [MergedContextConfiguration@37770c96 testClass = SSOErrorLogServiceTest, locations = '{}', classes = '{class com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], dirtiesContext [false].
Журналы с @Transaction
[EL Finer]: transaction: 2016-09-30 14:30:50.747--UnitOfWork(1448667590)--Thread(Thread[Test worker,5,main])--begin unit of work flush
[EL Finer]: transaction: 2016-09-30 14:30:50.747--UnitOfWork(1448667590)--Thread(Thread[Test worker,5,main])--end unit of work flush
[EL Finest]: query: 2016-09-30 14:30:50.747--UnitOfWork(1448667590)--Thread(Thread[Test worker,5,main])--Execute query ResultSetMappingQuery(name="dbo.spSSOLogError" )
[EL Finest]: connection: 2016-09-30 14:30:50.748--ServerSession(1432568628)--Connection(708660831)--Thread(Thread[Test worker,5,main])--Connection acquired from connection pool [default].
[EL Finer]: transaction: 2016-09-30 14:30:50.748--ClientSession(287210054)--Connection(708660831)--Thread(Thread[Test worker,5,main])--begin transaction
[EL Finest]: connection: 2016-09-30 14:30:50.748--ClientSession(287210054)--Thread(Thread[Test worker,5,main])--reconnecting to external connection pool
[EL Fine]: sql: 2016-09-30 14:30:50.75--ClientSession(287210054)--Connection(29007067)--Thread(Thread[Test worker,5,main])--EXECUTE dbo.spSSOLogError @errorMessage = ?, @request = ?
bind => [Message, Request]
2016-09-30 14:30:50,772 | TRACE | org.springframework.transaction.interceptor.TransactionAspectSupport:519 - Completing transaction for [com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceImpl.logError]
2016-09-30 14:30:50,772 | TRACE | org.springframework.transaction.support.AbstractPlatformTransactionManager:926 - Triggering beforeCommit synchronization
2016-09-30 14:30:50,772 | TRACE | org.springframework.transaction.support.AbstractPlatformTransactionManager:939 - Triggering beforeCompletion synchronization
2016-09-30 14:30:50,772 | DEBUG | org.springframework.transaction.support.AbstractPlatformTransactionManager:755 - Initiating transaction commit
2016-09-30 14:30:50,773 | DEBUG | org.springframework.orm.jpa.JpaTransactionManager:512 - Committing JPA transaction on EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@8f90fd5]
[EL Finer]: transaction: 2016-09-30 14:30:50.773--UnitOfWork(1448667590)--Thread(Thread[Test worker,5,main])--begin unit of work commit
[EL Finer]: transaction: 2016-09-30 14:30:50.773--ClientSession(287210054)--Connection(29007067)--Thread(Thread[Test worker,5,main])--commit transaction
[EL Finest]: connection: 2016-09-30 14:30:50.775--ServerSession(1432568628)--Connection(708660831)--Thread(Thread[Test worker,5,main])--Connection released to connection pool [default].
[EL Finer]: transaction: 2016-09-30 14:30:50.775--UnitOfWork(1448667590)--Thread(Thread[Test worker,5,main])--end unit of work commit
[EL Finer]: transaction: 2016-09-30 14:30:50.775--UnitOfWork(1448667590)--Thread(Thread[Test worker,5,main])--resume unit of work
2016-09-30 14:30:50,775 | TRACE | org.springframework.transaction.support.AbstractPlatformTransactionManager:952 - Triggering afterCommit synchronization
2016-09-30 14:30:50,775 | TRACE | org.springframework.transaction.support.AbstractPlatformTransactionManager:968 - Triggering afterCompletion synchronization
2016-09-30 14:30:50,775 | TRACE | org.springframework.transaction.support.TransactionSynchronizationManager:331 - Clearing transaction synchronization
2016-09-30 14:30:50,776 | TRACE | org.springframework.transaction.support.TransactionSynchronizationManager:243 - Removed value [org.springframework.orm.jpa.EntityManagerHolder@4569d6c9] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@33d8b566] from thread [Test worker]
2016-09-30 14:30:50,776 | TRACE | org.springframework.transaction.support.TransactionSynchronizationManager:243 - Removed value [org.springframework.jdbc.datasource.ConnectionHolder@53d1206a] for key [JDBC URL = jdbc:sqlserver://somehost:1433;databaseName=Database, Username = test, partitions = 1, max (per partition) = 10, min (per partition) = 0, idle max age = 60 min, idle test period = 240 min, strategy = DEFAULT] from thread [Test worker]
2016-09-30 14:30:50,776 | DEBUG | org.springframework.orm.jpa.JpaTransactionManager:600 - Closing JPA EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@8f90fd5] after transaction
2016-09-30 14:30:50,776 | DEBUG | org.springframework.orm.jpa.EntityManagerFactoryUtils:432 - Closing JPA EntityManager
[EL Finer]: transaction: 2016-09-30 14:30:50.776--UnitOfWork(1448667590)--Thread(Thread[Test worker,5,main])--release unit of work
[EL Finer]: connection: 2016-09-30 14:30:50.776--ClientSession(287210054)--Thread(Thread[Test worker,5,main])--client released
2016-09-30 14:30:50,776 | TRACE | org.springframework.test.context.TestContextManager:394 - afterTestMethod(): instance [com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest@314c32e8], method [public void com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest.test_logError()], exception [null]
2016-09-30 14:30:50,777 | DEBUG | org.springframework.test.context.support.DirtiesContextTestExecutionListener:94 - After test method: context [DefaultTestContext@6cce3af3 testClass = SSOErrorLogServiceTest, testInstance = com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest@314c32e8, testMethod = test_logError@SSOErrorLogServiceTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@37770c96 testClass = SSOErrorLogServiceTest, locations = '{}', classes = '{class com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], class dirties context [false], class mode [null], method dirties context [false].
2016-09-30 14:30:50,777 | TRACE | org.springframework.test.context.TestContextManager:437 - afterTestClass(): class [class com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest]
2016-09-30 14:30:50,778 | DEBUG | org.springframework.test.context.support.DirtiesContextTestExecutionListener:126 - After test class: context [DefaultTestContext@6cce3af3 testClass = SSOErrorLogServiceTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [MergedContextConfiguration@37770c96 testClass = SSOErrorLogServiceTest, locations = '{}', classes = '{class com.somecompany.platform.jpa.ssoerrorlog.SSOErrorLogServiceTest$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], dirtiesContext [false].
Комментарии:
1. Как вы указали источник данных, может ли у него быть ограничение на повторное использование соединения? в 366102 упоминается нечто подобное, но ранее у меня сложилось впечатление, что код закрывал все соединения в блоках finally. Можете ли вы включить отладку, которая выводит данные из стека, из которого были получены ошибочные соединения? Некоторые серверы могут справиться с этим, но я не знаю, есть ли такая опция в вашей настройке.
2. Я не думаю, что это ограничение на повторное использование, потому что самое первое соединение не освобождается, когда я запускаю его в тесте. Установив порог обнаружения утечки в источнике данных, я довольно быстро вижу в выходных данных журнала, какие соединения не освобождаются, но не смог получить никаких выходных данных, которые могли бы указать, почему. Я предполагаю, что это потому, что SP вставляет данные и нуждается в @Transaction для фиксации.
3. Если внешней транзакции нет, она должна автоматически фиксироваться и освобождаться. Установите для ведения журнала значение finest и посмотрите, что может регистрироваться.
4. Из журналов ясно, что соединение не освобождается без @Transactional, но, как вы сказали, я бы ожидал, что оно автоматически зафиксируется и будет освобождено.
Ответ №1:
Вы пытались изменить @PersistenceContext
на @PersistanceUnit?
EntityManagers, полученные с помощью @PersistenceContext
диспетчера объектов, управляемых контейнером, поскольку контейнер будет отвечать за управление «менеджером объектов», в то время как EntityManagers, полученные с помощью @PersistenceUnit / entityManagerFactory.createEntityManager()
диспетчера объектов, управляемых приложением, и разработчик должен управлять определенными вещами в коде (например, для освобождения ресурсов, полученных EntityManager).
Ответ №2:
Попробуйте освободить запрос.
query.unwrap(ProcedureOutputs.class).release();
Ответ №3:
Мы столкнулись с той же проблемой при execute()
вызове. Соединение не закрывалось. Просмотр кода сделал его более понятным: выполнить ()
Вызов executeUpdate() решил проблему.