Java ';连接太多';重新部署webapp后出错

Java ';连接太多';重新部署webapp后出错,java,mysql,spring,hibernate,spring-boot,Java,Mysql,Spring,Hibernate,Spring Boot,我正在使用AbstractRoutingDataSource在我的应用程序中创建多租户。我注意到,在从IDE重新部署了几次webapp之后,我最终得到了MySQL错误“连接太多”。 经过进一步的调查,我发现当我运行MySQL命令showprocesslist,我看到每次部署后打开的连接量增加了10,这可能意味着连接池在某种程度上仍然存在。 在我使用AbstractRoutingDataSource之前,我使用了默认的spring数据源配置(使用application.properties),并且

我正在使用
AbstractRoutingDataSource
在我的应用程序中创建多租户。我注意到,在从IDE重新部署了几次webapp之后,我最终得到了MySQL错误“连接太多”。 经过进一步的调查,我发现当我运行MySQL命令
showprocesslist,我看到每次部署后打开的连接量增加了10,这可能意味着连接池在某种程度上仍然存在。
在我使用
AbstractRoutingDataSource
之前,我使用了默认的spring数据源配置(使用
application.properties
),并且运行良好

以下是多租户配置类:

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by Alon Segal on 16/03/2017.
 */
@Configuration
public class MultitenantConfiguration {
    @Autowired
    private DataSourceProperties properties;

    /**
     * Defines the data source for the application
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(
            prefix = "spring.datasource"
    )
    public DataSource dataSource() {

        //Creating datasources map "resolvedDataSources" here

        MultitenantDataSource dataSource = new MultitenantDataSource();
        dataSource.setDefaultTargetDataSource(defaultDataSource());
        dataSource.setTargetDataSources(resolvedDataSources);

        // Call this to finalize the initialization of the data source.
        dataSource.afterPropertiesSet();

        return dataSource;
    }

    /**
     * Creates the default data source for the application
     *
     * @return
     */
    private DataSource defaultDataSource() {
        .
        .
        .
    }
}
和数据源类:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * Created by Alon Segal on 16/03/2017.
 */
public class MultitenantDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContext.getCurrentTenant();
    }
}
我还尝试使用
@Bean(destromethod=“close”)
,但在
AbstractRoutingDataSource
上没有定义close方法

我到处找,但找不到答案。有人能帮我理解是什么阻止了连接池在重新部署之间释放吗


提前感谢。

好的,所以我最终解决了这个问题,放弃了使用Spring的
AbstractRoutingDataSource
,而是使用Hibernate的多租户机制,该机制基于中可以找到的解决方案

长话短说

您需要执行3个步骤:

步骤1:创建CurrentTenantIdentifierResolver

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {

    @Override
    public String resolveCurrentTenantIdentifier() {
        String tenantId = TenantContext.getCurrentTenant();
        if (tenantId != null) {
            return tenantId;
        }
        return DEFAULT_TENANT_ID;
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}
步骤2:创建多租户连接提供程序

@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {

    @Autowired
    private DataSource dataSource;

    @Override
    public Connection getAnyConnection() throws SQLException {
        return dataSource.getConnection();
    }

    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException {
        connection.close();
    }

    @Override
    public Connection getConnection(String tenantIdentifie) throws SQLException {
        String tenantIdentifier = TenantContext.getCurrentTenant();
        final Connection connection = getAnyConnection();
        try {
            if (tenantIdentifier != null) {
                connection.createStatement().execute("USE " + tenantIdentifier);
            } else {
                connection.createStatement().execute("USE " + DEFAULT_TENANT_ID);
            }
        }
        catch ( SQLException e ) {
            throw new HibernateException(
                    "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
                    e
            );
        }
        return connection;
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        try {
            connection.createStatement().execute( "USE " + DEFAULT_TENANT_ID );
        }
        catch ( SQLException e ) {
            throw new HibernateException(
                    "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
                    e
            );
        }
        connection.close();
    }

    @SuppressWarnings("rawtypes")
    @Override
    public boolean isUnwrappableAs(Class unwrapType) {
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> unwrapType) {
        return null;
    }

    @Override
    public boolean supportsAggressiveRelease() {
        return true;
    }

}
@组件
公共类MultiTenantConnectionProviderImpl实现MultiTenantConnectionProvider{
@自动连线
私有数据源;
@凌驾
公共连接getAnyConnection()引发SQLException{
返回dataSource.getConnection();
}
@凌驾
public void releaseAnyConnection(连接连接)引发SQLException{
connection.close();
}
@凌驾
公共连接getConnection(字符串tenantIdentifie)引发SQLException{
字符串tenantIdentifier=TenantContext.getCurrentTenant();
最终连接=getAnyConnection();
试一试{
if(租户标识符!=null){
connection.createStatement().execute(“使用”+tenantIdentifier);
}否则{
connection.createStatement().execute(“使用”+默认租户ID);
}
}
捕获(SQLE异常){
抛出新的HibernateeException(
无法将JDBC连接更改为指定的架构[“+tenantIdentifier+”],
E
);
}
回路连接;
}
@凌驾
public void releaseConnection(字符串租户标识符,连接连接)引发SQLException{
试一试{
connection.createStatement().execute(“使用”+默认租户ID);
}
捕获(SQLE异常){
抛出新的HibernateeException(
无法将JDBC连接更改为指定的架构[“+tenantIdentifier+”],
E
);
}
connection.close();
}
@抑制警告(“原始类型”)
@凌驾
公共布尔IsUnwrapbleas(类unwrapType){
返回false;
}
@凌驾
公共展开(类展开类型){
返回null;
}
@凌驾
公共布尔值supportsAggressiveRelease(){
返回true;
}
}
步骤3:将其连接起来

@Configuration
public class HibernateConfig {

    @Autowired
    private JpaProperties jpaProperties;

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
                                                                       MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
                                                                       CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
        Map<String, Object> properties = new HashMap<>();
        properties.putAll(jpaProperties.getHibernateProperties(dataSource));
        properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
        properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
        properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);

        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.autorni");
        em.setJpaVendorAdapter(jpaVendorAdapter());
        em.setJpaPropertyMap(properties);
        return em;
    }

}
@配置
公共类HibernateConfig{
@自动连线
私人物业;
@豆子
公共JpaVendorAdapter JpaVendorAdapter(){
返回新的HibernateJavaEndorapter();
}
@豆子
公共LocalContainerEntityManagerFactoryBean entityManagerFactory(数据源数据源,
MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl){
映射属性=新的HashMap();
properties.putAll(jpapproperties.getHibernateProperties(dataSource));
properties.put(Environment.MULTI_-TENANT,MULTI-tenancystategy.SCHEMA);
properties.put(Environment.MULTI_-TENANT_-CONNECTION_-PROVIDER、MULTI-tenantconnectionproviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER,currentTenantIdentifierResolverImpl);
LocalContainerEntityManagerFactoryBean em=新的LocalContainerEntityManagerFactoryBean();
em.setDataSource(数据源);
em.setPackagesToScan(“com.autorni”);
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(属性);
返回em;
}
}

您的
resolvedDataSource
s应该关闭。很明显,您并没有将它们创建为SpringBean,这意味着它们会挂起而不是关闭。要么将这些已解析的数据源制作成sprig bean,以便spring可以关闭它,要么在
multi-tenantdatasource
中实现一个close方法来关闭它们。谢谢@M.Denium的回答。如何将它们创建为SpringBean?它们是根据配置文件动态创建的。那么在
MultitenantDataSource
中创建
close
方法并关闭连接到其中的所有
targetDataSources
可能是最简单的方法。为什么不使用pool?像hikari或者apache@ali这并没有解决我的问题,但它还是有用的。我采用了Hikari连接池,它显然更强大。