GVKun编程网logo

orika 实现对象复制(实现复制对象的命令是)

20

在本文中,我们将给您介绍关于orika实现对象复制的详细内容,并且为您解答实现复制对象的命令是的相关问题,此外,我们还将为您提供关于03-SpringBoot工程下如何实现对HikariCP连接池的整

在本文中,我们将给您介绍关于orika 实现对象复制的详细内容,并且为您解答实现复制对象的命令是的相关问题,此外,我们还将为您提供关于03-SpringBoot工程下如何实现对HikariCP连接池的整合?【转】、04-SpringBoot工程下如何实现对HikariCP连接池的整合、04-SpringBoot工程下如何实现对HikariCP连接池的整合?、04-SpringBoot工程下如何实现对HikariCP链接池的整合?的知识。

本文目录一览:

orika 实现对象复制(实现复制对象的命令是)

orika 实现对象复制(实现复制对象的命令是)

1、新建 maven 工程 orika-demo,引入 orika 依赖,其中 pom.xml 如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>orika.demo</groupId>
    <artifactId>orika-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>ma.glasnost.orika</groupId>
            <artifactId>orika-core</artifactId>
            <version>1.4.1</version>
        </dependency>
    </dependencies>
</project>

2、测试场景一:类名不同,属性完全相同的复制

public class UserDTO {
    private String name;
    private String sex;

    public UserDTO(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "UserDTO{" +
                "name=''" + name + ''\'''' +
                ", sex=''" + sex + ''\'''' +
                ''}'';
    }
}
public class UserDO {
    private String name;
    private String sex;

    public UserDO(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "UserDO{" +
                "name=''" + name + ''\'''' +
                ", sex=''" + sex + ''\'''' +
                ''}'';
    }
}
public class Test {
    public static void main(String[] args){
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        UserDTO userDTO = new UserDTO("张三","男");
        UserDO userDO = mapperFactory.getMapperFacade().map(userDTO,UserDO.class);
        System.out.println(userDO.toString());
    }
}
UserDO{name=''张三'', sex=''男''}

3、测试场景二:类名不同,部分属性不同的复制

public class UserDTO {
    private String name;
    private String sex;

    public UserDTO(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "UserDTO{" +
                "name=''" + name + ''\'''' +
                ", sex=''" + sex + ''\'''' +
                ''}'';
    }
}
public class NewUserDTO {
    private String name;
    private String newSex;

    public NewUserDTO(String name, String newSex) {
        this.name = name;
        this.newSex = newSex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNewSex() {
        return newSex;
    }

    public void setNewSex(String newSex) {
        this.newSex = newSex;
    }

    @Override
    public String toString() {
        return "NewUserDTO{" +
                "name=''" + name + ''\'''' +
                ", newSex=''" + newSex + ''\'''' +
                ''}'';
    }
}
public class Test {
    public static void main(String[] args){
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        UserDTO userDTO = new UserDTO("张三","男");
        //如果不使用byDefault,则只会拷贝被field配置的属性,最后使用register使其生效
        mapperFactory.classMap(UserDTO.class,NewUserDTO.class).field("sex", "newSex").byDefault().register();
        NewUserDTO newUserDTO = mapperFactory.getMapperFacade().map(userDTO,NewUserDTO.class);
        System.out.println(newUserDTO.toString());
    }
}
NewUserDTO{name=''张三'', newSex=''男''}

4、测试场景三:集合复制,集合内类的属性名相同

public class UserDTO {
    private String name;
    private String sex;

    public UserDTO(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "UserDTO{" +
                "name=''" + name + ''\'''' +
                ", sex=''" + sex + ''\'''' +
                ''}'';
    }
}
public class UserDO {
    private String name;
    private String sex;

    public UserDO(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "UserDO{" +
                "name=''" + name + ''\'''' +
                ", sex=''" + sex + ''\'''' +
                ''}'';
    }
}
public class Test {
    public static void main(String[] args){
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        List<UserDTO> userDTOList = new ArrayList<UserDTO>();
        userDTOList.add(new UserDTO("张三","男"));
        userDTOList.add(new UserDTO("李英","女"));
        List<UserDO> userDOList = mapperFactory.getMapperFacade().mapAsList(userDTOList,UserDO.class);
        System.out.println(userDOList.toString());
    }
}
[UserDO{name=''张三'', sex=''男''}, UserDO{name=''李英'', sex=''女''}]

5、测试场景四:集合复制,集合内类的属性名部分不同

public class UserDTO {
    private String name;
    private String sex;

    public UserDTO(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "UserDTO{" +
                "name=''" + name + ''\'''' +
                ", sex=''" + sex + ''\'''' +
                ''}'';
    }
}
public class NewUserDTO {
    private String name;
    private String newSex;

    public NewUserDTO(String name, String newSex) {
        this.name = name;
        this.newSex = newSex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNewSex() {
        return newSex;
    }

    public void setNewSex(String newSex) {
        this.newSex = newSex;
    }

    @Override
    public String toString() {
        return "NewUserDTO{" +
                "name=''" + name + ''\'''' +
                ", newSex=''" + newSex + ''\'''' +
                ''}'';
    }
}
public class Test {
    public static void main(String[] args){
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        List<UserDTO> userDTOList = new ArrayList<UserDTO>();
        userDTOList.add(new UserDTO("张三","男"));
        userDTOList.add(new UserDTO("李英","女"));
        mapperFactory.classMap(UserDTO.class, NewUserDTO.class).field("sex","newSex").byDefault().register();
        List<NewUserDTO> newUserDTOList = mapperFactory.getMapperFacade().mapAsList(userDTOList,NewUserDTO.class);
        System.out.println(newUserDTOList.toString());
    }
}
[NewUserDTO{name=''张三'', newSex=''男''}, NewUserDTO{name=''李英'', newSex=''女''}]

6、测试场景五:类复制,类里面包含集合属性,类的属性部分不同,集合属性名字相同,但是集合里面的类属性部分不同

public class UserListDTO {
    private String listName;
    private List<UserDTO> userList;

    public String getListName() {
        return listName;
    }

    public void setListName(String listName) {
        this.listName = listName;
    }

    public List<UserDTO> getUserList() {
        return userList;
    }

    public void setUserList(List<UserDTO> userList) {
        this.userList = userList;
    }

    @Override
    public String toString() {
        return "UserListDTO{" +
                "listName=''" + listName + ''\'''' +
                ", userList=" + userList +
                ''}'';
    }
}
public class NewUserListDTO {
    private String newListName;
    private List<NewUserDTO> userList;

    public String getNewListName() {
        return newListName;
    }

    public void setNewListName(String newListName) {
        this.newListName = newListName;
    }

    public List<NewUserDTO> getUserList() {
        return userList;
    }

    public void setUserList(List<NewUserDTO> userList) {
        this.userList = userList;
    }

    @Override
    public String toString() {
        return "NewUserListDTO{" +
                "newListName=''" + newListName + ''\'''' +
                ", userList=" + userList +
                ''}'';
    }
}
public class Test {
    public static void main(String[] args){
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        List<UserDTO> userDTOList = new ArrayList<UserDTO>();
        userDTOList.add(new UserDTO("张三","男"));
        userDTOList.add(new UserDTO("李英","女"));
        UserListDTO userListDTO = new UserListDTO();
        userListDTO.setListName("list001");
        userListDTO.setUserList(userDTOList);
        mapperFactory.classMap(UserDTO.class, NewUserDTO.class).field("sex","newSex").byDefault().register();
        mapperFactory.classMap(UserListDTO.class, NewUserListDTO.class).field("listName","newListName").byDefault().register();
        NewUserListDTO newUserListDTO = mapperFactory.getMapperFacade().map(userListDTO,NewUserListDTO.class);
        System.out.println(newUserListDTO.toString());
    }
}
NewUserListDTO{newListName=''list001'', userList=[NewUserDTO{name=''张三'', newSex=''男''}, NewUserDTO{name=''李英'', newSex=''女''}]}

 

03-SpringBoot工程下如何实现对HikariCP连接池的整合?【转】

03-SpringBoot工程下如何实现对HikariCP连接池的整合?【转】

池化思想分析

池化思想是我们项目开发过程中的一种非常重要的思想,如整数池,字符串池,对象池、连接池、线程池等都是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。例如Integer对象的内部池应用,代码如下:

package com.cy.java.pool;
public class TestInteger01 {
    public static void main(String[] args) {
        Integer n1=100;//Integer.valueOf(100) 编译时优化
        Integer n2=100;
        Integer n3=200;
        Integer n4=200;//池中没有则new Integer(200)
        System.out.println(n1==n2);//true
        System.out.println(n3==n4);//false 
    }
    
}

数据库连接池简介

背景分析

目开发过程中应用程序与数据库交互时,“获得连接”或“释放连接”是非常消耗系统资源的两个过程,频繁地进行数据库连接的建立和关闭会极大影响系统的性能,若多线程并发量很大,这样耗时的数据库连接就可能让系统变得卡顿。因为TCP连接的创建开支十分昂贵,并且数据库所能承载的TCP并发连接数也有限制,针对这种场景,数据库连接池应运而生。如下图所示:

image.png

思考:假如现在是让你去设计一个连接池,你会从什么角度进行设计?
第一:物理存储结构(基于什么结构去存储数据)
第二:基于什么算法从池中取连接?
第三:基于什么算法从池中移除连接?
第四:当池中没有连接时,基于什么方式处理连接请求?
第五:池是可以共享,我们需要考虑池在访问的时并发安全?

连接池原理分析

在系统初始化的时候,在内存中开辟一片空间,将一定数量的数据库连接作为对象存储在对象池里,并对外提供数据库连接的获取和归还方法。用户访问数据库时,并不是建立一个新的连接,而是从数据库连接池中取出一个已有的空闲连接对象;使用完毕归还后的连接也不会马上关闭,而是由数据库连接池统一管理回收,为下一次借用做好准备。如果由于高并发请求导致数据库连接池中的连接被借用完毕,其他线程就会等待,直到有连接被归还。整个过程中,连接并不会关闭,而是源源不断地循环使用,有借有还。数据库连接池还可以通过设置其参数来控制连接池中的初始连接数、连接的上下限数,以及每个连接的最大使用次数、最大空闲时间等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

Java中的连接池

Java官方,为了在应用程序中更好的应用连接池技术,定义了一套数据源规范,例如javax.sql.DataSource接口,基于这个接口,很多团队或个人创建了不同的连接池对象。然后我们的应用程序中通过耦合与DataSource接口,便可以方便的切换不同厂商的连接池。Java项目中通过连接池获取连接的一个基本过程,如下图所示:

image.png

在上图中,用户通过DataSource对象的getConnection()方法,获取一个连接。假如池中有连接,则直接将连接返回给用户。假如池中没有连接,则会调用Dirver(驱动,由数据库厂商进行实现)对象的connect方法从数据库获取,拿到连接以后,可以将连接在池中放一份,然后将连接返回给调用方。连接需求方再次需要连接时,可以从池中获取,用完以后再还给池对象。

数据库连接池在Java数据库相关中间件产品群中,应该算是底层最基础的一类产品,作为企业应用开发必不可少的组件,无数天才们为我们贡献了一个又一个的优秀产品,它们有的随时代发展,功成身退,有的则还在不断迭代,老而弥坚,更有新生代产品,或性能无敌,或功能全面。目前市场上常见的连接池有DBCP、C3P0,DRUID,HikariCP等。

SpringBoot工程下HikariCP整合测试

数据初始化

打开mysql控制台,然后按如下步骤执行goods.sql文件。
第一步:登录mysql。

mysql –uroot –proot

第二步:设置控制台编码方式。

set names utf8;

第三步:执行goods.sql文件(切记不要打开文件复制到mysql客户端运行)。

source d:/goods.sql

其中goods.sql文件内容如下:

drop database if exists dbgoods;
create database dbgoods default character set utf8;
use dbgoods;
create table tb_goods(
     id bigint primary key auto_increment,
     name varchar(100) not null,
     remark text,
     createdTime datetime not null
)engine=InnoDB;
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());

创建项目Module并添加相关依赖

第一步:基于IDEA创建项目Module,如图所示:

image.png

第二步:添加依赖
1) mysql数据库驱动依赖。

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

2) spring对象jdbc支持(此时会默认帮我们下载HiKariCP连接池)。

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

配置HikariCP连接池

打开application.properties配置文件,添加如下内容(必写)。

spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

hikariCP 其它额外配置(可选),代码如下(具体配置不清晰的可自行百度):

spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1 

HikariCP 连接池测试

单元测试API设计及应用分析,如图所示:

image.png

在项目中添加单元测试类及测试方法,代码如下:

package com.cy.pj.common.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class DataSourceTests {
    @Autowired
    private DataSource dataSource;
    @Test
    public void testConnection() throws Exception{
        System.out.println(dataSource.getConnection());
    }
}

在当前测试类中我们需要:

  • 掌握单元测试类、测试方法编写规范。
  • 理解DataSource的设计规范及规范的实现。
  • 分析在测试类中dataSource属性指向的对象是谁?
  • 分析在测试类中DataSource的实现类对象由谁创建和管理?
  • 思考基于DataSource接口获取连接的基本过程是怎样的?

测试BUG分析

  • 数据库不存在,如图所示:

image.png

  • 类编译错误,DataSource为javax.sql包中的类型,如图所示:

image.png

  • 连接错误:数据库连接不上,如图所示:

image.png

基于HikariCP实现JDBC操作(练习)

业务分析

基于HikariCP,借助JDBC技术访问商品库中的数据。

API架构设计

基于业务,进行API设计,如图所示:

image.png

业务时序图分析

基于业务需求,进行商品查询过程的的时序图设计,如图所示:

image.png

业务代码设计及实现

第一步:定义GoodsDao接口,例如:

package com.cy.pj.goods.dao;
import java.util.List;
import java.util.Map;
/**
 * 商品模块数据访问层接口
 */
public interface GoodsDao {
    /**
 * 查询所有商品信息,将每一行记录存储到一个map对象,然后将多个存储到list集合.
 */ List<Map<String,Object>> findGoods();
}

第二步:创建GoodsDao接口实现类,代码如下:

package com.cy.pj.goods.dao;
/**
 * 此对象为一个商品数据层访问对象,现在要求在此类中定义一个方法,这个方法基于JDBC从从数据库获取商品信息,并将其封装到map集合,要求一个行记录一个map对象(key为表中字段名,值为字段名对应的值),多个map存储到list集合. @Repository此注解通常用于描述数据层实现类对象,本质上就是一个特殊的@Component, 都是要交给spring框架管理的一个Bean对象
 */
@Repository
public class DefaultGoodsDao implements  GoodsDao{
       @Autowired
       private DataSource dataSource;//hikariCP
       /**查询商品信息,一行记录映射为内存中的一个map对象*/
       public List<Map<String,Object>> findGoods(){
           Connection conn=null;//java.sql.*
           Statement stmt=null;
           ResultSet rs=null;
           String sql="select * from tb_goods";
           //1.获取连接(从连接池获取)
           try {
               conn=dataSource.getConnection();
               //2.创建statement对象
               stmt=conn.createStatement();
               //3.发送sql
               rs=stmt.executeQuery(sql);
               //4.处理结果
               List<Map<String,Object>> list=new ArrayList<>();
               while(rs.next()){//循环一次取一行,一行记录映射为一个map对象
                  list.add( rowMap(rs));//将存储了一行记录的map对象再存储到list集合
               }
               return list;
           }catch (SQLException e){
               e.printStackTrace();
               throw new RuntimeException(e);//转换为非检查异常(编译时不检测的异常)
           }finally{
               //5. 释放资源
               close(rs,stmt,conn);
           }
       }

定义行映射方法

 private Map<String,Object> rowMap(ResultSet rs)throws SQLException{
           Map<String,Object> rowMap=new HashMap<>();
           //方法1映射
           //rowMap.put("id",rs.getInt("id"));
           //rowMap.put("name",rs.getString("name"));
           //rowMap.put("remark",rs.getString("remark"));
           //rowMap.put("createdTime",rs.getTimestamp("createdTime"));
           //方法2映射
           ResultSetMetaData rsmd=rs.getMetaData();//获取元数据(包括表中的字段名)
           int columnCount=rsmd.getColumnCount();//获取列的数量
           for(int i=0;i<columnCount;i++){
               rowMap.put(rsmd.getColumnLabel(i+1),rs.getObject(rsmd.getColumnLabel(i+1)));
               //getColumnLabel(i+1);获取表中字段名或字段名对应的别名
           }
           return rowMap;
       }

定义释放资源的方法

 private void close(ResultSet rs,Statement stmt,Connection conn){
           if(rs!=null)try{rs.close();}catch(Exception e){e.printStackTrace();}
           if(stmt!=null)try{stmt.close();}catch(Exception e){e.printStackTrace();}
           //这里的连接是返回到了池中
           if(conn!=null)try{conn.close();}catch(Exception e){e.printStackTrace();}
       }
}

测试代码的编写及运行

定义单元测试类,并对其查询过程进行单元测试,例如:

package com.cy.pj.goods.dao;

@SpringBootTest
public class GoodsDaoTests {

      @Autowired
      private GoodsDao goodsDao;

      @Test
      void testFindGoods(){
          List<Map<String,Object>> list= goodsDao.findGoods();
          for(Map<String,Object> map:list){
              System.out.println(map);
          }
      }

}

测试运行过程中的BUG分析

对测试过程中出现的问题进行记录,分析,总结.

总结(Summary)

总之,数据库连接池的为我们的项目开发及运行带来了很多优点,具体如下:

  • 资源重用更佳。

由于数据库连接得到复用,减少了大量创建和关闭连接带来的开销,也大大减少了内存碎片和数据库临时进程、线程的数量,使得整体系统的运行更加平稳。

  • 系统调优更简便。

使用了数据库连接池以后,由于资源重用,大大减少了频繁关闭连接的开销,大大降低了TIME_WAIT的出现频率。

  • 系统响应更快。

数据库连接池在应用初始化的过程中一般都会提前准备好一些数据库连接,业务请求可以直接使用已经创建的连接,而不需要等待创建连接的开销。初始化数据库连接配合资源重用,使得数据库连接池可以大大缩短系统整体响应时间。

  • 连接管理更灵活。

数据库连接池作为一款中间件,用户可以自行配置连接的最小数量、最大数量、最大空闲时间、获取连接超时间、心跳检测等。另外,用户也可以结合新的技术趋势,增加数据库连接池的动态配置、监控、故障演习等一系列实用的功能。

**个人理解总结:

类似享元模式。参考菜鸟网站介绍进一步学习理解。
享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
意图:运用共享技术有效地支持大量细粒度的对象。

主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。

如何解决:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

关键代码:用 HashMap 存储这些对象。

应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。

优点:大大减少对象的创建,降低系统的内存,使效率提高。

缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。

注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。

04-SpringBoot工程下如何实现对HikariCP连接池的整合

04-SpringBoot工程下如何实现对HikariCP连接池的整合

池化思想分析

池化思想是我们项目开发过程中的一种非常重要的思想,如整数池,字符串池,对象池、连接池、线程池等都是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。例如Integer对象的内部池应用,代码如下:

package com.cy.java.pool;
public class TestInteger01 {
    public static void main(String[] args) {
        Integer n1=100;//Integer.valueOf(100) 编译时优化
        Integer n2=100;
        Integer n3=200;
        Integer n4=200;//池中没有则new Integer(200)
        System.out.println(n1==n2);//true
        System.out.println(n3==n4);//false 
    }
    
}

数据库连接池简介

背景分析

目开发过程中应用程序与数据库交互时,“获得连接”或“释放连接”是非常消耗系统资源的两个过程,频繁地进行数据库连接的建立和关闭会极大影响系统的性能,若多线程并发量很大,这样耗时的数据库连接就可能让系统变得卡顿。因为TCP连接的创建开支十分昂贵,并且数据库所能承载的TCP并发连接数也有限制,针对这种场景,数据库连接池应运而生。如下图所示:

image.png

思考:假如现在是让你去设计一个连接池,你会从什么角度进行设计?
第一:物理存储结构(基于什么结构去存储数据)
第二:基于什么算法从池中取连接?
第三:基于什么算法从池中移除连接?
第四:当池中没有连接时,基于什么方式处理连接请求?
第五:池是可以共享,我们需要考虑池在访问的时并发安全?

连接池原理分析

在系统初始化的时候,在内存中开辟一片空间,将一定数量的数据库连接作为对象存储在对象池里,并对外提供数据库连接的获取和归还方法。用户访问数据库时,并不是建立一个新的连接,而是从数据库连接池中取出一个已有的空闲连接对象;使用完毕归还后的连接也不会马上关闭,而是由数据库连接池统一管理回收,为下一次借用做好准备。如果由于高并发请求导致数据库连接池中的连接被借用完毕,其他线程就会等待,直到有连接被归还。整个过程中,连接并不会关闭,而是源源不断地循环使用,有借有还。数据库连接池还可以通过设置其参数来控制连接池中的初始连接数、连接的上下限数,以及每个连接的最大使用次数、最大空闲时间等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

Java中的连接池

Java官方,为了在应用程序中更好的应用连接池技术,定义了一套数据源规范,例如javax.sql.DataSource接口,基于这个接口,很多团队或个人创建了不同的连接池对象。然后我们的应用程序中通过耦合与DataSource接口,便可以方便的切换不同厂商的连接池。Java项目中通过连接池获取连接的一个基本过程,如下图所示:

image.png

在上图中,用户通过DataSource对象的getConnection()方法,获取一个连接。假如池中有连接,则直接将连接返回给用户。假如池中没有连接,则会调用Dirver(驱动,由数据库厂商进行实现)对象的connect方法从数据库获取,拿到连接以后,可以将连接在池中放一份,然后将连接返回给调用方。连接需求方再次需要连接时,可以从池中获取,用完以后再还给池对象。

数据库连接池在Java数据库相关中间件产品群中,应该算是底层最基础的一类产品,作为企业应用开发必不可少的组件,无数天才们为我们贡献了一个又一个的优秀产品,它们有的随时代发展,功成身退,有的则还在不断迭代,老而弥坚,更有新生代产品,或性能无敌,或功能全面。目前市场上常见的连接池有DBCP、C3P0,DRUID,HikariCP等。

SpringBoot工程下HikariCP整合测试

数据初始化

打开mysql控制台,然后按如下步骤执行goods.sql文件。
第一步:登录mysql。

mysql –uroot –proot

第二步:设置控制台编码方式。

set names utf8;

第三步:执行goods.sql文件(切记不要打开文件复制到mysql客户端运行)。

source d:/goods.sql

其中goods.sql文件内容如下:

drop database if exists dbgoods;
create database dbgoods default character set utf8;
use dbgoods;
create table tb_goods(
     id bigint primary key auto_increment,
     name varchar(100) not null,
     remark text,
     createdTime datetime not null
)engine=InnoDB;
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());

创建项目Module并添加相关依赖

第一步:基于IDEA创建项目Module,如图所示:

image.png

第二步:添加依赖
1) mysql数据库驱动依赖。

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

2) spring对象jdbc支持(此时会默认帮我们下载HiKariCP连接池)。

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

配置HikariCP连接池

打开application.properties配置文件,添加如下内容(必写)。

spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

hikariCP 其它额外配置(可选),代码如下(具体配置不清晰的可自行百度):

spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1 

HikariCP 连接池测试

单元测试API设计及应用分析,如图所示:

image.png

在项目中添加单元测试类及测试方法,代码如下:

package com.cy.pj.common.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class DataSourceTests {
    @Autowired
    private DataSource dataSource;
    @Test
    public void testConnection() throws Exception{
        System.out.println(dataSource.getConnection());
    }
}

在当前测试类中我们需要:

  • 掌握单元测试类、测试方法编写规范。
  • 理解DataSource的设计规范及规范的实现。
  • 分析在测试类中dataSource属性指向的对象是谁?
  • 分析在测试类中DataSource的实现类对象由谁创建和管理?
  • 思考基于DataSource接口获取连接的基本过程是怎样的?

测试BUG分析

  • 数据库不存在,如图所示:

image.png

  • 类编译错误,DataSource为javax.sql包中的类型,如图所示:

image.png

  • 连接错误:数据库连接不上,如图所示:

image.png

基于HikariCP实现JDBC操作(练习)

业务分析

基于HikariCP,借助JDBC技术访问商品库中的数据。

API架构设计

基于业务,进行API设计,如图所示:

image.png

业务时序图分析

基于业务需求,进行商品查询过程的的时序图设计,如图所示:

image.png

业务代码设计及实现

第一步:定义GoodsDao接口,例如:

package com.cy.pj.goods.dao;
import java.util.List;
import java.util.Map;
/**
 * 商品模块数据访问层接口
 */
public interface GoodsDao {
    /**
 * 查询所有商品信息,将每一行记录存储到一个map对象,然后将多个存储到list集合.
 */ List<Map<String,Object>> findGoods();
}

第二步:创建GoodsDao接口实现类,代码如下:

package com.cy.pj.goods.dao;
/**
 * 此对象为一个商品数据层访问对象,现在要求在此类中定义一个方法,这个方法基于JDBC从从数据库获取商品信息,并将其封装到map集合,要求一个行记录一个map对象(key为表中字段名,值为字段名对应的值),多个map存储到list集合. @Repository此注解通常用于描述数据层实现类对象,本质上就是一个特殊的@Component, 都是要交给spring框架管理的一个Bean对象
 */
@Repository
public class DefaultGoodsDao implements  GoodsDao{
       @Autowired
       private DataSource dataSource;//hikariCP
       /**查询商品信息,一行记录映射为内存中的一个map对象*/
       public List<Map<String,Object>> findGoods(){
           Connection conn=null;//java.sql.*
           Statement stmt=null;
           ResultSet rs=null;
           String sql="select * from tb_goods";
           //1.获取连接(从连接池获取)
           try {
               conn=dataSource.getConnection();
               //2.创建statement对象
               stmt=conn.createStatement();
               //3.发送sql
               rs=stmt.executeQuery(sql);
               //4.处理结果
               List<Map<String,Object>> list=new ArrayList<>();
               while(rs.next()){//循环一次取一行,一行记录映射为一个map对象
                  list.add( rowMap(rs));//将存储了一行记录的map对象再存储到list集合
               }
               return list;
           }catch (SQLException e){
               e.printStackTrace();
               throw new RuntimeException(e);//转换为非检查异常(编译时不检测的异常)
           }finally{
               //5. 释放资源
               close(rs,stmt,conn);
           }
       }

定义行映射方法

 private Map<String,Object> rowMap(ResultSet rs)throws SQLException{
           Map<String,Object> rowMap=new HashMap<>();
           //方法1映射
           //rowMap.put("id",rs.getInt("id"));
           //rowMap.put("name",rs.getString("name"));
           //rowMap.put("remark",rs.getString("remark"));
           //rowMap.put("createdTime",rs.getTimestamp("createdTime"));
           //方法2映射
           ResultSetMetaData rsmd=rs.getMetaData();//获取元数据(包括表中的字段名)
           int columnCount=rsmd.getColumnCount();//获取列的数量
           for(int i=0;i<columnCount;i++){
               rowMap.put(rsmd.getColumnLabel(i+1),rs.getObject(rsmd.getColumnLabel(i+1)));
               //getColumnLabel(i+1);获取表中字段名或字段名对应的别名
           }
           return rowMap;
       }

定义释放资源的方法

 private void close(ResultSet rs,Statement stmt,Connection conn){
           if(rs!=null)try{rs.close();}catch(Exception e){e.printStackTrace();}
           if(stmt!=null)try{stmt.close();}catch(Exception e){e.printStackTrace();}
           //这里的连接是返回到了池中
           if(conn!=null)try{conn.close();}catch(Exception e){e.printStackTrace();}
       }
}

测试代码的编写及运行

定义单元测试类,并对其查询过程进行单元测试,例如:

package com.cy.pj.goods.dao;

@SpringBootTest
public class GoodsDaoTests {

      @Autowired
      private GoodsDao goodsDao;

      @Test
      void testFindGoods(){
          List<Map<String,Object>> list= goodsDao.findGoods();
          for(Map<String,Object> map:list){
              System.out.println(map);
          }
      }

}

测试运行过程中的BUG分析

对测试过程中出现的问题进行记录,分析,总结.

总结(Summary)

总之,数据库连接池的为我们的项目开发及运行带来了很多优点,具体如下:

  • 资源重用更佳。

由于数据库连接得到复用,减少了大量创建和关闭连接带来的开销,也大大减少了内存碎片和数据库临时进程、线程的数量,使得整体系统的运行更加平稳。

  • 系统调优更简便。

使用了数据库连接池以后,由于资源重用,大大减少了频繁关闭连接的开销,大大降低了TIME_WAIT的出现频率。

  • 系统响应更快。

数据库连接池在应用初始化的过程中一般都会提前准备好一些数据库连接,业务请求可以直接使用已经创建的连接,而不需要等待创建连接的开销。初始化数据库连接配合资源重用,使得数据库连接池可以大大缩短系统整体响应时间。

  • 连接管理更灵活。

数据库连接池作为一款中间件,用户可以自行配置连接的最小数量、最大数量、最大空闲时间、获取连接超时间、心跳检测等。另外,用户也可以结合新的技术趋势,增加数据库连接池的动态配置、监控、故障演习等一系列实用的功能。

04-SpringBoot工程下如何实现对HikariCP连接池的整合?

04-SpringBoot工程下如何实现对HikariCP连接池的整合?

池化思想分析

池化思想是我们项目开发过程中的一种非常重要的思想,如底层为我们创建的整数池,字符串池,对象池、连接池、线程池等都是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。例如Integer对象的内部池应用,代码如下:

package com.cy.java.pool;
public class TestInteger01 {
    public static void main(String[] args) {
        Integer n1=100;//Integer.valueOf(100) 编译时优化
        Integer n2=100;
        Integer n3=200;
        Integer n4=200;//池中没有则new Integer(200)
        System.out.println(n1==n2);//true
        System.out.println(n3==n4);//false 
    }
    
}

简要分析:为什么n1=n2是true,n3=n4是false呢?主要看这个值是否在整数池(-128~127)这个范围内,在范围内就用,不在范围内就重新new。

数据库连接池简介

背景分析

目前开发过程中应用程序与数据库交互时,“获得连接”或“释放连接”是非常消耗系统资源的两个过程,频繁地进行数据库连接的建立和关闭会极大影响系统的性能,若多线程并发量很大,这样耗时的数据库连接就可能让系统变得卡顿。因为TCP连接的创建开支十分昂贵,并且数据库所能承载的TCP并发连接数也有限制,针对这种场景,数据库连接池应运而生。如下图所示:

image.png

思考:假如设计一个连接池,从什么角度进行设计?
第一:物理存储结构(基于什么结构去存储数据)
第二:基于什么算法从池中取连接?
第三:基于什么算法从池中移除连接?
第四:当池中没有连接时,基于什么方式处理连接请求?
第五:池是可以共享,需要考虑池在访问的时并发安全?

连接池原理分析

在系统初始化的时候,在内存中开辟一片空间,将一定数量的数据库连接作为对象存储在对象池里,并对外提供数据库连接的获取和归还方法。用户访问数据库时,并不是建立一个新的连接,而是从数据库连接池中取出一个已有的空闲连接对象;使用完毕归还后的连接也不会马上关闭,而是由数据库连接池统一管理回收,为下一次借用做好准备。如果由于高并发请求导致数据库连接池中的连接被借用完毕,其他线程就会等待,直到有连接被归还。整个过程中,连接并不会关闭,而是源源不断地循环使用,有借有还。数据库连接池还可以通过设置其参数来控制连接池中的初始连接数、连接的上下限数,以及每个连接的最大使用次数、最大空闲时间等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

Java中的连接池

Java官方,为了在应用程序中更好的应用连接池技术,定义了一套数据源规范,例如javax.sql.DataSource接口,基于这个接口,很多团队或个人创建了不同的连接池对象。然后我们的应用程序中通过耦合与DataSource接口,便可以方便的切换不同厂商的连接池。Java项目中通过连接池获取连接的一个基本过程,如下图所示:

image.png

在上图中,用户通过DataSource对象的getConnection()方法,获取一个连接。假如池中有连接,则直接将连接返回给用户。假如池中没有连接,则会调用Dirver(驱动,由数据库厂商进行实现)对象的connect方法从数据库获取,拿到连接以后,可以将连接在池中放一份,然后将连接返回给调用方。连接需求方再次需要连接时,可以从池中获取,用完以后再还给池对象。

SpringBoot工程下HikariCP整合测试

数据初始化

打开mysql控制台,然后按如下步骤执行goods.sql文件。
第一步:登录mysql。

mysql –uroot –proot

第二步:设置控制台编码方式。

set names utf8;

第三步:执行goods.sql文件(切记不要打开文件复制到mysql客户端运行)。

source d:/goods.sql

其中goods.sql文件内容如下:

drop database if exists dbgoods;
create database dbgoods default character set utf8;
use dbgoods;
create table tb_goods(
     id bigint primary key auto_increment,
     name varchar(100) not null,
     remark text,
     createdTime datetime not null
)engine=InnoDB;
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());

创建项目Module并添加相关依赖

第一步:基于IDEA创建项目Module,如图所示:

image.png

第二步:添加依赖
1) mysql数据库驱动依赖。

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

2) spring对象jdbc支持(此时会默认帮我们下载HiKariCP连接池)。

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

配置HikariCP连接池

打开application.properties配置文件,添加如下内容(必写)。

spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

hikariCP 其它额外配置(可选),代码如下(具体配置不清晰的可自行百度):

spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1 

HikariCP 连接池测试

单元测试API设计及应用分析,如图所示:

image.png

在项目中添加单元测试类及测试方法,代码如下:

package com.cy.pj.common.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class DataSourceTests {
/**
 * DataSource由谁定义?java官方
 * 定义了什么?定义了从数据库或连接池获取连接的一种规范
 * 我们为什么要耦合于它?类与类之间存在依赖(耦合)时,尽量依赖于抽象规范,然后基于规范实现获取连接
 * 运行时这个变量指向的对象类型是谁?hikariDataSource,你怎么知道的(断点,日志)
 */
    @Autowired
    private DataSource dataSource;
    @Test
    public void testConnection() throws Exception{
     //获取dataSoure对象对应的具体类型是什么
System.out.println(dataSource.getClass().getName());//com.zaxxer.hikari.HikariDataSource
//DataSource获取连接的一个过程是怎样的
System.out.println(dataSource.getConnection());
    }
}

在当前测试类中我们需要:

  • 掌握单元测试类、测试方法编写规范。
  • 理解DataSource的设计规范及规范的实现。
  • 分析在测试类中dataSource属性指向的对象是谁?
  • 分析在测试类中DataSource的实现类对象由谁创建和管理?
  • 思考基于DataSource接口获取连接的基本过程是怎样的?

测试BUG分析

  • 数据库不存在,如图所示:

image.png
解决办法:数据库的名字写错了,如dbgoods写成了dbgood。

  • 类编译错误,DataSource为javax.sql包中的类型,如图所示:

image.png
解决办法:1.查看dataSource这个类是否引用的javax.sql这个包,2.是否依赖添加成功。

  • 连接错误:数据库连接不上,如图所示:

image.png
解决方法:查看xml文件中的spring.datasource.username=root和spring.datasource.password=root的用户名是否正确。

基于HikariCP实现JDBC操作(练习)

业务分析

基于HikariCP,借助JDBC技术访问商品库中的数据。

API架构设计

基于业务,进行API设计,如图所示:

image.png

业务时序图分析

基于业务需求,进行商品查询过程的的时序图设计,如图所示:

image.png

业务代码设计及实现

第一步:定义GoodsDao接口,例如:

package com.cy.pj.goods.dao;
import java.util.List;
import java.util.Map;
/**
 * 商品模块数据访问层接口
 */
public interface GoodsDao {
    /**
 * 查询所有商品信息,将每一行记录存储到一个map对象,然后将多个存储到list集合.
 */ List<Map<String,Object>> findGoods();
}

第二步:创建GoodsDao接口实现类,代码如下:
方法一:

package com.cy.pj.goods.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Repository
public class GoodsGoodsImpl implements GoodsDao {
    @Autowired
 private DataSource dataSource;
    @Override
 public List<Map<String, Object>> findGoods() {
        Connection conn=null;
        Statement sta=null;
        ResultSet rs=null;
    //1.获取连接(从连接池获取)
 try {
      conn=dataSource.getConnection();
    //2.创建statement对象
 sta=conn.createStatement();
    //3.发送sql
 String sql="select * from tb_goods";
      rs=sta.executeQuery(sql);
    //4.处理结果
 List<Map<String,Object>> list=new ArrayList<>();
      while (rs.next()) {//循环一次取一行,一行记录映射为一个map对象
 Map<String,Object> map=new HashMap<>();
          map.put("id", rs.getInt("id"));
          map.put("name", rs.getString("name"));
          map.put("remark", rs.getString("remark"));
          map.put("createdTime", rs.getTimestamp("createdTime"));
          list.add(map);
      }
      return list;
        }catch (SQLException e){ e.printStackTrace();
        return null;
        }finally {
    //5. 释放资源
 if (rs!=null)try{rs.close();}catch(Exception e){e.printStackTrace();}
      if (sta!=null)try{sta.close() ;}catch(Exception e){e.printStackTrace();}
      if (conn!=null)try{conn.close();}catch (Exception e){e.printStackTrace();}
  }
    }
}

方法二:优化

 package com.cy.pj.goods.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Repository
public class GoodsDaoImpl implements GoodsDao {
    @Autowired
 private DataSource dataSource;
    @Override
 public List<Map<String, Object>> findGoods() {
        Connection con = null;
        Statement sta = null;
        ResultSet rs = null;
        //1.获取连接
 try {
            con = dataSource.getConnection();
            //2.创建statement
 sta = con.createStatement();
            //3.发送sql
 String sql = "select id,name,remark,createdTime from tb_goods";
            rs = sta.executeQuery(sql);
            //4.处理结果
 List<Map<String, Object>> list = new ArrayList<>();
            //获取结果集中的元数据(描述数据的数据-例如表名,字段名,....)
 ResultSetMetaData rsmd = rs.getMetaData();
            while (rs.next()) {
                list.add(rowMap(rsmd, rs));
            }
            return list;
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        } finally {
            //5.释放内存
 close(rs, sta, con);
        }
    }
  //封装结果集
private Map<String,Object> rowMap(ResultSetMetaData rsmd,ResultSet rs)throws SQLException{
    Map<String,Object> map=new HashMap<>();
    int count=rsmd.getColumnCount();
    for(int i=1;i<=count;i++){
        map.put(rsmd.getColumnLabel(i), rs.getObject(i));
    }
    return map;
}
  //封装释放内存
private void close(ResultSet rs,Statement sta,Connection conn){
    if (rs!=null)try { rs.close();}catch (Exception e){e.printStackTrace();}
    if(sta!=null)try{sta.close();}catch(Exception e){e.printStackTrace();}
    //这里的连接是返回到了池中
 if(conn!=null)try{conn.close();}catch(Exception e){e.printStackTrace();}
    }

测试代码的编写及运行

定义单元测试类,并对其查询过程进行单元测试,例如:

package com.cy.pj.goods.dao;

@SpringBootTest
public class GoodsDaoTests {

      @Autowired
      private GoodsDao goodsDao;

      @Test
      void testFindGoods(){
          List<Map<String,Object>> list= goodsDao.findGoods();
          for(Map<String,Object> map:list){
              System.out.println(map);
          }
      }

}

测试运行过程中的BUG分析

常见的就是上边介绍的BUG。

总结(Summary)

总之,数据库连接池的为我们的项目开发及运行带来了很多优点,具体如下:

  • 资源重用更佳。

由于数据库连接得到复用,减少了大量创建和关闭连接带来的开销,也大大减少了内存碎片和数据库临时进程、线程的数量,使得整体系统的运行更加平稳。

  • 系统调优更简便。

使用了数据库连接池以后,由于资源重用,大大减少了频繁关闭连接的开销,大大降低了TIME_WAIT的出现频率。

  • 系统响应更快。

数据库连接池在应用初始化的过程中一般都会提前准备好一些数据库连接,业务请求可以直接使用已经创建的连接,而不需要等待创建连接的开销。初始化数据库连接配合资源重用,使得数据库连接池可以大大缩短系统整体响应时间。

  • 连接管理更灵活。

数据库连接池作为一款中间件,用户可以自行配置连接的最小数量、最大数量、最大空闲时间、获取连接超时间、心跳检测等。另外,用户也可以结合新的技术趋势,增加数据库连接池的动态配置、监控、故障演习等一系列实用的功能。

04-SpringBoot工程下如何实现对HikariCP链接池的整合?

04-SpringBoot工程下如何实现对HikariCP链接池的整合?

池化思想分析

池化思想是我们项目开发过程中的一种非常重要的思想,如整数池,字符串池,对象池、连接池、线程池等都是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。例如Integer对象的内部池应用,代码如下:

package com.cy.java.pool;
public class TestInteger01 {
    public static void main(String[] args) {
        Integer n1=100;//Integer.valueOf(100) 编译时优化
        Integer n2=100;
        Integer n3=200;
        Integer n4=200;//池中没有则new Integer(200)
        System.out.println(n1==n2);//true
        System.out.println(n3==n4);//false 
    }
    
}

数据库连接池简介

背景分析

目开发过程中应用程序与数据库交互时,“获得连接”或“释放连接”是非常消耗系统资源的两个过程,频繁地进行数据库连接的建立和关闭会极大影响系统的性能,若多线程并发量很大,这样耗时的数据库连接就可能让系统变得卡顿。因为TCP连接的创建开支十分昂贵,并且数据库所能承载的TCP并发连接数也有限制,针对这种场景,数据库连接池应运而生。如下图所示:image
思考:假如现在是让你去设计一个连接池,你会从什么角度进行设计?
第一:物理存储结构(基于什么结构去存储数据)
第二:基于什么算法从池中取连接?
第三:基于什么算法从池中移除连接?
第四:当池中没有连接时,基于什么方式处理连接请求?
第五:池是可以共享,我们需要考虑池在访问的时并发安全?

连接池原理分析

在系统初始化的时候,在内存中开辟一片空间,将一定数量的数据库连接作为对象存储在对象池里,并对外提供数据库连接的获取和归还方法。用户访问数据库时,并不是建立一个新的连接,而是从数据库连接池中取出一个已有的空闲连接对象;使用完毕归还后的连接也不会马上关闭,而是由数据库连接池统一管理回收,为下一次借用做好准备。如果由于高并发请求导致数据库连接池中的连接被借用完毕,其他线程就会等待,直到有连接被归还。整个过程中,连接并不会关闭,而是源源不断地循环使用,有借有还。数据库连接池还可以通过设置其参数来控制连接池中的初始连接数、连接的上下限数,以及每个连接的最大使用次数、最大空闲时间等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

Java中的连接池

Java官方,为了在应用程序中更好的应用连接池技术,定义了一套数据源规范,例如javax.sql.DataSource接口,基于这个接口,很多团队或个人创建了不同的连接池对象。然后我们的应用程序中通过耦合与DataSource接口,便可以方便的切换不同厂商的连接池。Java项目中通过连接池获取连接的一个基本过程,如下图所示:image
在上图中,用户通过DataSource对象的getConnection()方法,获取一个连接。假如池中有连接,则直接将连接返回给用户。假如池中没有连接,则会调用Dirver(驱动,由数据库厂商进行实现)对象的connect方法从数据库获取,拿到连接以后,可以将连接在池中放一份,然后将连接返回给调用方。连接需求方再次需要连接时,可以从池中获取,用完以后再还给池对象。

数据库连接池在Java数据库相关中间件产品群中,应该算是底层最基础的一类产品,作为企业应用开发必不可少的组件,无数天才们为我们贡献了一个又一个的优秀产品,它们有的随时代发展,功成身退,有的则还在不断迭代,老而弥坚,更有新生代产品,或性能无敌,或功能全面。目前市场上常见的连接池有DBCP、C3P0,DRUID,HikariCP等。

SpringBoot工程下HikariCP整合测试

数据初始化

打开mysql控制台,然后按如下步骤执行goods.sql文件。
第一步:登录mysql。

mysql –uroot –proot

第二步:设置控制台编码方式。

set names utf8;

第三步:执行goods.sql文件(切记不要打开文件复制到mysql客户端运行)。

source d:/goods.sql

其中goods.sql文件内容如下:


drop database if exists dbgoods;
create database dbgoods default character set utf8;
use dbgoods;
create table tb_goods(
     id bigint primary key auto_increment,
     name varchar(100) not null,
     remark text,
     createdTime datetime not null
)engine=InnoDB;
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());
insert into tb_goods values (null,''mysql'',''RDBMS'',now());
insert into tb_goods values (null,''Oracle'',''RDBMS'',now());
insert into tb_goods values (null,''java'',''very good'',now());

创建项目Module并添加相关依赖

第一步:基于IDEA创建项目Module,如图所示:image
第二步:添加依赖
1) mysql数据库驱动依赖。

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

2) spring对象jdbc支持(此时会默认帮我们下载HiKariCP连接池)。

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

配置HikariCP连接池

打开application.properties配置文件,添加如下内容(必写)。

spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

hikariCP 其它额外配置(可选),代码如下(具体配置不清晰的可自行百度):

spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1

HikariCP 连接池测试

单元测试API设计及应用分析,如图所示:image
在项目中添加单元测试类及测试方法,代码如下:

package com.cy.pj.common.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class DataSourceTests {
    @Autowired
    private DataSource dataSource;
    @Test
    public void testConnection() throws Exception{
        System.out.println(dataSource.getConnection());
    }
}

在当前测试类中我们需要:

  • 掌握单元测试类、测试方法编写规范。
  • 理解DataSource的设计规范及规范的实现。
  • 分析在测试类中dataSource属性指向的对象是谁?
  • 分析在测试类中DataSource的实现类对象由谁创建和管理?
  • 思考基于DataSource接口获取连接的基本过程是怎样的?

测试BUG分析

  • 数据库不存在,如图所示:image
  • 类编译错误,DataSource为javax.sql包中的类型,如图所示:image
  • 连接错误:数据库连接不上,如图所示:image

基于HikariCP实现JDBC操作(练习)


业务分析

基于HikariCP,借助JDBC技术访问商品库中的数据。

API架构设计

基于业务,进行API设计,如图所示:
image

业务时序图分析

基于业务需求,进行商品查询过程的的时序图设计,如图所示:image

业务代码设计及实现

第一步:定义GoodsDao接口,例如:

package com.cy.pj.goods.dao;
import java.util.List;
import java.util.Map;
/**
 * 商品模块数据访问层接口
 */
public interface GoodsDao {
    /**
 * 查询所有商品信息,将每一行记录存储到一个map对象,然后将多个存储到list集合.
 */ List<Map<String,Object>> findGoods();
}

第二步:创建GoodsDao接口实现类,代码如下:

package com.cy.pj.goods.dao;
/**
 * 此对象为一个商品数据层访问对象,现在要求在此类中定义一个方法,这个方法基于JDBC从从数据库获取商品信息,并将其封装到map集合,要求一个行记录一个map对象(key为表中字段名,值为字段名对应的值),多个map存储到list集合. @Repository此注解通常用于描述数据层实现类对象,本质上就是一个特殊的@Component, 都是要交给spring框架管理的一个Bean对象
 */
@Repository
public class DefaultGoodsDao implements  GoodsDao{
       @Autowired
       private DataSource dataSource;//hikariCP
       /**查询商品信息,一行记录映射为内存中的一个map对象*/
       public List<Map<String,Object>> findGoods(){
           Connection conn=null;//java.sql.*
           Statement stmt=null;
           ResultSet rs=null;
           String sql="select * from tb_goods";
           //1.获取连接(从连接池获取)
           try {
               conn=dataSource.getConnection();
               //2.创建statement对象
               stmt=conn.createStatement();
               //3.发送sql
               rs=stmt.executeQuery(sql);
               //4.处理结果
               List<Map<String,Object>> list=new ArrayList<>();
               while(rs.next()){//循环一次取一行,一行记录映射为一个map对象
                  list.add( rowMap(rs));//将存储了一行记录的map对象再存储到list集合
               }
               return list;
           }catch (SQLException e){
               e.printStackTrace();
               throw new RuntimeException(e);//转换为非检查异常(编译时不检测的异常)
           }finally{
               //5. 释放资源
               close(rs,stmt,conn);
           }
       }

定义行映射方法

 private Map<String,Object> rowMap(ResultSet rs)throws SQLException{
           Map<String,Object> rowMap=new HashMap<>();
           //方法1映射
           //rowMap.put("id",rs.getInt("id"));
           //rowMap.put("name",rs.getString("name"));
           //rowMap.put("remark",rs.getString("remark"));
           //rowMap.put("createdTime",rs.getTimestamp("createdTime"));
           //方法2映射
           ResultSetMetaData rsmd=rs.getMetaData();//获取元数据(包括表中的字段名)
           int columnCount=rsmd.getColumnCount();//获取列的数量
           for(int i=0;i<columnCount;i++){
               rowMap.put(rsmd.getColumnLabel(i+1),rs.getObject(rsmd.getColumnLabel(i+1)));
               //getColumnLabel(i+1);获取表中字段名或字段名对应的别名
           }
           return rowMap;
       }

定义释放资源的方法

 private void close(ResultSet rs,Statement stmt,Connection conn){
           if(rs!=null)try{rs.close();}catch(Exception e){e.printStackTrace();}
           if(stmt!=null)try{stmt.close();}catch(Exception e){e.printStackTrace();}
           //这里的连接是返回到了池中
           if(conn!=null)try{conn.close();}catch(Exception e){e.printStackTrace();}
       }
}

测试代码的编写及运行

定义单元测试类,并对其查询过程进行单元测试,例如:

package com.cy.pj.goods.dao;

@SpringBootTest
public class GoodsDaoTests {

      @Autowired
      private GoodsDao goodsDao;

      @Test
      void testFindGoods(){
          List<Map<String,Object>> list= goodsDao.findGoods();
          for(Map<String,Object> map:list){
              System.out.println(map);
          }
      }

}

测试运行过程中的BUG分析

对测试过程中出现的问题进行记录,分析,总结.

总结(Summary)

总之,数据库连接池的为我们的项目开发及运行带来了很多优点,具体如下:

  • 资源重用更佳。

由于数据库连接得到复用,减少了大量创建和关闭连接带来的开销,也大大减少了内存碎片和数据库临时进程、线程的数量,使得整体系统的运行更加平稳。

  • 系统调优更简便。

使用了数据库连接池以后,由于资源重用,大大减少了频繁关闭连接的开销,大大降低了TIME_WAIT的出现频率。

  • 系统响应更快。

数据库连接池在应用初始化的过程中一般都会提前准备好一些数据库连接,业务请求可以直接使用已经创建的连接,而不需要等待创建连接的开销。初始化数据库连接配合资源重用,使得数据库连接池可以大大缩短系统整体响应时间。

  • 连接管理更灵活。

数据库连接池作为一款中间件,用户可以自行配置连接的最小数量、最大数量、最大空闲时间、获取连接超时间、心跳检测等。另外,用户也可以结合新的技术趋势,增加数据库连接池的动态配置、监控、故障演习等一系列实用的功能。

关于orika 实现对象复制实现复制对象的命令是的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于03-SpringBoot工程下如何实现对HikariCP连接池的整合?【转】、04-SpringBoot工程下如何实现对HikariCP连接池的整合、04-SpringBoot工程下如何实现对HikariCP连接池的整合?、04-SpringBoot工程下如何实现对HikariCP链接池的整合?等相关知识的信息别忘了在本站进行查找喔。

本文标签: