SpringBoot 默认数据库连接池 HikariCP

article/2025/9/14 11:04:05

目录

 引言

1、问题描述

2、SpringBoot默认的数据库连接池

3、HikariCP是什么

4、测试依赖

5、配置文件

5.1、数据库连接参数

5.2、连接池数据基本参数

5.3、连接检查参数

5.4、事务相关参数

5.5、JMX参数

6、HikariCP源码浅析

6.1、HikariConfig--连接池配置的加载

6.2、HikariPool--连接池

1、HikariPool UML图

2、PoolBase

3、HikariPool

4、如何获取一个链接对象

6.3、ConcurrentBag--更少的锁冲突

7、HikariCP为什么快?

7.1、通过代码设计和优化大幅减少线程间的锁竞争

7.2、引入了更多 JDK 的特性

7.3、使用 javassist 直接修改 class 文件生成动态代理

8、JDK 、CGLib 、ASM 、Javassist 性能测试

1、测试代码

2、测试结果


 引言

        咱们开发项目的过程中用到很多的开源数据库链接池,比如druid、c3p0、BoneCP等等,前端时间在部署新服务的时候发现了个问题,排查完毕问题正好学习学习SpringBoot的默认的数据库连接池HikariCP的一些知识。HikariCP官网地址: https://github.com/brettwooldridge/HikariCP

1、问题描述

        我们新项目部署上线之后在观察日志的时候发现了这个警告,经过排查是发现DB方面的问题,保留现场如下。

2、SpringBoot默认的数据库连接池

        Spring-Boot-2.0.0-M1版本将默认的数据库连接池从tomcat jdbc pool改为了HikariCP。

3、HikariCP是什么

        HikariCP 是用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能,另外,和 druid 一样,HikariCP 也支持监控功能。

        HikariCP 是目前最快的连接池,就连风靡一时的 BoneCP 也停止维护,主动让位给它,SpringBoot 也把它设置为默认连接池。

4、测试依赖

        既然官网说HikariCP是最快的数据库连接池,不妨我们进行一些尝试,验证一下官网放出的狠话。验证也比较简单,只需要在项目中添加依赖即可。

 <!-- JNDI数据源 --><resource-ref><res-ref-name>jdbc/hikariCP-test</res-ref-name><res-type>javax.sql.DataSource</res-type><res-auth>Container</res-auth></resource-ref>

5、配置文件

        上面一步添加完依赖,接下来具体实操之前先了解一下HikariCP的各种配置信息。

5.1、数据库连接参数

        注意,这里url在后面拼接了多个参数用于避免乱码、时区报错问题。

#-------------基本属性--------------------------------
jdbcUrl=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true
username=root
password=root
#JDBC驱动使用的Driver实现类类名
#默认为空。会根据jdbcUrl来解析
driverClassName=com.mysql.cj.jdbc.Driver

5.2、连接池数据基本参数

#-------------连接池大小相关参数--------------------------------
#最大连接池数量
#默认为10。可通过JMX动态修改
maximumPoolSize=10#最小空闲连接数量
#默认与maximumPoolSize一致。可通过JMX动态修改
minimumIdle=0

5.3、连接检查参数

        注意:针对连接失效的问题,HikariCP 强制开启借出测试和空闲测试,不开启回收测试,可选的只有泄露测试。所有的超时时间都可以根据JMX设置。

#-------------连接检测情况--------------------------------
#用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'
#如果驱动支持JDBC4,建议不设置,因为这时默认会调用Connection.isValid()方法来检测,该方式效率会更高
#默认为空
connectionTestQuery=select 1 from dual#检测连接是否有效的超时时间,单位毫秒
#最小允许值250 ms
#默认5000 ms。
validationTimeout=5000#连接保持空闲而不被驱逐的最小时间。单位毫秒。
#该配置只有再minimumIdle < maximumPoolSize才会生效,最小允许值为10000 ms。
#默认值10000*60 = 10分钟。
idleTimeout=600000#连接对象允许“泄露”的最大时间。单位毫秒
#最小允许值为2000 ms。
#默认0,表示不开启泄露检测。
leakDetectionThreshold=0#连接最大存活时间。单位毫秒
#最小允许值30000 ms
#默认30分钟。可通过JMX动态修改
maxLifetime=1800000#获取连接时最大等待时间,单位毫秒
#获取时间超过该配置,将抛出异常。最小允许值250 ms
#默认30000 ms。
connectionTimeout=300000

5.4、事务相关参数

#-------------事务相关的属性--------------------------------
#当连接返回池中时是否设置自动提交
#默认为true
autoCommit=true#当连接从池中取出时是否设置为只读
#默认值false
readOnly=false#连接池创建的连接的默认的TransactionIsolation状态
#可用值为下列之一:NONE,TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE
#默认值为空,由驱动决定
transactionIsolation=TRANSACTION_REPEATABLE_READ

5.5、JMX参数

#-------------JMX--------------------------------#是否允许通过JMX挂起和恢复连接池
#默认为false
allowPoolSuspension=false#是否开启JMX
#默认false
registerMbeans=true#数据源名。
#默认自动生成
poolName=

6、HikariCP源码浅析

6.1、HikariConfig--连接池配置的加载

        在HikariCP 中,HikariConfig用于加载配置,它的加载要更加简洁。直接从PropertyElf.setTargetFromProperties(Object, Properties)方法开始看。

// 这个方法就是将properties的参数设置到HikariConfig中
public static void setTargetFromProperties(final Object target, final Properties properties)
{if (target == null || properties == null) {return;}// 在这里会利用反射获取List<Method> methods = Arrays.asList(target.getClass().getMethods());// 遍历properties.forEach((key, value) -> {if (target instanceof HikariConfig && key.toString().startsWith("dataSource.")) {// 如果是dataSource.*的参数,直接加入到dataSourceProperties属性((HikariConfig) target).addDataSourceProperty(key.toString().substring("dataSource.".length()), value);}else {// 如果不是,则通过set方法设置setProperty(target, key.toString(), value, methods);}});
}
private static void setProperty(final Object target, final String propName, final Object propValue, final List<Method> methods)
{final Logger logger = LoggerFactory.getLogger(PropertyElf.class);// use the english locale to avoid the infamous turkish locale bug// 拼接参数的setter方法名 首字母大写String methodName = "set" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);// 获取对应的Method 对象Method writeMethod = methods.stream().filter(m -> m.getName().equals(methodName) && m.getParameterCount() == 1).findFirst().orElse(null);// 如果不存在,按另一套规则拼接参数的setter方法名 全部大写if (writeMethod == null) {String methodName2 = "set" + propName.toUpperCase(Locale.ENGLISH);writeMethod = methods.stream().filter(m -> m.getName().equals(methodName2) && m.getParameterCount() == 1).findFirst().orElse(null);}// 如果该参数setter方法不存在,则抛出异常,从这里可以看出,HikariCP 中不能存在配错参数名的情况if (writeMethod == null) {logger.error("Property {} does not exist on target {}", propName, target.getClass());throw new RuntimeException(String.format("Property %s does not exist on target %s", propName, target.getClass()));}// 调用setter方法来配置具体参数。try {Class<?> paramClass = writeMethod.getParameterTypes()[0];if (paramClass == int.class) {writeMethod.invoke(target, Integer.parseInt(propValue.toString()));}else if (paramClass == long.class) {writeMethod.invoke(target, Long.parseLong(propValue.toString()));}else if (paramClass == boolean.class || paramClass == Boolean.class) {writeMethod.invoke(target, Boolean.parseBoolean(propValue.toString()));}else if (paramClass == String.class) {writeMethod.invoke(target, propValue.toString());}else {try {logger.debug("Try to create a new instance of \"{}\"", propValue.toString());writeMethod.invoke(target, Class.forName(propValue.toString()).newInstance());}catch (InstantiationException | ClassNotFoundException e) {logger.debug("Class \"{}\" not found or could not instantiate it (Default constructor)", propValue.toString());writeMethod.invoke(target, propValue);}}}catch (Exception e) {logger.error("Failed to set property {} on target {}", propName, target.getClass(), e);throw new RuntimeException(e);}
}

6.2、HikariPool--连接池

        HikariPool 是一个非常重要的类,它负责管理连接。

1、HikariPool UML图

HikariPoolMXBean:采用JMX控制HikariPool的入口。

/*** The javax.management MBean for a Hikari pool instance.** @author Brett Wooldridge*/
public interface HikariPoolMXBean

2、PoolBase

        HikariPool链接池的配置信息。

3、HikariPool

        连接池的管理。

属性:

//配置信息。
public final HikariConfig config;
//指标记录器包装类。HikariCP支持Metrics监控
IMetricsTrackerDelegate metricsTracker;
//创建新连接的任务,Callable实现类。一般调用一次创建一个连接
private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(null /*logging prefix*/);
//创建新连接的任务,Callable实现类。一般调用一次创建一个连接,与前者区别在于它创建最后一个连接,会打印日志
private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding ");
private final Collection<Runnable> addConnectionQueueReadOnlyView;
//执行PoolEntryCreator任务的线程池。以addConnectionQueueReadOnlyView作为等待队列
private final ThreadPoolExecutor addConnectionExecutor;
//执行关闭连接的线程池
private final ThreadPoolExecutor closeConnectionExecutor;
//用于执行HouseKeeper(连接检测任务和维持连接池大小)等任务
private final ScheduledExecutorService houseKeepingExecutorService;
//存放连接对象的包。用于borrow、requite、add和remove对象。
private final ConcurrentBag<PoolEntry> connectionBag;

4、如何获取一个链接对象

/*** Get a connection from the pool, or timeout after the specified number of milliseconds.** @param hardTimeout the maximum time to wait for a connection from the pool* @return a java.sql.Connection instance* @throws SQLException thrown if a timeout occurs trying to obtain a connection*/
public Connection getConnection(final long hardTimeout) throws SQLException
{// 如果我们设置了allowPoolSuspension为true,则这个锁会生效,这个是基于信号量的锁 MAX_PERMITS = 10000,正常情况不会用完,除非你挂起了连接池(通过JMX等方式),10000个permits会被消耗完suspendResumeLock.acquire();final long startTime = currentTime();try {// 剩余超时时间long timeout = hardTimeout;// 循环获取,除非获取到了连接或者超时do {// 从ConcurrentBag中拿出一个元素PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);// 前面说过,只有超时情况才会返回空,这时会跳出循环并抛出异常if (poolEntry == null) {break; // We timed out... break and throw exception}final long now = currentTime();// 如果// 1、元素被标记为丢弃// 2、空闲时间过长// 3、连接无效则会丢弃该元素// 1&2&3 --> 4、并关闭连接if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);timeout = hardTimeout - elapsedMillis(startTime);}else {metricsTracker.recordBorrowStats(poolEntry, startTime);// 创建Connection代理类,该代理类就是使用Javassist生成的return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);}} while (timeout > 0L);metricsTracker.recordBorrowTimeoutStats(startTime);// 超时抛出异常throw createTimeoutException(startTime);}catch (InterruptedException e) {Thread.currentThread().interrupt();throw new SQLException(poolName + " - Interrupted during connection acquisition", e);}finally {// 释放一个permitsuspendResumeLock.release();}
}

6.3、ConcurrentBag--更少的锁冲突

        在 HikariCP 中ConcurrentBag用于存放PoolEntry对象(封装了Connection对象,IConcurrentBagEntry实现类),本质上可以将它就是一个资源池。

 属性:

//存放着当前线程返还的PoolEntry对象。如果当前线程再次借用资源,会先从这个列表中获取。注意,这个列表的元素可以被其他线程“偷走”
private final ThreadLocal<List<Object>> threadList;
//添加元素的监听器,由HikariPool实现,在该实现中,如果waiting - addConnectionQueue.size() >= 0,则会让addConnectionExecutor执行PoolEntryCreator任务
private final IBagStateListener listener;
//当前等待获取链接的线程数
private final AtomicInteger waiters;
//元素是否使用弱引用
private final boolean weakThreadLocals;
//这是一个无容量的阻塞队列,每个插入操作需要阻塞等待删除操作,而删除操作不需要等待,如果没有元素插入,会返回null,如果设置了超时时间则需要等待。
private final SynchronousQueue<T> handoffQueue;
//存放着状态为使用中、未使用和保留三种状态的PoolEntry对象。注意,CopyOnWriteArrayList是一个线程安全的集合,在每次写操作时都会采用复制数组的方式来增删元素,读和写使用的是不同的数组,避免了锁竞争
private final CopyOnWriteArrayList<T> sharedList;

方法:

        在以下方法中,唯一可能出现线程切换到就是handoffQueue.poll(timeout, NANOSECONDS)。

/*** The method will borrow a BagEntry from the bag, blocking for the* specified timeout if none are available.** @param timeout how long to wait before giving up, in units of unit* @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter* @return a borrowed instance from the bag or null if a timeout occurs* @throws InterruptedException if interrupted while waiting*/
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
{// 1. 首先从threadList获取对象// Try the thread-local list first// 获取绑定在当前线程的List<Object>对象,注意这个集合的实现一般为FastList,这是HikariCP自己实现的final List<Object> list = threadList.get();for (int i = list.size() - 1; i >= 0; i--) {// 获取当前元素,并将它从集合中删除final Object entry = list.remove(i);@SuppressWarnings("unchecked")// 如果设置了weakThreadLocals,则存放的是WeakReference对象final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;// 采用CAS方式将获取的对象状态由未使用改为使用中,如果失败说明其他线程正在使用它。if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {return bagEntry;}}// 2.如果还没获取到,会从sharedList中获取对象// Otherwise, scan the shared list ... then poll the handoff queue// 等待获取连接的线程数+1final int waiting = waiters.incrementAndGet();try {// 遍历sharedListfor (T bagEntry : sharedList) {// 采用CAS方式将获取的对象状态由未使用改为使用中,如果当前元素正在使用,则无法修改成功,进入下一循环if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {// If we may have stolen another waiter's connection, request another bag add.if (waiting > 1) {// 通知监听器添加包元素。如果waiting - addConnectionQueue.size() >= 0,则会让addConnectionExecutor执行PoolEntryCreator任务listener.addBagItem(waiting - 1);}return bagEntry;}}// 通知监听器添加包元素listener.addBagItem(waiting);// 3.如果还没获取到,会轮训进入handoffQueue队列获取连接对象timeout = timeUnit.toNanos(timeout);do {final long start = currentTime();// 从handoffQueue队列中获取并删除元素。这是一个无容量的阻塞队列,插入操作需要阻塞等待删除操作,而删除操作不需要等待,如果没有元素插入,会返回null,如果设置了超时时间则需要等待final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);// 这里会出现三种情况,// 1.超时,返回null// 2.获取到元素,但状态为正在使用,继续执行// 3.获取到元素,元素状态未未使用,修改未使用并返回if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {return bagEntry;}timeout -= elapsedNanos(start);} while (timeout > 10_000);// 超时返回nullreturn null;}finally {// 等待获取连接的线程数-1waiters.decrementAndGet();}
}

7、HikariCP为什么快?

7.1、通过代码设计和优化大幅减少线程间的锁竞争

        1、元素状态的引入,以及使用CAS方法修改状态。在ConcurrentBag中,使用使用中、未使用、删除和保留等表示元素的状态,而不是使用不同的集合来维护不同状态的元素。元素状态这一概念的引入非常关键,为后面的几点提供了基础。 ConcurrentBag的方法中多处调用 CAS 方法来判断和修改元素状态,这一过程不需要加锁。

        2、threadList 的使用。当前线程归还的元素会被绑定到ThreadLocal,该线程再次获取元素时,在该元素未被偷走的前提下可直接获取到,不需要去 sharedList 遍历获取;

7.2、引入了更多 JDK 的特性

        尤其是 concurrent 包的工具。相比较于DBCP、C3P0等数据库链接池问世较晚,很方便的享受JDK的升级带来的方便。

        1、采用CopyOnWriteArrayList来存放元素。在CopyOnWriteArrayList中,读和写使用的是不同的数组,避免了两者的锁竞争,至于多个线程写入,则会加 ReentrantLock 锁。

        2、sharedList 的读写控制。borrow 和 requite 对 sharedList 来说都是不加锁的,缺点就是会牺牲一致性。用户线程无法进行增加元素的操作,只有 addConnectionExecutor 可以,而 addConnectionExecutor 只会开启一个线程执行任务,所以 add 操作不会存在锁竞争。至于 remove 是唯一会造成锁竞争的方法,这一点我认为也可以参照 addConnectionExecutor 来处理,在加入任务队列前把 PoolEntry 的状态标记为删除中。

7.3、使用 javassist 直接修改 class 文件生成动态代理

        1、使用 javassist 直接修改 class 文件生成动态代理,精简了很多不必要的字节码,提高代理方法运行速度。尤其JDK1.8优化以后JDK的动态代理,CGlib代理已经和javassist、asm等一个数量级。

8、JDK 、CGLib 、ASM 、Javassist 性能测试

        环境:JDK 1.8,CGLib 3.3.0, ASM JDK自带的ASM包,Javassist 3.26.0-GA。

        数据为执行三次,每次调用5千万次代理方法的结果。

1、测试代码

package cn.zzs.proxy;import javassist.*;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.FieldVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.DecimalFormat;/*** @author lly**/
public class App {public static void main(String[] args) throws Exception {CountService delegate = new CountServiceImpl();long time = System.currentTimeMillis();CountService jdkProxy = createJdkDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create JDK Proxy: " + time + " ms");time = System.currentTimeMillis();CountService cglibProxy = createCglibDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create CGLIB Proxy: " + time + " ms");time = System.currentTimeMillis();CountService javassistProxy = createJavassistDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create JAVAASSIST Proxy: " + time + " ms");time = System.currentTimeMillis();CountService javassistBytecodeProxy = createJavassistBytecodeDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create JAVAASSIST Bytecode Proxy: " + time + " ms");time = System.currentTimeMillis();CountService asmBytecodeProxy = createAsmBytecodeDynamicProxy(delegate);time = System.currentTimeMillis() - time;System.out.println("Create ASM Proxy: " + time + " ms");System.out.println("================");for (int i = 0; i < 3; i++) {test(jdkProxy, "Run JDK Proxy: ");test(cglibProxy, "Run CGLIB Proxy: ");test(javassistProxy, "Run JAVAASSIST Proxy: ");test(javassistBytecodeProxy, "Run JAVAASSIST Bytecode Proxy: ");test(asmBytecodeProxy, "Run ASM Bytecode Proxy: ");System.out.println("----------------");}}private static void test(CountService service, String label)throws Exception {service.count(); // warm upint count = 50000000;long time = System.currentTimeMillis();for (int i = 0; i < count; i++) {service.count();}time = System.currentTimeMillis() - time;System.out.println(label + time + " ms, " + new DecimalFormat().format(count / time * 1000) + " t/s");}private static CountService createJdkDynamicProxy(final CountService delegate) {CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{CountService.class}, new JdkHandler(delegate));return jdkProxy;}private static class JdkHandler implements InvocationHandler {final Object delegate;JdkHandler(Object delegate) {this.delegate = delegate;}public Object invoke(Object object, Method method, Object[] objects)throws Throwable {return method.invoke(delegate, objects);}}private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {Enhancer enhancer = new Enhancer();enhancer.setCallback(new CglibInterceptor(delegate));enhancer.setInterfaces(new Class[]{CountService.class});CountService cglibProxy = (CountService) enhancer.create();return cglibProxy;}private static class CglibInterceptor implements MethodInterceptor {final Object delegate;CglibInterceptor(Object delegate) {this.delegate = delegate;}public Object intercept(Object object, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable {return methodProxy.invoke(delegate, objects);}}private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setInterfaces(new Class[]{CountService.class});Class<?> proxyClass = proxyFactory.createClass();CountService javassistProxy = (CountService) proxyClass.newInstance();((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));return javassistProxy;}private static class JavaAssitInterceptor implements MethodHandler {final Object delegate;JavaAssitInterceptor(Object delegate) {this.delegate = delegate;}public Object invoke(Object self, Method m, Method proceed,Object[] args) throws Throwable {return m.invoke(delegate, args);}}private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {ClassPool mPool = new ClassPool(true);CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");mCtc.addInterface(mPool.get(CountService.class.getName()));mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));Class<?> pc = mCtc.toClass();CountService bytecodeProxy = (CountService) pc.newInstance();Field filed = bytecodeProxy.getClass().getField("delegate");filed.set(bytecodeProxy, delegate);return bytecodeProxy;}private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);String className = CountService.class.getName() + "AsmProxy";String classPath = className.replace('.', '/');String interfacePath = CountService.class.getName().replace('.', '/');classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[]{interfacePath});MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);initVisitor.visitCode();initVisitor.visitVarInsn(Opcodes.ALOAD, 0);initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");initVisitor.visitInsn(Opcodes.RETURN);initVisitor.visitMaxs(0, 0);initVisitor.visitEnd();FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);fieldVisitor.visitEnd();MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);methodVisitor.visitCode();methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");methodVisitor.visitInsn(Opcodes.IRETURN);methodVisitor.visitMaxs(0, 0);methodVisitor.visitEnd();classWriter.visitEnd();byte[] code = classWriter.toByteArray();CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();Field filed = bytecodeProxy.getClass().getField("delegate");filed.set(bytecodeProxy, delegate);return bytecodeProxy;}private static class ByteArrayClassLoader extends ClassLoader {public ByteArrayClassLoader() {super(ByteArrayClassLoader.class.getClassLoader());}public synchronized Class<?> getClass(String name, byte[] code) {if (name == null) {throw new IllegalArgumentException("");}return defineClass(name, code, 0, code.length);}}}

2、测试结果

Create JDK Proxy: 9 ms
Create CGLIB Proxy: 149 ms
Create JAVAASSIST Proxy: 115 ms
Create JAVAASSIST Bytecode Proxy: 58 ms
Create ASM Proxy: 1 ms
================
Run JDK Proxy: 479 ms, 104,384,000 t/s
Run CGLIB Proxy: 541 ms, 92,421,000 t/s
Run JAVAASSIST Proxy: 754 ms, 66,312,000 t/s
Run JAVAASSIST Bytecode Proxy: 194 ms, 257,731,000 t/s
Run ASM Bytecode Proxy: 202 ms, 247,524,000 t/s
----------------
Run JDK Proxy: 404 ms, 123,762,000 t/s
Run CGLIB Proxy: 325 ms, 153,846,000 t/s
Run JAVAASSIST Proxy: 681 ms, 73,421,000 t/s
Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s
Run ASM Bytecode Proxy: 180 ms, 277,777,000 t/s
----------------
Run JDK Proxy: 381 ms, 131,233,000 t/s
Run CGLIB Proxy: 339 ms, 147,492,000 t/s
Run JAVAASSIST Proxy: 674 ms, 74,183,000 t/s
Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s
Run ASM Bytecode Proxy: 181 ms, 276,243,000 t/s
----------------

资料:

动态代理方案性能对比 - 梁飞的博客 - ITeye博客

GitHub - wwadge/bonecp: BoneCP is a Java JDBC connection pool implementation that is tuned for high performance by minimizing lock contention to give greater throughput for your applications. It beats older connection pools such as C3P0 and DBCP but SHOULD NOW BE CONSIDERED DEPRECATED in favour of HikariCP.

GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection pool at last.

JDK动态代理与CGLib动态代理相关问题_程序员面试经验分享的博客-CSDN博客

02Hikari源码解析之ConcurrentBag、FastList分析_concurrentbag解析_一直打铁的博客-CSDN博客

数据库连接池性能比对(hikari druid c3p0 dbcp jdbc)_c3p0和hikari那个好_把酒问天的博客-CSDN博客

https://www.cnblogs.com/flyingeagle/articles/7102282.html

使用Javassist来动态创建,修改和代理类 - 算法之名的个人空间 - OSCHINA - 中文开源技术交流社区


http://chatgpt.dhexx.cn/article/yEQfibhX.shtml

相关文章

HikariCP配置手册

必须配置 1、dataSourceClassName或者jdbcUrl &#xff08;二选一&#xff09; 在Springboot 自动装配&#xff0c;或使用Mysql DataSource 情况下 请使用jdbcUrl。其他情况&#xff0c;HikariCP建议使用DataSourceClassName dataSourceClassName &#xff1a;意思是JDBC驱动…

HikariCP 创建连接

PoolEntry PoolEntry 是 HikariCP 中对数据库物理连接的封装。 那我们现在探索问题的关键点就是&#xff1a; PoolEntry.connection 是如何创建的&#xff1b;连接是何时创建的&#xff1b; 我们先看下 HikariCP 中数据源、连接、连接池之间的关系。 创建连接 连接池的初…

hikaricp mysql_配置HikariCP连接池

配置HikariCP连接池 数据库配置 spring.datasource.driver-class-namecom.mysql.jdbc.Driver 指定 spring 连接数据源驱动 spring.datasource.typecom.zaxxer.hikari.HikariDataSource 指定 spring 连接数据源类型 spring.datasource.urljdbc:mysql:///dbgoods?serverTimezone…

HikariCP连接池

背景 自己在写点代码的时候之前pom文件的内容都是从前一个项目中复制的&#xff0c;并没有认真分析一下各种依赖的关系&#xff0c;有时候重复的问题会出现好多次&#xff0c;比如说连接数据库报错&#xff0c;有时候配置没有问题&#xff0c;可能是依赖的jar包的问题&#xf…

HikariCP数据库连接池详解

HikariCP数据库连接池详解 1. 数据库连接池概述2. 为什么需要连接池3. HikariCP概述4. HikariCP特点5. HikariCP配置6. HikariCP案例验证 1. 数据库连接池概述 连接池是一种常用的技术&#xff0c;为什么需要连接池呢&#xff1f; 这个需要从TCP说起。假如我们的服务器跟数据库…

hikaricp mysql_HikariCP数据库连接池

摘要: 原创出处 hacpai.com/article/1582096971127 「jianzh5」欢迎转载&#xff0c;保留摘要&#xff0c;谢谢&#xff01; 什么是数据库连接池&#xff1a; 连接池是一种常用的技术&#xff0c;为什么需要连接池呢&#xff1f;这个需要从TCP说起。假如我们的服务器跟数据库没…

hikaricp使用

hikaricp数据库连接池是目前很流行的数据源 1.需要引入mysql的驱动、hikaricp依赖 <properties><java.version>1.8</java.version><hikari.version>2.7.9</hikari.version><mysql.version>8.0.13</mysql.version></properties&g…

自定义HikariCP连接池

文章目录 一、简介1、概述2、地址 二、配置参数1、Hikari原生参数2、Springboot中参数 三、springboot中使用四、自定义数据源1、各模块2、完整代码3、多数据源 五、多数据源dynamic中使用1、简介2、引入依赖3、参数配置 六、XMind整理 一、简介 1、概述 官方解释&#xff1a…

HikariCP源码分析

文章目录 1. 基本用法1.1 添加依赖1.2 创建DataSource1.3 获取连接 2. 源码分析2.1 API2.2 Pool2.2.1 获取连接2.2.2 添加连接2.2.3 维护连接 2.3 metrics2.3.1 dropwizard2.3.2 prometheus 3. 最佳实践 HikariCP是一个快速&#xff0c;简单可靠的JDBC连接池&#xff0c;Spring…

hikaricp mysql_HikariCP

软件简介 HikariCP 是一个高性能的 JDBC 连接池组件。下图是性能的比较测试结果&#xff1a; 使用方法&#xff1a; HikariConfig config new HikariConfig(); config.setMaximumPoolSize(100); config.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlData…

数据库连接池HikariCP

HikariCP 现在已经有很多公司在使用HikariCP了&#xff0c;HikariCP还成为了SpringBoot默认的连接池&#xff0c;伴随着SpringBoot和微服务&#xff0c;HikariCP 必将迎来广泛的普及。 下面带大家从源码角度分析一下HikariCP为什么能够被Spring Boot 青睐&#xff0c;文章目录…

HikariCP 了解一下

作者 | 黄永灿 后端开发攻城狮&#xff0c;关注服务端技术与性能优化。 前言 在我们的工作中&#xff0c;免不了要和数据库打交道&#xff0c;而要想和数据库打好交道&#xff0c;选择一款合适的数据库连接池就至关重要&#xff0c;目前业界广为人知的数据库连接池有 Tomcat JD…

离线数仓-03-数仓系统搭建(ODS,DIM,DWD,DWS,ADS)

文章目录 数仓分层为什么要分层数据运营层&#xff1a;ODS&#xff08;Operational Data Store&#xff09;数据仓库层&#xff1a;DW&#xff08;Data Warehouse&#xff09;维表层&#xff1a;DIM&#xff08;Dimension&#xff09;数据明细层&#xff1a;DWD&#xff08;Dat…

数仓数据分层(ODS DWD DWS ADS)换个角度看

数仓数据分层简介 1. 背景 数仓是什么, 其实就是存储数据,体现历史变化的一个数据仓库. 因为互联网时代到来,基于数据量的大小,分为了传统数仓和现代数仓.传统数仓,使用传统的关系型数据库进行数据存储,因为关系型数据库本身可以使用SQL以及函数等做数据分析.所以把数据存储和…

湖仓一体电商项目(十):业务实现之编写写入DWD层业务代码

文章目录 业务实现之编写写入DWD层业务代码 一、代码编写

【实时数仓】CDC简介、实现DWD层业务数据的处理(主要任务、接收kafka数据、动态分流*****)

文章目录 一 CDC简介1 什么是CDC2 CDC的种类3 Flink-CDC 二 准备业务数据-DWD层1 主要任务&#xff08;1&#xff09;接收Kafka数据&#xff0c;过滤空值数据&#xff08;2&#xff09;实现动态分流功能&#xff08;3&#xff09;把分好的流保存到对应表、主题中 2 接收Kafka数…

数仓开发之DWD层(二)

目录 三&#xff1a;流量域用户跳出事务事实表 3.1 主要任务 3.2 思路分析 3.3 图解 3.4 代码 四&#xff1a;交易域加购事务事实表 4.1 主要任务 4.2 思路分析 4.3 图解 4.4 代码 三&#xff1a;流量域用户跳出事务事实表 3.1 主要任务 过滤用户跳出明细数据。 3.2 思…

电商数仓(dwd 层)

一、dwd 层介绍 1、对用户行为数据解析。 2、对核心数据进行判空过滤。 3、对业务数据采用维度模型重新建模&#xff0c;即维度退化。 二、dwd 层用户行为数据 2.1 用户行为启动表 dwd_start_log 1、数据来源 ods_start_log -> dwd_start_log 2、表的创建 drop table…

详解数仓中的数据分层:ODS、DWD、DWM、DWS、ADS

何为数仓DW Data warehouse(可简写为DW或者DWH)数据仓库,是在数据库已经大量存在的情况下,它是一整套包括了etl、调度、建模在内的完整的理论体系。 数据仓库的方案建设的目的,是为前端查询和分析作为基础,主要应用于OLAP(on-line Analytical Processing),支持复杂的分析…