Java架构师—HikariCP数据源与MyBatis整合

Java架构师:HikariCP与MyBatis高效整合实战
本文详细介绍了Java架构师如何整合高效数据源HikariCP与MyBatis,包括HikariCP的特性、微优化,以及两者在SpringBoot中的整合步骤。同时,文章还探讨了MyBatis逆向生成工具的使用和SpringBoot整合mybatis-pagehelper进行分页操作的方法。

前言

       HikariCP数据源简介;数据层HikariCP与MyBatis整合;MyBatis逆向生成工具;SpringBoot整合mybatis-pagehelper。




一、HikariCP数据源

1.1 简述

       HikariCP:光中它更快。嗨·卡·里 [嗨·卡··lē](产地:日语):光;射线。
       SpingBoot由1.X升级为2.X,默认的数据源发生更改,选用HikariCP作为默认数据源。
       快速、简单、可靠。HikariCP 是一个“零开销”生产就绪的 JDBC 连接池。大约130Kb,库非常轻。在这里https://github.com/brettwooldridge/HikariCP了解下如何做到这一点。
       与其他 JDBC 操作相比,操作数很少。大量的性能提升来自于包装 、等的“委托”的优化。getConnection()ConnectionStatement

1.2 微优化

       HikariCP包含许多微观优化,这些优化本身几乎无法测量,但共同提高整体性能。其中一些优化是在数百万次调用中摊销的几分之一毫秒来衡量的。

  • ArrayList

       一个不平凡的(性能方面)优化是:不再使用ArrayList在ConnectionProxy用于跟踪开放实例Statement的实例。当关闭Statement时,必须将其从此Connection中删除,关闭时,必须迭代集合并关闭所有打开的Statement实例,最后必须清除该集合。ArrayList用于通用用途,在每次调用get(int index)时执行范围检查。但是,由于可以提供有关范围的保证,因此此检查只是开销。
       此外,该remove(Object)实现从头到尾执行扫描,但是JDBC编程中的常见模式是在使用后立即关闭语句,或者以相反的打开顺序关闭语句。对于这些情况,从尾部开始的扫描将表现得更好。因此,ArrayList被替换为自定义类FastList,该类消除了范围检查并执行从尾到头的删除扫描。

  • ConcurrentBag

       HikariCP包含一个名为ProcurrentBag的自定义无锁集合。这个想法是从C# .NET ConcurrentBag类中借用的,但内部实现完全不同。ConcurrentBag 提供:

  1. 无锁设计
  2. 线程本地缓存
  3. 队列窃取
  4. 直接交接优化

…从而实现高度并发、极低的延迟,并最大限度地减少错误共享的发生。

  • 调用:vsinvokevirtual vs invokestatic

       为了生成连接、语句和 ResultSet 实例的代理,HikariCP 最初使用单例工厂,在 静态字段 (PROXY_FACTORY) 的情况下保存ConnectionProxy。

       有十几种类似于以下内容的方法:

public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
    return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}

       使用原始的单例工厂,生成的字节码如下所示:

    public final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
    flags: ACC_PRIVATE, ACC_FINAL
    Code:
      stack=5, locals=3, args_size=3
         0: getstatic     #59                 // Field PROXY_FACTORY:Lcom/zaxxer/hikari/proxy/ProxyFactory;
         3: aload_0
         4: aload_0
         5: getfield      #3                  // Field delegate:Ljava/sql/Connection;
         8: aload_1
         9: aload_2
        10: invokeinterface #74,  3           // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
        15: invokevirtual #69                 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
        18: return

       可以看到,首先有一个getstatic调用来获取静态字段PROXY_FACTORY的值,以及(最后)invokevirtual通过getProxyPreparedStatement()对实例的ProxyFactory调用。
       删除了单例工厂(由Javassist生成),并将其替换为具有static方法的最终类(其主体由Javassist生成)。Java 代码变为:

    public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
    {
        return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
    }

       其中getProxyPreparedStatement()是在static类ProxyFactory中定义的方法。生成的字节码为:

    private final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
    flags: ACC_PRIVATE, ACC_FINAL
    Code:
      stack=4, locals=3, args_size=3
         0: aload_0
         1: aload_0
         2: getfield      #3                  // Field delegate:Ljava/sql/Connection;
         5: aload_1
         6: aload_2
         7: invokeinterface #72,  3           // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
        12: invokestatic  #67                 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
        15: areturn

这里有三点需要注意:

  1. getstatic呼叫已消失;
  2. 该invokevirtual调用将替换为更易于由 JVM 优化的invokestatic调用;
  3. 最后,乍一看可能没有注意到的是堆栈大小从 5 个元素减少到 4 个元素。这是因为在invokevirtual堆栈上有 ProxyFactory 实例的隐式传递(i.e this),并且在调用getProxyPreparedStatement()时从堆栈中附加(unseen)该值的 pop。

总而言之,此更改从堆栈中删除了静态字段访问、推送和弹出,并使调用更易于 JIT 优化,因为调用站点保证不会更改。

二、数据层HikariCP与MyBatis整合

2.1 pom中引入数据源驱动与mybatis依赖

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.41</version>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

2.2 在yml中配置内置tomcat、数据源和mybatis

# web访问端口号 约定:8088
server:
  port: 8088
  tomcat:
    uri-encoding: UTF-8
    max-http-post-size: 80KB
############################################################
# web 配置数据源信息
# datasource 数据源的相关配置
# type 数据源类型:HikariCP
# driver-class-name mysql驱动
# type 数据源类型:HikariCP
# connection-timeout 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQ
# minimum-idle 最小连接数
# maximum-pool-size 最大连接数
# auto-commit 自动提交
# idle-timeout 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
# pool-name 连接池名字
# max-lifetime 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟
# connection-test-query 测试sql
############################################################
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3318/foodie-shop-dev?characterEncoding=UTF-8&useSSL=false&useUnicode=true&serverTimezone=UTC
    username: root
    password: 123456
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      connection-timeout: 30000
      minimum-idle: 5
      maximum-pool-size: 20
      auto-commit: true
      idle-timeout: 600000
      pool-name: DateSourceHikariCP
      max-lifetime: 1800000
      connection-test-query: SELECT 1
############################################################
# mybatis 配置
# type-aliases-package 所有POJO类所在包路径
# mapper-locations mapper映射文件
############################################################
mybatis:
  type-aliases-package: com.imooc.pojo
  mapper-locations: classpath:mapper/*.xml

2.3 数据库连接数详解

       HikariCP默认maximum-pool-size最大连接数是10,最小连接数minimum-idle未设置,默认与最大连接数一致。
       maximum-pool-size根据服务器配置设置,如果服务器内存4G设置成10,8G设置成20。
设置连接数的两种方案:

  • 最大连接数与最小连接数保持一致,都为10或20。
  • 最小连接数设置为5或10,最大连接数设置为10或20。

三、MyBatis逆向生成工具

       mybatis-generator工具用于逆向生成pojo实体类,mapper.xml以及mapper.xml所对应的Java接口的映射类。工具仓库地址:https://github.com/jxtx92/mybatis-generator
MyBatis逆向生成工具
在generatorConfig.xml中:

  • 设置通用mapper所在目录
  • 对应生成的pojo所在包
  • 数据库表
    执行GeneratorDisplay的main方法即可根据数据库表逆向生成所需文件。注意:如果多次生成,要删除mapper.xml中重复的部分。
    在自己项目中只要保持文件路径一致,将生成的文件拷贝到自己的项目中,并引入此工具。

3.1 在pom中引入通用mapper工具

        <!-- 通用mapper逆向工具 -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>

3.2 在yml中引入通用mapper配置

############################################################
# mybatis mapper 配置
# mappers 通用Mapper配置
# not-empty 在进行数据库操作的时候,判断表达式 username != null,是否追加 username != ''
# identity 数据库方言
############################################################
mapper:
  mappers: com.imooc.my.mapper.MyMapper
  not-empty: false
  identity: MYSQL

3.3 引入MyMapper接口类

package com.imooc.my.mapper;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;
/**
* 继承自己的MyMapper
*/
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
}

3.4 通用Mapper接口所封装的常用方法

  1. MyMapper所继承的父类,如:
 interface MyMapper<T> extends Mapper<T>, MySqlMapper<T>

       这里有两个父类, Mapper<T>MySq lMapper<T>,可以打开 MySqLMapper<T>看一下:

 interface MySqLMapper<T> extends InsertListMapper<T>,InsertUseGeneratedKeysMapper<T>{}

       这里面又继承了了两个mapper,从类名上可以看得出来,是用于操作数据库的,这两个类里又分别包含了如下方法,简单归类一下:

方法名操作备注
insertList ( list )数据批量插入主键须自增
insertUseGeneratedKeys(record)插入表数据主键须自增

       很明显,在传统 Java Web 开发,这两个方法使用是没有问题的,但是我们的数据库表主键设计肯定是全局唯一的,所以不可能使用自增长id(如何设计全局唯一分布式主键?),所以这两个方法在开发中是不会使用的!

  1. 再来看一下 Mapper<T>中所继承的父类,如下:
 interface Mapper<T> extends BaseMapper<T>, Examp leMapper<T>, RowBoundsMapper<T>,

       分别来看一下各个父类中的方法有些啥?

  • BaseMapper<T>
方法操作
BaseSelectMapperT selectOne(T record)根据实体类中的属性查询表数据,返回单个实体
List select(T record)根据实体类中的属性查询表数据,返回符合条件的list
List selectAll()返回该表所有记录
int selectCount(T record)根据条件查询记录数
T selectByPrimaryKey(Object key)根据主键查询单条记录
boolean existsWithPrimaryKey(Object key)查询主键是否存在,返回 true 或 false
BaselnsertMapperint insert(T record)插入一条记录,属性为空也会保存
int insertSelective(T record)插入一条记录,属性为空不保存,会使用默认值
BaseUpdateMapperint updateByPrimaryKey(T record)根据实体类更新数据库,属性有 nul 会覆盖原记录
int udateByPrimaryKeySelective(T record)根据实体类更新数据库,属性有 null 改属性会忽略
BaseDeleteMapperint delete(T record)根据实体类中属性多条件删除记录
int deleteByPrimaryKey(Object key)根据主键删除记录
  • Examp leMapper<T>, Example 类是用于提供给用户实现自定义条件的,也就是 where 条件,主要方法见如下表格:
方法
SelectByExampleMapperList selectByExample(Object example)
selectOneByExample(Object example)T SelectOneByExampleMapper
SelectCountByExampleMapperint selectCountByExample(Object example)
DeleteByExampleMapperint deleteByExample(Object example)
SelectCountByExampleMapperint selectCountByExample(Object example)
UpdateByExampleMapperint updateByExample(T record,@ Param (“example”) Object example)
UpdateByExampleSelectiveMapperint updateByExampleSelective(T record, Object example)
  • RowBoundsMapper<T>,这个是用于做分页的,可以使用 page-helper 组件来替代分页实现。

       总结:通用 mapper 所提供的 CRUD 方法对单表操作,大大提高开发效率,当然复杂的多表操作还是需要在 mapper.xmI 中自己去编写 sqI 代码实现。

四、SpringBoot整合mybatis-pagehelper

4.1 引入分页插件依赖

        <!--pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

4.2 配置yml

############################################################
# 分页插件配置
# helper-dialect 数据库方言
# support-methods-arguments 是否支持参数传入
############################################################
pagehelper:
  helper-dialect: mysql
  support-methods-arguments: true

4.3 使用分页插件

       在查询前使用分页插件,原理:统一拦截sql,为其提供分页功能。

        /**
         * page: 第几页
         * pageSize: 每页显示条数
         */
        PageHelper.startPage(page, pageSize);

4.4 分页数据封装到 PagedGridResult.java 传给前端

        PageInfo<?> pageList = new PageInfo<>(list);
        PagedGridResult grid = new PagedGridResult();
        grid.setPage(page);
        grid.setRows(list);
        grid.setTotal(pageList.getPages());
        grid.setRecords(pageList.getTotal());

总结

HikariCP数据源简介;数据层HikariCP与MyBatis整合;MyBatis逆向生成工具;SpringBoot整合mybatis-pagehelper。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值