← Назад к вопросам

Как можно закрыть connection?

2.0 Middle🔥 191 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как можно закрыть connection

Управление соединениями с БД — критичная часть разработки. Покажу все способы закрытия connection и best practices.

1. Проблема утечки соединений

Когда соединение не закрывается, оно остаётся в памяти и блокирует ресурсы:

public class ConnectionLeakExample {
    public static void main(String[] args) throws SQLException {
        Connection conn = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/mydb", "root", "password");
        
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM users");
        
        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
        
        // Забыли закрыть соединение!
        // conn.close();
        // stmt.close();
        // rs.close();
        // 
        // Результат: утечка, через 10000 запросов сервер БД упадёт
    }
}

2. Способ 1: Явное закрытие (try-finally) — СТАРЫЙ ПОДХОД

public class ManualCloseExample {
    public static void getUserName(Long userId) throws SQLException {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        
        try {
            conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mydb", "root", "password");
            stmt = conn.createStatement();
            rs = stmt.executeQuery("SELECT name FROM users WHERE id = " + userId);
            
            if (rs.next()) {
                System.out.println(rs.getString("name"));
            }
        } finally {
            // Обязательно закрыть в обратном порядке: ResultSet → Statement → Connection
            if (rs != null) rs.close();
            if (stmt != null) stmt.close();
            if (conn != null) conn.close();
        }
    }
}

Проблемы:

  • Много кода
  • Легко забыть закрыть
  • Если rs.close() выбросит исключение, stmt и conn не закроются

3. Способ 2: Try-with-resources (РЕКОМЕНДУЕТСЯ) — Java 7+

Лучший способ для JDBC:

public class TryWithResourcesExample {
    public static void getUserName(Long userId) throws SQLException {
        String query = "SELECT name FROM users WHERE id = ?";
        
        try (Connection conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mydb", "root", "password");
             PreparedStatement stmt = conn.prepareStatement(query)) {
            
            stmt.setLong(1, userId);
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    System.out.println(rs.getString("name"));
                }
            }
        } // Все ресурсы закроются автоматически (в обратном порядке)
    }
}

Как это работает:

// Компилятор преобразует в:
try {
    Connection conn = DriverManager.getConnection(...);
    PreparedStatement stmt = conn.prepareStatement(query);
    // code...
} finally {
    if (stmt != null) {
        try {
            stmt.close();
        } catch (Exception e) {
            // подавляет исключение при закрытии
        }
    }
    if (conn != null) {
        try {
            conn.close();
        } catch (Exception e) {
            // подавляет исключение при закрытии
        }
    }
}

Преимущества:

  • Автоматическое закрытие
  • Правильный порядок (обратный)
  • Безопасность исключений
  • Меньше кода

4. Способ 3: Connection Pool (DataSource) — PRODUCTION

В production используй connection pool вместо создания новых соединений:

// Конфигурация Hikari CP (самый быстрый pool)
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        config.setUsername("root");
        config.setPassword("password");
        config.setMaximumPoolSize(10); // Максимум 10 соединений
        config.setMinimumIdle(2);      // Минимум 2 в ожидании
        config.setConnectionTimeout(30000); // 30 секунд на получение
        config.setIdleTimeout(600000);      // 10 минут неактивности
        config.setMaxLifetime(1800000);     // 30 минут жизни соединения
        
        return new HikariDataSource(config);
    }
}

Как работает pool:

Первый запрос:
1. Создаёт 2 соединения (minimumIdle) → хранит в pool
2. Выделяет одно соединение для использования
3. После использования возвращает в pool (не закрывает!)
4. Другой запрос может использовать это же соединение

Преимущество: переиспользование соединений → нет overhead создания

Использование с Spring Data JPA:

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .driverClassName("com.mysql.cj.jdbc.Driver")
            .url("jdbc:mysql://localhost:3306/mydb")
            .username("root")
            .password("password")
            .build();
    }
}

// Использование (Spring автоматически закрывает соединения из pool)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);
}

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUser(String email) {
        // Соединение берётся из pool, автоматически закрывается
        return userRepository.findByEmail(email);
    }
}

application.properties:

# Hikari CP (по умолчанию в Spring Boot)
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

5. Способ 4: Template Method Pattern (JdbcTemplate)

Spring JDBC Template автоматически закрывает соединения:

@Repository
public class UserRepository {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public List<String> getAllUserNames() {
        // JdbcTemplate управляет соединением внутри
        return jdbcTemplate.queryForList(
            "SELECT name FROM users",
            String.class
        );
    }
    
    public User getUserById(Long id) {
        return jdbcTemplate.queryForObject(
            "SELECT id, name, email FROM users WHERE id = ?",
            new Object[]{id},
            (rs, rowNum) -> new User(
                rs.getLong("id"),
                rs.getString("name"),
                rs.getString("email")
            )
        );
        // Соединение закроется автоматически
    }
}

6. Способ 5: Reactive Streams (R2DBC) — будущее

Для асинхронной работы с БД:

@Configuration
@EnableR2dbcRepositories
public class R2dbcConfig {
    @Bean
    public ConnectionFactory connectionFactory() {
        return new MySQLConnectionFactory(
            MySQLConnectionConfiguration.builder()
                .host("localhost")
                .port(3306)
                .database("mydb")
                .username("root")
                .password("password")
                .build()
        );
    }
}

@Repository
public interface UserReactiveRepository extends ReactiveCrudRepository<User, Long> {
    Mono<User> findByEmail(String email);
}

@Service
public class UserReactiveService {
    @Autowired
    private UserReactiveRepository userRepository;
    
    public Mono<User> getUser(String email) {
        return userRepository.findByEmail(email)
            .doFinally(signalType -> {
                // Соединение закроется асинхронно
                System.out.println("Connection closed");
            });
    }
}

7. Spring Data JPA (ORM) — автоматическое управление

ОРМ скрывает управление соединениями:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(unique = true)
    private String email;
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);
}

@Service
@Transactional // Управляет сессией и соединением
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
        // Соединение закроется после метода
    }
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}

8. Отладка утечек соединений

// Как найти утечку?
@Configuration
public class DataSourceDebugConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        // ... другие настройки
        
        // Включи логирование утечек
        config.setLeakDetectionThreshold(15000); // 15 секунд
        
        return new HikariDataSource(config);
    }
}

// Логирование в application.properties
logging.level.com.zaxxer.hikari.pool.HikariPool=DEBUG

Если соединение занято более 15 секунд, Hikari выведет в лог:

[WARN] HikariPool - Connection is still open, stack trace:
java.lang.Exception: Connection opened at
    at UserService.getUser(UserService.java:25)

9. Лучшие практики

// ✅ ХОРОШО: используй try-with-resources для прямых JDBC
try (Connection conn = dataSource.getConnection();
     PreparedStatement stmt = conn.prepareStatement(query)) {
    // code
}

// ✅ ХОРОШО: используй Spring Data JPA
@Service
public class MyService {
    @Autowired
    private MyRepository repository;
    
    public void process() {
        repository.save(entity); // Соединение управляется автоматически
    }
}

// ✅ ХОРОШО: используй Connection Pool
@Bean
public DataSource dataSource() {
    return DataSourceBuilder.create()
        .driverClassName("com.mysql.cj.jdbc.Driver")
        .url(env.getProperty("spring.datasource.url"))
        .build();
}

// ❌ ПЛОХО: забыл закрыть
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
// утечка!

// ❌ ПЛОХО: закрывает в неправильном порядке
stmt.close();
conn.close();
rs.close(); // слишком поздно

// ❌ ПЛОХО: создаёт новое соединение для каждого запроса
for (int i = 0; i < 1000; i++) {
    Connection conn = DriverManager.getConnection(url, user, pass);
    // slow!
}

Вывод

Используй в этом порядке:

  1. Spring Data JPA (ORM, самое простое) → для обычных операций
  2. JdbcTemplate → когда нужен SQL, но хочешь автоматизма
  3. Try-with-resources → для прямого JDBC
  4. Connection Pool → всегда в production (Hikari CP)
  5. R2DBC → для асинхронных операций

Основное правило: никогда не создавай новое соединение для каждого запроса — используй pool, он переиспользует существующие соединения.