Соединение oracle.jdbc.driver.T4CConnection@73e0fb8c отмечено как нарушенное из-за SQLSTATE(08003), ErrorCode(17008)

Вопрос или проблема

@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, что означает "Закрытое соединение".

Причины возникновения ошибки

  1. Закрытие соединения: В вашем методе performBackgroundVerification вы получаете соединение через jdbcTemplate.getDataSource().getConnection(), а затем сначала выполняете операции, во время которых вы закрываете это соединение в блоке finally. Это приводит к ситуации, когда, если вы попытаетесь повторно использовать это соединение в enrollStudents, вы получите ошибку закрытого соединения.

  2. Пул соединений: Хранилище соединений, созданное HikariCP, подразумевает, что соединения должны управляться пулом, и их не следует закрывать вручную. Если вы закрываете соединение, принадлежащее пулу, следующие попытки получить это соединение (или соединение, помеченное в том же контексте) будут неудачными.

Решение проблемы

Есть несколько подходов к решению этой проблемы:

  1. Избегание ручного закрытия соединений: Убедитесь, что нельзя закрывать соединение в методах, которые извлекают их из пула (например, performBackgroundVerification и enrollStudents). Вместо этого, если нужно освободить ресурсы, полагайтесь на механизм управления временем жизни соединений, применяемый пулом.

  2. Обработка взаимодействия с базой данных: Рассмотрите возможность обертки методов взаимодействия с базой данных с использованием шаблонов, таких как 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 для упрощения кода поможет избежать подобных ошибок и сделает ваш код более чистым и понятным.

Оцените материал
Добавить комментарий

Капча загружается...