JDBC基础 -获取连接的方式、结果集、批处理、事务处理、连接池、Apache-DBUtils

概述

  • jdbc是为了访问不同的数据库提供的统一的接口,为使用者屏蔽了细节问题
  • 使用jdbc可以连接任何提供了jdbc驱动程序的数据库系统,从而完成对数据库的各种操作

原理图

如果不同的数据库,我们的方法不统一,不利于程序管理

在这里插入图片描述

java厂商使用以下的规则:使用一套接口规范,不同的数据库厂商实现,在java程序中调用接口的方法

在这里插入图片描述

快速入门(增删改)

步骤如下

  1. 注册驱动:加载Dirver类
  2. 获取连接:得到Connection
  3. 执行增删改查:发送SQL给mysql执行
  4. 释放资源:关闭相关连接
package jdbc;

import com.mysql.jdbc.Driver;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;

public class JDBC1 {
    public static void main(String[] args) throws Exception{
        //首先,将jar包加入库

        //1.注册驱动 com.mysql.jdbc.Driver
        Driver driver = new Driver();

        //2.得到连接
        /*
        * jdbc:mysql:// 规定的协议,通过jdbc的方式连接mysql
        * localhost 主机 也可以是ip地址
        * 3306 端口
        * testdb 连接到mysql dbms的具体的数据库
        * MySQL的连接本质就是socket连接
        * */
        String url = "jdbc:mysql://localhost:3306/testdb";

        //将用户名和密码放到Properties对象中
        Properties properties = new Properties();
        properties.setProperty("user","root");
        properties.setProperty("password","yb0os1");

        //connect 网络连接
        Connection connect = driver.connect(url, properties);

        //3.执行sql语句
        String sql = "insert into actor values(null,'刘德华'),(null,'王恬心');";
        //statement 用于执行静态SQL语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        //返回的是影响的行数
        int rows = statement.executeUpdate(sql);
        System.out.println(rows>0?"成功":"失败");
        //4.注册驱动
        statement.close();
        connect.close();
    }
}

获取数据库的五种方式

方式一:获取Driver实现类对象

属于静态加载,灵活性差,依赖性强(因为直接new的Dirver,在前面已经导入好确定的包了)

Dirver driver = new Driver();
string url = "xxx";
Properties info = new Properties();
info.setProperties("user","xxx");
info.setProperties("password","xxx");
Connection conn = driver.connect(url,info);

方式二:反射

Class<Driver> driverClass = com.mysql.jdbc.Driver.class;
Driver driver = driverClass.newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","yb0os1");
Connection connect = driver.connect(url, properties);
System.out.println(connect);

方式三:使用DriverManager代替Driver

        Class<Driver> driverClass = com.mysql.jdbc.Driver.class;
        Driver driver = driverClass.newInstance();
        String url = "jdbc:mysql://localhost:3306/testdb";
        String user = "root";
        String password = "yb0os1";
        DriverManager.registerDriver(driver);
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);

方式四:Class.forName自动完成注册驱动(推荐)

        //使用反射加载了Driver类
        /*
        * 看看源码
        * 静态代码块 在类加载的时候执行一次 
       static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
        * */
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/testdb";
        String user = "root";
        String password = "yb0os1";
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);

注意

mysqL驱动5.1.6可以无需CLass .forName(“com.mysql.jdbc.Driver”);
从jdk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动而是自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册

方式五:使用properties

        Properties properties = new Properties();
        properties.load(new FileInputStream("./info.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String database = properties.getProperty("database");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");

        Class.forName(driver);
        Connection connection = DriverManager.getConnection(url + database, user, password);
        Statement statement = connection.createStatement();

statement、PreparedStatament、CallableStatement

是一个接口

用于执行静态SQL语句并返回其生成的结果的对象

在连接建立完成之后,对数据库的访问、执行命令或者SQL语句,可以通过:

  • Statement:存在SQL注入问题
  • PreparedStatament:预处理
  • CallableStatement:存储过程

SQL注入就是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,恶意攻击数据库。

在JDBC中防范SQL注入,只要使用PreparedStatement就OK了

statement存在的sql注入问题

package jdbc.statementSQL;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

public class testsql {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.load(new FileInputStream("./info.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String database = properties.getProperty("database");
        Class.forName(driver);
        Connection connection = DriverManager.getConnection(url + database, user, password);
        Statement statement = connection.createStatement();
        //在数据库中root账户对应的密码不是输入的内容
        //execute 执行任意sql语句 返回布尔值
        ResultSet resultSet = statement.executeQuery("select * from actor where id = 'root' and name = ''or '1'='1';");
        if (resultSet.next())
            System.out.println("登录成功");
        resultSet.close();
        statement.close();
        connection.close();
    }
}

PreparedStatement的使用

  • 在SQL语句中可以使用?代表参数,调用setxxx方法为这些?的位置赋值,该方法是有两个参数,第一个参数为?参数的索引(从1开始),第二个参数是?参数所代表的具体的值
  • 调用executeQuery返回ResultSet结果集
  • 调用executeUpdate进行增加、删除、修改操作,返回被影响的行数

好处就是:不需要拼接sql语句、有效解决sql注入安全问题、减少编译次数 提高效率

防止sql注入的原理:预处理

在执行sql语句之前,数据库服务前先将sql语句进行编译,确定sql语句的基本“结构”,放在缓存区,当真正执行sql语句时,直接从缓存区中去拿取,而不会进行再次编译。这样,即使在执行sql语句时,发现用户传入的参数中带有sql关键字,也不会被识别编译,只会被当成参数替换占位符,拼接在sql语句中,这样就很好的避免的sql注入。

package jdbc.preparedStatement;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class demo {
    public static void main (String[] args) throws Exception {
        Properties properties = new Properties();
        properties.load(new FileInputStream("./info.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String database = properties.getProperty("database");
        Class.forName(driver);
        Connection connection = DriverManager.getConnection(url + database, user, password);

        //需要直接使用sql语句与preparedStatement关联起来
        String sql = "select * from actor where id=? and name=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1,"root");
//        preparedStatement.setString(2,"'or '1'='1");//万能密码不可以用了
        preparedStatement.setString(2,"yb0os1");


        //执行
        //这里不要填sql语句 因为前面已经关联起来了
        //如果还是加了sql语句 那么使用的带有?的 执行的没有被预处理的sql语句
        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next())
            System.out.println("登录成功");
		
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

ResultSet结果集(查)

是一个接口

表示数据库结果集的数据表,一般通过执行查询数据库语句生成

该对象保持一个光标指向其当前的数据行,最初光标位于第一行之前,next方法可以使光标移动到下一行,当resultset对象中没有更多行时返回false(可以使用while循环遍历结果集)

        Properties properties = new Properties();
        properties.load(new FileInputStream("./info.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String database = properties.getProperty("database");
        Class.forName(driver);
        Connection connection = DriverManager.getConnection(url + database, user, password);
        Statement statement = connection.createStatement();
		// 获取结果集 一开始在第一行之前 先进行next到第一行 然后继续 到最后一行继续next返回了fasle
        ResultSet resultSet = statement.executeQuery("select id,name from actor;");
		//还可以previous 就是向前移动一行 向前的一行不存在 就是返回false
        while (resultSet.next()){
            //对于每一行的每一列进行获取 也可以根据列名来获取
            // getXxx(列的索引||列名)
            System.out.println(resultSet.getInt(1)+"-"+resultSet.getString(2));
        }
		resultSet.close();
        statement.close();
        connection.close();

在这里插入图片描述

rows是一个ArrayList,存储着每行数据

封装JDBCUtils

package jdbc;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

//工具类
public class JDBCUtils {
    private static String user;
    private static String password;
    private static String driver;
    private static String url;

    // 静态代码块:为了个上述的属性赋值 读取文件
    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("./mysql.properties"));
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            Class.forName(driver);//这里可以不用这样加载类 因为自动加载类
        } catch (Exception e) {
            //将编译异常转换为运行异常
            //调用者可以选择捕获该异常,或者选择默认处理该异常的方式
            throw new RuntimeException(e);
        }
    }

    // 连接
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url,user,password);
        } catch (SQLException e) {
            //同理 抛出 如果处理交给调用者
            throw new RuntimeException(e);
        }
    }

    // 关闭连接
    /*
    * 这里涉及到 ResultSet、Statement、Connection
    * 先判断是否存在 再选择是否要释放
    * null代表不释放
    * */
    public static void close(ResultSet resultSet, Statement statement,Connection connection){
        try {
            if (resultSet!=null)resultSet.close();
            if (statement!=null)statement.close();
            if (connection!=null)connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

事务处理

  • JDBC程序中,当一个Connection对象创建时,默认情况下会自动提交事务:每次执行一个SQL语句,如果执行成功就会向数据库提交,不能回滚。

  • 需要多个SQL语句作为一个整体执行的时候需要事务,可以使用Connection的setAutoCommit(false)取消自动提交事务

  • commit()方法提交事务; rollback()方法回滚事务

package jdbc;

import org.junit.Test;

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

public class Transaction {
    Connection connection = null;
    PreparedStatement preparedStatement=null;
    String sql1="update account set balance = balance - 100 where name='马云';";
    String sql2="update account set balance = balance + 100 where name='马化腾';";

    @Test
    public void noTransaction(){
        try {
            connection = JDBCUtils.getConnection();//默认情况下 connection默认自动提交
            preparedStatement = connection.prepareStatement(sql1);
            preparedStatement.executeUpdate();

            int i = 1/0; // 抛出异常 使后续的修改不可达
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                connection.close();
                preparedStatement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
    @Test
    public void useTransaction(){
        try {
            connection = JDBCUtils.getConnection();
            connection.setAutoCommit(false);//不让他自动提交 相当于开始的事务
            preparedStatement = connection.prepareStatement(sql1);
            preparedStatement.executeUpdate();

            int i = 1/0; // 抛出异常 使后续的修改不可达
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate();

            connection.commit();//提交
        } catch (Exception e) {
            try {
                //抛出以上进入这里 可以进行回滚 撤销执行的SQL
                //默认回滚到事务开始的状态 也就是connection.setAutoCommit(false);这里
                System.out.println("发生异常,进行回滚");
                connection.rollback();
                throw new RuntimeException(e);
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            try {
                connection.close();
                preparedStatement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

批处理

  • 当需要一次性处理多条记录的时候,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,效率up
  • JDBC批量处理语句的方法:
    • addBatch():添加需要批处理的SQL语句或者参数
    • executeBatch():执行批量处理语句
    • clearBatch():清空批处理包的语句
  • JDBC连接MySQL时,要使用批处理的话要在url中加上?rewriteBatchedStatements=true
  • 批处理往往结合preparedStatement使用,既减少编译次数,又减少运行次数,大大提高了效率
package jdbc;

import org.junit.Test;

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

public class Batch {
    @Test
    public void noBatch() throws Exception {
        /*
         * 不使用批处理添加五千条数据
         * */
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into actor values (?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            preparedStatement.setString(1, i + "");
            preparedStatement.setString(2, "tom" + i);
            preparedStatement.executeUpdate();
        }
        long end = System.currentTimeMillis();
        System.out.println("不使用批处理耗时" + (end - start) + "毫秒");//10502毫秒
        JDBCUtils.close(null, preparedStatement, connection);
    }

    @Test
    public void useBatch() throws Exception {
        /*
         * 使用批处理添加五千条数据
         * 记得再url上加入 ?rewriteBatchedStatements=true
         * */
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into actor values(?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            preparedStatement.setString(1, i + "");
            preparedStatement.setString(2, "tom" + i);
            // 这里做了什么工作?
            preparedStatement.addBatch();

            //当加入1000条sql语句的时候 批量处理一下
            if ((i + 1) % 1000 == 0) {
                preparedStatement.executeBatch();
                //清空
                preparedStatement.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("使用批处理耗时" + (end - start) + "毫秒");//64毫秒
        JDBCUtils.close(null, preparedStatement, connection);
    }
}

我们来看看addBatch

public void addBatch() throws SQLException {
    synchronized(this.checkClosed().getConnectionMutex()) {
        //进来先判断这个ArrayList数组是否存在 存在就跳过这个if 否则新建
        if (this.batchedArgs == null) {
            this.batchedArgs = new ArrayList();
        }
		
        //this.parameterValues.length就是?的个数 
        //检查每一个参数是否设置 也就是是否预处理了
        for(int i = 0; i < this.parameterValues.length; ++i) {
            this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
        }
        
		//将预处理之后的sql语句加入数组之中
        this.batchedArgs.add(new BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
    }
}

在这里插入图片描述

数据库连接池

传统获取Connection的弊端

  • 传统的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证IP、用户名、密码(0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将会占用很多的系统资源,容易导致服务器崩溃。
  • 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将会导致数据库内存泄漏,MySQL崩溃
  • 传统获取连接的方式,不能控制创建的连接数量,如果一下子连接数量过多也可能导致内存泄露,MySQL崩溃
  • 解决传统开发中的数据库连接问题,采用数据库连接池
    // 建立5000连接
	public void noUsePoll(){
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = JDBCUtils.getConnection();
            //进行sql处理
            //先假设没有关闭 "Too many connections"  这就是一下子涌进太多的sql连接

            //进行关闭测试时间
            JDBCUtils.close(null,null,connection);
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方式5000次连接,所需时间:"+(end-start)+"ms"); //5839ms 这是一个非常耗时的
    }

连接池简介

  • 预先在缓冲池中放入一定数量的连接,当需要建立连接时,只需要从“缓冲池”取出一个,使用完毕之后放回去(这里不是关闭仅仅时还给缓冲池)
  • 数据库连接池负责分配、管理和释放数据库连接,允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个新连接
  • 当应用程序向连接池请求的连接数量超过池内最大的连接数量时,这些请求将被加入到等待队列之中
  • 大白话讲:我们建立连接的时候是把连接池里面的连接“拿过来”,释放连接的时候是把“拿过来”的连接“还回去”在这里插入图片描述

DataSource只是一个接口,是让第三方进行实现的

  • C3P0 数据库连接池:速度相对较慢,稳定性不错
  • DBCP 数据库连接池:速度较c3p0快,不稳定
  • Proxool 数据库连接池:有监控连接池状态的功能,稳定性不如c3p0
  • BoneCP 数据库连接池:速度快
  • Druid(德鲁伊):集DBCP、Proxool 、C3P0优点于一身的数据库连接池(推荐

C3P0

package jdbc;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.io.FileInputStream;
import java.lang.annotation.Target;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class C3P0using {
    @Test
    // 方式1:相关参数在程序中指定 user url password
    public void testC3P0_01() throws Exception {
        // 1、创建一个数据源对象
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        //2、通过配置文件获取相关连接信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("./mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //给数据源设置相关参数
        comboPooledDataSource.setDriverClass(driver);//连接是由comboPooledDataSource管理
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);
        comboPooledDataSource.setJdbcUrl(url);

        //初始化数据源的连接数:初始化的连接池里面有多少个连接,可以增加到maxPoolSize
        comboPooledDataSource.setInitialPoolSize(10);
        //数据源的最大连接数:第51个进入等待队列
        comboPooledDataSource.setMaxPoolSize(50);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; ++i) {
            //从 DataSource 接口实现
            Connection connection = comboPooledDataSource.getConnection();

            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("c3p0 5000次连接时间:"+(end-start)+"ms");//1108ms
    }

    @Test
    // 方式2:使用配置文件模板来完成
    // 1、将 c3p0-config.xml 放到src下面
    public void testC3P0_02() throws SQLException {
        // 2、填入的是xml中设置的连接池的名称
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("yb0os1");
        Connection connection = comboPooledDataSource.getConnection();
        System.out.println("连接成功");
        connection.close();
    }
}

c3p0-config.xml

<c3p0-config>
<!--数据源的名称 也就是 连接池-->
  <named-config name="yb0os1">
<!-- 驱动类 -->
  <property name="driverClass">com.mysql.jdbc.Driver</property>
  <!-- url-->
  	<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/testdb</property>
  <!-- 用户名 -->
  		<property name="user">root</property>
  		<!-- 密码 -->
  	<property name="password">yb0os1</property>
  	<!-- 每次增长的连接数-->
    <property name="acquireIncrement">5</property>
    <!-- 初始的连接数 -->
    <property name="initialPoolSize">10</property>
    <!-- 最小连接数 -->
    <property name="minPoolSize">5</property>
   <!-- 最大连接数 -->
    <property name="maxPoolSize">50</property>

	<!-- 可连接的最多的命令对象数 -->
    <property name="maxStatements">5</property> 
    
    <!-- 每个连接对象可连接的最多的命令对象数 -->
    <property name="maxStatementsPerConnection">2</property>
  </named-config>
</c3p0-config>

Druid(德鲁伊)

druid.properties

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/testdb?rewriteBatchedStatements=true
username=root
password=yb0os1
#初始化的连接数
initialSize=10
#最小连接数
minIdle=5
#最大连接数
maxActive=50
#最大等待时间 在等待队列的最长等待时间 ms
maxWait=5000
package jdbc;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;

public class Druidusing {
    @Test
    public void useDruid() throws Exception {
        //1、加入jar包
        //2、配置文件 druid.properties
        //3、创建properties对象 读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("./src/druid.properties"));
        //4、创建一个指定参数的数据库连接池  基于properties进行配置
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; ++i) {
            Connection connection = dataSource.getConnection();

            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("druid 500000 次连接时间:"+(end-start)+"ms");//349ms

    }
}

基于德鲁伊的封装

package jdbc;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCUtilsByDruid {
    private static DataSource ds;
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("./src/druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //数据库连接池close不是真正的释放 而是将connection放回连接池
    public static void close(ResultSet resultSet, Statement statement,Connection connection){
        try {
            //这里的close和JDBCUtils的close是不一样的 和JDBCUtils运行类型是不一样的
            //connection只是一个接口 他实现类是不一样的 一个是实现的德鲁伊 一个实现的是mysql的
            if (resultSet!=null)resultSet.close();
            if (statement!=null)statement.close();
            if (connection!=null)connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

Apache-DBUtils

分析 JDBCUtilsByDruid 存在的问题

  • 关闭connection后,结果集resultSet无法使用
  • resultSet不利用数据的管理

自己的方法解决

在这里插入图片描述

package jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class useUtilsByDruid {
    private static final List<Actor> mess = new ArrayList<>();
    public static void main(String[] args) throws Exception {
        Connection connection = JDBCUtilsByDruid.getConnection();
        String sql = "select * from actor where id <=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,10 );
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()){
            mess.add(new Actor(resultSet.getString("id"),resultSet.getString("name")));
//            System.out.println(resultSet.getString("id")+"-"+resultSet.getString("name"));
        }
        JDBCUtilsByDruid.close(resultSet,preparedStatement,connection);
        //关闭了之后还是可以使用数据
        for (Actor actor : mess) {
            System.out.println(actor.getId()+"-"+actor.getName());
        }
    }
}

Apache-DBUtils

  • commons-dbutils是Apache组织提供的一个开源的JDBC工具类库,他是对JDBC的封装,使用dbutils能极大的简化jdbc编码的工作量
  • DbUtils类
    • QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增删改查、批处理
    • ResultSetHandler接口:该接口用于处理 java.sql.ResultRet ,将数据按要求转换成另一种形式

在这里插入图片描述

package jdbc;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

public class UseDbUtils {
    Connection connection = null;

    //查询:返回的是多行多列记录
    @Test
    public void testQueryMany() {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("./src/druid.properties"));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            connection = dataSource.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            String sql = "select * from actor where id <= ?";

            /*query的参数:连接、sql语句、存放的容器、参数
             * 1、query方法 就是执行sql语句 得到 ResultSet 封装到ArrayList集合中
             * 2、返回集合
             * 3、connection:连接
             * 4、sql:执行的sql语句
             * 5、new BeanListHandler<>(Actor.class):将ResultSet->Actor对象->封装到ArrayList
             *   底层使用反射机制 获取Actor的属性
             * 6、 10:给?赋值 是一个可变形参
             * 7、底层得到的 ResultSet 会在query关闭;创建的preparedStatement也在底层关闭
             * */
            List<Actor> list =
                    queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 10);
            for (Actor actor : list) {
                System.out.println(actor.getId() + "-" + actor.getName());
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    //查询:返回的是单行多列记录
    @Test
    public void testQuerySingle() {
        try {
            connection = JDBCUtilsByDruid.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            String sql = "select * from actor where id = ?";

            Actor actor =
                    queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10);

            if (actor != null) System.out.println(actor.getId() + "-" + actor.getName());

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(null, null, connection);
        }
    }

    //查询:返回的是单行单列记录
    @Test
    public void testQueryScaler() {
        try {
            connection = JDBCUtilsByDruid.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            String sql = "select `name` from actor where id = ?";

            Object obj =
                    queryRunner.query(connection, sql, new ScalarHandler(), 10);
            System.out.println(obj);

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(null, null, connection);
        }
    }

    //dml操作 增删改
    @Test
    public void testDML() {
        try {
            connection = JDBCUtilsByDruid.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            String sql = "update actor set name=? where id =?";
            //返回值是受影响的行数
            //dml都是update
            int affectedRow = queryRunner.update(connection, sql, "张三丰", "100");
            System.out.println(affectedRow > 0 ? "执行成功" : "执行未影响到表");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

query函数

public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        T result = null;

        try {
            stmt = this.prepareStatement(conn, sql);
            this.fillStatement(stmt, params);
            rs = this.wrap(stmt.executeQuery());
            result = rsh.handle(rs);
        } catch (SQLException var33) {
            this.rethrow(var33, sql, params);
        } finally {
            try {
                this.close(rs);
            } finally {
                this.close((Statement)stmt);
            }
        }

        return result;
    }

BasicDao

apache-dbutils+druid还有一些不足

  • SQL语句是固定的,不能通过参数传入,通用性不足
  • 对于select操作,如果有返回值,返回值类型不能固定需要使用泛型
  • 将来的表有很多,业务很复杂,不可能只靠一个java类完成

DAO:data access object 访问数据的对象

这样的通用类,称为 BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。在BaiscDao 的基础上,实现一张表对应一个Dao,更好的完成功能,比如 Customer表-Customer.java类(iavabean)-CustomerDao.java

在这里插入图片描述

BasicDAO.java

package dao_.dao;

import dao_.utils.JDBCByDruid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class BasicDAO<T> {
    private QueryRunner qr = new QueryRunner();

    /***
     * dml操作 也就是增删改的操作
     * @param sql 要执行的sql语句
     * @param params sql语句中的参数
     * @return 受影响的行数
     */
    public int update(String sql,Object...params){
        Connection connection = JDBCByDruid.getConnection();
        try {
            return qr.update(connection,sql,params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCByDruid.close(null,null,connection);
        }
    }


    /***
     * 查询多行多列
     * @param sql 执行的sql语句
     * @param clazz 类的class对象,反射用到
     * @param params sql语句中的参数
     * @return 返回的数据
     */
    public List<T> queryMulti(String sql,Class<T>clazz,Object...params){
        Connection connection = JDBCByDruid.getConnection();
        try {
            return qr.query(connection,sql,new BeanListHandler<>(clazz),params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCByDruid.close(null,null,connection);
        }
    }

    /***
     * 查询单行多列
     * @param sql 执行的sql语句
     * @param clazz 类的class对象,反射用到
     * @param params sql语句中的参数
     * @return 返回的数据
     */
    public T querySingle(String sql,Class<T>clazz,Object...params){
        Connection connection = JDBCByDruid.getConnection();
        try {
            return qr.query(connection,sql,new BeanHandler<>(clazz),params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCByDruid.close(null,null,connection);
        }
    }

    /***
     * 查询单行单列
     * @param sql 执行的sql语句
     * @param params sql语句中的参数
     * @return 返回的数据
     */
    public Object queryScalar(String sql,Object...params){
        Connection connection = JDBCByDruid.getConnection();
        try {
            return qr.query(connection,sql,new ScalarHandler(),params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCByDruid.close(null,null,connection);
        }
    }
}
  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值