Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как можно закрыть 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!
}
Вывод
Используй в этом порядке:
- Spring Data JPA (ORM, самое простое) → для обычных операций
- JdbcTemplate → когда нужен SQL, но хочешь автоматизма
- Try-with-resources → для прямого JDBC
- Connection Pool → всегда в production (Hikari CP)
- R2DBC → для асинхронных операций
Основное правило: никогда не создавай новое соединение для каждого запроса — используй pool, он переиспользует существующие соединения.