Открытые соединения с Hibernate + Spring + C3P0 + Многопоточность
У меня есть следующий абстрактный класс конфигурации:
@Configuration
@ComponentScan("my.app")
@EnableTransactionManagement
public abstract class AbstractApplicationConfig {
protected HibernateTransactionManager newHibernateTransactionManager(final LocalSessionFactoryBean localSessionFactoryBean) {
final HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager();
hibernateTransactionManager.setSessionFactory(localSessionFactoryBean.getObject());
return hibernateTransactionManager;
}
protected LocalSessionFactoryBean newLocalSessionFactoryBean(final DataSource dataSource, final String packagesToScan) {
final LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setPackagesToScan(packagesToScan);
localSessionFactoryBean.setDataSource(dataSource);
final Properties properties = new Properties();
properties.setProperty("hibernate.dialect", env.getRequiredProperty("databasePlatform"));
properties.setProperty("hibernate.show_sql", env.getRequiredProperty("showSql"));
properties.setProperty("hibernate.jdbc.batch_size", 10);
properties.setProperty("hibernate.order_inserts", true);
properties.setProperty("hibernate.order_updates", true);
properties.setProperty("hibernate.jdbc.batch_versioned_data", true);
properties.setProperty("hibernate.c3p0.min_size", 10);
properties.setProperty("hibernate.c3p0.max_size", 50);
properties.setProperty("hibernate.c3p0.timeout","300");
properties.setProperty("hibernate.c3p0.max_statements", 50);
properties.setProperty("hibernate.c3p0.idle_test_period", "3000");
localSessionFactoryBean.setHibernateProperties(properties);
return localSessionFactoryBean;
}
protected DataSource newDataSource() {
final ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
dataSource.setDriverClass(env.getRequiredProperty("dataSource.driverClassName"));
dataSource.setJdbcUrl(env.getRequiredProperty("dataSource.url"));
dataSource.setUser(env.getRequiredProperty("dataSource.username"));
dataSource.setPassword(env.getRequiredProperty("dataSource.password"));
dataSource.setPreferredTestQuery("SELECT 1 FROM DUAL");
dataSource.setMaxPoolSize(Integer.valueOf(50));
dataSource.setMinPoolSize(Integer.valueOf(10));
dataSource.setMaxStatements(Integer.valueOf(50));
} catch (final IllegalStateException e) {
e.printStackTrace();
} catch (final PropertyVetoException e) {
e.printStackTrace();
}
return dataSource;
}
}
И я использую это так:
@Configuration
public class SchemaApplicationConfig extends AbstractApplicationConfig {
/** DATASOURCES **/
@Bean
public DataSource dsSchema() {
final DataSource dataSource = newDataSource(DataSourcesNames.SCHEMA1);
return dataSource;
}
/** SESIONFACTORIES **/
@Bean
public LocalSessionFactoryBean sfSchema() {
final LocalSessionFactoryBean localSessionFactoryBean = newLocalSessionFactoryBean(dsSchema(), "es.miapp.schema");
return localSessionFactoryBean;
}
/** TRANSACTIONMANAGERS **/
@Bean
public HibernateTransactionManager txSchema() {
final HibernateTransactionManager hibernateTransactionManager = newHibernateTransactionManager(sfSchema());
return hibernateTransactionManager;
}
}
В моем приложении я запускаю процесс с несколькими потоками:
public static void main(String... args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(SchemaApplicationConfig.class);
MainProcess mainProcess = context.getBean(MainProcess.class)
mainProcess.execute();
context.close();
System.out.println("ENDS");
}
@Service
@Scope("prototype")
public class MainProcessImpl implements MainProcess{
@Autowired
protected ApplicationContext applicationContext;
public void execute() {
final ExecutorService executorService = Executors.newFixedThreadPool(5);
final List<Future<?>> futures = new ArrayList<Future<?>>();
for (int i = 0; i < 5; i++) {
final MyService runnable = (MyService) applicationContext.getBean(MyServiceBean.class);
final Future<?> future = executorService.submit(runnable);
futures.add(future);
}
for (final Future<?> future : futures) {
try {
future.get();
} catch (final InterruptedException e) {
e.printStackTrace();
} catch (final ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();
try {
executorService.awaitTermination(2, TimeUnit.MINUTES);
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
}
MyServiceBean выглядит так:
@Service
@Scope("prototype")
public class MyServiceBean implements Runnable {
@Autowired
private MyDao myDao;
@Override
public void run() {
// Do Stuff and create entity
myDao.insert(entity);
}
}
И мой Дао выглядит так:
@Repository
public class MyDaoImpl implements MyDao {
@Autowired
@Qualifier("sfSchema")
private SessionFactory sessionFactory;
@Override
@Transactional("txSchema")
public void insert(Entity entity) {
sessionFactory.getCurrentSession().persist(entity);
sessionFactory.getCurrentSession().flush();
}
}
Проблема в том, что Hibernate не закрывает все соединения, и после нескольких запусков этого процесса я получаю следующую ошибку:
Caused by: java.net.BindException: Address already in use: connect
Если я продолжаю выполнять экземпляры, в какой-то момент программа зависает, пытаясь выполнить вставку:
[09-08-2018] [11:15:43,961] [DEBUG] [TEST] checkoutStatement: com.mchange.v2.c3p0.stmt.GlobalMaxOnlyStatementCache stats -- total size: 18; checked out: 6; num connections: 6; num keys: 18
[09-08-2018] [11:15:43,961] [DEBUG] [TEST] insert into ENTITY... COMPLETE SQL
[09-08-2018] [11:15:43,961] [DEBUG] [TEST] Executing batch size: 1
Проверяя Oracle, я вижу, что у моего пользователя уже открыто 46 соединений, и они продолжают расти с каждым прогоном...
Идеи?