在运行时获取数据库主机名

Getting the database host name at runtime

有一个连接到 Oracle 数据的 Springboot 应用程序。数据库的 URL 配置为

spring.datasource.url=jdbc:oracle:thin:@(DESCRIPTION=\
  (LOAD_BALANCE=OFF)(FAILOVER=ON)\
  (ADDRESS=(PROTOCOL=TCP)(HOST=domainName1.com) (PORT=1521))\
  (ADDRESS=(PROTOCOL=TCP)(HOST=domainName2.com)(PORT=1521))\
  (CONNECT_DATA=(SERVICE_NAME=xyz)))

此 URL 配置为当一台主机关闭时应用程序连接到第二个数据库。 到数据库的URL在应用程序健康检查中打印如下所示

Hello 
version    : 4.0.0
build      : 2022-03-3 
datasource :    oracle.jdbc.OracleDriver
db url     :    jdbc:oracle:thin:@(DESCRIPTION=(LOAD_BALANCE=OFF)(FAILOVER=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=domainName1.com) (PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=domainName2.com)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=xyz)))
db status  :    ok 

我的问题是如何只获取正在使用的数据库的主机名,这意味着我如何获取应用程序当前正在使用的主机(domainName1 或 domainName2)并将其显示在健康检查中?例如下图。

Hello 
version    : 4.0.0
build      : 2022-03-3
datasource :    oracle.jdbc.OracleDriver
db url     :    jdbc:oracle:thin:@domainName1.com: 1521/coldv1
db status  :    ok (LVZ count = 379)

我用于此健康检查的 java 代码如下所示。

@GetMapping(path = "/healthcheck",
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public String getServiceStatus(){
        String[] activeProfiles = environment.getActiveProfiles();
        final BeanWrapper accessor = PropertyAccessorFactory.forBeanPropertyAccess(dataSource);
        final String driverClassName = String.valueOf(accessor.getPropertyValue("driverClassName"));
        String url = null;

        if(activeProfiles[1].equals("external_tomcat")) {
            url = String.valueOf(accessor.getPropertyValue("url"));
        }else{
            try {
                String[] dataSourceProperties = nameService.getDataSource();
                url = dataSourceProperties[0];
            } catch (SQLException ex) {
//                throwables.printStackTrace();
                log.error(ex.getMessage(), ex);
            }
        }

        String version = buildProperties.getVersion();
        String buildTimestamp = String.valueOf(buildProperties.getTime());
        BigDecimal count = nameService.getCount("Table_name_of_database");

        StringBuilder result = new StringBuilder("Hello")
                .append("\nversion    :\t")
                .append(version)
                .append("\nbuild      :\t")
                .append(buildTimestamp)
                .append("\ndatasource :\t")
                .append(driverClassName)
                .append("\ndb url     :\t")
                .append(url)
                .append("\ndb status  :\tok  ")
                .append(count.intValue())
                .append(")");
        return result.toString();
    }

NameService.java

 @Override
    public String[] getDataSource() throws SQLException {
        return getDataSourceProperties();
    }


   

    public String[] getDataSourceProperties() {
        String[] dataSourceProperties = new String[2];
        HikariDataSource dataSource = getDataSourceFromHibernateEntityManager();
        if(dataSource.getJdbcUrl() != null){
            dataSourceProperties[0] = dataSource.getJdbcUrl();
            dataSourceProperties[1] = dataSource.getDataSourceClassName();
        }
        return dataSourceProperties;
    }

一种解决方案是执行 SQL 查询以检索服务器主机名,例如:

SELECT host_name FROM v$instance

有关详细信息,请参阅 this question

数据库实例有此数据可用,因此您应该询问数据库实例。

使用 v$instance 很诱人,但这需要额外的 select 权限。

SELECT sys_context('USERENV','SERVER_HOST') server_host
 FROM dual;

不需要额外权限。

如果您无法像其他 2 个答案那样从数据库中获取它,则使用 @Value:

将 spring 属性 注入到组件中
@Value("spring.datasource.url")
String datasource;

然后从字符串中解析出值。

您是否考虑过更原生的方法? 我的意思是你为什么不装饰数据源实现。

您可以使用 org.springframework.beans.factory.config.BeanPostProcessor 及其变体来实现此目的,并使用一个对象来包装您的数据源和 decorates/override Datasource#getConnection 以通知或保存值或 属性 你想要某个地方。

使用

dataSource.getConnection().getMetaData().getURL();

@Olivier 和@ik_zelf 两个答案都适合我。 我在 spring 引导应用程序中实现的方式是使用以下代码片段。

public String getHostNameFromUrl() {
        String sql = "SELECT host_name FROM v$instance";
//        String sql = "SELECT sys_context('USERENV','SERVER_HOST') server_host FROM dual"; this also works
        Query query = entityManager.createNativeQuery(sql);
        return query.getSingleResult().toString();
}

稍后在控制器中我调用此方法显示在健康检查中。