首页 > 解决方案 > Spring @Transactional 没有返回到 Hikari 池的连接

问题描述

我有一个基于 REST 的应用程序服务器,它基于嵌入式码头、spring boot 和 hibernate。它使用 MS SQL 后端数据库。

我的 @Transactional 方法有效,但似乎没有关闭数据库连接,最终导致没有可用的 hikari 池成员:

java.sql.SQLTransientConnectionException: HikariPoolProduct - Connection is not available, request timed out after 30003ms.

在上述之后不久,我将看到以下 hikari 统计数据:

10:29:24.774 [HikariPoolProduct housekeeper] DEBUG HikariPoolProduct - Pool stats (total=50, active=49, idle=1, waiting=0)
10:29:24.774 [HikariPoolProduct housekeeper] DEBUG HikariPoolProduct - Fill pool skipped, pool is at sufficient level.

我希望 active 为 1 或 0。

在我的测试用例中,我将 hikari 池大小设置为 50。当我反复让我的客户端通过 REST 调用服务器 CertificatePolicyEntityResource.get() 时,它工作正常,直到它尝试 #51,然后它如上所示失败。

我正在使用@Transactional 方法,并且我很确定包装器/代理应该在返回之前释放 hikari 池成员(通过 db virtual/proxy close)。这似乎没有发生。

我的代码调用树:

client -> [REST] -> CertificatePolicyEntityResource.get() ->
CertificatePolicyEntityServletAdapter.get() ->
DomainRegistryModelProxy.certificatePolicyService() // Use spring ApplicationContext to retrieve CertificatePolicyServiceRepositoryImpl
CertificatePolicyServiceRepositoryImpl.size() and .allCertificatePolicies() // These are @Transactional

这是我的服务类 CertificatePolicyServiceRepositoryImpl ,它具有 @Transactional 方法:

package cmb.domain.model.certpolicy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Accessors( fluent = true )
public class CertificatePolicyServiceRepositoryImpl implements CertificatePolicyService {
        @Getter
        private CertificatePolicyRepository             certificatePolicyRepository;

        public CertificatePolicyServiceRepositoryImpl(CertificatePolicyRepository certificatePolicyRepository) {
                this.certificatePolicyRepository = certificatePolicyRepository;
        }

        @Override
        @Transactional
        public CertificatePolicy certificatePolicyOfId(String id) {
                return certificatePolicyRepository().certificatePolicyOfId(id);
        }

        @Override
        @Transactional
        public List<CertificatePolicy> allCertificatePolicies() {
                return certificatePolicyRepository().allCertificatePolicies();
        }

    @Override
        @Transactional
        public List<CertificatePolicy> allActiveCertificatePolicies() {
                return certificatePolicyRepository().allActiveCertificatePolicies();
        }

        @Override
        @Transactional(readOnly = true)
        public int size() {
                return certificatePolicyRepository().size();
        }

}

CertificatePolicyEntityResource.java:

package cmb.cabridge.port.servlet.resource;

import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
    
@Path(CaBridgeCommandValues.RESOURCE_CERTIFICATEPOLICYENTITY)
public class CertificatePolicyEntityResource extends AbstractCaBridgeResource {
        @BeanParam
        private NetworkRequestContext                                   requestContext;

        @GET
        @Path(CaBridgeCommandValues.SUBRESOURCE_CERTIFICATEPOLICYENTITY_GET + "/{id}")
        public CertificatePolicyEntityResponse get(@PathParam("id") String idList,
                        @QueryParam(CertificatePolicyRequestFlags.FLAG_MINIENTITY) boolean miniEntityEnabled) {
                final String function = "Get by ID";
                ResponseHandler handler = createResponseHandler(m, function);

                try {
                        processRequest(requestContext,
                                        CaBridgeCommand.getInstance().create(CaBridgeCommandValues.CertificatePolicyEntityGet));
                        CertificatePolicyEntityResponse response = new CertificatePolicyEntityServletAdapter(caBridgeSessionContext())
                                        .get(idList, miniEntityEnabled);

                        handler.succeeded(response);
                        return response;
                } catch (Throwable e) {
                        CertificatePolicyEntityResponse response = new CertificatePolicyEntityResponse();
                        handler.failed(e, response);
                        return response;
                }
        }

}

适配器:

package cmb.cabridge.port.servlet.adapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
    
@Component
public class CertificatePolicyEntityServletAdapter extends AbstractCaBridgeCommonServletAdapter {

        private CertificatePolicyService certificatePolicyService() {
            /*
             * Retrieve bean using Spring ApplicationContext
             */
                return DomainRegistryModelProxy.certificatePolicyService();
        }

        public CertificatePolicyEntityResponse get(String searchValue, boolean miniEntityEnabled) {
                List<CertificatePolicyEntity> results = new ArrayList<>();
                entityConverter().setMiniEntityEnabled(miniEntityEnabled);

                if (searchValue == null || searchValue.equalsIgnoreCase(GlobalConstantStandard.ALL)) {
                        if (certificatePolicyService().size() > 0)
                                certificatePolicyService().allCertificatePolicies().stream().forEach(
                                                item -> results.add(entityConverter.toEntity(item))
                                                                );
                } else {
                        List<String> ids = StringTool.splitAsList(searchValue, ",");
                        for (String id : ids) {
                                CertificatePolicy item = certificatePolicyService()
                                                                                        .certificatePolicyOfIdOrName(id);
                                if (item != null)
                                        results.add(entityConverter.toEntity(item));
                        }
                }

                CertificatePolicyEntityResponse response = new CertificatePolicyEntityResponse();
                if (results.size() == 0) {
                        String message = "No " + what + " found for ID/Name(s) " + searchValue;
                        log.info("%s", message);
                        response.setMessage(message);
                        response.setResult(Result.FAILED);
                        response.setFailureType(FailureType.NOTFOUND);
                } else {
                        response.setEntities( results );
                        response.setResult(Result.SUCCEEDED);
                }

                return response;
        }
}

数据源类:

package cmb.cabridge.infrastructure.persistence.hibernate;

import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import cmb.common.infrastructure.DatabaseConfigurationTool;
import cmb.domain.model.CmbDomainModelMarker;
import cmb.product.domain.model.CmbProductDomainModelMarker;

import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * CAB Hibernate Product Database Configuration
 */
@Configuration
@EnableJpaRepositories(
    entityManagerFactoryRef = HibernateProductDatabaseConfiguration.PRODUCT_ENTITY_MANAGER,
    transactionManagerRef = HibernateProductDatabaseConfiguration.PRODUCT_TX_MANAGER
)
@EnableTransactionManagement
public class HibernateProductDatabaseConfiguration {
        private Trace                                                                   log = TraceFactory.create(this);
        /*
         * Packages that Spring should scan to find @Entity classes
         */
        private static final String [] PACKAGES_TO_SCAN = {
                        MangoDomainModelMarker.class.getPackage().getName(),
                        CmbDomainModelMarker.class.getPackage().getName(),
                        CmbProductDomainModelMarker.class.getPackage().getName()
        };

        public static final String                                              PRODUCT_ENTITY_MANAGER = "productEntityManager";
        public static final String                                              PRODUCT_TX_MANAGER = "productTransactionManager";
        public static final String                                              SESSION_FACTORY_PRODUCT = "sessionFactoryProduct";
        // Default size of 10 is too small for CAB
        private static final int                                                MAX_POOL_SIZE = 250;
        private static final int                                                MAX_LIFETIME_SECONDS = 2 * 60; // XXX debug value
        //private static final int                                              MAX_LIFETIME_SECONDS = 60 * 60; // 1 hour
        private static final int                                                IDLE_TIMEOUT_SECONDS = 15 * 60;
        private static final int                                                LEAK_DETECTION_THRESHOLD_SECONDS = 30;
        private static final int                                                CONNECTION_TIMEOUT_SECONDS = 30;

        @Bean(name = SESSION_FACTORY_PRODUCT)
        @Primary
        public SessionFactory sessionFactoryProduct() {
                return sessionFactoryProductBean().getObject();
        }

        @Bean
        @Primary
    public LocalSessionFactoryBean sessionFactoryProductBean() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(productDataSource());
        sessionFactory.setPackagesToScan( PACKAGES_TO_SCAN );
        sessionFactory.setHibernateProperties(hibernateProperties());

        return sessionFactory;
    }

    @Bean
        @Primary
    public DataSource productDataSource() {
        /*
         * Get the PersistenceConfiguration from persistconfig.properties and
         * then build Creator to format the configuration data as needed.
         */
        PersistenceConfiguration cf = ProductConfig.getPersistenceConfiguration();
        if (cf == null || cf.getDataBaseName() == null)
                throw new ConfigurationException("Failed to load persistence configuration");
        HibernatePropertiesCreator propCreator = new HibernatePropertiesCreator(cf);

        log.info("Creating Product DataSource with url %s", propCreator.createUrl());

        /*
         * Use Hikari specific config so we can configure maximumPoolSize which
         * is critical to having a large enough pool.
         */
        HikariConfig config = new HikariConfig();
        config.setPoolName("HikariPool" + "Product");
        config.setJdbcUrl( propCreator.createUrl() );
        config.setUsername( cf.getUserName() );
        config.setPassword( cf.getUserPassword() );
        config.setDriverClassName( propCreator.getDriverClass() );
        config.setMaximumPoolSize( DatabaseConfigurationTool.maxPoolSize("product", MAX_POOL_SIZE) );
        config.setConnectionTimeout(CONNECTION_TIMEOUT_SECONDS * 1000);
        config.setMaxLifetime(MAX_LIFETIME_SECONDS * 1000);
//      config.setIdleTimeout(IDLE_TIMEOUT_SECONDS * 1000);
        // Report connection leaks with stacktrace
        config.setLeakDetectionThreshold( LEAK_DETECTION_THRESHOLD_SECONDS * 1000 );

        return new HikariDataSource( config );
    }

    @Bean
        @Primary
    public PlatformTransactionManager productTransactionManager() {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(sessionFactoryProduct());
        return transactionManager;
    }

    private final Properties hibernateProperties() {
        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update");
        //hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        // Use new IdentifierGenerator
        hibernateProperties.setProperty("hibernate.id.new_generator_mappings", "true");

        return hibernateProperties;
    }

}

标签: javaspringspring-data-jpa

解决方案


推荐阅读