spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.41k stars 40.51k forks source link

JDBC SQLExceptionTranslator is always lazily initialized, requiring an available datababse connection for it #28842

Open mcekovic opened 2 years ago

mcekovic commented 2 years ago

Currently (as of Spring Boot 2.6), when data source is initialized, by default JDBC exception translator is lazily initialized, meaning, the first time JDBC exception occurs, exception translator will be initialized. However for exception translator initialization, an available JDBC connection is required. This may be problematic in certain scenarios (please see some of them later in the issue description), as this is a 'Chicken and Egg' problem.

As Spring Boot already has the feature to detect JDBC driver from JDBC URL, and as DatabaseDriver class already has database product name, it could be possible to eagerly initialize JDBC exception translator w/o the need for JDBC connection (using database product name obtained from JDBC URL via DatabaseDriver enum).

Some of the scenarios when the current behavior may cause problems:

  1. In the recent versions of Oracle UCP, seems that the UCP pool starts to destroy itself by listening to the shutdown hook, meaning it starts destruction before some Spring Beans that depend on it are destroyed. If no JDBC exceptions happened at all since a Spring Boot app has been started, and an exception occurs the first time during the Spring context destruction, it could not be translated as it is not possible to obtain a database connection.

  2. In general, if database is unavailable at the moment when the first JDBC exception occurs, exception translation would not be possible. For example when network link to the database is broken before any JDBC exception happens, SQL exception with a cause like socket read IO error cannot be translated.

It is possible to make a workaround like:

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    var jdbcTemplate = new JdbcTemplate(dataSource);
    jdbcTemplate.setExceptionTranslator(new SQLErrorCodeSQLExceptionTranslator("Oracle"));
    return jdbcTemplate;
}

However, it is required to explicitly put the specific database product name (although Spring Boot can determine it via DatabaseDriver class, database product name is not publicly accessible by the application).

mcekovic commented 2 years ago

P. S. It might not be related to Spring Data JDBC, it is more directly related to plain Spring JDBC (i.e. JdbcTemplate). I.e. property prefix is more likely related to 'spring.datasource' or 'spring.jdbc'.

snicoll commented 2 years ago

The problem is that we can't reliably link a DataSource with its jdbcUrl. When the JdbcTemplate is created, all we have is "the" DataSource, which could very well have been configured by the user. Flagging for team attention to see if I've missed something.

wilkinsona commented 2 years ago

I don't think you've missed something. The only thing that I can think of is to update DataSourceAutoConfiguration to define a bean alongside the DataSource that provides some metadata about it. When the user defines their own DataSource bean, both that auto-configured DataSource bean and this metadata bean would back off. In places where the DataSource is consumed we could also optionally inject the metadata bean and, if it's present, use that to infer things about the DataSource that's also been injected.