Java基础(二十五):JDBC

文章目录

  • 一、JDBC
    • 1.1 概述
    • 1.2 快速入门
  • 二、获取数据库连接的5种方式
  • 三、ResultSet
  • 四、Statement与PreparedStatement
    • 4.1 Statement存在SQL注入
    • 4.2 PreparedStatement
  • 五、JDBC API
  • 六、事务
  • 七、批处理
  • 八、数据库的连接方式
    • 8.1 传统的连接方式
    • 8.2 数据库的连接池技术
      • 8.2.1 C3P0
      • 8.2.2 德鲁伊连接池
  • 九、自定义连接数据库的工具类
    • 9.1 自定义JDBCUtils工具类
    • 9.2 使用Druid实现JDBCUtils工具类
  • 十、使用开源的DBUtils工具类操作数据库
    • 10.1 问题引入
    • 10.2 使用DBUtils+数据连接池(Driud)方式,操作数据库
  • 十一、【最终版】BasicDao+Druid


一、JDBC

1.1 概述

  1. 为访问不同的数据库提供了统一的接口
  2. Java程序员使用jdbc,可以连接任何提供了jdbc驱动程序的数据库系统

1.2 快速入门

JDBC编写步骤:

  1. 注册驱动 - 加载Driver类
  2. 获取连接 - 得到Connection
  3. 执行增删改查 - 发送SQL命令给到mysql执行
  4. 释放资源 - 关闭相关的连接

第一步:新建一个libs文件夹,将mysql的jdbc的jar包放入其中,然后右击add as library将jar包导入项目

Java基础(二十五):JDBC_第1张图片

第二步:向数据库mydb的表actor插入一条数据

package JDBC_Exercise;


import com.mysql.cj.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * @author 神代言
 * @version 1.0
 */
public class Jdbc01 {
    public static void main(String[] args) throws SQLException {
        //将mysql.jar 拷贝到该目录下,加载为库

        //1. 注册驱动
        // 创建一个driver对象:new com.cj.mysql.jdbc.Driver   8.0版本需要.cj
        Driver driver = new Driver();

        //2. 得到连接
        //解读:jdbc:mysql:// 规定好的协议,通过jdbc的方式来连接mysql
        //(2)localhost 主机,可以是IP地址
        //(3)3306 表示mysql监听的端口
        //(4)jdbc 表示连接到mysql dbms 的哪个数据库
        //(5)mysql的连接本质:就是socket连接
        //(6)serverTimezone=UTC需要设置时区
        String url = "jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC";
        //将 用户名和密码放入到Properties 对象
        Properties properties = new Properties();
        //说明:user  和 password 是规定好的,后边的值根据实际情况来写
        properties.setProperty("user", "root");//用户
        properties.setProperty("password", "123456");//密码
        //根据url连接数据库
        Connection connect = driver.connect(url, properties);

        //3. 执行sql语句
        String sql = "insert into actor values(null,'吴彦祖','男','2023-09-14','1235')";
        //用于执行静态sql语句,并返回结果
        Statement statement = connect.createStatement();
        int i = statement.executeUpdate(sql);//如果是 dml 语句,返回的就是影响行数
        System.out.println(i > 0 ? "成功" : "失败");

        //4. 关闭连接资源
        statement.close();
        connect.close();
    }
}

二、获取数据库连接的5种方式

package JDBC_Exercise;


import com.mysql.cj.jdbc.Driver;
import org.junit.jupiter.api.Test;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class Jdbc01 {
    public static void main(String[] args)  {

    }
    @Test
    public void jdbcConnect01() throws SQLException{
        //方式一:静态加载
        Driver driver = new Driver();
        String url = "jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC";
        Properties properties = new Properties();
        properties.setProperty("user", "root");//用户
        properties.setProperty("password", "123456");//密码
        Connection connect = driver.connect(url, properties);
        System.out.println("方式1 " + connect);
        connect.close();
    }
    @Test
    public void jdbcConnect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
        //方式二:使用反射加载Diver类 动态加载,更加灵活
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver)aClass.newInstance();
        String url = "jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC";
        Properties properties = new Properties();
        properties.setProperty("user", "root");//用户
        properties.setProperty("password", "123456");//密码
        Connection connect = driver.connect(url, properties);
        System.out.println("方式2 " + connect);
        connect.close();
    }
    @Test
    public void jdbcConnect03()throws Exception{
        //方式三:使用DriverManager替代Manager进行 统一管理
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver)aClass.newInstance();
        String url = "jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC";
        String user = "root";
        String password = "123456";

        DriverManager.registerDriver(driver);//注册Driver驱动

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式3 " + connection);
        connection.close();
    }
    @Test
    public void jdbcConnect04()throws Exception{
        //方式四:使用Class.forName自动完成注册驱动,简化代码
        // 在加载Driver类时,自动完成了注册
        Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");//可以省略,但是建议写上
        String url = "jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC";
        String user = "root";
        String password = "123456";

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式4 " + connection);
        connection.close();
    }
    @Test
    public void jdbcConnect05()throws Exception{
        //方式五:在第四种上优化,设置配置文件,更加灵活
		// ------使用的最多------
        // 从配置文件拿到信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        Class<?> aClass = Class.forName(driver);//可以省略,但是建议写上
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式5 " + connection);
        connection.close();
    }
}

三、ResultSet

  1. 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成;
  2. ResultSet 对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前;
  3. next 方法将光标移动到下一行,并且由于在 ResultSet 对象中没有更多行时返回 false ,因此可以在 while 循环中使用循环来遍历结果集。
package JDBC_Exercise;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class ResultSet_ {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        // 1.注册驱动
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        // 2.得到连接
        Class<?> aClass = Class.forName(driver);//可以省略,但是建议写上
        Connection connection = DriverManager.getConnection(url, user, password);
        // 3.得到statement
        Statement statement = connection.createStatement();
        // 4.组织SQL语句
        String sql = "select id,name,sex,borndate,phone from actor";
        ResultSet resultSet = statement.executeQuery(sql);
        while (resultSet.next()) {
            int id = resultSet.getInt(1);
            String name = resultSet.getString(2);
            String sex = resultSet.getString(3);
            Date date = resultSet.getDate(4);
            String phone = resultSet.getString(5);
            System.out.println(id + "\t" + name + "\t" + sex
                    + "\t" + date + "\t" + phone);
        }
        // 5.关闭
        resultSet.close();
        statement.close();
        connection.close();
    }
}


四、Statement与PreparedStatement

4.1 Statement存在SQL注入

演示SQL注入:

package JDBC_Exercise;

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

public class Statement_ {
    public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
        Scanner scanner = new Scanner(System.in);
        //让用户输入管理员名和密码
        System.out.print("请输入名字:");
        /*  next 接收到 空格就结束
            如果希望看到sql注入,则这里需要使用nextLine */
        String admin_name = scanner.nextLine();
        System.out.print("请输入电话:");
        String admin_pwd = scanner.nextLine();

        //通过properties获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1.注册驱动
        Class.forName(driver);
        //2.得到连接
        Connection connection = DriverManager.getConnection(url, user, password);
        //3.得到statement
        Statement statement = connection.createStatement();
        //4.组织一个sql语句
        String sql = "select name,phone from actor " +
                "where name ='" + admin_name + "' and pwd = '" + admin_pwd + "'";
        ResultSet resultSet = statement.executeQuery(sql);
        if (resultSet.next()) {  //查询到一条记录,则说明存在
            System.out.println("成功");
        } else {
            System.out.println("失败");
        }
        resultSet.close();
        statement.close();
        connection.close();
    }
}

结果分析:名字和电话显然是错的,但是这条语句还能成功,显然存在SQL、注入。

请输入名字:1' or
请输入电话:or '1'= '1
成功

4.2 PreparedStatement

  1. 不再使用 + 拼接SQL语句,减少语法错误
  2. 有效的解决了sql注入问题
  3. 大大减少了编译次数,效率较高
package JDBC_Exercise;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class PreparedStatement_ {
    public static void main(String[] args) throws Exception{

        Scanner scanner = new Scanner(System.in);
        //让用户输入管理员名和密码
        System.out.print("请输入管理员的名字:");//next 接收到 空格就结束
        String admin_name = scanner.nextLine();//如果希望看到sql注入,则这里需要使用nextLine
        System.out.print("请输入管理员的密码:");
        String admin_pwd = scanner.nextLine();


        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");


        Class.forName(driver);
        Connection connection = DriverManager.getConnection(url, user, password);

        //3.得到PreparedStatement
        // 3.1 组织一个sql语句 ?:相当于占位符
        String sql = "select name ,pwd from admin where NAME =? and pwd =?";
        // 3.2 preparedStatement 实现了 PreparedStatement 接口的一个实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        // 3.3 给? 赋值
        preparedStatement.setString(1,admin_name);
        preparedStatement.setString(2,admin_pwd);
        //4.执行
        // 这里执行 executeQuery ,不要写sql,之前已经赋值
        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next()){  //查询到一条记录,则说明该管理存在
            System.out.println("成功");
        }else {
            System.out.println("失败");
        }
        //5.关闭
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

五、JDBC API

Java基础(二十五):JDBC_第2张图片

Java基础(二十五):JDBC_第3张图片


六、事务

Java基础(二十五):JDBC_第4张图片

不使用事务

@Test
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.创建PrepareStatement 对象
    try {
        connection = JDBCUtils.getConnection();//在默认情况下,connection对象是默认自动提交
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.executeUpdate();//执行第一条sql,并且自动提交commit
        int i = 1/0;         // 会抛出异常,会导致前一条语句执行成功,后一条语句不执行
        preparedStatement = connection.prepareStatement(sql2);
        preparedStatement.executeUpdate();//执行第二条sql,并且自动提交commit
    } catch (SQLException e) {
        throw new RuntimeException(e);
    } finally {
        //关闭资源
        JDBCUtils.close(null,preparedStatement,connection);
    }
}

使用事务

@Test
//使用事务解决
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.创建PrepareStatement 对象
    try {
        connection = JDBCUtils.getConnection();//在默认情况下,connection对象是默认自动提交
        //将connection 设置为不自动提交
        connection.setAutoCommit(false);//相当于开启了事务
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.executeUpdate();//执行第一条sql
        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) {
            throw new RuntimeException(ex);
        }
        throw new RuntimeException(e);
    } finally {
        //关闭资源
        JDBCUtils.close(null,preparedStatement,connection);
    }
}

七、批处理

  1. 当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
  2. JDBC的批量处理语句包括下面方法:
    JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数? rewriteBatchedStatements=true
    addBatch():添加需要批量处理的SQL语句或参数;
    executeBatch():执行批量处理语句;
    clearBatch():清空批处理包的语句;
  3. 批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高

代码演示

package JDBC_Exercise;

import org.junit.jupiter.api.Test;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class Batch_ {
    @Test
    public void noBatch() throws Exception {//3000ms
        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++) {
            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);
    }
    //使用批量处理添加数据
    @Test
    public void batch() 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++) {
            preparedStatement.setObject(1,"jack" + i);
            preparedStatement.setObject(2,"666");
            //将sql语句加入到批处理包中 -> 源码
            preparedStatement.addBatch();
            //当有1000条记录时,再批量执行
            if ((i + 1) % 1000 == 0){
                preparedStatement.executeBatch();
                //清空
                preparedStatement.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("batch的方式 耗时=" + (end - start));
        JDBCUtils.close(null,preparedStatement,connection);
    }
}

八、数据库的连接方式

8.1 传统的连接方式

Java基础(二十五):JDBC_第5张图片

8.2 数据库的连接池技术

这里懒得写了,参考了这篇博客

Java基础(二十五):JDBC_第6张图片

Java基础(二十五):JDBC_第7张图片

8.2.1 C3P0

前期工作:配置文件
将C3P0的jar包按照上述jdbc的方式导入项目中,并且书写c3p0-config.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!--配置连接池mysql-->
    <named-config name="c3p0_mysql">           <!--数据源名称代表连接池 这里等会连接时候使用-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>    <!--驱动类-->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mysql?characterEncoding=utf-8&serverTimezone=UTC</property>  <!--url-->
        <property name="user">root</property>            <!--用户名-->
        <property name="password"></property>      <!--密码-->
        <property name="initialPoolSize">10</property>  <!--初始化的连接数-->
        <property name="acquireIncrement">5</property>  <!--每次增长的连接数-->
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">60</property>  <!--最大的连接数-->
        <property name="minPoolSize">10</property>   <!--最小的连接数-->
    </named-config>
    <!--配置连接池2,可以配置多个-->
</c3p0-config>

方式一:

@Test
public void testC3P0_01() throws Exception{
    //1.创建一个数据源对象
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    //2.通过配置文件mysql.propertie获取相关的信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\mysql.properties"));
    //读取相关的属性值
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

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

    //设置初始化连接数
    comboPooledDataSource.setInitialPoolSize(10);
    //设置最大连接数
    comboPooledDataSource.setMaxPoolSize(50);
    //测试连接池的效率,测试对mysql 5000ci操作
    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次mysql 耗时=" + (end-start));

}

方式二:

//方式二:使用配置文件模板来完成

//1. 将c3p0 提供的 xml 文件拷贝到src目录下
//2. 该文件指定了连接数据库和连接池的相关参数
@Test
public void testC3P0_02() throws SQLException {
    ComboPooledDataSource com = new ComboPooledDataSource("c3p0_mysql");
    //测试5000次连接MySQL
    long start = System.currentTimeMillis();
    System.out.println("开始执行");
    for (int i = 0; i < 5000; i++) {
        Connection connection = com.getConnection();
        //System.out.println("连接成功---");
        connection.close();
    }
    long end = System.currentTimeMillis();
    System.out.println("c390的第二种方式 耗时=" + (end-start));
}

8.2.2 德鲁伊连接池

前期工作:配置文件
将driud的jar包按照上述jdbc的方式导入项目中,并且书写druid.properties文件
Java基础(二十五):JDBC_第8张图片
演示

@Test
public void testDruid() throws Exception {
    //1. 加入jar 包
    //2. 加入配置文件druid.properties
    //3. 创建Properties对象,读取配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\druid.properties"));
    //4. 创建一个指定参数的数据库连接池,Druid连接池
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    long start = System.currentTimeMillis();
    for (int i = 0; i < 500000; i++) {
        Connection connection = dataSource.getConnection();
       //System.out.println("连接成功");
        connection.close();
    }
    //druid连接池 操作5000次 耗时268
    long end = System.currentTimeMillis();
    System.out.println("druid连接池 耗时" + (end - start));//耗时332
}

九、自定义连接数据库的工具类

9.1 自定义JDBCUtils工具类

JDBCUtils

package JDBC_Exercise;

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

/**
 * @author 神代言
 * @version 1.0
 * JDBC连接数据库的工具类
 */
public class JDBCUtils {
    //定义相关的属性(4个),因为因为只需要一次,因此,我们做成static
    private static String user;//用户名
    private static String password;//密码
    private static String url;//url
    private static String driver;//驱动名字
    //在static代码块中去初始化
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\mysql.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 或者 PrepareStatement
        3.Connection
        4.如果需要关闭资源,就传入对象否则传入null
     */
    public static void close(ResultSet resultSet, Statement statement, Connection connection){
        //判断是否为null
        try {
            if (resultSet!=null){
                resultSet.close();
            }
            if (statement!=null){
                statement.close();
            }
            if (connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            //将编译异常转成运行异常抛出
            throw new RuntimeException(e);
        }
    }
}

测试JDBCUtils

package JDBC_Exercise;

import org.junit.jupiter.api.Test;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class JDBCUtils_Use {
    @Test
    public void testDML(){
        //1.得到连接
        Connection connection = null;
        //2.组织sql语句
        String sql = "update actor set name = ? where id = ?";
        //delete 与 insert
        PreparedStatement preparedStatement = null;
        //3.创建PrepareStatement 对象
        try {
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            //给占位符赋值
            preparedStatement.setObject(1,"周星驰");
            preparedStatement.setObject(2,1);
            //执行
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtils.close(null,preparedStatement,connection);
        }
    }
    @Test
    public void testSelect(){
        //1.得到连接
        Connection connection = null;
        //2.组织sql语句
        String sql = "select * from actor";
        //delete 与 insert
        PreparedStatement preparedStatement = null;
        ResultSet set = null;
        //3.创建PrepareStatement 对象
        try {
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            //执行
            set = preparedStatement.executeQuery();
            //遍历该结果集
            while (set.next()) {
                int id = set.getInt("id");
                String name = set.getString("name");
                String sex = set.getString("sex");
                Date borndate = set.getDate("borndate");
                String phone = set.getString("phone");
                System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtils.close(set,preparedStatement,connection);
        }
    }
}

9.2 使用Druid实现JDBCUtils工具类

编写工具类

package JDBC_Exercise;

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;

/**
 * @author 神代言
 * @version 1.0
 */
public class JDBCUtilsByDriud {
    private static DataSource ds;
    // 在静态代码块中完成ds的初始化
    static{
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 编写getConnection方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    // 关闭连接
    // 注意:Driud连接池技术在这里不是断掉连接,而是把连接放回连接池
    /*
        1.ResultSet 结果集
        2.Statement 或者 PrepareStatement
        3.Connection
        4.如果需要关闭资源,就传入对象否则传入null
     */
    public static void close(ResultSet resultSet, Statement statement, Connection connection){
        //判断是否为null
        try {
            if (resultSet!=null){
                resultSet.close();
            }
            if (statement!=null){
                statement.close();
            }
            if (connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            //将编译异常转成运行异常抛出
            throw new RuntimeException(e);
        }
    }
}

测试工具类

package JDBC_Exercise;

import org.junit.jupiter.api.Test;

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

/**
 * @author 神代言
 * @version 1.0
 * 测试JDBCUtilsByDriud工具类
 */
public class JDBCUtilsByDriud_Use {
    @Test
    public void testDML(){
        //1.得到连接
        Connection connection = null;
        //2.组织sql语句
        String sql = "update actor set name = ? where id = ?";
        //delete 与 insert
        PreparedStatement preparedStatement = null;
        //3.创建PrepareStatement 对象
        try {
            connection = JDBCUtilsByDriud.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            //给占位符赋值
            preparedStatement.setObject(1,"周星驰");
            preparedStatement.setObject(2,1);
            //执行
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtilsByDriud.close(null,preparedStatement,connection);
        }
    }
    @Test
    public void testSelect(){
        //1.得到连接
        Connection connection = null;
        //2.组织sql语句
        String sql = "select * from actor";
        //delete 与 insert
        PreparedStatement preparedStatement = null;
        ResultSet set = null;
        //3.创建PrepareStatement 对象
        try {
            connection = JDBCUtilsByDriud.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            //执行
            set = preparedStatement.executeQuery();
            //遍历该结果集
            while (set.next()) {
                int id = set.getInt("id");
                String name = set.getString("name");
                String sex = set.getString("sex");
                Date borndate = set.getDate("borndate");
                String phone = set.getString("phone");
                System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtilsByDriud.close(set,preparedStatement,connection);
        }
    }
}

十、使用开源的DBUtils工具类操作数据库

10.1 问题引入

问题引入

  1. resultSet与connection是关联的,关闭connection后,resultSet结果集无法使用;
  2. resultSet不利于数据管理。
    Java基础(二十五):JDBC_第9张图片

如何解决上述问题?使用原始的方法,创建一个Actor实体类,将resultSet结果集放入一个Actor数组中。

@Test
public ArrayList<Actor> testSelectToArrayList(){
    System.out.println("我们使用Druid方式完成");
    //1.得到连接
    Connection connection = null;
    //2.组织sql语句
    String sql = "select * from actor";
    //delete 与 insert
    PreparedStatement preparedStatement = null;
    ResultSet set = null;
    ArrayList<Actor> list = new ArrayList<Actor>();
    //3.创建PrepareStatement 对象
    try {
        connection = JDBCUtilsByDruid.getConnection();
        System.out.println(connection.getClass());//运行类型
        preparedStatement = connection.prepareStatement(sql);
        //执行
        set = preparedStatement.executeQuery();
        //遍历该结果集
        while (set.next()) {
            int id = set.getInt("id");
            String name = set.getString("name");
            String sex = set.getString("sex");
            Date borndate = set.getDate("borndate");
            String phone = set.getString("phone");
            //把得到的result 的记录,封装到 Actor 对象,放入到list集合
            list.add(new Actor(id,name,sex,borndate,phone));
        }
        System.out.println("list集合数据=" + list);
    } catch (SQLException e) {
        throw new RuntimeException(e);
    } finally {
        //关闭资源
        JDBCUtilsByDruid.close(set,preparedStatement,connection);
    }
    return list;
}

但是,显然这样还是很麻烦,因此,接下来介绍Apache提供的开源工具类-DBUtils。

10.2 使用DBUtils+数据连接池(Driud)方式,操作数据库


前期工作:配置文件
将DBUtils和driud的jar包按照上述jdbc的方式导入项目中,并且书写druid.properties文件

Actor实体类

package JDBC_Exercise;

import java.util.Date;

/**
 * @author 神代言
 * @version 1.0
 */
public class Actor {
    private Integer id;
    private String name;
    private String sex;
    private Date borndate;
    private String phone;
    public Actor(){
    }

    public Actor(Integer id, String name, String sex, Date borndate, String phone) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.borndate = borndate;
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Actor{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", borndate=" + borndate +
                ", phone='" + phone + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    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;
    }

    public Date getBorndate() {
        return borndate;
    }

    public void setBorndate(Date borndate) {
        this.borndate = borndate;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

}

演示DBUtils+数据连接池(Driud)

package JDBC_Exercise;

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.jupiter.api.Test;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class DBUtils_Use {
    // 使用apache-DBUtils工具类+driud连接池方式,完成对数据库的操作

    @Test
    // 返回结果是多行多列的情况 BeanListHandler
    public void testQueryMany() throws SQLException {
        //1.先得到一个连接 druid
        Connection connection = JDBCUtilsByDruid.getConnection();
        //2.使用DBUtils 类和接口,先引入DBUtils相关的jar文件,加入到本Project
        //3.创建一个 QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4.就可以执行相关的方法,返回ArrayList结果集
        String sql = "select * from actor where id >= ?";
        //解读 query 方法就是执行一个sql 语句,得到resultset --封装--> ArrayList 集合中,然后返回集合
        //     query 底层会将resultset和PreparedStatement关闭
        // 1)connection:连接
        // 2)sql:执行的sql语句
        // 3)new BeanListHandler<>(actor.class):将resultset -> actor对象 -> 封装到 ArrayList
        //   底层使用反射机制 去获取actor类的属性,然后进行封装
        // 4)1 就是给sql语句中?赋值的,可以有多个值,因为是可变参数
        List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
        System.out.println("输出集合的的信息");
        for(Actor actor : list){
            System.out.println(actor);
        }
        //释放资源
        JDBCUtilsByDruid.close(null,null,connection);
    }
    @Test
    // 返回结果是单行多列的情况 BeanHandler
    public void testQuerySingle() throws SQLException {
        Connection connection = JDBCUtilsByDruid.getConnection();
        QueryRunner queryRunner = new QueryRunner();
        String sql = "select * from actor where id = ?";
        // 单行所以使用的是BeanHandler
        Actor query = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 4);
        System.out.println(query);
        JDBCUtilsByDruid.close(null,null,connection);
    }
    @Test
    // 返回结果是单行单列的情况 ScalarHandler
    public void testScalar() throws SQLException {
        Connection connection = JDBCUtilsByDruid.getConnection();
        QueryRunner queryRunner = new QueryRunner();
        String sql = "select name from actor where id = ?";
        //
        Object query = queryRunner.query(connection, sql, new ScalarHandler(), 1);
        System.out.println(query);
        JDBCUtilsByDruid.close(null,null,connection);
    }
    @Test
    // 完成DML(update,insert,delete)
    public void testDML() throws SQLException {
        Connection connection = JDBCUtilsByDruid.getConnection();
        QueryRunner queryRunner = new QueryRunner();
        String sql = "update actor set name = ? where id = ?";
        // 返回结果是受影响的行数
        int affectedRow = queryRunner.update(connection, sql, "张三丰", 1);
        System.out.println(affectedRow);
        JDBCUtilsByDruid.close(null,null,connection);
    }
}

十一、【最终版】BasicDao+Druid

上述apache-DBUtils+Druid 简化了JDBC开发,但还有不足:

  1. SQL 语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行 增删改查;
  2. 对于select 操作,如果有返回值,返回类型不能固定,需要使用泛型;
  3. 将来的表很多,业务需求复杂,不可能只靠一个Java类完成

框架结构:

  1. com.utils:编写工具类JDBCUtilsByDruid(用于连接/关闭数据库)
  2. com.domain:java bean实体类
  3. com.dao:存放***Dao和BasicDao(其他DAO的父类)(DAO数据访问对象,用于操作数据库的增删改查)
  4. com.test:编写测试类

Java基础(二十五):JDBC_第10张图片
Actor实体类和JDBCUtilsByDruid工具类上面有,这里就不演示了

BasicDao

package JDBC_Exercise.dao;

import JDBC_Exercise.utils.JDBCUtilsByDruid;
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;

/**
 * @author 神代言
 * @version 1.0
 * 其他Dao的父类,是Dao的共性
 */
public class BasicDao<T> { //泛型
    private QueryRunner qr = new QueryRunner();

    // 更新
    public int update(String sql, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            int update = qr.update(connection, sql, parameters);
            return update;
        } catch (SQLException e) {
            // 将编译异常->运行异常 抛出
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(null, null, connection);
        }
    }

    // 查询(多行)
    /**
     *
     * @param sql sql语句
     * @param classz 传入一个类的class对象,例如:Actor.class
     * @param parameters sql语句中的?对应的参数
     * @return
     */
    public List<T> queryMultiply(String sql, Class<T> classz, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            return qr.query(connection, sql, new BeanListHandler<T>(classz), parameters);
        } catch (SQLException e) {
            // 将编译异常->运行异常 抛出
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(null, null, connection);
        }
    }
    //查询单行结果的通用方法
    public T querySingle(String sql,Class<T> clazz,Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            return qr.query(connection,sql,new BeanHandler<T>(clazz),parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);//将一个编译异常->运行异常
        } finally {
            JDBCUtilsByDruid.close(null,null,connection);
        }
    }
    //查询单行单列的方法,即返回单值的方法
    public Object  queryScalar(String sql,Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            return qr.query(connection,sql,new ScalarHandler<>(),parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);//将一个编译异常->运行异常
        } finally {
            JDBCUtilsByDruid.close(null,null,connection);
        }
    }
}

ActorDao

package JDBC_Exercise.dao;

import JDBC_Exercise.domain.Actor;

/**
 * @author 神代言
 * @version 1.0
 * 继承BasicDao,并将自己的class类型传入实现泛型
 */
public class ActorDao extends BasicDao<Actor> {
    // 继承BasicDao的方法
    // 也可以编写自己的dao方法
}

测试Dao

package JDBC_Exercise.test;

import JDBC_Exercise.dao.ActorDao;
import JDBC_Exercise.domain.Actor;
import org.junit.jupiter.api.Test;

import java.util.List;

/**
 * @author 神代言
 * @version 1.0
 */
public class TestDao {
    @Test
    public void testActorDao(){
        //1.查询多行记录
        ActorDao actorDao = new ActorDao();
        List<Actor> actors = actorDao.queryMultiply("select * from actor where id >= ?", Actor.class, 1);
        for (Actor actor : actors) {
            System.out.println(actor);
        }
        //2.查询单行记录
        Actor actor = actorDao.querySingle("select * from actor where id = ?", Actor.class, 1);
        System.out.println("查询单行结果");
        System.out.println(actor);
        //3.查询单行单列
        Object o = actorDao.queryScalar("select name from actor where id =?", 3);
        System.out.println("====查询单行单列值=======");
        System.out.println(o);
        //4.dml 操作
        int update = actorDao.update("insert into actor values(null,?,?,?,?)", "岳小琳", "女", "1999-06-01", "13266");
        System.out.println(update > 0?"执行成功":"执行没有影响");
    }

}

特别说明
本文章是个人整理的学习笔记,参考b站韩顺平老师的课程(【零基础 快速学Java】韩顺平 零基础30天学会Java)。老师讲的非常好,有兴趣的可以去看一下。

你可能感兴趣的:(Java基础,java)