MySQL Day11 再谈事务与DBCP、C3P0数据库连接池的配置

1 再谈事务

一个事务本质在于:其内部的功能要么都成功,要么都失败!

之前的blog中谈过事务,那一篇是在Sequel Pro中测试的,此处重点谈论如何用Idea来操作事务!

ACID原则

原子性:要么全部完成,要么都不完成

一致性:总数不变

隔离性:多个进程互不干扰

持久性:一旦提交不可逆,持久化到数据库内

隔离性的问题:

脏读:一个事务读取了另一个没有提交的事务

不可重复读:在同一个事务内,重复读取表中的数据,数据发生了改变

幻读(虚读):在一个事务内,读取到了别人插入的数据,导致前后读出来的结果不一致。

代码实现

  1. 开启事务 conn.setAutoCommit(false);
  2. 一组业务执行完毕,提交事务!
  3. 可以在catch语句中显示的定义回滚语句,但默认失败就会回滚

源码:(其中用到上篇博客中的封装类)

package com.demut.demo04;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestTransaction {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();

            conn.setAutoCommit(false); // 关闭自动提交-->开启事务

            String sql1 = "update account set money = money-100 where name = 'A'";
            st = conn.prepareStatement(sql1);
            st.executeUpdate();

            //int x = 1/0; //可以故意设置报错

            String sql2 = "update account set money = money+100 where name = 'B'";
            st = conn.prepareStatement(sql2);
            st.executeUpdate();

            //业务完毕,提交事务
            conn.commit();
            System.out.println("成功!");
        } catch (SQLException e) {
            try {
                conn.rollback(); //如果失败则回滚事务
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

2 数据库连接池

为什么需要用到数据库连接池呢?其原因在于我们每次连接数据库都经历了数据库连接 --- 执行完毕 --- 释放的过程,而其中的连接 --- 释放 尤其浪费系统资源。

举个例子:我们如果要去银行办理事务,倘若银行采用来一个人 --- 开门 --- 办理结束 --- 关门 --- 来一个人...的模式,十分耗费时间以及精力。所以我们开一次门,设置特定的业务员,使用业务员来对接每一位来的用户,这样就能很好的减少资源浪费,当然每位业务员也有自己的精力,所以有最大连接数,最小连接数,以及常用连接数!类似这样的思路,我们引入数据库的池化技术。

池化技术:准备一些预先的资源,过来就连接预先准备好的

--- 开门 --- 业务员: 等待 --- 服务 --

设置常用连接数:比如10个

设置最小连接数:比如10个

设置最大连接数:业务最高承载上限,例如15个

如果达到最大连接数,排队等待,如果等待超时(例如100ms)就提示其异常。

其本质在于:编写连接池,实现一个接口DataSource

开源数据源实现

现在的主流数据源有:DBCP、C3P0、Druid(阿里巴巴)

使用了这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了!下面开始介绍使用方法:

2.1 DBCP

需要用到dbcp-jar包 :下载链接

pool-jar包:下载链接

如果导入后测试出现NoClassDefFoundError错误,则还需导入logging.jar包:下载链接

随后将包导入到idea的lib目录中,如果不知道如何导入,见我的另一篇博客:链接

另外需要添加一个配置文件:dbcp.properties,注意直接将该文件创建在src目录下!

dbcp.properties文件代码如下:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy
username=root
password=123456

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000

#<!-- 连接编码设置 -->

connectionProperties=useUnicode=true;characterEncoding=utf8

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

编写JdbcUtils_DBCP工具类:

package com.demut.demo05.utils;

import org.apache.commons.dbcp2.BasicDataSourceFactory;

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

public class JdbcUtils_DBCP {

    private static DataSource dataSource = null;
    //加载数据库驱动
    static {
        try {
            //获取文件流
            InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            Properties properties = new Properties();
            properties.load(in); //加载流
            //创建数据源 工厂模式 --> 创建
            dataSource = BasicDataSourceFactory.createDataSource(properties);

        }catch (Exception e){
            e.printStackTrace();
        }

    }

    //获取连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection(); //从数据源中获取连接
    }

    //释放连接资源
    public static void free(Connection conn, Statement st, ResultSet rs){
        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st !=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn !=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

另编写测试类,测试使用DBCP连接:

package com.demut.demo05;

import com.demut.demo05.utils.JdbcUtils_DBCP;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

public class TestDBCP {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils_DBCP.getConnection();
            //区别
            //使用?占位符代替参数
            String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
            pst = conn.prepareStatement(sql); //预编译SQL,先写sql,不执行

            //手动给参数赋值:
            pst.setInt(1,6); //此处的1表示第一个问好
            pst.setString(2,"Jever");
            pst.setString(3,"111111");
            pst.setString(4,"[email protected]");
            //注意: sql.Date 数据库     java.sql.Date()
            //      util.Date  Java     new Date().getTime() 获得时间戳
            pst.setDate(5,new java.sql.Date(new Date().getTime()));

            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("插入成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_DBCP.free(conn,pst,rs);
        }
    }
}
/*测试结果:
插入成功!
*/

2.2 C3P0

需要下载的jar包:c3p0-x.x.x.x、mchange-commons-java-x.x.xx

提取码:tyek

同样需要导入jar包,同上!

创建配置文件c3p0-config.xml

<c3p0-config>
    <!--使用默认的配置读取数据库连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&amp;characterEncoding=utf8</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <!-- 连接池参数 -->
        <!--初始化申请的连接数量-->
        <property name = "acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <!--最小的连接数量-->
        <property name = "minPoolSize">5</property>
        <!--最大的连接数量-->
        <property name="maxPoolSize">20</property>
        <!--超时时间-->
        <property name="checkoutTimeout">3000</property>
    </default-config>

    <!--
    C3P0的命名配置
    -->
    <named-config name="MySQL">
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&amp;characterEncoding=utf8</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <!-- 连接池参数 -->
        <!--初始化申请的连接数量-->
        <property name = "acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <!--最小的连接数量-->
        <property name = "minPoolSize">5</property>
        <!--最大的连接数量-->
        <property name="maxPoolSize">20</property>
        <!--超时时间-->
        <property name="checkoutTimeout">3000</property>
    </named-config>
</c3p0-config>

编写JdbcUtils_C3P0工具类:

package com.demut.demo05.utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JdbcUtils_C3P0 {
    private static DataSource dataSource = null;

    static {
        try {
            //创建数据源 工厂模式 --> 创建
            dataSource = new ComboPooledDataSource("MySQL"); //配置文件写法
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //获取连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    //释放连接资源
    public static void release(Connection conn, Statement st, ResultSet rs){
        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st !=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn !=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

另编写测试类,测试使用C3P0连接:

package com.demut.demo05;

import com.demut.demo05.utils.JdbcUtils_C3P0;
import com.demut.demo05.utils.JdbcUtils_DBCP;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

public class TestC3P0 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils_C3P0.getConnection(); //原来是自己实现的,现在是用别人实现的
            //区别
            //使用?占位符代替参数
            String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
            pst = conn.prepareStatement(sql); //预编译SQL,先写sql,不执行

            //手动给参数赋值:
            pst.setInt(1,5); //此处的1表示第一个问好
            pst.setString(2,"Jever");
            pst.setString(3,"111111");
            pst.setString(4,"[email protected]");
            //注意: sql.Date 数据库     java.sql.Date()
            //      util.Date  Java     new Date().getTime() 获得时间戳
            pst.setDate(5,new java.sql.Date(new Date().getTime()));

            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("插入成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_C3P0.release(conn,pst,rs);
        }
    }
}
/*运行结果:
四月 19, 2020 4:15:24 下午 com.mchange.v2.log.MLog <clinit>
信息: MLog clients using java 1.4+ standard logging.
四月 19, 2020 4:15:25 下午 com.mchange.v2.c3p0.C3P0Registry banner
信息: Initializing c3p0-0.9.2.1 [built 20-March-2013 10:47:27 +0000; debug? true; trace: 10]
四月 19, 2020 4:15:25 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 5, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 3000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> MySQL, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.cj.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge0yxa914df8d71bm1ykq|2e5d6d97, idleConnectionTestPeriod -> 0, initialPoolSize -> 10, jdbcUrl -> jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&characterEncoding=utf8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 5, numHelperThreads -> 3, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
插入成功!

说明: 上部分为日志,可以自行修改,最后一行为结果!
*/

Druid我们随后再说!(此处挖个坑)

2.3 结论

无论使用什么数据源,本质都是一样的,DataSource接口不会变,方法就不会变!

写在最后

Keep your servant also from willful sins; may they not rule over me. Then I will be blameless, innocent of great transgression. (Psalms 19 : 13)

To Demut!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章