java - 部署 Web 应用程序后出现“连接过多”错误

标签 java mysql spring hibernate spring-boot

我正在使用 AbstractRoutingDataSource 在我的应用程序中创建 Multi-Tenancy 。我注意到,在从 IDE 中重新部署了几次 Web 应用程序后,我最终收到了 MySQL 错误“连接过多”。 经过进一步调查,我发现当我运行MySQL命令show processlist;时,我看到每次部署后打开的连接量增加了10,这可能意味着连接池在某种程度上仍然存在还活着在某个地方。 在使用 AbstractRoutingDataSource 之前,我使用了默认的 spring 数据源配置(使用 application.properties)并且工作正常。

这是 Multi-Tenancy 配置类:

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(destroyMethod = "close"),但AbstractRoutingDataSource上没有定义close方法。

我到处搜索但找不到并回答。有人可以帮助我了解是什么阻止了连接池在重新部署之间被释放吗?

提前致谢。

最佳答案

好吧,我最终通过放弃使用 Spring 的 AbstractRoutingDataSource 解决了这个问题,而是使用 Hibernate 的 Multi-Tenancy 机制(基于 this article 中可以找到的解决方案)。 .

长话短说

您需要执行 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 步:创建 MultiTenantConnectionProvider

@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;
    }

}

第 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;
    }

}

关于java - 部署 Web 应用程序后出现“连接过多”错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42948999/

相关文章:

java - 在java中的相似类之间复制字段

mysql - 平均值的平均值

java - 我的服务在一个类中工作,但在另一个类中返回 null

java - launch4j 到底做了什么?

mysql - 读取用户输入并将其发送到 MySQL

java - 如何在运行时在 spring 中重新加载/刷新属性而不重新启动 jvm?

java - Spring JPA 和 persistence.xml

spring基础mvc示例应用,注解扫描混淆

java - 精确 PrefixQuery 得分更高

java - 如何将 Java 项目连接到 MySQL 服务器