springboot+MybatisPlus+HikariCP实现多数据源动态切换

在实际工作中,我们的系统可能是多个数据源,通过集成MyBatis-PlusHikariCP实现多数据源动态切换是一个常见的需求。以下是一个详细的实现步骤,你可以根据自己的需要进行调整。

1. 添加依赖

首先,在pom.xml文件中添加所需的依赖:

s-Plus和HikariCP实现多数据源动态切换是一个常见的需求。以下是一个详细的实现步骤,你可以根据自己的需要进行调整。

1. 添加依赖
首先,在pom.xml文件中添加所需的依赖:

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.5</version>
		<relativePath />
</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<!-- MyBatis-Plus -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.5</version>
		</dependency>
		<!-- 数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

HikariCP数据源不需要额外导包,springboot自动引入

2. 配置数据源属性

application.yml文件中配置多个数据源属性:

3. 配置数据源

创建一个配置类来配置多个数据源:

package com.work.config;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.work.enums.DatabaseTypeEnum;
import com.zaxxer.hikari.HikariDataSource;

/**
 * springboot整合mybatis-plus 实现多数据源动态切换
 * 配置数据源和事务管理器 
 * @author summer
 */
@Configuration
@MapperScan(basePackages = "com.work.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
	/**
	 * 主库数据源
	 * @return
	 */
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    
    /**
     * 从库数据源
     * @return
     */
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    
    /**
     * 动态切换数据源
     * @param masterDataSource
     * @param slaveDataSource
     * @return
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                               @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseTypeEnum.MASTER.getDatabaseType(), masterDataSource);
        targetDataSources.put(DatabaseTypeEnum.SLAVE.getDatabaseType(), slaveDataSource);

        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        //设置默认的数据源,当没有指定数据源时,将使用主库为默认数据源
        dataSource.setDefaultTargetDataSource(masterDataSource);
        //将配置好的多数据源设置到DynamicRoutingDataSource中
        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }
    
	/**
	 * 配置sqlSession
	 * @param dataSource
	 * @return
	 * @throws Exception
	 */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
    	 // 导入mybatissqlsession配置
        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        // 指明数据源
        sessionFactory.setDataSource(dataSource);
        //多数据源必须在定义数据源时添加以下2句,否则分页插件无效
        Interceptor[] interceptors = new Interceptor[] { mybatisPlusInterceptor() };
        sessionFactory.setPlugins(interceptors);
        // 返回SqlSessionFactory实例,用于创建SqlSession,使用SqlSession调用sql时不需要指定数据源,因为就在此处指定了
        return sessionFactory.getObject();
    }
    
    /**
     * 配置事务管理器
     * @param dataSource
     * @return
     */
    @Bean
    public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    
    /**
	 * mybatis-Plus 分页配置
	 * 注意:如果是单数据源,此时分页插件能正常使用,而如果是多数据源,那么还需在配置SqlSessionFactory的地方再添加配置,否则分页时查询总数total还是0
	 * @return
	 */
	@Bean
	public MybatisPlusInterceptor mybatisPlusInterceptor() {
		MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
		PaginationInnerInterceptor paginationInnerInterceptor=new PaginationInnerInterceptor();
		paginationInnerInterceptor.setDbType(DbType.MYSQL);
		paginationInnerInterceptor.setOverflow(true);
		mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
		return mybatisPlusInterceptor;
	}
}

4.定义数据库类型枚举

package com.work.enums;
/**
 * 数据库类型枚举
 * @author summer
 *
 */
public enum DatabaseTypeEnum {
	MASTER("master", "主库"),
    SLAVE("slave", "从库"),
    ;
    private final String databaseType;
    private final String msg;
    DatabaseTypeEnum(String databaseType, String msg) {
        this.databaseType = databaseType;
        this.msg = msg;
    }
	public String getDatabaseType() {
		return databaseType;
	}
	public String getMsg() {
		return msg;
	}
}

5.动态切换数据源

使用AOP或者注解的方式实现数据源的动态切换

package com.work.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.work.config.DynamicRoutingDataSource;
import com.work.enums.DatabaseTypeEnum;

/**
 * AOP 实现动态数据源切换
 * @author summer
 *
 */
@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(com.work.target.ReadOnly)")
    public void useSlaveDataSource() {
    	DynamicRoutingDataSource.setDataSource(DatabaseTypeEnum.SLAVE.getDatabaseType());
    }

    @Before("@annotation(com.work.target.WriteOnly)")
    public void useMasterDataSource() {
    	DynamicRoutingDataSource.setDataSource(DatabaseTypeEnum.MASTER.getDatabaseType());
    }

    //需要每次清空ThreadLocal的内容,防止内存泄漏
    @After("@annotation(com.work.target.ReadOnly) || @annotation(com.work.target.WriteOnly)")
    public void clearDataSource() {
    	DynamicRoutingDataSource.clearDataSource();
    }
}

创建自定义注解:

package com.work.target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 读数据标识注解
 * @author summer
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
	
}

package com.work.target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 写数据标识注解
 * @author summer
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteOnly {
	
}

实现DynamicRoutingDataSource类来管理数据源切换:

package com.work.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 动态主从数据源
 * 使用 ThreadLocal来持有当前线程的数据源类型
 * @author summer
 *
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource{
	private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return CONTEXT_HOLDER.get();
    }
}

6.编写测试类

实体类:

package com.work.model.db;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import lombok.Data;
@Data
@TableName(value = "check_code_info")
public class CheckCodeInfo {
	/**
	 * 主键ID  自增长
	 */
	@TableId(type = IdType.AUTO)
	private Integer id;
	private String token;
    private String checkCode;
    /**
     * 使用状态1:未使用2:已使用
     */
    private Integer status;
    /**
     * 创建时间
     */
//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    
    /**
     * 更新时间
     */
//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    /**
     * 过期时间
     */
//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date expiTime;
}

mapper类:

package com.work.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.work.model.db.CheckCodeInfo;

public interface CheckCodeInfoMapper extends BaseMapper<CheckCodeInfo>{
    
}

Service类:

package com.work.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.work.model.db.CheckCodeInfo;

public interface CheckCodeInfoService {
	/**
	 * 读数据
	 * @param 
	 * @return
	 */
	IPage<CheckCodeInfo> getCheckCodeInfoList(int currentPage, int pageSize);
	
	/**
	 * 添加数据
	 */
	boolean addCheckCodeInfo();
}

Service实现类:

首先,在pom.xml文件中添加所需的依赖:

package com.work.service.impl;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.work.mapper.CheckCodeInfoMapper;
import com.work.model.db.CheckCodeInfo;
import com.work.service.CheckCodeInfoService;
import com.work.target.ReadOnly;
import com.work.target.WriteOnly;

import cn.hutool.core.util.RandomUtil;

@Service
public class CheckCodeInfoServiceImpl implements CheckCodeInfoService {
	@Autowired
	private CheckCodeInfoMapper checkCodeInfoMapper;
	/**
	 * 测试分页查询
	 * 读数据注解
	 */
	@ReadOnly
	@Override
	public IPage<CheckCodeInfo> getCheckCodeInfoList(int currentPage, int pageSize) {
//		LambdaQueryWrapper<CheckCodeInfo> lambdaQueryWrapper=new LambdaQueryWrapper<CheckCodeInfo>();
		//分页参数  mybatis-plus自带的分页,传入当前页码和每页条数
		Page<CheckCodeInfo> page=new Page<CheckCodeInfo>(currentPage,pageSize);
		//调用Mapper的分页查询方法,传入Page对象和查询条件(这里为null表示无条件查询)
		IPage<CheckCodeInfo> pageResult=checkCodeInfoMapper.selectPage(page, null);
		// 从 从库查询数据
		return pageResult;
	}
	
	/**
	 * 测试写数据
	 * 写数据注解
	 */
	@WriteOnly
	@Override
	public boolean addCheckCodeInfo() {
		CheckCodeInfo checkCodeInfo = new CheckCodeInfo();
		checkCodeInfo.setToken(RandomUtil.randomString(30));
		checkCodeInfo.setCheckCode(RandomUtil.randomNumbers(3));
		checkCodeInfo.setCreateTime(new Date());
		checkCodeInfo.setUpdateTime(new Date());
		int num = checkCodeInfoMapper.insert(checkCodeInfo);
		// 写入主库
		return num > 0 ? true : false;
	}
}

测试Controller类:

package com.work.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.work.common.result.CommonResult;
import com.work.model.db.CheckCodeInfo;
import com.work.service.CheckCodeInfoService;

/**
 * 动态切换多数据源,测试Controller类
 * @author summer
 *
 */
@RestController
@RequestMapping("/checkCode")
public class DataSourceTestController {
	@Autowired
    private CheckCodeInfoService checkCodeInfoService;
    
    /**
     * 测试数据源动态切换,从 从库读取数据
     * @return
     */
    @GetMapping("/getCheckCodeInfoList")
    public CommonResult<IPage<CheckCodeInfo>> getCheckCodeInfoList(@RequestParam(defaultValue = "1") int currentPage,
            @RequestParam(defaultValue = "10") int pageSize) {
    	return CommonResult.success(checkCodeInfoService.getCheckCodeInfoList(currentPage,pageSize));
    }
    
    /**
     * 测试数据源动态切换,从 从库读取数据
     * @return
     */
    @PostMapping("/addCheckCodeInfo")
    public CommonResult<Boolean> addCheckCodeInfo() {
    	return CommonResult.success(checkCodeInfoService.addCheckCodeInfo());
    }
}

postMan测试:

主库:

测试从库查询:

从库:

7.控制台结果,也能看到确实使用了HikariCP的数据库连接池,这样就大公告成了

Spring Boot 2.x基础教程:默认数据源Hikari的配置详解

 

通过上一节的学习,我们已经学会如何应用Spring中的JdbcTemplate来完成对MySQL的数据库读写操作。接下来通过本篇文章,重点说说在访问数据库过程中的一个重要概念:数据源(Data Source),以及Spring Boot中对数据源的创建与配置。

基本概念

在开始说明Spring Boot中的数据源配置之前,我们先搞清楚关于数据访问的这些基本概念:

什么是JDBC?

Java数据库连接(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。

JDBC API主要位于JDK中的java.sql包中(之后扩展的内容位于javax.sql包中),主要包括(斜体代表接口,需驱动程序提供者来具体实现):

  • DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
  • Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。
  • Connection:数据库连接,负责与进行数据库间通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
  • Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。
  • CallableStatement:用以调用数据库中的存储过程。
  • SQLException:代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况(即错误)。

什么是数据源?

可以看到,在java.sql中并没有数据源(Data Source)的概念。这是由于在java.sql中包含的是JDBC内核API,另外还有个javax.sql包,其中包含了JDBC标准的扩展API。而关于数据源(Data Source)的定义,就在javax.sql这个扩展包中。

实际上,在JDBC内核API的实现下,就已经可以实现对数据库的访问了,那么我们为什么还需要数据源呢?主要出于以下几个目的:

  1. 封装关于数据库访问的各种参数,实现统一管理
  2. 通过对数据库的连接池管理,节省开销并提高效率

在Java这个自由开放的生态中,已经有非常多优秀的开源数据源可以供大家选择,比如:DBCP、C3P0、Druid、HikariCP等。

而在Spring Boot 2.x中,对数据源的选择也紧跟潮流,采用了目前性能最佳的HikariCP。接下来,我们就来具体说说,这个Spring Boot中的默认数据源配置。

默认数据源:HikariCP

由于Spring Boot的自动化配置机制,大部分对于数据源的配置都可以通过配置参数的方式去改变。只有一些特殊情况,比如:更换默认数据源,多数据源共存等情况才需要去修改覆盖初始化的Bean内容。本节我们主要讲Hikari的配置,所以对于使用其他数据源或者多数据源的情况,在之后的教程中学习。

在Spring Boot自动化配置中,对于数据源的配置可以分为两类:

  • 通用配置:以spring.datasource.*的形式存在,主要是对一些即使使用不同数据源也都需要配置的一些常规内容。比如:数据库链接地址、用户名、密码等。这里就不做过多说明了,通常就这些配置:
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  • 数据源连接池配置:以spring.datasource.<数据源名称>.*的形式存在,比如:Hikari的配置参数就是spring.datasource.hikari.*形式。下面这个是我们最常用的几个配置项及对应说明:
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=500000
spring.datasource.hikari.max-lifetime=540000
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.connection-test-query=SELECT 1

这些配置的含义:

  • spring.datasource.hikari.minimum-idle: 最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
  • spring.datasource.hikari.maximum-pool-size: 最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
  • spring.datasource.hikari.idle-timeout: 空闲连接超时时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
  • spring.datasource.hikari.max-lifetime: 连接最大存活时间,不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
  • spring.datasource.hikari.connection-timeout: 连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒
  • spring.datasource.hikari.connection-test-query: 用于测试连接是否可用的查询语句

更多完整配置项可查看下表:

name 描述 构造器默认值 默认配置validate之后的值 validate重置
autoCommit 自动提交从池中返回的连接 TRUE TRUE
connectionTimeout 等待来自池的连接的最大毫秒数 SECONDS.toMillis(30) = 30000 30000 如果小于250毫秒,则被重置回30秒
idleTimeout 连接允许在池中闲置的最长时间 MINUTES.toMillis(10) = 600000 600000 如果idleTimeout+1秒>maxLifetime 且 maxLifetime>0,则会被重置为0(代表永远不会退出);如果idleTimeout!=0且小于10秒,则会被重置为10秒
maxLifetime 池中连接最长生命周期 MINUTES.toMillis(30) = 1800000 1800000 如果不等于0且小于30秒则会被重置回30分钟
connectionTestQuery 如果您的驱动程序支持JDBC4,我们强烈建议您不要设置此属性 null null
minimumIdle 池中维护的最小空闲连接数 -1 10 minIdle<0或者minIdle>maxPoolSize,则被重置为maxPoolSize
maximumPoolSize 池中最大连接数,包括闲置和使用中的连接 -1 10 如果maxPoolSize小于1,则会被重置。当minIdle<=0被重置为DEFAULT_POOL_SIZE则为10;如果minIdle>0则重置为minIdle的值
metricRegistry 该属性允许您指定一个 Codahale / Dropwizard MetricRegistry 的实例,供池使用以记录各种指标 null null
healthCheckRegistry 该属性允许您指定池使用的Codahale / Dropwizard HealthCheckRegistry的实例来报告当前健康信息 null null
poolName 连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置 null HikariPool-1
initializationFailTimeout 如果池无法成功初始化连接,则此属性控制池是否将 fail fast 1 1
isolateInternalQueries 是否在其自己的事务中隔离内部池查询,例如连接活动测试 FALSE FALSE
allowPoolSuspension 控制池是否可以通过JMX暂停和恢复 FALSE FALSE
readOnly 从池中获取的连接是否默认处于只读模式 FALSE FALSE
registerMbeans 是否注册JMX管理Bean(MBeans) FALSE FALSE
catalog 为支持 catalog 概念的数据库设置默认 catalog driver default null
connectionInitSql 该属性设置一个SQL语句,在将每个新连接创建后,将其添加到池中之前执行该语句。 null null
driverClassName HikariCP将尝试通过仅基于jdbcUrl的DriverManager解析驱动程序,但对于一些较旧的驱动程序,还必须指定driverClassName null null
transactionIsolation 控制从池返回的连接的默认事务隔离级别 null null
validationTimeout 连接将被测试活动的最大时间量 SECONDS.toMillis(5) = 5000 5000 如果小于250毫秒,则会被重置回5秒
leakDetectionThreshold 记录消息之前连接可能离开池的时间量,表示可能的连接泄漏 0 0 如果大于0且不是单元测试,则进一步判断:(leakDetectionThreshold < SECONDS.toMillis(2) or (leakDetectionThreshold > maxLifetime && maxLifetime > 0),会被重置为0 . 即如果要生效则必须>0,而且不能小于2秒,而且当maxLifetime > 0时不能大于maxLifetime
dataSource 这个属性允许你直接设置数据源的实例被池包装,而不是让HikariCP通过反射来构造它 null null
schema 该属性为支持模式概念的数据库设置默认模式 driver default null
threadFactory 此属性允许您设置将用于创建池使用的所有线程的java.util.concurrent.ThreadFactory的实例。 null null
scheduledExecutor 此属性允许您设置将用于各种内部计划任务的java.util.concurrent.ScheduledExecutorService实例 null null

代码示例

本文的相关例子可以查看下面仓库中的chapter3-2目录:

如果您觉得本文不错,欢迎Star支持,您的关注是我坚持的动力!

参考资料

在第2章节中,我们介绍了如何通过Spring Boot来实现HTTP接口,以及围绕HTTP接口相关的单元测试、文档生成等实用技能。但是,这些内容还不足以帮助我们构建一个动态应用的服务端程序。不论我们是要做App、小程序、还是传统的Web站点,对于用户的信息、相关业务的内容,通常都需要对其进行存储,而不是像第2章节中那样,把用户信息存储在内存中(重启就丢了!)。

对于信息的存储,现在已经有非常非常多的产品可以选择,其中不乏许多非常优秀的开源免费产品,比如:MySQL,Redis等。接下来,在第3章节,我们将继续学习在使用Spring Boot开发服务端程序的时候,如何实现对各流行数据存储产品的增删改查操作。

作为数据访问章节的第一篇,我们将从最为常用的关系型数据库开始。通过一个简单例子,学习在Spring Boot中最基本的数据访问工具:JdbcTemplate。

数据源配置

在我们访问数据库的时候,需要先配置一个数据源,下面分别介绍一下几种不同的数据库配置方式。

首先,为了连接数据库需要引入jdbc支持,在pom.xml中引入如下配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

嵌入式数据库支持

嵌入式数据库通常用于开发和测试环境,不推荐用于生产环境。Spring Boot提供自动配置的嵌入式数据库有H2、HSQL、Derby,你不需要提供任何连接配置就能使用。

比如,我们可以在pom.xml中引入如下配置使用HSQL

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>

连接生产数据源

以MySQL数据库为例,先引入MySQL连接的依赖包,在pom.xml中加入:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

src/main/resources/application.properties中配置数据源信息

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

注意:因为Spring Boot 2.1.x默认使用了MySQL 8.0的驱动,所以这里采用com.mysql.cj.jdbc.Driver,而不是老的com.mysql.jdbc.Driver

连接JNDI数据源

当你将应用部署于应用服务器上的时候想让数据源由应用服务器管理,那么可以使用如下配置方式引入JNDI数据源。

spring.datasource.jndi-name=java:jboss/datasources/customers

使用JdbcTemplate操作数据库

Spring的JdbcTemplate是自动配置的,你可以直接使用@Autowired或构造函数(推荐)来注入到你自己的bean中来使用。

下面就来一起完成一个增删改查的例子:

准备数据库

先创建User表,包含属性nameage。可以通过执行下面的建表语句::

CREATE TABLE `User` (
  `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,
  `age` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

编写领域对象

根据数据库中创建的User表,创建领域对象:

@Data
@NoArgsConstructor
public class User {

    private String name;
    private Integer age;

}

这里使用了Lombok的@Data@NoArgsConstructor注解来自动生成各参数的Set、Get函数以及不带参数的构造函数。如果您对Lombok还不了解,可以看看这篇文章:Java开发神器Lombok的使用与原理

编写数据访问对象

  • 定义包含有插入、删除、查询的抽象接口UserService
public interface UserService {

    /**
     * 新增一个用户
     *
     * @param name
     * @param age
     */
    int create(String name, Integer age);

    /**
     * 根据name查询用户
     *
     * @param name
     * @return
     */
    List<User> getByName(String name);

    /**
     * 根据name删除用户
     *
     * @param name
     */
    int deleteByName(String name);

    /**
     * 获取用户总量
     */
    int getAllUsers();

    /**
     * 删除所有用户
     */
    int deleteAllUsers();

}
  • 通过JdbcTemplate实现UserService中定义的数据访问操作
@Service
public class UserServiceImpl implements UserService {

    private JdbcTemplate jdbcTemplate;

    UserServiceImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public int create(String name, Integer age) {
        return jdbcTemplate.update("insert into USER(NAME, AGE) values(?, ?)", name, age);
    }

    @Override
    public List<User> getByName(String name) {
        List<User> users = jdbcTemplate.query("select NAME, AGE from USER where NAME = ?", (resultSet, i) -> {
            User user = new User();
            user.setName(resultSet.getString("NAME"));
            user.setAge(resultSet.getInt("AGE"));
            return user;
        }, name);
        return users;
    }

    @Override
    public int deleteByName(String name) {
        return jdbcTemplate.update("delete from USER where NAME = ?", name);
    }

    @Override
    public int getAllUsers() {
        return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
    }

    @Override
    public int deleteAllUsers() {
        return jdbcTemplate.update("delete from USER");
    }

}

编写单元测试用例

  • 创建对UserService的单元测试用例,通过创建、删除和查询来验证数据库操作的正确性。
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter31ApplicationTests {

    @Autowired
    private UserService userSerivce;

    @Before
    public void setUp() {
        // 准备,清空user表
        userSerivce.deleteAllUsers();
    }

    @Test
    public void test() throws Exception {
        // 插入5个用户
        userSerivce.create("Tom", 10);
        userSerivce.create("Mike", 11);
        userSerivce.create("Didispace", 30);
        userSerivce.create("Oscar", 21);
        userSerivce.create("Linda", 17);

        // 查询名为Oscar的用户,判断年龄是否匹配
        List<User> userList = userSerivce.getByName("Oscar");
        Assert.assertEquals(21, userList.get(0).getAge().intValue());

        // 查数据库,应该有5个用户
        Assert.assertEquals(5, userSerivce.getAllUsers());

        // 删除两个用户
        userSerivce.deleteByName("Tom");
        userSerivce.deleteByName("Mike");

        // 查数据库,应该有5个用户
        Assert.assertEquals(3, userSerivce.getAllUsers());

    }

}

上面介绍的JdbcTemplate只是最基本的几个操作,更多其他数据访问操作的使用请参考:JdbcTemplate API

通过上面这个简单的例子,我们可以看到在Spring Boot下访问数据库的配置依然秉承了框架的初衷:简单。我们只需要在pom.xml中加入数据库依赖,再到application.properties中配置连接信息,不需要像Spring应用中创建JdbcTemplate的Bean,就可以直接在自己的对象中注入使用。

 

Hikari 数据源参数配置说明


###############################################hikari############################################
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
#指定数据库驱动名称,不支持XA data。如果使用jdbcUrl配置则不需要配置该值。默认值:none。
#参考https://github.com/brettwooldridge/HikariCP#popular-datasource-class-names
spring.datasource.hikari.dataSourceClassName=
spring.datasource.url=jdbc:mysql://192.168.25.69:3306/bdc?characterEncoding=UTF8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.hikari.autoCommit=true
#连接建立超时时间 单位ms 默认30000
spring.datasource.hikari.connectionTimeout=30000
#空闲连接超时时间 单位ms 最小10000(10s) 默认600000(10min)
#当minimumIdle小于maximumPoolSize时有效
#0 空闲连接永远不会被移除
spring.datasource.hikari.idleTimeout=30000
#最小空闲连接数,默认值maximumPoolSize。当空闲连接低于该值且总连接数低于maximumPoolSize时,HikariCP会立即添加连接来保证一个高效的性能
#但是,HikariCP 不建议配置该值,建议使用默认值,让HikariCP维护一个固定连接数的连接池来保持高效。
spring.datasource.hikari.minimumIdle=
#最大连接数(包含空闲和正在使用的连接),默认值10.当连接数达到该值时,新的连接申请会被阻塞直到超时。
spring.datasource.hikari.maximumPoolSize=10
#一个连接在连接池中的最长存活时间,正在被使用的链接如果超时会等到关闭后被移除
#最小30000ms (30 seconds). Default: 1800000 (30 minutes)
#0 连接永不超时
spring.datasource.hikari.maxLifetime=1800000 
#连接测试查询语句,如果驱动支持JDBC4,强烈建议不配置该属性
#尝试不配置该属性来运行连接池,如果驱动不支持JDBC4,HikariCP会打出一个错误日志通知你。默认值:none
spring.datasource.hikari.connectionTestQuery=
#指标记录,默认:none。这个属性只适用于程序配置或ioc容器,允许你指定一个Codahale/Dropwizard MetricRegistry来记录指标。
#参考:https://github.com/brettwooldridge/HikariCP/wiki/Dropwizard-Metrics
spring.datasource.hikari.metricRegistry=
#健康检查,默认:none。允许你指定一个Codahale/Dropwizard HealthCheckRegistry来报告当前健康信息。
#参考https://github.com/brettwooldridge/HikariCP/wiki/Dropwizard-HealthChecks
spring.datasource.hikari.healthCheckRegistry=
#连接池名称。Default: auto-generated
spring.datasource.hikari.poolName=hikariPool
 
##########################HikariCP不常用的配置#####################
spring.datasource.hikari.initializationFailTimeout=1
#是否在其自己的事务中隔离内部池查询
#由于这些查询通常是只读查询,所以很少需要将它们封装在自己的事务中。
#此属性仅适用于禁用autoCommit的情况。默认值:false
spring.datasource.hikari.isolateInternalQueries=false
#连接池是否可以被JMX挂起和恢复。在挂起状态下,获取连接将不会超时,会被阻塞直到连接池恢复。,默认false。
spring.datasource.hikari.allowPoolSuspension=false
#指定从连接池获取到的连接在默认情况下是否是只读模式的。这个取决于数据库和应用,默认false。
spring.datasource.hikari.readOnly=false
#是否注册Mbeans,默认false。
spring.datasource.hikari.registerMbeans=false
#连接初始化sql。当一个连接被创建后,在放入连接池之前,执行这条语句来确认是否是有效连接。默认值none。
spring.datasource.hikari.connectionInitSql=
#支配驱动类名称,一般不需要配置,hikari会根绝jdbcUrl来解析使用响应的驱动,如果找不到会打印错误日志来通知你。
spring.datasource.hikari.driverClassName=com.mysql.jdbc.Driver
#指定从连接池获取到的连接的默认事务隔离级别。枚举值来自Connection class的常量。例如TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ等。
#默认情况下是数据库的默认隔离级别。
#spring.datasource.hikari.transactionIsolation=
#连接测试超时时间。最小值250ms,不能超过connectionTimeout。默认值5000
spring.datasource.hikari.validationTimeout=5000
#连接离开连接池后一定时间,会通过日志记录可能存在连接泄漏。该属性配置的是这个时间。
#0 表示不启用连接泄漏检测。最小值是2000 单位ms。默认值0。
spring.datasource.hikari.leakDetectionThreshold=0
#指定数据源。你可以自己封装数据源,不用通过hikari来反射生成。
#配置了该属性后,dataSourceClassName和所有DataSource-specific的属性都会被忽略。
#spring.datasource.hikari.dataSource=
#指定hikari连接池创建线程的线程工厂。默认值none。ThreadFactory 实例
spring.datasource.hikari.threadFactory=
#提供ScheduledExecutorService 实例。HikariCP在setRemoveOnCancelPolicy(true)时会使用。默认值none。
spring.datasource.hikari.scheduledExecutor=
#为支持catalog的数据库设置默认的catalog,默认依赖于jdbc驱动。
spring.datasource.hikari.catalog=
#为支持schema的数据库设置默认的schema,默认依赖于jdbc驱动。
spring.datasource.hikari.schema=
 

在第2章节中,我们介绍了如何通过Spring Boot来实现HTTP接口,以及围绕HTTP接口相关的单元测试、文档生成等实用技能。但是,这些内容还不足以帮助我们构建一个动态应用的服务端程序。不论我们是要做App、小程序、还是传统的Web站点,对于用户的信息、相关业务的内容,通常都需要对其进行存储,而不是像第2章节中那样,把用户信息存储在内存中(重启就丢了!)。

对于信息的存储,现在已经有非常非常多的产品可以选择,其中不乏许多非常优秀的开源免费产品,比如:MySQL,Redis等。接下来,在第3章节,我们将继续学习在使用Spring Boot开发服务端程序的时候,如何实现对各流行数据存储产品的增删改查操作。

作为数据访问章节的第一篇,我们将从最为常用的关系型数据库开始。通过一个简单例子,学习在Spring Boot中最基本的数据访问工具:JdbcTemplate。

数据源配置

在我们访问数据库的时候,需要先配置一个数据源,下面分别介绍一下几种不同的数据库配置方式。

首先,为了连接数据库需要引入jdbc支持,在pom.xml中引入如下配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

嵌入式数据库支持

嵌入式数据库通常用于开发和测试环境,不推荐用于生产环境。Spring Boot提供自动配置的嵌入式数据库有H2、HSQL、Derby,你不需要提供任何连接配置就能使用。

比如,我们可以在pom.xml中引入如下配置使用HSQL

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>

连接生产数据源

以MySQL数据库为例,先引入MySQL连接的依赖包,在pom.xml中加入:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

src/main/resources/application.properties中配置数据源信息

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

注意:因为Spring Boot 2.1.x默认使用了MySQL 8.0的驱动,所以这里采用com.mysql.cj.jdbc.Driver,而不是老的com.mysql.jdbc.Driver

连接JNDI数据源

当你将应用部署于应用服务器上的时候想让数据源由应用服务器管理,那么可以使用如下配置方式引入JNDI数据源。

spring.datasource.jndi-name=java:jboss/datasources/customers

使用JdbcTemplate操作数据库

Spring的JdbcTemplate是自动配置的,你可以直接使用@Autowired或构造函数(推荐)来注入到你自己的bean中来使用。

下面就来一起完成一个增删改查的例子:

准备数据库

先创建User表,包含属性nameage。可以通过执行下面的建表语句::

CREATE TABLE `User` (
  `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,
  `age` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

编写领域对象

根据数据库中创建的User表,创建领域对象:

@Data
@NoArgsConstructor
public class User {

    private String name;
    private Integer age;

}

这里使用了Lombok的@Data@NoArgsConstructor注解来自动生成各参数的Set、Get函数以及不带参数的构造函数。如果您对Lombok还不了解,可以看看这篇文章:Java开发神器Lombok的使用与原理

编写数据访问对象

  • 定义包含有插入、删除、查询的抽象接口UserService
public interface UserService {

    /**
     * 新增一个用户
     *
     * @param name
     * @param age
     */
    int create(String name, Integer age);

    /**
     * 根据name查询用户
     *
     * @param name
     * @return
     */
    List<User> getByName(String name);

    /**
     * 根据name删除用户
     *
     * @param name
     */
    int deleteByName(String name);

    /**
     * 获取用户总量
     */
    int getAllUsers();

    /**
     * 删除所有用户
     */
    int deleteAllUsers();

}
  • 通过JdbcTemplate实现UserService中定义的数据访问操作
@Service
public class UserServiceImpl implements UserService {

    private JdbcTemplate jdbcTemplate;

    UserServiceImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public int create(String name, Integer age) {
        return jdbcTemplate.update("insert into USER(NAME, AGE) values(?, ?)", name, age);
    }

    @Override
    public List<User> getByName(String name) {
        List<User> users = jdbcTemplate.query("select NAME, AGE from USER where NAME = ?", (resultSet, i) -> {
            User user = new User();
            user.setName(resultSet.getString("NAME"));
            user.setAge(resultSet.getInt("AGE"));
            return user;
        }, name);
        return users;
    }

    @Override
    public int deleteByName(String name) {
        return jdbcTemplate.update("delete from USER where NAME = ?", name);
    }

    @Override
    public int getAllUsers() {
        return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
    }

    @Override
    public int deleteAllUsers() {
        return jdbcTemplate.update("delete from USER");
    }

}

编写单元测试用例

  • 创建对UserService的单元测试用例,通过创建、删除和查询来验证数据库操作的正确性。
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter31ApplicationTests {

    @Autowired
    private UserService userSerivce;

    @Before
    public void setUp() {
        // 准备,清空user表
        userSerivce.deleteAllUsers();
    }

    @Test
    public void test() throws Exception {
        // 插入5个用户
        userSerivce.create("Tom", 10);
        userSerivce.create("Mike", 11);
        userSerivce.create("Didispace", 30);
        userSerivce.create("Oscar", 21);
        userSerivce.create("Linda", 17);

        // 查询名为Oscar的用户,判断年龄是否匹配
        List<User> userList = userSerivce.getByName("Oscar");
        Assert.assertEquals(21, userList.get(0).getAge().intValue());

        // 查数据库,应该有5个用户
        Assert.assertEquals(5, userSerivce.getAllUsers());

        // 删除两个用户
        userSerivce.deleteByName("Tom");
        userSerivce.deleteByName("Mike");

        // 查数据库,应该有5个用户
        Assert.assertEquals(3, userSerivce.getAllUsers());

    }

}

上面介绍的JdbcTemplate只是最基本的几个操作,更多其他数据访问操作的使用请参考:JdbcTemplate API

通过上面这个简单的例子,我们可以看到在Spring Boot下访问数据库的配置依然秉承了框架的初衷:简单。我们只需要在pom.xml中加入数据库依赖,再到application.properties中配置连接信息,不需要像Spring应用中创建JdbcTemplate的Bean,就可以直接在自己的对象中注入使用。

logging.level.com.zaxxer.hikari=DEBUG

以下是application.yml配置:

spring.datasource:
  url: "jdbc:mysql://<server>:<port>/<db>"
  username: "myusername"
  password: "mypwd"
  hikari:
    pool-name: "MyAppDataSourcePool"

我通过启用找到了默认属性

logging.level.com.zaxxer.hikari=DEBUG

以下是application.yml配置:

spring.datasource:
  url: "jdbc:mysql://<server>:<port>/<db>"
  username: "myusername"
  password: "mypwd"
  hikari:
    pool-name: "MyAppDataSourcePool"

发现以下默认属性:(您可以覆盖此属性,但如果您设置较低的值或设置其他属性,则会被忽略)

allowPoolSuspension.............false
autoCommit......................true
catalog.........................none
connectionInitSql...............none
connectionTestQuery.............none
connectionTimeout...............30000
dataSource......................none
dataSourceClassName.............none
dataSourceJNDI..................none
dataSourceProperties............{password=<masked>}
driverClassName................."com.mysql.cj.jdbc.Driver"
exceptionOverrideClassName......none
healthCheckProperties...........{}
healthCheckRegistry.............none
idleTimeout.....................600000
initializationFailTimeout.......1
isolateInternalQueries..........false
jdbcUrl.........................jdbc:mysql://<server>:<port>/<db>
leakDetectionThreshold..........0
maxLifetime.....................1800000
maximumPoolSize.................10
metricRegistry..................none
metricsTrackerFactory...........none
minimumIdle.....................10
password........................<masked>
poolName........................"MyAppDataSourcePool"
readOnly........................false
registerMbeans..................false
scheduledExecutor...............none
schema..........................none
threadFactory...................internal
transactionIsolation............default
username........................"myusername"
validationTimeout...............5000
 

您可以使用 Spring Boot 提供的专用应用程序属性来配置 Hikari 连接池。您可以在文档中找到完整列表(在页面中搜索“spring.datasource.hikari”)。 HikariCP 项目中的HikariConfig类也是检查所有可用配置项和默认值的好地方。

关于如何调整连接池的大小,Hikari 项目本身提供了一个有趣的指南。

hikaricp - Spring Boot 2.x Hikari 连接默认属性和值 - VoidCC

 

一、简介

1、概述

官方解释: 快速、简单、可靠。HikariCP 是一个“零开销”的生产就绪 JDBC 连接池。大约 130Kb,库非常轻。

2、地址

Github

代码语言:javascript
复制
https://github.com/brettwooldridge/HikariCP

二、配置参数

1、Hikari原生参数

1. 重要

  • driverClassName
  • jdbcUrl
  • username
  • password

2. 经常使用

  • autoCommit 此属性控制从池返回的连接的默认自动提交行为。 它是一个布尔值。 默认值:true
  • connectionTimeout 此属性控制客户端(即您)将等待来自池的连接的最大毫秒数。 如果超过此时间而连接不可用,则会抛出 SQLException。 可接受的最低连接超时为 250 毫秒。 默认值:30000(30 秒)
  • idleTimeout 此属性控制允许连接在池中闲置的最长时间。 此设置仅在 minimumIdle 定义为小于 maximumPoolSize 时适用。 一旦池达到 minimumIdle 连接数,空闲连接将不会被淘汰。 连接是否因空闲而退出取决于最大 +30 秒的变化,以及 +15 秒的平均变化。 在此超时之前,连接永远不会因空闲而退出。 值为 0 表示永远不会从池中删除空闲连接。 允许的最小值为 10000 毫秒(10 秒)。 默认值:600000(10 分钟)
  • keepaliveTime 此属性控制 HikariCP 尝试保持连接活动的频率,以防止它被数据库或网络基础设施超时。 该值必须小于 maxLifetime 值。 “keepalive”只会发生在空闲连接上。 当针对给定连接的“keepalive”时间到达时,该连接将从池中删除,“ping”,然后返回到池中。 “ping”是以下之一:调用 JDBC4 isValid() 方法,或执行 connectionTestQuery。 通常,池外持续时间应以个位数毫秒甚至亚毫秒为单位进行测量,因此对性能的影响很小或没有明显影响。 允许的最小值为 30000 毫秒(30 秒),但最好是分钟范围内的值。 默认值:0(禁用)
  • maxLifetime 此属性控制池中连接的最长生命周期。 一个正在使用的连接永远不会被淘汰,只有当它关闭时才会被删除。 在逐个连接的基础上,应用较小的负衰减以避免池中的质量灭绝。 我们强烈建议设置此值,它应该比任何数据库或基础设施强加的连接时间限制短几秒。 值为 0 表示没有最大生命周期(无限生命周期),当然要服从 idleTimeout 设置。 允许的最小值为 30000 毫秒(30 秒)。 默认值:1800000(30 分钟)
  • connectionTestQuery 如果您的驱动程序支持 JDBC4,我们强烈建议您不要设置此属性。 这是针对不支持 JDBC4 Connection.isValid() API 的“传统”驱动程序。 这是将在从池中为您提供连接之前执行的查询,以验证与数据库的连接是否仍然有效。 再次尝试在没有此属性的情况下运行池,如果您的驱动程序不兼容 JDBC4HikariCP 将记录错误以通知您。 默认值:无
  • minimumIdle 此属性控制 HikariCP 尝试在池中维护的最小空闲连接数。 如果空闲连接低于这个值并且池中的总连接小于 maximumPoolSizeHikariCP 将尽最大努力快速有效地添加额外的连接。 但是,为了获得最大性能和对峰值需求的响应,我们建议不要设置此值,而是允许 HikariCP 充当固定大小的连接池。 默认值:与 maximumPoolSize 相同
  • maximumPoolSize 此属性控制允许池达到的最大大小,包括空闲和使用中的连接。 基本上这个值将决定到数据库后端的最大实际连接数。 一个合理的值最好由您的执行环境决定。 当池达到此大小时,并且没有空闲连接可用时,对 getConnection() 的调用将在超时前阻塞最多 connectionTimeout 毫秒。 请阅读有关池大小的信息。 默认值:10
  • metricRegistry 此属性仅可通过编程配置或 IoC 容器使用。 此属性允许您指定 Codahale/Dropwizard MetricRegistry 的实例,供池使用以记录各种指标。 有关详细信息,请参阅指标 wiki 页面。 默认值:无
  • healthCheckRegistry 此属性仅可通过编程配置或 IoC 容器使用。 此属性允许您指定 Codahale/Dropwizard HealthCheckRegistry 的实例,供池使用以报告当前健康信息。 有关详细信息,请参阅健康检查 wiki 页面。 默认值:无
  • poolName 此属性表示连接池的用户定义名称,主要出现在日志记录和 JMX 管理控制台中以识别池和池配置。 默认值:自动生成

3. 不经常使用

  • initializationFailTimeout 此属性控制如果池无法成功地使用初始连接播种,池是否将“快速失败”。 任何正数都被视为尝试获取初始连接的毫秒数; 在此期间应用程序线程将被阻塞。 如果在此超时发生之前无法获取连接,则会抛出异常。 此超时在 connectionTimeout 期限之后应用。 如果值为零 (0),HikariCP 将尝试获取并验证连接。 如果获得连接,但验证失败,将抛出异常并且池不会启动。 但是,如果无法获得连接,池将启动,但稍后获得连接的努力可能会失败。 小于零的值将绕过任何初始连接尝试,并且池将在尝试在后台获取连接时立即启动。 因此,以后获得连接的努力可能会失败。 默认值:1
  • isolateInternalQueries 此属性确定 HikariCP 是否在其自己的事务中隔离内部池查询,例如连接存活测试。 由于这些通常是只读查询,因此很少需要将它们封装在自己的事务中。 此属性仅在禁用 autoCommit 时适用。 默认值:false
  • allowPoolSuspension 此属性控制是否可以通过 JMX 暂停和恢复池。 这对于某些故障转移自动化场景很有用。 当池暂停时,对 getConnection() 的调用不会超时,并将一直保持到池恢复。 默认值:false
  • readOnly 该属性控制从池中获取的连接是否默认为只读模式。 请注意,某些数据库不支持只读模式的概念,而其他数据库则在 Connection 设置为只读时提供查询优化。 您是否需要此属性在很大程度上取决于您的应用程序和数据库。 默认值:false
  • registerMbeans 此属性控制是否注册 JMX 管理 Bean(“MBean”)。 默认值:false
  • catalog 此属性为支持目录概念的数据库设置默认目录。 如果未指定此属性,则使用 JDBC 驱动程序定义的默认目录。 默认值:驱动程序默认值
  • connectionInitSql 此属性设置一个 SQL 语句,该语句将在每次创建新连接后执行,然后再将其添加到池中。 如果此 SQL 无效或抛出异常,将被视为连接失败并遵循标准重试逻辑。 默认值:无
  • driverClassName HikariCP 将尝试通过仅基于 jdbcUrl 的 DriverManager 解析驱动程序,但对于一些较旧的驱动程序,还必须指定 driverClassName。 除非您收到指示未找到驱动程序的明显错误消息,否则请忽略此属性。 默认值:无
  • transactionIsolation 此属性控制从池返回的连接的默认事务隔离级别。 如果未指定此属性,则使用 JDBC 驱动程序定义的默认事务隔离级别。 仅当您有对所有查询通用的特定隔离要求时才使用此属性。 此属性的值是来自连接类的常量名称,例如 TRANSACTION_READ_COMMITTEDTRANSACTION_REPEATABLE_READ 等。默认值:驱动程序默认值
  • validationTimeout 此属性控制将测试连接的活动性的最长时间。 该值必须小于 connectionTimeout。 可接受的最低验证超时为 250 毫秒。 默认值:5000
  • leakDetectionThreshold 此属性控制在记录指示可能的连接泄漏的消息之前连接可以离开池的时间量。 值为 0 表示禁用泄漏检测。 启用泄漏检测的最低可接受值为 2000(2 秒)。 默认值:0
  • dataSource 此属性仅可通过编程配置或 IoC 容器使用。 这个属性允许你直接设置 DataSource 的实例被池包装,而不是让 HikariCP 通过反射来构造它。 这在某些依赖注入框架中很有用。 指定此属性时,将忽略 dataSourceClassName 属性和所有特定于数据源的属性。 默认值:无
  • schema 此属性为支持模式概念的数据库设置默认模式。 如果未指定此属性,则使用 JDBC 驱动程序定义的默认模式。 默认值:驱动程序默认值
  • threadFactory 此属性仅可通过编程配置或 IoC 容器使用。 此属性允许您设置 java.util.concurrent.ThreadFactory 的实例,该实例将用于创建池使用的所有线程。 在某些受限的执行环境中需要它,在这些环境中线程只能通过应用程序容器提供的 ThreadFactory 创建。 默认值:无
  • scheduledExecutor 此属性仅可通过编程配置或 IoC 容器使用。 此属性允许您设置将用于各种内部计划任务的 java.util.concurrent.ScheduledExecutorService的实例。 如果为HikariCP 提供 ScheduledThreadPoolExecutor实例,建议使用setRemoveOnCancelPolicy(true)`。 默认值:无

2、Springboot中参数

  • spring.datasource.hikari.data-source-class-name:驱动类
  • spring.datasource.hikari.jdbc-url:url地址
  • spring.datasource.hikari.username:用户名
  • spring.datasource.hikari.password:密码
  • spring.datasource.hikari.pool-name:连接池的用户定义名称
  • spring.datasource.hikari.auto-commit:获取连接最大时长(用于从池获取毫秒数)
  • spring.datasource.hikari.minimum-idle:最小空闲连接数
  • spring.datasource.hikari.maximum-pool-size:最大连接数
  • spring.datasource.hikari.connection-timeout:获取连接最大时长(用于从池获取毫秒数)
  • spring.datasource.hikari.max-lifetime:池中连接的最大生存周期(从创建开始计算)(30000ms=30s)
  • spring.datasource.hikari.keepalive-time:连接活跃度检查时间
  • spring.datasource.hikari.connection-test-query:连接检查语句
  • spring.datasource.hikari.idle-timeout:连接在池中处于空闲状态的最长时间(空闲开始计算)
  • spring.datasource.hikari.allow-pool-suspension:此属性控制是否可以通过JMX挂起和恢复池
  • spring.datasource.hikari.catalog:此属性为支持目录概念的数据库设置默认目录
  • spring.datasource.hikari.connection-init-sql:此属性设置一个SQL语句,该语句将在每次创建新连接后执行,然后再将其添加到池中
  • spring.datasource.hikari.data-source-j-n-d-i
  • spring.datasource.hikari.data-source-properties
  • spring.datasource.hikari.driver-class-name:HikariCP将尝试通过仅基于的DriverManager解析驱动程序,但对于一些较旧的驱动程序,还必须指定
  • spring.datasource.hikari.exception-override-class-name
  • spring.datasource.hikari.health-check-properties
  • spring.datasource.hikari.initialization-fail-timeout:此属性控制如果无法成功为池设定初始连接的种子,则池是否会“快速故障”
  • spring.datasource.hikari.isolate-internal-queries:此属性确定HikariCP是否隔离内部池查询
  • spring.datasource.hikari.leak-detection-threshold:此属性控制在记录指示可能存在连接泄漏的消息之前,连接可以离开池的时间
  • spring.datasource.hikari.login-timeout
  • spring.datasource.hikari.metrics-tracker-factory
  • spring.datasource.hikari.read-only:此属性控制默认情况下从池中获取的连接是否处于只读模式。
  • spring.datasource.hikari.register-mbeans:此属性控制是否注册了JMX管理Bean
  • spring.datasource.hikari.scheduled-executor
  • spring.datasource.hikari.schema:此属性为支持模式概念的数据库设置默认模式
  • spring.datasource.hikari.transaction-isolation:此属性控制从池返回的连接的默认事务隔离级别
  • spring.datasource.hikari.validation-timeout:此属性控制测试连接是否有效的最长时间

三、springboot中使用

springboot中默认内置的默认数据库连接池HikariPool。所以直接使用进行连接即可。

代码语言:javascript
复制
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

日志

代码语言:javascript
复制
2023-05-23 23:01:34.734 DEBUG 42836 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet       1131 : Completed 200 OK
2023-05-23 23:01:34.844 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@4774440d
2023-05-23 23:01:34.974 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@638743e8
2023-05-23 23:01:35.070 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@122910ce
2023-05-23 23:01:35.173 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7fb4c201
2023-05-23 23:01:35.270 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@71c0f332
2023-05-23 23:01:35.364 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@34a64c99
2023-05-23 23:01:35.462 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@609524be
2023-05-23 23:01:35.568 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       729 : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7f6f167e
2023-05-23 23:01:35.569 DEBUG 42836 --- [onnection adder] com.zaxxer.hikari.pool.HikariPool       421 : HikariPool-1 - After adding stats (total=10, active=0, idle=10, waiting=0)

四、自定义数据源

1、各模块

需要使用Jdbc自己实现查询,并且查询过程中,使用数据库连接池进行数据源管理。

获取数据源

代码语言:javascript
复制
public HikariDataSource getDataSource() {
    HikariDataSource config = new HikariDataSource();
    config.setDriverClassName("");
    config.setJdbcUrl("");
    config.setUsername("");
    config.setPassword("");
    config.setPoolName("");
    config.setAutoCommit(true);
    config.setMinimumIdle(1);
    config.setMaximumPoolSize(10);
    config.setConnectionTimeout(111);
    config.setMaxLifetime(1);
    config.setKeepaliveTime(111);
    config.setConnectionTestQuery("SELECT 1");
    config.setIdleTimeout(1);
    config.setAllowPoolSuspension(false);
    return config;
}

从数据源获取链接

代码语言:javascript
复制
/**
 * 获取数据库连接
 *
 * @param driverClassName 驱动类(com.mysql.cj.jdbc.Driver)
 * @param jdbcUrl         jdbcUrl(jdbc:mysql://12.13.39.17:3306)
 * @param username        用户名
 * @param password        密码
 * @return
 */
public HikariDataSource test(String driverClassName, String jdbcUrl, String username, String password) {
    if (HIKARI_DATA_SOURCE == null) {
        synchronized (DataSourcePool.class) {
            if (HIKARI_DATA_SOURCE == null) {
                HIKARI_DATA_SOURCE = getDataSource(driverClassName, jdbcUrl, username, password);
            }
        }
    }
    return HIKARI_DATA_SOURCE;
}

使用:

代码语言:javascript
复制
String driverClassName = "com.mysql.cj.jdbc.Driver";
String jdbcUrl = "jdbc:mysql://12.13.39.17:3306/datasource";
String username = "root";
String password = "root";
String sql = "select * from datasource";
//  获取链接
HikariDataSource dataSource = dataSourcePool.getDataSource(driverClassName, jdbcUrl, username, password);
//  执行SQL
Connection connection = null;
connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
ps.execute();
//	关闭连接
connection.close();

2、完整代码

DataSourcePool

代码语言:javascript
复制
package com.lydms.demohikari.client;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.stereotype.Component;

@Component
public class DataSourcePool {

    private static volatile HikariDataSource HIKARI_DATA_SOURCE = null;

    /**
     * 获取数据库连接
     *
     * @param driverClassName 驱动类(com.mysql.cj.jdbc.Driver)
     * @param jdbcUrl         jdbcUrl(jdbc:mysql://12.13.39.17:3306)
     * @param username        用户名
     * @param password        密码
     * @return
     */
    public HikariDataSource test(String driverClassName, String jdbcUrl, String username, String password) {
        if (HIKARI_DATA_SOURCE == null) {
            synchronized (DataSourcePool.class) {
                if (HIKARI_DATA_SOURCE == null) {
                    HIKARI_DATA_SOURCE = getDataSource(driverClassName, jdbcUrl, username, password);
                }
            }
        }
        return HIKARI_DATA_SOURCE;
    }

    public HikariDataSource getDataSource(String driverClassName, String jdbcUrl, String username, String password) {
        HikariDataSource config = new HikariDataSource();
        config.setDriverClassName(driverClassName);
        config.setJdbcUrl(jdbcUrl);
        config.setUsername(username);
        config.setPassword(password);

        config.setPoolName("");
        config.setAutoCommit(true);
        config.setMinimumIdle(1);
        config.setMaximumPoolSize(10);
        config.setConnectionTimeout(111);
        config.setMaxLifetime(1);
        config.setKeepaliveTime(111);
        config.setConnectionTestQuery("SELECT 1");
        config.setIdleTimeout(1);
        config.setAllowPoolSuspension(false);
        return config;
    }
}

DddServiceImpl

代码语言:javascript
复制
import com.lydms.demohikari.client.DataSourcePool;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

@Service
@Slf4j
public class DddServiceImpl {

    @Autowired
    private DataSourcePool dataSourcePool;

    public void get() {
        String driverClassName = "com.mysql.cj.jdbc.Driver";
        String jdbcUrl = "jdbc:mysql://12.13.39.17:3306/datasource";
        String username = "root";
        String password = "root";
        String sql = "select * from datasource";
        //  获取链接
        HikariDataSource dataSource = dataSourcePool.getDataSource(driverClassName, jdbcUrl, username, password);
        //  执行SQL
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            PreparedStatement ps = connection.prepareStatement(sql);
            ps.execute();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
          //	关闭连接
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                log.error("关闭连接失败");
            }
        }
    }
}

3、多数据源

代码语言:javascript
复制
private static final HashMap<String, HikariDataSource> HIKARI_DATA_SOURCE_HASH_MAP = new HashMap<>();
/**
 * 获取数据库连接
 *
 * @param driverClassName 驱动类(com.mysql.cj.jdbc.Driver)
 * @param jdbcUrl         jdbcUrl(jdbc:mysql://12.13.39.17:3306)
 * @param username        用户名
 * @param password        密码
 * @return
 */
public HikariDataSource test(String driverClassName, String jdbcUrl, String username, String password) {
    String key = driverClassName + jdbcUrl + username + password;
    HikariDataSource hikariDataSource = HIKARI_DATA_SOURCE_HASH_MAP.get(key);
    if (hikariDataSource == null) {
        synchronized (DataSourcePool.class) {
            hikariDataSource = HIKARI_DATA_SOURCE_HASH_MAP.get(key);
            if (hikariDataSource == null) {
                hikariDataSource = getDataSource(driverClassName, jdbcUrl, username, password);
            }
        }
    }
    return hikariDataSource;
}

五、多数据源dynamic中使用

1、简介

文档地址:

代码语言:javascript
复制
https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611

多数据源既动态数据源,项目开发逐渐扩大,单个数据源、单一数据源已经无法满足需求项目的支撑需求。由此延伸了多数据源的扩展。

dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。

其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x

  • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 支持数据库敏感配置信息 加密 ENC()。
  • 支持每个数据库独立初始化表结构schema和数据库database。
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
  • 支持 自定义注解 ,需继承DS(3.2.0+)。
  • 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
  • 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
  • 提供 自定义数据源来源 方案(如全从数据库加载)。
  • 提供项目启动后 动态增加移除数据源 方案。
  • 提供Mybatis环境下的 纯读写分离 方案。
  • 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
  • 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
  • 提供 **基于seata的分布式事务方案。
  • 提供 本地多数据源事务方案。

2、引入依赖

1、引入dynamic-datasource-spring-boot-starter。

代码语言:javascript
复制
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>

2、项目引入``HikariCP`依赖

pringBoot2.x.x默认引入了HikariCP,除非对版本有要求无需再次引入。

SpringBoot 1.5.x需手动引入,对应的版本请根据自己环境和HikariCP官方文档自行选择。

代码语言:javascript
复制
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>${version}</version>
</dependency>

3、参数配置

  1. 如果参数都未配置,则保持原组件默认值。
  2. 如果配置了全局参数,则每一个数据源都会继承对应参数。
  3. 每一个数据源可以单独设置参数覆盖全局参数。

特别注意,hikaricp原生设置某些字段名和本组件不一致,本组件是根据参数反射设置,而原生hikaricp字段名称和set名称不一致。 所以大家理解,以本组件字段名称为准。

代码语言:javascript
复制
spring:
  datasource:
    dynamic:
      hikari:  # 全局hikariCP参数,所有值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
        catalog:
        connection-timeout:
        validation-timeout:
        idle-timeout:
        leak-detection-threshold:
        max-lifetime:
        max-pool-size:
        min-idle:
        initialization-fail-timeout:
        connection-init-sql:
        connection-test-query:
        dataSource-class-name:
        dataSource-jndi-name:
        schema:
        transaction-isolation-name:
        is-auto-commit:
        is-read-only:
        is-isolate-internal-queries:
        is-register-mbeans:
        is-allow-pool-suspension:
        data-source-properties: 
        health-check-properties:
      datasource:
        master:
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic?characterEncoding=utf8&useSSL=false
          hikari: # 以下参数针对每个库可以重新设置hikari参数
            max-pool-size:
            idle-timeout:
#           ......

六、XMind整理

代码语言:javascript
复制
https://download.csdn.net/download/weixin_44624117/87816645
posted @ 2025-02-07 11:57  CharyGao  阅读(331)  评论(0)    收藏  举报