JDBC02

封装 JDBCUtils

需求引出

因为操作数据库的步骤都相同,所以我们可以抽象出一个工具类,用来连接数据库和关闭连接

 

 工具类

package com.recorder.jdbcutils;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

/**
 * @author 紫英
 * @version 1.0
 * @discription 数据库连接工具类
 */
public class JdbcUtils {

    //定义相关的属性(4 个), 因为只需要一份,因此,我们做出 static
    private static String user; //用户名
    private static String password; //密码
    private static String url; //url
    private static String driver; //驱动名

    //在 static 代码块初始化
    static {

        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\jdbc.properties"));
            //读取相关的属性值
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            //在实际开发中,我们可以这样处理
            //1. 将编译异常转成 运行异常
            //2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
            throw new RuntimeException(e);
        }

    }

    //连接数据库, 返回 Connection
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            //1. 将编译异常转成 运行异常
            // 2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
            throw new RuntimeException(e);
        }
    }

    //关闭相关资源
    /* 1. ResultSet 结果集
    2. Statement 或者 PreparedStatement
    3. Connection
    4. 如果需要关闭资源,就传入对象,否则传入 null
    */
    public static void close(ResultSet set, Statement statement, Connection connection) {


        try {
            //判断是否为空
            if (set != null) {
                set.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            //将编译异常转成运行异常抛出
            throw new RuntimeException(e);
        }
    }


}

使用工具类执行DML语句

package com.recorder.jdbcutils;

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

/**
 * @author 紫英
 * @version 1.0
 * @discription DML语句
 */
public class Test {
    
    @org.junit.jupiter.api.Test
    public void dmlTest(){

        //1.得到链接
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        //2.组织SQL
        String sql = "insert into admin values(?,?)";
        try {
            connection = JdbcUtils.getConnection();
            //3.创建preparedstatement对象
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, "along");
            preparedStatement.setString(2, "666");
            preparedStatement.executeUpdate();
            System.out.println("添加成功");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            //dml语句没有结果集所以写null
            JdbcUtils.close(null, preparedStatement, connection);
        }

    }

}

 

事务

基本介绍

 

 注意:4.5.也都是调用connection的对应方法

 

经典应用案例——转账业务

 

 代码实现:

CREATE TABLE account (
    id INT PRIMARY KEY auto_increment,
    NAME VARCHAR ( 32 ) NOT NULL DEFAULT '',
    balance double not null default 0) character set utf8;
insert into account values(null, '马云',3000);
insert into account values(null, '马化腾',10000);

 

package com.recorder.jdbc;

import com.recorder.jdbcutils.JdbcUtils;
import org.junit.jupiter.api.Test;

import java.sql.*;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @author 紫英
 * @version 1.0
 * @discription JDBC中使用事务
 */
public class Transaction_ {
    @Test
    //1.未使用事务
    public void noTransaction() {

        //操作转账的业务
        // 1. 得到连接
        Connection connection = null;
        //2. 组织一个 sql(转钱)
        String sql = "update account set balance = balance - 100 where id = 1";
        String sql2 = "update account set balance = balance + 100 where id = 2";
        PreparedStatement preparedStatement = null;

        //3. 创建 PreparedStatement 对象

        try {
            connection = JdbcUtils.getConnection();
            //执行第一条
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.executeUpdate();
            int i = 1 / 0; //抛出异常
            //执行第二条
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JdbcUtils.close(null, preparedStatement, connection);
        }


    }

    @Test
    //2.使用事务
    public void useTransaction() {

        //操作转账的业务
        // 1. 得到连接
        Connection connection = null;
        //2. 组织一个 sql(转钱)
        String sql = "update account set balance = balance - 100 where id = 1";
        String sql2 = "update account set balance = balance + 100 where id = 2";
        PreparedStatement preparedStatement = null;

        //3. 创建 PreparedStatement 对象

        try {
            connection = JdbcUtils.getConnection();
            //将 connection 设置为不自动提交
            connection.setAutoCommit(false);//相当于开启事务

            //执行第一条
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.executeUpdate();
            int i = 1 / 0; //抛出异常
            //执行第二条
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate();

            //两条sql都执行完过后进行提交
            connection.commit();

        } catch (SQLException e) {
            //这里我们可以进行回滚,即撤销执行的 SQL
            // 默认回滚到事务开始的状态.
            System.out.println("执行发生了异常,撤销执行的 sql");
            try {
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            //关闭资源
            JdbcUtils.close(null, preparedStatement, connection);
        }
    }
}

数据查看:

1.初始状态

 

 2.不使用事务,并在两条sql中间穿插异常,可以看到,业务出现了问题

 

 

 

 3.使用事务,并在两条sql中间穿插异常,由于catch语句中回滚无变化

4.使用事务正常操作,转账成功

 

 

 

批处理

基本介绍

 

url=jdbc:mysql://localhost:3306/db01?rewriteBatchedStatements=true

代码实现

 

CREATE TABLE admin2 ( 
id INT PRIMARY KEY auto_increment, 
username VARCHAR ( 32 ) NOT NULL, 
`password` VARCHAR ( 32 ) NOT NULL );

 

package com.recorder.batch;

import com.recorder.jdbcutils.JdbcUtils;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;

/**
 * @author 紫英
 * @version 1.0
 * @discription 批处理添加数据
 */
public class Batch01 {

    //传统方法,添加 5000条数据到 admin2
    @Test
    public void noBatch() throws Exception {

        Connection connection = JdbcUtils.getConnection();
        String sql = "insert into admin2 values(null, ?, ?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        System.out.println("开始执行");
        long start = System.currentTimeMillis();//开始时间
        for (int i = 0; i < 5000; i++) {
            //5000 执行
            preparedStatement.setString(1, "jack" + i);
            preparedStatement.setString(2, "666");
            preparedStatement.executeUpdate();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方式 耗时=" + (end - start));//传统方式耗时
        //关闭连接
        JdbcUtils.close(null, preparedStatement, connection);
    }

 

//使用批量操作方式添加5000条数据 到 admin2
    @Test
    public void useBatch() throws Exception {

        Connection connection = JdbcUtils.getConnection();
        String sql = "insert into admin2 values(null, ?, ?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        System.out.println("开始执行");
        long start = System.currentTimeMillis();//开始时间
        for (int i = 0; i < 5000; i++) {
            //5000 执行
            preparedStatement.setString(1, "jack" + i);
            preparedStatement.setString(2, "666");
            //将 sql 语句加入到批处理包中 -> 看源码
            preparedStatement.addBatch();
            if ((i + 1) % 1000 == 0) {//满 1000 条 sql
                preparedStatement.executeBatch();
                // 清空一把
                preparedStatement.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("批处理方式 耗时=" + (end - start));//传统的方式 耗时
        //关闭连接
        JdbcUtils.close(null, preparedStatement, connection);

    }

速度快了大概1000倍
}

 

 

源码解读

 

 

 

 

5.批处理语句可以帮助我们减少发送sql语句的网络开销,而且使用预编译也会减少编译次数,因此效率更高

(以下案例中5000条数据只需发送五次)

 扩容就按照ArrayList的扩容机制,进行1.5倍扩容

 public void addBatch() throws SQLException {
        synchronized(this.checkClosed().getConnectionMutex()) {
            if (this.batchedArgs == null) {
                this.batchedArgs = new ArrayList();  //第一次的时候创建一个ArrayList数组
            }

            for(int i = 0; i < this.parameterValues.length; ++i) {
                this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
            }

            this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
        }
    }

 

posted @ 2022-02-19 21:28  紫英626  阅读(23)  评论(0编辑  收藏  举报

紫英