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

前言

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


文章目录

  • 前言
  • 一、HikariCP数据源
    • 1.1 简述
    • 1.2 微优化
  • 二、数据层HikariCP与MyBatis整合
    • 2.1 pom中引入数据源驱动与mybatis依赖
    • 2.2 在yml中配置内置tomcat、数据源和mybatis
    • 2.3 数据库连接数详解
  • 三、MyBatis逆向生成工具
    • 3.1 在pom中引入通用mapper工具
    • 3.2 在yml中引入通用mapper配置
    • 3.3 引入MyMapper接口类
    • 3.4 通用Mapper接口所封装的常用方法
  • 四、SpringBoot整合mybatis-pagehelper
    • 4.1 引入分页插件依赖
    • 4.2 配置yml
    • 4.3 使用分页插件
    • 4.4 分页数据封装到 PagedGridResult.java 传给前端
  • 总结


一、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依赖

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.41version>
        dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.0version>
        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
Java架构师—HikariCP数据源与MyBatis整合_第1张图片
在generatorConfig.xml中:

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

3.1 在pom中引入通用mapper工具

        
        <dependency>
            <groupId>tk.mybatisgroupId>
            <artifactId>mapper-spring-boot-starterartifactId>
            <version>2.1.5version>
        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>

       这里有两个父类, MapperMySq lMapper,可以打开 MySqLMapper看一下:

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

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

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

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

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

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

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

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

四、SpringBoot整合mybatis-pagehelper

4.1 引入分页插件依赖

        
        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelper-spring-boot-starterartifactId>
            <version>1.2.12version>
        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。

你可能感兴趣的:(Java架构师之路,java,架构,数据库,database)