中小微型开发架构实战之2 jdbc

JDBC

什么是JDBC

维基百科的简介:
Java 数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
JDBC也是Sun Microsystems的商标。JDBC是面向关系型数据库的。
简单地说,就是用于执行SQL语句的一类JavaAPI,通过JDBC使得我们可以直接使用Java编程来对关系数据库进行操作。
通过封装,可以使开发人员使用纯Java API完成SQL的执行。

JDBC 规范定义接口,具体的实现由各大数据库厂商来实现。

JDBC优势

  1. 程序员如果想开发访问数据库的项目,只需要使用java api中的java.sql包下的几个类即可,不用关心各大数据库厂商的具体实现方式
  2. 同一套java代码,只需要更改数据库连接属性,数据库厂商驱动,即可切换到另一个数据库

在这里插入图片描述

建库建表

# unsigned 将数据类型无符号化。例如 bigint 原本区间  (-2 147 483 648,2 147 483 647)   unsigned之后是 (0,4 294 967 295)
CREATE TABLE `biz_tags` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '书签名',
  `description` varchar(100) DEFAULT NULL COMMENT '描述',
  `create_time` datetime DEFAULT current_timestamp() COMMENT '添加时间',
  `update_time` datetime DEFAULT current_timestamp() COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
;
CREATE TABLE `biz_article_tags` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `tag_id` bigint(20) unsigned NOT NULL COMMENT '标签表主键',
  `article_id` bigint(20) unsigned NOT NULL COMMENT '文章ID',
  `create_time` datetime DEFAULT current_timestamp() COMMENT '添加时间',
  `update_time` datetime DEFAULT current_timestamp() COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
;

导包

数据库使用的是mariadb,这里引入mariadb专有的驱动

<!-- 数据库连接驱动 -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>2.7.2</version>
</dependency>
<!-- 测试类框架 -->
 <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

数据库接入核心代码

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class DbHelper extends BaseHelper{
    private static DbHelper dbHelper = new DbHelper();
    private static final String USERNAME_KEY = "username";
    private static String USERNAME = "";
    private static final String PASSWORD_KEY = "password";
    private static String PASSWORD = "";
    private static final String URL_KEY = "url.mariadb";
    private static String URL = "";
    private static final String DRIVER_KEY = "driver.mariadb";
    private static String DRIVER = "";

    static {
        Properties prop = new Properties();
        InputStream inputStream = DbHelper.class.getResourceAsStream("/jdbc.properties");
        if (null == inputStream) {
            throw new RuntimeException("未找到数据库配置文件,jdbc.properties");
        }
        try {
            prop.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(prop.containsKey(USERNAME_KEY)){
            USERNAME = prop.get(USERNAME_KEY).toString();
        }
        if(prop.containsKey(PASSWORD_KEY)){
            PASSWORD = prop.get(PASSWORD_KEY).toString();
        }
        if(prop.containsKey(URL_KEY)){
            URL = prop.get(URL_KEY).toString();
        }
        if(prop.containsKey(DRIVER_KEY)){
            DRIVER = prop.get(DRIVER_KEY).toString();
        }
        System.out.println(USERNAME);
        System.out.println(PASSWORD);
        System.out.println(URL);
        System.out.println(DRIVER);
    }

    @Override
    protected DataSource dataSource() {
        return null;
    }

    @Override
    public Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    public static DbHelper getInstance(){
        return dbHelper;
    }
}

数据库连接公共重构代码,如下:


import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * @author heshiyuan
 * @date 2021/2/23 16:33
 */
public abstract class BaseHelper {
    private DataSource dataSource;

    /**
     * 获取数据源
     * @return
     */
    protected abstract DataSource dataSource();

    public Connection getConnection(){
        if(null == dataSource){
            dataSource = dataSource();
            if(null == dataSource){
                throw new IllegalArgumentException("数据源加载失败");
            }
        }
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        throw new IllegalArgumentException("连接池获取连接失败");
    }

    /**
     * 释放连接回连接池
     * @param conn
     * @param stmt
     * @param rs
     */
    public void release(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
}

src/main/resources/jdbc.properties

username=root
password=root@mariadb
driver.mariadb=org.mariadb.jdbc.Driver
driver.mysql=com.mysql.jdbc.Driver
url.mariadb=jdbc:mariadb://localhost:3306/dblog
url.mysql=jdbc:mysql://localhost:3306/dblog

crud代码


import org.hsy.java.jdbc.entity.Tag;
import org.hsy.java.jdbc.utils.BaseHelper;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author heshiyuan
 * @date 2021/2/22 11:44
 */
public class TagDao {

    BaseHelper baseHelper;
    Connection connection;
    public TagDao(BaseHelper baseHelper) {
        this.baseHelper = baseHelper;
        connection = baseHelper.getConnection();
    }
    // 本方法模拟了事务的回滚案例
    public int testTransaction(Tag tag) {
        Savepoint savepoint1=null, savepoint2=null, savepoint3 = null;
        PreparedStatement ps = null;
        PreparedStatement ps2 = null;
        try {
            connection.setAutoCommit(false);
            savepoint1 = connection.setSavepoint();
            ps = connection.prepareStatement(
                    "update biz_tags set description = ?, update_time = now() where id = ?");
            ps.setString(1, "2222");
            ps.setLong(2, tag.getId());
            ps.executeUpdate();
            savepoint2 = connection.setSavepoint();
            ps2 = connection.prepareStatement(
                    "update biz_article_tags set update_time = now() where tag_id = ?");
            ps2.setLong(1, tag.getId());
            ps2.executeUpdate();
            savepoint3 = connection.setSavepoint();
            int i = 2/0;
            connection.commit();
            return 1;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                connection.rollback(savepoint2);
                connection.commit();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            baseHelper.release(connection, ps, null);
        }
        return 0;
    }
    /**
     * 增加一条记录
     * @param tag
     * @return
     */
    public int insert(Tag tag) {
        PreparedStatement ps = null;
        try {
            ps = connection.prepareStatement(
                    "INSERT INTO biz_tags (id, name, description, create_time, update_time) " +
                            "VALUES ("+tag.getId()+", '"+tag.getName()+"', '"+tag.getDescription()+"', now(), now());");
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            baseHelper.release(connection, ps, null);
        }
        return 0;
    }

    public int delete(Long id){
        PreparedStatement preparedStatement = null;
        try {
            preparedStatement = connection.prepareStatement("delete from biz_tags where id = ?");
            preparedStatement.setLong(1, id);
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            baseHelper.release(connection, preparedStatement, null);
        }
        return 0;
    }

    /**
     * 修改一条记录
     * @param tag
     * @return
     */
    public int update(Tag tag){
        PreparedStatement ps = null;
        try {
            ps = connection.prepareStatement(
                    "update biz_tags set name = ?, description = ?, update_time = now() where id = ?");
            ps.setString(1, tag.getName());
            ps.setString(2, tag.getDescription());
            ps.setLong(3, tag.getId());
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            baseHelper.release(connection, ps, null);
        }
        return 0;
    }
    public Tag findOne(Long id){
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            ps = connection.prepareStatement(
                    "select * from biz_tags where id = ?");
            ps.setLong(1, id);
            resultSet = ps.executeQuery();
            if(resultSet.next()){
                return convertTag(resultSet);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            baseHelper.release(connection, ps, resultSet);
        }
        return null;
    }

    private Tag convertTag(ResultSet resultSet) throws SQLException {
        Tag tag = new Tag();
        tag.setId(resultSet.getLong("id"));
        tag.setName(resultSet.getString("name"));
        tag.setDescription(resultSet.getString("description"));
        tag.setCreateTime(resultSet.getDate("create_time"));
        tag.setUpdateTime(resultSet.getDate("update_time"));
        return tag;
    }

    public List<Tag> findList(Tag tag){
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            ps = connection.prepareStatement(
                    "select * from biz_tags");
            resultSet = ps.executeQuery();
            List<Tag> returnList = new ArrayList<>();
            while (resultSet.next()){
                returnList.add(convertTag(resultSet));
            }
            return returnList;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            baseHelper.release(connection, ps, resultSet);
        }
        return new ArrayList<>();
    }

测试类代码


import org.hsy.java.jdbc.entity.Tag;
import org.hsy.java.jdbc.utils.C3p0Helper;
import org.hsy.java.jdbc.utils.DbHelper;
import org.hsy.java.jdbc.utils.DbcpHelper;
import org.junit.Assert;
import org.junit.Test;

/**
 * @author heshiyuan
 * @date 2021/2/22 14:44
 */
public class TagDaoTest {
    TagDao tagDao = new TagDao(DbHelper.getInstance());

    @Test
    public void insert() {
        Tag tag = new Tag();
        tag.setId(100L);
        tag.setName("临时测试insert");
        tag.setDescription("我是描述呀insert");
        Assert.assertEquals(1, tagDao.insert(tag));
    }
    @Test
    public void delete() {
        Assert.assertEquals(1, tagDao.delete(100L));
    }
    @Test
    public void update() {
        Tag tag = new Tag();
        tag.setId(100L);
        tag.setName("临时测试update");
        tag.setDescription("我是描述呀update");
        Assert.assertEquals(1, tagDao.update(tag));
    }
    @Test
    public void findOne() {
        Assert.assertEquals(java.util.Optional.of(100L).get(), tagDao.findOne(100L).getId());
    }
    @Test
    public void findList() {
        System.out.println(tagDao.findList(new Tag()));
    }

    @Test
    public void testTransaction(){
        Tag tag = new Tag();
        tag.setId(100L);
        tagDao.testTransaction(tag);
    }

分析

上述源码中涉及到几个重要的类,单独分析一下。

DriverManager

  1. 管理和注册数据库驱动(public static void registerDriver(java.sql.Driver driver)
    throws SQLException)
  2. 建立数据库连接(public static Connection getConnection(String url,
    String user, String password) throws SQLException )

Connection

是一个接口,具体由数据库厂商实现。

方法备注
Statement createStatement() throws SQLException;创建一个Statement对象,发送sql请求
PreparedStatement prepareStatement(String sql) throws SQLException;预编译一条待执行的sql,效率比Statement高。
CallableStatement prepareCall(String sql) throws SQLException;调用存储过程。
String nativeSQL(String sql) throws SQLException;执行数据库本地sql
void setAutoCommit(boolean autoCommit) throws SQLException;设置事务是否自动提交
void commit() throws SQLException;提交当前事务
void rollback() throws SQLException;回滚当前事务
void rollback(Savepoint savepoint) throws SQLException;回滚事务到某一个点
void close() throws SQLException;关闭当前数据库连接
DatabaseMetaData getMetaData() throws SQLException;获取数据库字段的元数据,数据类型,长度等等
void setTransactionIsolation(int level) throws SQLException;设置数据库的事务级别
Savepoint setSavepoint(String name) throws SQLException;在当前事务中设置事务保存点,方便控制事务的回滚位置

PreparedStatement

数据库的执行动作类。
使用 占位符“?”可以有效防止sql注入事件的发生。

方法说明
ResultSet executeQuery() throws SQLException;执行一条查询操作
int executeUpdate() throws SQLException;执行一条更新操作
void setInt(int parameterIndex, int x) throws SQLException;替换sql中的“?”,parameterIndex the first parameter is 1, the second is 2, …
void setString(int parameterIndex, String x) throws SQLException;替换sql中的“?”字符串类型,parameterIndex the first parameter is 1, the second is 2, …
default long executeLargeUpdate() throws SQLException执行insert,update,delete,ddl等

ResultSet

sql执行的结果集

方法说明
boolean next() throws SQLException;判断游标下一个对应点有无数据
void close() throws SQLException;释放结果集
int getInt(String columnLabel) throws SQLException;获取对应列名的int数据类型值
String getString(String columnLabel) throws SQLException;获取对应列名的varchar数据类型值

在这里插入图片描述

释放资源

  1. 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接

  2. 释放原则:先开的后关,后开的先关。ResultSet > Statement > Connection

  3. 放在哪个代码块中:finally 块

连接池

必要性

  1. 建立数据库连接
  2. 数据库操作
  3. 短信数据库连接

这种模式开发,存在的问题

上面使用DbHelper操作数据库,连接使用 DriverManager 来获取Connection,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(从数据库获取连接耗时0.05s~1s的时间)。

操作数据库的时候,就向数据库获取一个Connection,执行完成后再断开连接。

这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。

若是一个微型项目(qps:1000+),频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。

对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启项目,或者重启数据库。

这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

因此,数据库连接池就凸显出他的价值。

连接池

数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。

  1. 能够定制池子的connection总数(防止内存泄漏,防止系统资源占用过大)
  2. 资源重用,能够重复使用数据库连接(加快数据库处理速度)
  3. 快速响应数据库操作

开源连接池技术框架

  • c3p0
  • dbcp
  • druid

DBCP

DBCP(DataBase connection pool)数据库连接池是 apache上的一个Java连接池项目。
DBCP通过连接池预先同数据库建立一些连接放在内存中(即连接池中),应用程序需要建立数据库连接时直接从池中申请一个连接使用,用完后由连接池回收该连接,从而达到连接复用,减少资源消耗的目的。

依赖

  • commons-dbcp-1.4.jar(第一代dbcp技术,最后更新于2010-2-7)
  • commons-pool-1.6.jar(内置于commons-dbcp)

配置文件

src/main/resources/dbcp.properties

username=root
password=root@mariadb
driverClassName=org.mariadb.jdbc.Driver
url=jdbc:mariadb://localhost:3306/dblog?useUnicode=true&characterEncoding=utf-8

# 初始化连接
dataSource.initialSize=10
# 最大空闲连接
dataSource.maxIdle=10
# 最小空闲连接
dataSource.minIdle=10
# 最大连接数量
dataSource.maxActive=10
# 是否在自动回收超时连接的时候打印连接的超时错误
dataSource.logAbandoned=true
# 是否自动回收超时连接
dataSource.removeAbandoned=true
# 超时时间(以秒数为单位)
# 设置超时时间有一个要注意的地方,超时时间=现在的时间-程序中创建Connection的时间,如果 maxActive比较大,比如超过100,
# 那么removeAbandonedTimeout可以设置长一点比如180,也就是三分钟无响应的连接进行回收,当然应用的不同设置长度也不同。
dataSource.removeAbandonedTimeout=180
# 超时等待时间以毫秒为单位
# maxWait代表当Connection用尽了,多久之后进行回收丢失连接
dataSource.maxWait=1000

数据库连接配置


import org.apache.commons.dbcp.BasicDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.Properties;

/**
 * @author heshiyuan
 */
public class DbcpHelper extends BaseHelper{
    private static DbcpHelper dbcpHelper = new DbcpHelper();
    private static DataSource dataSource = null;
    static {
        Properties prop = new Properties();
        InputStream inputStream = DbcpHelper.class.getResourceAsStream("/dbcp.properties");
        if (null == inputStream) {
            throw new RuntimeException("未找到数据库配置文件,dbcp.properties");
        }
        try {
            prop.load(inputStream);
            dataSource = BasicDataSourceFactory.createDataSource(prop);
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected DataSource dataSource() {
        return dataSource;
    }

    public static DbcpHelper getInstance(){
        return dbcpHelper;
    }
}

测试类


import org.hsy.java.jdbc.entity.Tag;
import org.hsy.java.jdbc.utils.DbcpHelper;
import org.junit.Assert;
import org.junit.Test;

import java.util.logging.Logger;

/**
 * @author heshiyuan
 * @date 2021/2/22 14:44
 */
public class DbcpTest {
    Logger logger = Logger.getLogger(DbcpTest.class.getName());
    TagDao tagDao = new TagDao(DbcpHelper.getInstance());

    @Test
    public void insert() {
        Tag tag = new Tag();
        tag.setId(100L);
        tag.setName("临时测试insert");
        tag.setDescription("我是描述呀insert");
        Assert.assertEquals(1, tagDao.insert(tag));
    }
    @Test
    public void delete() {
        Assert.assertEquals(1, tagDao.delete(100L));
    }
    @Test
    public void update() {
        Tag tag = new Tag();
        tag.setId(100L);
        tag.setName("临时测试update");
        tag.setDescription("我是描述呀update");
        Assert.assertEquals(1, tagDao.update(tag));
    }
    @Test
    public void findOne() {
        Assert.assertEquals(java.util.Optional.of(100L).get(), tagDao.findOne(100L).getId());
    }
    @Test
    public void findList() {
        logger.info(tagDao.findList(new Tag()).toString());
    }

    @Test
    public void testTransaction(){
        Tag tag = new Tag();
        tag.setId(100L);
        tagDao.testTransaction(tag);
    }
}

以上也可实现对数据库的crud操作。

C3P0

C3P0是一个开源的JDBC连接池,它实现了数据源与JNDI绑定,支持JDBC3规范和实现了JDBC2的标准扩展说明的Connection和Statement池的DataSources对象。

即将用于连接数据库的连接整合在一起形成一个随取随用的数据库连接池(Connection pool)。

包引入

  • c3p0-0.9.1.2.jar

配置文件

src/main/resources/c3p0-config.xml

<c3p0-config>
    <default-config>
        <!--mysql数据库连接的各项参数-->
        <!--配置数据库连接池的初始连接数、最小链接数、获取连接数、最大连接数、最大空闲时间-->
        <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">10</property>
        <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
        <property name="acquireIncrement">0</property>
        <!--连接池中保留的最大连接数。Default: 15 -->
        <property name="maxPoolSize">10</property>
        <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
        <property name="maxIdleTime">30</property>

        <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
        <property name="acquireRetryAttempts">30</property>

        <!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
        <property name="acquireRetryDelay">1000</property>

        <!--连接关闭时默认将所有未提交的操作回滚。Default: false -->
        <property name="autoCommitOnClose">false</property>

        <!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么
        属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试
        使用。Default: null-->
        <!--<property name="automaticTestTable">Test</property>-->

        <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效
        保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试
        获取连接失败后该数据源将申明已断开并永久关闭。Default: false-->
        <property name="breakAfterAcquireFailure">false</property>

        <!--当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出
        SQLException,如设为0则无限期等待。单位毫秒。Default: 0 -->
        <!--<property name="checkoutTimeout">100</property>-->

        <!--通过实现ConnectionTester或QueryConnectionTester的类来测试连接。类名需制定全路径。
        Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester-->
        <!--<property name="connectionTesterClassName"></property>-->

        <!--指定c3p0 libraries的路径,如果(通常都是这样)在本地即可获得那么无需设置,默认null即可
        Default: null-->
        <property name="factoryClassLocation">null</property>

        <!--强烈不建议使用该方法,将这个设置为true可能会导致一些微妙而奇怪的bug-->
        <property name="forceIgnoreUnresolvedTransactions">false</property>

        <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
        <property name="idleConnectionTestPeriod">60</property>

        <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements
        属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
        如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0-->
        <property name="maxStatements">100</property>

        <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
        <!--<property name="maxStatementsPerConnection"></property>-->

        <!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能
        通过多线程实现多个操作同时被执行。Default: 3-->
        <property name="numHelperThreads">3</property>

        <!--定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意:
        测试的表必须在初始数据源的时候就存在。Default: null-->
       <!-- <property name="preferredTestQuery">select id from test where id=1</property>-->

        <!--用户修改系统配置参数执行前最多等待300秒。Default: 300 -->
        <property name="propertyCycle">300</property>

        <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的
        时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable
        等方法来提升连接测试的性能。Default: false -->
        <property name="testConnectionOnCheckout">false</property>

        <!--如果设为true那么在取得连接的同时将校验连接的有效性。Default: false -->
        <property name="testConnectionOnCheckin">false</property>

        <!--早期的c3p0版本对JDBC接口采用动态反射代理。在早期版本用途广泛的情况下这个参数
        允许用户恢复到动态反射代理以解决不稳定的故障。最新的非反射代理更快并且已经开始
        广泛的被使用,所以这个参数未必有用。现在原先的动态反射与新的非反射代理同时受到
        支持,但今后可能的版本可能不支持动态反射代理。Default: false-->
        <property name="usesTraditionalReflectiveProxies">false</property>
    </default-config>

    <!--配置连接池mairadb-->
    <named-config name="mariadb">
        <property name="driverClass">org.mariadb.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mariadb://localhost:3306/dblog</property>
        <property name="user">root</property>
        <property name="password">root@mariadb</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">10</property>
        <property name="maxPoolSize">10</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">100</property>
    </named-config>
</c3p0-config>

数据库连接操作

import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
/**
 * @author heshiyuan
 */
public class C3p0Helper extends BaseHelper{
    private static DataSource dataSource = new ComboPooledDataSource("mariadb");
    private static C3p0Helper c3p0Helper = new C3p0Helper();

    /**
     * 获取实例
     * @return
     */
    public static C3p0Helper getInstance(){
        return c3p0Helper;
    }

    @Override
    protected DataSource dataSource() {
        return dataSource;
    }
}

测试类


import org.hsy.java.jdbc.entity.Tag;
import org.hsy.java.jdbc.utils.C3p0Helper;
import org.junit.Assert;
import org.junit.Test;

import java.util.logging.Logger;

/**
 * @author heshiyuan
 * @date 2021/2/22 14:44
 */
public class C3P0Test {
    Logger logger = Logger.getLogger(C3P0Test.class.getName());
    TagDao tagDao = new TagDao(C3p0Helper.getInstance());

    @Test
    public void insert() {
        Tag tag = new Tag();
        tag.setId(100L);
        tag.setName("临时测试insert");
        tag.setDescription("我是描述呀insert");
        Assert.assertEquals(1, tagDao.insert(tag));
    }
    @Test
    public void delete() {
        Assert.assertEquals(1, tagDao.delete(100L));
    }
    @Test
    public void update() {
        Tag tag = new Tag();
        tag.setId(100L);
        tag.setName("临时测试update");
        tag.setDescription("我是描述呀update");
        Assert.assertEquals(1, tagDao.update(tag));
    }
    @Test
    public void findOne() {
        Assert.assertEquals(java.util.Optional.of(100L).get(), tagDao.findOne(100L).getId());
    }
    @Test
    public void findList() {
        logger.info(tagDao.findList(new Tag()).toString());
    }

    @Test
    public void testTransaction(){
        Tag tag = new Tag();
        tag.setId(100L);
        tagDao.testTransaction(tag);
    }
}

以上实现了数据库的增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)操作

性能大PK

case

观察jdbc, dbcp, c3p0耗时性能(此处只关注时间复杂度,忽略空间复杂度,忽略jvm消耗系统资源情况)
硬件:
* MacBook Pro (Retina, 13-inch, Early 2015)
* cpu 3.1 GHz Dual-Core Intel Core i7
* memory 16 GB 1867 MHz DDR3

软件:
* 连接池最大连接数10
* 连接池最小连接数10
* 连接池初始化连接数10
* 最大空闲时间,30秒内未使用则连接被丢弃
* maxStatements=100

case 1. 单线程对数据库insert批量(1000, 2000, 3000)数据,每条数据insert前获取一次数据库连接
期望:T(jdbc) 远大于 T(dbcp) 近似于 T(c3p0)
实际:

    1000条数据:3次
    T(jdbc) = 4.349s 4.204s 5.43s
    T(dbcp) = 2.391s, 2.131s, 2.375s
    T(c3p0) = 2.570, 2.499s, 2.509s
    T(jdbc) >> T(c3p0) > T(dbcp)
    2000条数据:3次
    T(jdbc) = 7.321s 6.563s 8.406s
    T(dbcp) = 4.299s 4.188s 3.486s
    T(c3p0) = 3.991s 4.134s 4.420s
    T(jdbc) >> T(dbcp) > T(c3p0) 
    3000条数据:3次
    T(jdbc) = 9.498s 9.791s 8.513s
    T(dbcp) = 4.913s 5.001s 4.5s
    T(c3p0) = 5.9s 5.617s 5.107s
    T(jdbc) >> T(c3p0) > T(dbcp)

实操多线程方案时遇到以下问题:

  1. 每个线程单独操作一条insert时,jdbc直接报错,无法创建太多线程(数据库不支持1000个connection)
  2. 线程内操作dbcp,c3p0时无法生效,datasouce获取不到

综上本次不考虑多线程方案(理论上多线程时 dbcp,c3p0性能更优于jdbc)

实操

dbcp,c3p0连接池调参,保证参数一致

测试代码

// 测试代码类似,区别在于不通数据源实例
TagDao tagDao = new TagDao(DbHelper.getInstance());
IntStream.range(1, 3001).forEach(i -> {
    Tag tag = new Tag();
    tag.setName("insert" + i);
    tag.setDescription("我是描述 insert" + i);
    logger.info("threadId = "+Thread.currentThread().getId()+", 第 "+i+" 次,result = " + tagDao.insert(tag));
});

分析

综上实测,三次测试中jdbc均耗时最长(意料之中),dbcp两次性能优于c3p0,分别是1000条,3000条数据。

结论

使用连接池效果明显强于原生jdbc。

畅言

在整个开发过程中,查看了jdbc,dbcp,c3p0源码。
就源码可读性看,jdbc > dbcp > c3p0.

c3p0有很多注释的脏代码没有删除,影响阅读效果,而且代码不够简洁。

jdbc是java与关系型数据库的桥梁,中小微型的项目使用jdbc+连接池完全够用,由于jdbc是手写代码,拥有很强的扩展性。

多数据源,读写分离,事务控制也比较简单。缺点是需要编写大量代码。

可以利用java反射机制,针对PreparedStatement,ResultSet做简易的封装。

源码

java-jdbc

参考文献

JDBC - 使用C3p0数据源和dbcp数据源

详解C3P0(数据库连接池)

数据库连接池性能比对

分享是快乐的,也记录了个人成长历程以及知识备份,可能自身认知不足之处在所难免,不想误人子弟,如有错误大家指正,共同探讨进步。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 如有问题, 可邮件(shiyuan4work@126.com)咨询。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值