• Spring Data JPA 之 DataSource 详解及其加载过程


    17 DataSource 详解及其加载过程

    17.1 数据源是什么

    当我们⽤第三⽅⼯具去连接数据库(Mysql,Oracle 等)的时候,⼀般都会让我们选择数据源,如下图所示:

    在这里插入图片描述

    我们以 MySQL 为例,当选择 MySQL 的时候就会弹出如下图显示的界⾯:

    在这里插入图片描述

    其中,我们在选择了 Driver(驱动)和 Host、UserName、Password 等之后,就可以创建⼀个 Connection,然后连接到数据库⾥⾯了。

    同样的道理,在 Java ⾥⾯我们也需要⽤到 DataSource 去连接数据库,⽽ Java 定义了⼀套 JDBC 的协议标准,其中有⼀个 javax.sql.DataSource 接⼝类,通过实现此类就可以进⾏数据库连接,我们通过源码来分析⼀下。

    17.1.1 DataSource 源码分析

    public interface DataSource extends CommonDataSource, Wrapper {
        Connection getConnection() throws SQLException;
        Connection getConnection(String username, String password) throws SQLException;
    }
    
    • 1
    • 2
    • 3
    • 4

    我们通过源码可以很清楚地看到,DataSource 的主要⽬的就是获得数据库连接,就像我们前⾯⽤⼯具连接数据库⼀样,只不过⼯具是通过界⾯实现的,⽽ DataSource 是通过代码实现的。

    那么在程序⾥⾯如何实现呢?也有很多第三⽅的实现⽅式,常⻅的有C3P0、BBCP、Proxool、Druid、Hikari,⽽⽬前 Spring Boot ⾥⾯是采⽤ Hikari 作为默认数据源。Hikari 的优点是:开源,社区活跃,性能⾼,监控完整。我们通过⼯具看⼀下项⽬⾥⾯ DataSource 的实现类有哪些,如下图所示:

    在这里插入图片描述

    其中,当我采⽤默认数据源的时候,可以看到数据源的实现类有:h2 ⾥⾯的 JdbcDataSource、MySQL 连接⾥⾯的 MysqlDataSource,以及今天要重点介绍的 HikariDataSource(默认数据源,也是 Spring 社区推荐的最佳数据源)。

    我们直接打开 HikariDataSource 的源码看⼀下,它的关键代码如下:

    public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
    
        private final HikariPool fastPathPool;
        private volatile HikariPool pool;
    
        public HikariDataSource(HikariConfig configuration) {
            configuration.validate();
            configuration.copyStateTo(this);
    
            LOGGER.info("{} - Starting...", configuration.getPoolName());
            pool = fastPathPool = new HikariPool(this);
            LOGGER.info("{} - Start completed.", configuration.getPoolName());
    
            this.seal();
        }
    
        @Override
        public Connection getConnection() throws SQLException {
            if (isClosed()) {
                throw new SQLException("HikariDataSource " + this + " has been closed.");
            }
    
            if (fastPathPool != null) {
                return fastPathPool.getConnection();
            }
    
            // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
            HikariPool result = pool;
            if (result == null) {
                synchronized (this) {
                    result = pool;
                    if (result == null) {
                        validate();
                        LOGGER.info("{} - Starting...", getPoolName());
                        try {
                            pool = result = new HikariPool(this);
                            this.seal();
                        }
                        catch (PoolInitializationException pie) {
                            if (pie.getCause() instanceof SQLException) {
                                throw (SQLException) pie.getCause();
                            }
                            else {
                                throw pie;
                            }
                        }
                        LOGGER.info("{} - Start completed.", getPoolName());
                    }
                }
            }
    
            return result.getConnection();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    从上⾯的源码可以看到关键的两点问题:

    1. 数据源的关键配置属性有哪些?
    2. 连接怎么获得?连接池的作⽤如何?

    下⾯我们分别详解⼀下

    第⼀个问题,HikariConfig 的配置⾥⾯描述了 Hikari 数据源主要的配置属性,我们打开来看⼀下,如下所示:

    // Properties changeable at runtime through the HikariConfigMXBean
    //
    private volatile String catalog;
    private volatile long connectionTimeout;
    private volatile long validationTimeout;
    private volatile long idleTimeout;
    private volatile long leakDetectionThreshold;
    private volatile long maxLifetime;
    private volatile int maxPoolSize;
    private volatile int minIdle;
    private volatile String username;
    private volatile String password;
    
    // Properties NOT changeable at runtime
    //
    private long initializationFailTimeout;
    private String connectionInitSql;
    private String connectionTestQuery;
    private String dataSourceClassName;
    private String dataSourceJndiName;
    private String driverClassName;
    private String exceptionOverrideClassName;
    private String jdbcUrl;
    private String poolName;
    private String schema;
    private String transactionIsolationName;
    private boolean isAutoCommit;
    private boolean isReadOnly;
    private boolean isIsolateInternalQueries;
    private boolean isRegisterMbeans;
    private boolean isAllowPoolSuspension;
    private DataSource dataSource;
    private Properties dataSourceProperties;
    private ThreadFactory threadFactory;
    private ScheduledExecutorService scheduledExecutor;
    private MetricsTrackerFactory metricsTrackerFactory;
    private Object metricRegistry;
    private Object healthCheckRegistry;
    private Properties healthCheckProperties;
    
    private long keepaliveTime;
    
    private volatile boolean sealed;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    通过上⾯的源码我们可以看到数据源的关键配置信息:⽤户名、密码、连接池的配置、jdbcUrl、驱动的名字,等等,这些字段你可以参考课程开始时我介绍的⼯具,细⼼观察的话都可以找到对应关系,也就是创建数据源需要的⼀些配置项。

    上⾯提到的第 2 个问题,我们通过 getConnection ⽅法⾥⾯的代码可以看到 HikariPool 的⽤法,也就是说,我们是通过连接池来获得连接的,这个连接⽤过之后没有断开,⽽是重新放回到连接池⾥⾯(这个地⽅你⼀定要谨记,它也说明了 connection 是可以共享的)。

    ⽽连接池的⽤途你应该也知道,创建连接是⾮常昂贵的,所以需要⽤到连接池技术、共享现有的连接,以增加代码的执⾏效率。

    那么这个时候有⼀个问题是需要我们搞清楚并且牢记的,就是数据源和 driver(驱动)、数据库连接、连接池是什么关系?

    17.1.2 数据源、驱动、连接和连接池的关系

    我分为下述四点来说,⽅便你理解。

    1. 数据源的作⽤是给应⽤程序提供不同 DB 的连接 connection;
    2. 连接是通过连接池获取的,这主要是出于连接性能的考虑;
    3. 创建好连接之后,通过数据库的驱动来进⾏数据库操作;
    4. ⽽不同的 DB(MySQL / h2 / oracle),都有⾃⼰的驱动类和相应的驱动 Jar 包。

    我们⽤⼀个图来表示⼀下:

    在这里插入图片描述

    ⽽我们常说的 MySQL 驱动,其实就是 com.mysql.cj.jdbc.Driver,⽽这个类主要存在于 mysql-connection-java:8.0* 的 jar ⾥⾯,也就是我们经常说的不同的数据库所代表的驱动 jar 包。

    这⾥我们⽤的是 spring boot 2.6.2 版本引⽤的 mysql-connection-java 8.0 版本驱动 jar 包,不同的数据库引⽤的 jar 包是不⼀样的。例如,H2 数据源中,我们⽤的驱动类是 org.h2.Driver,其包含在 com.h2database:h2:1.4.*jar 包⾥⾯。

    接下来我们通过源码分析 Spring ⾥⾯的加载原理,来看下 Hikari 都有哪些配置项。

    17.2 数据源的加载原理和过程

    我们通过 spring.factories ⽂件可以看到 JDBC 数据源相关的⾃动加载的类 DataSourceAutoConfiguration,那么我们就从这个类开始分析。

    17.2.1 DataSourceAutoConfiguration 数据源的加载过程分析

    DataSourceAutoConfiguration 的关键源码如下所示:

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
    @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
    @AutoConfigureBefore(SqlInitializationAutoConfiguration.class)
    // 将spring.datasource.** 的配置放到 DataSourceProperties 对象⾥⾯;
    @EnableConfigurationProperties(DataSourceProperties.class)
    @Import({ DataSourcePoolMetadataProvidersConfiguration.class,
    		DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
    		DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
    public class DataSourceAutoConfiguration {
    
        // 默认集成的数据源,⼀般指的是 H2,⽅便我们快速启动和上⼿,⼀般不在⽣产环境应⽤;
    	@Configuration(proxyBeanMethods = false)
    	@Conditional(EmbeddedDatabaseCondition.class)
    	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    	@Import(EmbeddedDataSourceConfiguration.class)
    	protected static class EmbeddedDatabaseConfiguration {
    	}
    
        // 加载不同的数据源的配置
    	@Configuration(proxyBeanMethods = false)
    	@Conditional(PooledDataSourceCondition.class)
    	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
    			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
    			DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
    	protected static class PooledDataSourceConfiguration {
    	}
        ....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    从源码中我们可以得到以下三点最关键的信息:

    第⼀,通过 @EnableConfigurationProperties(DataSourceProperties.class) 可以看得出来 spring.datasource 的配置项有哪些,那么我们打开 DataSourceProperties 的源码看⼀下,关键代码如下:

    @ConfigurationProperties(prefix = "spring.datasource")
    public class DataSourceProperties implements BeanClassLoaderAware,
    InitializingBean {
        private ClassLoader classLoader;
        private String name;
        private boolean generateUniqueName = true;
        private Class<? extends DataSource> type;
        private String driverClassName;
        private String url;
        private String username;
        private String password;
    
        // 计算确定drivername的值是什么
        public String determineDriverClassName() {
    		if (StringUtils.hasText(this.driverClassName)) {
    			Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName);
    			return this.driverClassName;
    		}
    		String driverClassName = null;
            // 此段逻辑是,当我们没有配置⾃⼰的 drivername 的时候,它会根据我们配置的 DB 的 url ⾃动计算出来 drivername 的值是什么,所以就会发现我们现在很多 datasource ⾥⾯的配置都省去了 driver-name 的配置,这是 Spring Boot 的功劳
    		if (StringUtils.hasText(this.url)) {
    			driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
    		}
    		if (!StringUtils.hasText(driverClassName)) {
    			driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
    		}
    		if (!StringUtils.hasText(driverClassName)) {
    			throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this,
    					this.embeddedDatabaseConnection);
    		}
    		return driverClassName;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    我们通过 DatabaseDriver 的源码可以看到 MySQL 的默认驱动 Spring Boot 是采⽤ com.mysql.cj.jdbc.Driver 来实现的。

    在这里插入图片描述

    同时,@ConfigurationProperties(prefix = “spring.datasource”) 也告诉我们,application.yml ⾥⾯的 datasource 相关的公共配置可以以 spring.datasource 为开头,这样当启动的时候,DataSourceProperties 就会将 datasource 的⼀切配置⾃动加载进来。正如我们前⾯在 application.yml ⾥⾯的配置的⼀样。

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai
        username: root
        password: root
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这⾥有 url、username、password、driver-class-name 等关键配置,不同数据源的公共配置也不多。

    第⼆,我们通过下⾯这⼀段代码也可以看得出来不同的数据源的配置是什么样的。

    @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
             DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
             DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
    
    • 1
    • 2
    • 3

    为了再进⼀步了解,我们打开 DataSourceConfiguration 的源码,如下所示:

    abstract class DataSourceConfiguration {
    
        @SuppressWarnings("unchecked")
        protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
            return (T) properties.initializeDataSourceBuilder().type(type).build();
        }
    
        /**
    	 * Tomcat Pool DataSource configuration.
    	 */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
        @ConditionalOnMissingBean(DataSource.class)
        @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
                               matchIfMissing = true)
        static class Tomcat {
    
            @Bean
            @ConfigurationProperties(prefix = "spring.datasource.tomcat")
            org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
                org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
                                                                                     org.apache.tomcat.jdbc.pool.DataSource.class);
                DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
                String validationQuery = databaseDriver.getValidationQuery();
                if (validationQuery != null) {
                    dataSource.setTestOnBorrow(true);
                    dataSource.setValidationQuery(validationQuery);
                }
                return dataSource;
            }
    
        }
    
        /**
    	 * Hikari DataSource configuration.
    	 */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(HikariDataSource.class)
        @ConditionalOnMissingBean(DataSource.class)
        @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
                               matchIfMissing = true)
        static class Hikari {
    
            @Bean
            @ConfigurationProperties(prefix = "spring.datasource.hikari")
            HikariDataSource dataSource(DataSourceProperties properties) {
                HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
                if (StringUtils.hasText(properties.getName())) {
                    dataSource.setPoolName(properties.getName());
                }
                return dataSource;
            }
    
        }
    
        /**
    	 * DBCP DataSource configuration.
    	 */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
        @ConditionalOnMissingBean(DataSource.class)
        @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource",
                               matchIfMissing = true)
        static class Dbcp2 {
    
            @Bean
            @ConfigurationProperties(prefix = "spring.datasource.dbcp2")
            org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
                return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    我们通过上述源码可以看到最常⻅的三种数据源的配置:

    • HikariDataSource
    • tomcat 的 JDBC
    • apache 的 dbcp

    ⽽最终⽤哪个,就看你引⽤了哪个 datasoure 的 jar 包。不过 Spring Boot 2.0 之后就推荐使⽤ Hikari 数据源了,你了解⼀下就好。

    第三,我们通过 @ConfigurationProperties(prefix = “spring.datasource.hikari”) HikariDataSource dataSource(DataSourceProperties properties) 可以知道, application.properties ⾥⾯ spring.datasource.hikari 开头的配置会被映射到 HikariDataSource 对象中,⽽开篇我们就提到了,是 HikariDataSource 继承了 HikariConfig。

    所以顺理成章地,我们就可以知道 Hikari 数据源的配置有哪些了,如下图所示:

    image-20220807103617005

    Hikari 的配置⽐较多,你实际⼯作中想要了解详细配置,可以看⼀下官⽅⽂档:https://github.com/brettwooldridge/HikariCP,这⾥我只说⼀下我们最需要关⼼的配置,有如下⼏个:

    spring:
      datasource:
        hikari:
          # 最⼩空闲链接数量
          minimum-idle: 5
          # 空闲链接存活最⼤时间,默认 600000(10分钟)
          idle-timeout: 180000
          # 链接池最⼤链接数,默认是 10
          maximum-pool-size: 10
          # 此属性控制从池返回的链接的默认⾃动提交⾏为,默认值:true
          auto-commit: true
          # 数据源链接池的名称
          pool-name: MyHikariCP
          # 此属性控制池中链接的最⻓⽣命周期,值 0 表示⽆限⽣命周期,默认 1800000 即 30 分钟
          max-lifetime: 1800000
          # 数据库链接超时时间,默认 30 秒,即 30000
          connection-timeout: 30000
          connection-test-query: SELECT 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这⾥我介绍的主要是针对连接池的配置,研究过线程池和连接池原理的同学都知道,连接池我们不能配置得太⼤,因为连接池太⼤的话,会有额外的 CPU 开销,处理连接池的线程切换反⽽会增加程序的执⾏时间,减低性能;相应的,连接池也不能配置太⼩,太⼩的话可能会增加请求的等待时间,也会降低业务处理的吞吐量。

    17.2.2 Hikari 数据源下的 MySQL 配置实践

    下⾯我给你⼀个推荐⼀个常⻅的配置项。

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        # logger=Slf4JLogger&profileSQL=true 是⽤来 debug 显示 sql 的执⾏⽇志的
        url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&logger=Slf4JLogger&profileSQL=true
        username: root
        password: root
        hikari:
          # 最⼤和最⼩相对应减少创建线程池的消耗;
          minimum-idle: 8
          maximum-pool-size: 8
          # 空闲链接存活最⼤时间,默认 600000(10分钟)
          idle-timeout: 600000
          # 当释放连接到连接池之后,采⽤默认的⾃动提交事务
          auto-commit: true
          # 指定⼀个链接池的名字,⽅便我们分析线程问题
          pool-name: jpa-hikari-pool
          # 最⻓⽣命周期 15 分钟够了
          max-lifetime: 900000
          # 数据库链接超时时间,默认 30 秒,即 30000
          connection-timeout: 30000
          connection-test-query: SELECT 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    通过上⾯的⽇志配置,我们在启动的时候可以看到连接池的配置结果和 MySQL 的执⾏⽇志:

    如下⽇志,显示了Hikari 的 config 配置。

    在这里插入图片描述

    当我们执⾏⼀个⽅法的时候,到底要在⼀个 MySQL 的 connection 上⾯执⾏哪些 SQL呢?通过如下⽇志我们可以看得出来。

    在这里插入图片描述

    通过开启 com.zaxxer.hikari.pool.HikariPool 类的 debug 级别,可以实时看到连接池的使⽤情况:软件⽇志如下:

    2022-08-07 11:02:25.619 DEBUG 47090 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : jpa-hikari-pool - Pool stats (total=8, active=0, idle=8, waiting=0)
    2022-08-07 11:02:25.619 DEBUG 47090 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : jpa-hikari-pool - Fill pool skipped, pool is at sufficient level.
    
    • 1
    • 2

    通过上⾯的监控⽇志,你在实际⼯作中可以根据主机的 CPU 情况和业务处理的耗时情况,再对连接池做适当的调整,但是注意差距不要太⼤,不要⼀下将连接池配置⼏百个,那是错误的配置。

    ⽽除了上⾯的这些⽇志之外,Hikari 还提供了 Metrics 的监控指标,我们⼀般配合 Prometheus 使⽤,甚⾄可以利⽤ Granfan 配置⼀些告警,我们看⼀下

    17.2.3 Hikari 数据通过 Prometheus 的监控指标应用

    就像我们⽇志⾥⾯打印的⼀样,Hikari 的 Metirc 也帮我们提供了 Prometheus 的监控指标,实现⽅法很简单,代码如下所示:

    添加 maven 依赖

    <dependency>
        <groupId>io.micrometergroupId>
        <artifactId>micrometer-registry-prometheusartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    application.yml ⾥⾯添加

    management:
      endpoints:
        web:
          exposure:
            # Use "*" to expose all endpoints, or a comma-separated list to expose selected ones
            include: '*' # 如果是 yml 配置文件一定要是 "*" 或者 '*',如果是 .properties 一定是 * 不能加引号或者双引号
            exclude:
      endpoint: # 端点中对 health 进行配置
        metrics:
          enabled: true
        health:
          show-details: ALWAYS
      metrics:
        export:
          prometheus:
            enabled: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    然后我们启动项⽬,打开 http://localhost:8080/actuator/prometheus 就可以看到,Prometheus 的 Metrics ⾥⾯多了很多 HikariCP 的指标

    在这里插入图片描述

    当看到这些指标之后,我们就可以根据 Grafana 社区⾥⾯提供的 HikariCP 的监控 Dashboards 的配置⽂档地址:https://grafana.com/grafana/dashboards/6083,导⼊到我们⾃⼰的 Grafana ⾥⾯

    关于另外一个数据源 Druid 的配置,⽐如监控,官⽅的介绍还是挺详细的:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

    17.4 命名策略详解及其实践

    17.4.1 Hibernate 5 的命名策略

    我们在配置 @Entity 时,⼀定会有同学好奇表名、字段名、外键名、实体字段、@Column 和数据库的字段之间,映射关系是怎么样的?默认规则映射规则⼜是什么?如果和默认不⼀样该怎么扩展?

    我们下⾯只介绍 Hibernate 5 的命名策略,因为 H4 已经不推荐使⽤了,我们直接看最新的即可。Hibernate 5 ⾥⾯把实体和数据库的字段名和表名的映射分成了两个步骤。

    第⼀步:通过 ImplicitNamingStrategy 先找到实例⾥⾯定义的逻辑的字段名字

    这是通过ImplicitNamingStrategy 的实现类指定逻辑字段查找策略,也就是当实体⾥⾯定义了 @Table、@Column 注解的时候,以注解指定名字返回;⽽当没有这些注解的时候,返回的是实体⾥⾯的字段的名字。

    其中,org.hibernate.boot.model.naming.ImplicitNamingStrategy 是⼀个接⼝,ImplicitNamingStrategyJpaCompliantImpl 这个实现类兼容 JPA 2.0 的字段映射规范。除此之外,还有如下四个实现类:

    • ImplicitNamingStrategyLegacyHbmImpl:兼容 Hibernate ⽼版本中的命名规范;
    • ImplicitNamingStrategyLegacyJpaImpl:兼容 JPA 1.0 规范中的命名规范;
    • ImplicitNamingStrategyComponentPathImpl:@Embedded 等注解标志的组件处理是通过 attributePath 完成的,因此如果我们在使⽤ @Embedded 注解的时候,如果要指定命名规范,可以直接继承这个类来实现;
    • SpringImplicitNamingStrategy:默认的 spring data 2.2.3 的策略,只是扩展了 ImplicitNamingStrategyJpaCompliantImpl ⾥⾯的 JoinTableName 的⽅法。

    这⾥我们只需要关⼼ SpringImplicitNamingStrategy 就可以了,其他的我们基本上⽤不到。

    那么 SpringImplicitNamingStrategy 效果如何呢?我们举个例⼦看⼀下

    UserInfo 实体,代码如下:

    @Entity
    @Table(name = "userInfo")
    public class UserInfo extends BaseEntity {
        @Id
        @GeneratedValue(strategy= GenerationType.AUTO)
        private Long id;
        private Integer ages;
        private String lastName;
        @Column(name = "myAddress")
        private String emailAddress;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过第⼀步可以得到如下逻辑字段的映射结果

    UserInfo -> userInfo
    id -> id
    ages - >ages
    lastName -> lastName
    emailAddress -> myAddress
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第⼆步:通过 PhysicalNamingStrategy 将逻辑字段转化成数据库的物理字段名字。

    它的实现类负责将逻辑字段转化成带下划线,或者统⼀给字段加上前缀,⼜或者加上双引号等格式的数据库字段名字,其主要的接⼝是:

    org.hibernate.boot.model.naming.PhysicalNamingStrategy,⽽它的实现类也只有两个,如下所示:

    org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
    org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    
    • 1
    • 2

    PhysicalNamingStrategyStandardImpl:这个类什么都没⼲,即直接将第⼀个步骤得到的逻辑字段名字当成数据库的字段名字使⽤。这个主要的应⽤场景是,如果某些字段的命名格式不是下划线的格式,我们想通过 @Column 的⽅式显示声明的话,可以把默认第⼆步的策略改成 PhysicalNamingStrategyStandardImpl。那么如果再套⽤第⼀步的例⼦,经过这个类的转化会变成如下形式:

    userInfo -> userInfo
    id -> id
    ages -> ages
    lastName -> lastName
    myAddress -> myAddress
    
    • 1
    • 2
    • 3
    • 4
    • 5

    CamelCaseToUnderscoresNamingStrategy:这个类是将第⼀步得到的逻辑字段名字的⼤写字⺟前⾯加上下划线,并且全部转化成⼩写,将会标识出是否需要加上双引号。此种是默认策略。

    我们举个例⼦,第⼀步得到的逻辑字段就会变成如下映射:

    userInfo -> user_info
    id -> id
    ages -> ages
    lastName -> last_name
    myAddress -> my_address
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们把刚才的实体执⾏⼀下,可以看到⽣成的表的结构如下:

    Hibernate: create table user_info (id bigint not null, create_time timestamp, create_user_id integer, last_modified_time timestamp, last_modified_user_id integer, version integer, ages integer, my_address varchar(255), last_name varchar(255), telephone varchar(255), primary key (id))
    • 1

    你也可以通过在 CamelCaseToUnderscoresNamingStrategy 类⾥⾯设置断点,来⼀步⼀步地验证我们的说法

    17.4.2 加载原理与自定义方法

    如果我们修改默认策略,只需要在 application.properties ⾥⾯修改下⾯代码所示的两个配置,换成⾃⼰的⾃定义的类即可。

    spring:
      jpa:
        hibernate:
          naming:
            implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
            physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果我们直接搜索:spring.jpa.hibernate 就会发现,其默认配置是在 org.springframework.boot.autoconfigure.orm.jpa.HibernateProerties 这类⾥⾯的

    在这里插入图片描述

    其中,IMPLICIT_NAMING_STRATEGY 和 PHYSICAL_NAMING_STRATEGY 的值如下述代码所示,它是 Hibernate 5 的配置变量,⽤来改变 Hibernate的 naming 的策略。

    String IMPLICIT_NAMING_STRATEGY = "hibernate.implicit_naming_strategy";
    String PHYSICAL_NAMING_STRATEGY = "hibernate.physical_naming_strategy";
    
    • 1
    • 2

    如果我们⾃定义的话,直接继承 SpringPhysicalNamingStrategy 这个类,然后覆盖需要实现的⽅法即可。那么它实际的应⽤场景都有哪些呢?

    17.4.3 实际应用场景

    有时候我们接触到的系统可能是⽼系统,表和字段的命名规范不⼀定是下划线形式,有可能驼峰式的命名法,也有可能不同的业务有不同的表名前缀。不管是哪⼀种,我们都可以通过修改第⼆阶段:物理映射的策略,改成 PhysicalNamingStrategyStandardImpl 的形式,请看代码。

    spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    
    • 1

    这样可以使 @Column/@Table 等注解的⾃定义值⽣效,或者改成⾃定义的 MyPhysicalNamingStrategy。不过我不建议你修改 implicit-strategy,因为没有必要,你只要在 physical-strategy 上做⽂章就⾜够了。

    17.5 本章小结

    本节为你介绍了 Datasource 是什么,讲解了数据源和 Connection 的关系,并且通过源码分析,让你知道了不同的数据源应该怎么配置,最常⻅的数据源 Hikari 的配置和监控是怎样的。此外,我还给你介绍了和数据库相关的字段映射策略。

  • 相关阅读:
    设计模式 煎饼果子和装饰者模式
    Pycharm安装Tensorflow踩坑
    医生接诊时间难分配?看DHTMLX Scheduler如何助力门诊管理系统优化升级
    视频融合平台EasyCVR视频广场页脚优化为瀑布流式的实现方式
    会stm32有机会进大公司吗?
    websocket 请求头报错 Provisional headers are shown 的解决方法
    科研热点|多所高校公布2022国家杰青、优青基金获得者(附名单及查询方法)~
    nginx热升级
    .NET使用CsvHelper快速读取和写入CSV文件
    【luogu CF1140F】Extending Set of Points(线段树分治)
  • 原文地址:https://blog.csdn.net/qq_40161813/article/details/126322850