Вопрос или проблема
@Service
public class StudentService {
@Autowired
private StudentRepository studentRepository;
public void enrollStudents(List<Student> students) {
//Вызов 1-го метода (верификация)
studentRepository.performBackgroundVerification(students);
//Вызов 2-го метода (запись)
studentRepository.enrollStudents(students);
}
}
@Repository
public class StudentRepository {
public void performBackgroundVerification(List<Student> students) throws Exception {
Connection wrappedConnection = null;
OracleConnection oracleConnection = null;
try {
//Получить обернутое соединение
wrappedConnection = jdbcTemplate.getDataSource().getConnection();
//Получить соединение Oracle
oracleConnection = wrappedConnection.unwrap(OracleConnection.class);
//Создать объект Oracle (typ_student_obj)
Struct[] studentStruct = new Struct[students.size()];
for(int i=0; i<students.size(); i++) {
studentStruct[i] = oracleConnection.createStruct("TYP_STUDENT_OBJ", new Object[] {
students.get(i).getFirstName(), Date.valueOf(students.get(i).getDateOfBirth()), students.get(i).getAddress(), students.get(i).getAge()
});
}
//Создать массив Oracle (typ_student_tbl)
Array studentStructArray = oracleConnection.createOracleArray("TYP_STUDENT_TBL", studentStruct);
//Вызов хранимой процедуры
Map<String, Object> resultSet = new SimpleJdbcCall(jdbcTemplate)
.withSchemaName(schemaName)
.withCatalogName(PackageConstants.PKG_STUDENT)
.withProcedureName(StoredProcedureConstants.SP_INSERT_STUDENTS)
.declareParameters(new SqlParameter[] {
new SqlParameter("p_student_data", OracleTypes.ARRAY),
new SqlOutParameter("p_status", OracleTypes.VARCHAR),
})
.execute();
if(resultSet.get("p_status") != null) {
String verificationStatus = String.valueOf(resultSet.get("p_status"));
}
}
finally {
if(wrappedConnection != null && !wrappedConnection.isClosed()) {
wrappedConnection.close();
logger.info("ЗАКРЫТО ЛИ ОБЕРНУТОЕ СОЕДИНЕНИЕ? : "+wrappedConnection.isClosed());
}
if(oracleConnection != null && !oracleConnection.isClosed()) {
oracleConnection.close();
logger.info("ЗАКРЫТО ЛИ СОЕДИНЕНИЕ ORACLE? : "+oracleConnection.isClosed());
}
}
}
public void enrollStudents(List<Student> students) throws Exception {
Connection wrappedConnection = null;
OracleConnection oracleConnection = null;
try {
//Получить обернутое соединение
wrappedConnection = jdbcTemplate.getDataSource().getConnection();
//Получить соединение Oracle
oracleConnection = wrappedConnection.unwrap(OracleConnection.class);
//Создать объект Oracle (typ_student_obj)
//Создать массив Oracle (typ_student_tbl)
}
finally {
if(wrappedConnection != null && !wrappedConnection.isClosed()) {
wrappedConnection.close();
logger.info("ЗАКРЫТО ЛИ ОБЕРНУТОЕ СОЕДИНЕНИЕ? : "+wrappedConnection.isClosed());
}
if(oracleConnection != null && !oracleConnection.isClosed()) {
oracleConnection.close();
logger.info("ЗАКРЫТО ЛИ СОЕДИНЕНИЕ ORACLE? : "+oracleConnection.isClosed());
}
}
}
}
spring:
application:
name: test-jobs
profiles:
active: local
datasource:
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 60000
idle-timeout: 600000 #по умолчанию 600000, т.е. 10 минут
max-lifetime: 1800000 #по умолчанию 1800000, т.е. 30 минут
pool-name: testPool
Я получаю следующую ошибку, когда метод studentRepository.enrollStudents(students); вызывается из класса сервиса
2024-10-29 02:30:01.575 INFO 29665 --- [taskScheduler-2] com.karthik.util.HikariPoolUtil : ЗАКРЫТО ЛИ ОБЕРНУТОЕ СОЕДИНЕНИЕ? : true
2024-10-29 02:30:01.578 INFO 29665 --- [taskScheduler-2] com.karthik.util.HikariPoolUtil : ЗАКРЫТО ЛИ СОЕДИНЕНИЕ ORACLE? : true
2024-10-29 02:00:02.038 WARN 29665 --- [taskScheduler-2] com.zaxxer.hikari.pool.ProxyConnection : myPool - Соединение oracle.jdbc.driver.T4CConnection@73e0fb8c помечено как сломанное из-за SQLSTATE(08003), КодОшибки(17008)
java.sql.SQLRecoverableException: Закрытое соединение
at oracle.jdbc.driver.PhysicalConnection.clearWarnings(PhysicalConnection.java:2936) ~[ojdbc8-19.10.0.0.jar!/:19.10.0.0.0]
at com.zaxxer.hikari.pool.ProxyConnection.close(ProxyConnection.java:267) ~[HikariCP-4.0.3.jar!/:na]
Мое понимание заключается в том, что wrappedConnection = jdbcTemplate.getDataSource().getConnection();
должен возвращать новое соединение из пула соединений. Почему закрытие соединения в предыдущем методе вызывает эту ошибку.
Не могли бы вы помочь решить эту ошибку?
Ответ или решение
Ошибки, которые вы наблюдаете при выполнении метода enrollStudents
в вашем классе StudentRepository
, являются результатом неправильного управления соединениями в контексте использования пула соединений HikariCP. Основная проблема здесь заключается в том, что соединение было закрыто в методе performBackgroundVerification
, и когда вы пытаетесь использовать его снова в enrollStudents
, оно уже помечено как закрытое. Это приводит к ошибке SQLSTATE(08003), ErrorCode(17008
, что означает "Закрытое соединение".
Причины возникновения ошибки
-
Закрытие соединения: В вашем методе
performBackgroundVerification
вы получаете соединение черезjdbcTemplate.getDataSource().getConnection()
, а затем сначала выполняете операции, во время которых вы закрываете это соединение в блокеfinally
. Это приводит к ситуации, когда, если вы попытаетесь повторно использовать это соединение вenrollStudents
, вы получите ошибку закрытого соединения. -
Пул соединений: Хранилище соединений, созданное HikariCP, подразумевает, что соединения должны управляться пулом, и их не следует закрывать вручную. Если вы закрываете соединение, принадлежащее пулу, следующие попытки получить это соединение (или соединение, помеченное в том же контексте) будут неудачными.
Решение проблемы
Есть несколько подходов к решению этой проблемы:
-
Избегание ручного закрытия соединений: Убедитесь, что нельзя закрывать соединение в методах, которые извлекают их из пула (например,
performBackgroundVerification
иenrollStudents
). Вместо этого, если нужно освободить ресурсы, полагайтесь на механизм управления временем жизни соединений, применяемый пулом. -
Обработка взаимодействия с базой данных: Рассмотрите возможность обертки методов взаимодействия с базой данных с использованием шаблонов, таких как
JdbcTemplate
, которые автоматически обрабатывают установление и закрытие соединений.
Переписанный код
Пример исправленного кода:
@Repository
public class StudentRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
public void performBackgroundVerification(List<Student> students) throws Exception {
// Переход к использованию JdbcTemplate для выполнения запросов
// или вынести проверку в отдельный метод и использовать его в enrollStudents
}
public void enrollStudents(List<Student> students) throws Exception {
// Прямо здесь избежать получения и закрытия соединения пользователем
// Работайте с JdbcTemplate для выполнения любых необходимых операций
// Имейте в виду, чтобы использовать jdbcTemplate без ручного управления соединениями
}
}
Заключение
При разработке приложений с использованием пулов соединений, таких как HikariCP, крайне важно понимать, что соединения должны управляться автоматически. Ручное управление этими соединениями теряет смысл, если соединения являются элементами пула.Избегание закрытия соединений, получаемых из пула, и использование возможностей JdbcTemplate
для упрощения кода поможет избежать подобных ошибок и сделает ваш код более чистым и понятным.