Java基础之JDBC

JDBC

意义

  • JDBC 技术,现代开发几乎所有项目都围绕数据库开展

    • 任何一门编程语言发展都必须对数据库开发支持
  • Java 最开始就支持数据库开发标准 JDBC:Java DateBase Connectivity

    • JDBC 本质上不是一个技术,而是一种服务
  • Java 为 JDBC 提供专门模块:java.sql

    • JDBC 核心组成 DriverManager 类 及若干接口
      • Connection
      • Statement
      • PreparedStatement
      • ResultSet

使用意义

  • 为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题

    • 使用 jdbc 可连接任何提供了 jdbc 驱动程序的数据库系统,完成对数据库的各种操作
      • jdbc 是 Java 提供用于数据库操作的接口API,只需要面向接口编程
        • 面向接口编程:降低耦合度,提供程序扩展性
      • 不同数据库厂商针对接口提供不同实现
    • JDBC API 是一系列接口
      • 统一和规范了应用程序与数据库的连接、执行 sql 语句并得到返回结果等各类操作
      • 相关类和接口在 java.sqljavax.sql
  • JDBC 程序数据库访问四种形式

    • JDBC-ODBC 桥连接

      • 利用微软ODBC技术进行数据连接
        • 再利用JDBC访问ODBC技术进行数据库开发
      • Java 默认支持技术,无需任何额外配置
      • 流程:程序 → JDBC → ODBC → 数据库
      • 操作性能极差,使用人数极少
    • JDBC 连接

      • 直接利用 JDBC 进行数据库连接处理
      • 流程:程序 → JDBC → 数据库
      • 一般只连接本地数据库
    • JDBC 网络连接

      • 通过特定的网络协议连接指定的数据库服务
      • 流程:程序 → JDBC → 网络数据库(IP地址、端口号)
    • JDBC 协议链接

      • 自己编写指定协议实现数据库的访问
        • 需要对各种协议掌握

使用

  1. Java 连接 MySQL 需要驱动包,解压后得到 jar 库文件,在对应的项目中导入该库文件

  2. 确保已经启动数据库的数据监听服务(listene) 和 实例服务(SID)

  3. 导入 jar 包,连接 MySQL(Oracle)

    • 配置数据库驱动程序
      • 使用记事本进行程序编写
        • 手动配置到 classpath
        • 对 Eclipse 等开发工具无效
      • Eclipse
        • JavaBuilderPath 中配置驱动程序
      • IDEA
        • 导入 jar 包
  • 为什么单独配置数据库驱动
    • JDBC 是操作标准,不牵扯具体实现类
      • 由数据库厂商实现
  • MySQL 8.0 以上版本
    • 驱动包版本 mysql-connector-java-8.0.16.jar
    • com.mysql.jdbc.Driver 更换为 com.mysql.cj.jdbc.Driver
    • 不需要建立 SSL 连接时需要显式关闭
      • allowPublicKeyRetrieval=true 允许客户端从服务器获取公钥
      • 还需要设置 CST 时区
        • jdbc:mysql://localhost:3306/test_demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone= UTC
        • MySql 8.0 版本 url 设置
        • 显式关闭 SSL连接、允许服务器获取公钥、设置时区

流程

  1. 注册驱动:加载 Driver

    • 通过反射机制加载数据库驱动程序类
      • Class aClass = Class.forName(“com.mysql.jdbc.Driver”)
      • MySQL 驱动类
        • com.mysql.jdbc.Driver":8.0 之前版本
        • com.mysql.cj.jdbc.Driver:8.0 及以上版本
      • Oracle 驱动类
        • Oracle.jdbc.driver.OracleDriver
  2. 获取连接:得到 Connection 对象

    • 通过 DriverManager 类方法获取 Connection 对象
      • Connection con = DriverManager. getConnection(url, username, password);
        • 传入连接地址、用户名、密码
    • Driver 类直接获得 Connection 对象
      • 先反射创建 Driver 实例化对象
        • Driver driver = (Driver)aClass.getDeclaredConstructor().newInstance();
        • 强制转型 Class 对象为 Driver
      • Connection con = driver.connect("url", Properties)
        • 获取连接对象;传入连接地址、配置文件
        • 配置文件由 Propertiesload 方法通过 IO 流加载
          • 文件中包括数据库连接信息:用户名、密码
  3. 获取数据库操作对象,传入 sql 语句

    • PreparedStatement pre = con. prepareStatement(sql)
      • proparedStatement(sql)statement()
      • 两种数据库操作类
  4. 执行增删改查:写 sql 语句到数据库执行,再返回结果

    • int rows = pre.executeUpdate()
      • 执行 DML 语句
      • 返回受影响行数
    • ResultSet set = pre.executeQuery()
      • 执行 DQL 语句
      • 返回查询结果集
      • 处理查询到的数据记录
  5. 释放资源:关闭各种连接

    • 防止资源占用浪费
    • set.close()
    • pre.close()
    • con.close()
    /*
    // 编写配置文件
    Properties pro = new Properties();
    pro.setProperty("url","jdbc:mysql://localhost:3306/demo01");
    pro.setProperty("user","用户名");
    pro.setProperty("password","密码");
    pro.setProperty("driver","com.mysql.jdbc.Driver");
    pro.store(new FileOutputStream("src\\mysql.properties"),"reduce");
    */
    Properties pro = new Properties();
    // 加载配置文件
    pro.load(new FileInputStream("src\\mysql.properties"));
    // 读取配置文件中的信息
    String user = pro.getProperty("user");
    String password = pro.getProperty("password");
    String driver = pro.getProperty("driver");
    String url = pro.getProperty("url");
    try {
        // 反射加载驱动类,可自动加载
        Class.forName(driver);
        // 驱动管理类获取数据库连接对象
        Connection con = DriverManager. getConnection(url, user, password);
        // 编写sql语句
        String sql = "insert into new values(1, '张三')";
        // 获取操作数据库对象并传入 sql 语句
        // 链式编程直接执行 sql 语句
        int rows = con.preparedStatement(sql) .executeUpdate();
        // 执行 dml 语句返回受影响行数, > 0 表示执行成功
        if (rows > 0){
            System.out.println("插入成功");
        }
        //关闭释放资源
        state.close();
        con.close();
    } catch (SQLException | ClassNotFoundException e){
        e.printStackTrace();
    }
    
    • url:要连接的数据库路径

      • MySQL

        • jdbc:mysql://localhost:3306/demo

          • 参数部分含义
            jdbc:mysql://Java规定的 JDBC 连接 MySQL 的协议
            localhos:要链接的数据库的 IP 地址
            3306数据库端口号
            demo要连接的数据库库名
        • 数据库连接本质是 socket 连接

      • Oracle

        • jdbc:oracle:thin:@localhost:1521:demo

        • 参数部分含义
          jdbc:oracle:thinJDBC 连接 Oracle 协议
          @localhost:要链接的数据库的 IP 地址
          1521端口号
          demo要连接的数据库库名
    • Properties:包含数据库信息的配置文件

      • user 和 password 两个 key 是规定好的
      • values 是实际 数据库名 和 密码
    • sql:要执行的 sql 语句

      • sql 语句根据 sql 语法编写

连接方式

五种方式
  1. 直接使用 com.myql.jdbc.Driver 类创建驱动对象

    • 属于静态加载,灵活性差,依赖性强

    • // 加载Driver类
      Driver driver = new Driver();
      // 要连接的数据库的 url
      String url = "jdbc:mysql://localhost:3306/demo";
      // 将数据库的用户名、密码写入配置文件 pro
      Properties pro = new Properties();
      pro.setProperty("user","用户名");
      pro.setProperty("password","密码");
      // 创建连接,将 url 和 pro 传入
      Connection con = driver.connect(url, pro);
      // 编写 sql 语句
      String sql = "insert into table_name values()";
      // Statement 用于执行静态 sql 语句并返回其执行结果的对象
      Statement state = con.createStatement();
      // 执行sql语句,并返回受影响行数
      int rows = state.executeUpdate(sql);
      // 关闭释放资源
      state.close();
      con.close();
      
  2. 通过反射加载

    • 动态加载,减少依赖性

    • Class clazz = Class.forName("com.mysql.jdbc.Driver");
      Driver driver = (Driver)clazz.getDeclaredConstructor().newInstance();
      // 获得 Driver 对象后操作不变
      
  3. 使用 DriverManager 替换 Driver

    • 拓展性更好

    • Class clazz = Class.forName("com.mysql.jdbc.Driver");
      Driver driver = (Driver)clazz.newInstance();
      String url = "jdbc:mysql://localhost:3306/demo";
      // 无需创建 Properties
      String uesr = "用户名";
      String password = "密码";
      // 注册driver 驱动
      DriverManager.registerDriver(driver);
      // 获取连接;同时支持多种创建方式,可传入 Properties 配置文件
      Connection con = DriverManager.getConnection(url, user, password);
      // 获取连接对象后操作不变
      
  4. 使用 Class.forName() 自动完成注册驱动

    • Driver 类中静态代码块已经将驱动注册到 DriverManager

      • 加载 Driver 类后可直接使用驱动管理类 DriverManager 操作
      // 反射加载 Driver 类
      Class.forName("com.mysql.jdbc.Driver"); 					//可不写,建议加上
      String url = "jdbc:mysql://localhost:3306/demo";
      // 无需创建Properties
      String uesr = "用户名";
      String password = "密码";
      // 获取 Connection 连接,无需进行驱动注册
      Connection con = DriverManager.getConnection(url, user, password);
      // 获取连接后操作不变
      
    • mysql 驱动 5.1.6 后无需 Class.forName("com.mysql.jdbc.Driver")

      • jdk 1.5 后使用 jdbc4,不再需要显式调用 Class.forName() 加载驱动
      • 自动调用驱动包下的 META-INF\services\java.sql.Driver 文本中的类名称注册
    • 但建议写全

  5. 优化方式4

    • 增加配置文件,连接更加灵活

    • Properties pro = new Properties();
      // 加载配置文件
      pro.load(new FileInputStream( "src\\mysql.properties"));
      // 读取配置文件中的信息
      String user = pro.getProperty("user");
      String password = pro.getProperty( "password");
      String driver = pro.getProperty("driver");
      String url = pro.getProperty("url");
      // 加载驱动;可不写,建议写全
      Class.forName(driver); 
      // 使用 DriverManager 获取连接对象,支持直接传入 Properties 对象
      Connection con = DriverManager.getConnection(url, user, password);
      // 得到数据库操作对象并执行查询语句,得到查询结果集	
      Result set = con.prepareStatement(sql).executeQuery();
      // 结果集中存在数据
      while(set.next()){
          // 根据查询字段的顺序获取值,第一个字段
          String name = set.getString(1);
          // 根据字段名获取值
          int age = set.getInt("age");
          System.out.println(name + "\t" + age);
      }
      //关闭连接,释放资源
      set.close();
      con.close();
      
读取配置文件
  • ResuorceBundle 读取

    • 默认读取 classpath 路径下文件

      • src 目录下文件
    • 不能读取本地文件

    • ResourceBundle re = ResourceBundle.getBundle("文件m");
      String driver = re.getString("driver");
      String url = re.getString("url");
      String user = re.getString("user");
      String password = re.getString("password");
      Class.forName(driver);
      Connection con = DriverManager.getConnection(url, user, password);
      
  • Properties 类读取

    • Properties pro = new Properties();
      pro.load(new FileInputStream("文件路径"));
      String driver = pro.getProperty("driver");
      String url = pro.getProperty("url");
      String user = pro.getProperty("user");
      String password = pro.getProperty("password");
      Class.froName(driver);
      Connection con = DriverManager.getConnection(url, user, password);
      

JDBC_API

各种接口 API 中常用方法

DriverManager
  • 驱动管理类

    • getConnection(String url, String user, String password)

    • getConnection(String url, Properties info)

      • 获取连接对象
Connection
  • createStatement()
    • 创建 Statement 对象
  • prepareStatement(String sql)
    • 生成预处理对象
    • 对 sql 语句框架进行预编译处理
Statement
  • java.sql.Statement 是数据库操作接口

    • AutoCloseable 的子接口
      • 数据库操作后应关闭 Statement 操作
      • 即 sql 语句的执行是一个 Statement 接口对象
  • 建立连接后,需要对数据库进行访问,操作数据库:

    • Statement:可能有 SQL 注入问题
      • 操作方法
        • executeUptate(sql)
          • 执行 dml 语句,返回受影响行数
        • executeQuery(sql)
          • 执行查询语句,返回 ResultSet 结果集
        • execute(sql)
          • 执行任意SQL语句,返回布尔值
    • PreparedStatement:预处理
    • CallableStatement:存储过程
  • Statement 对象用于执行静态 sql 语句并返回其生成的结果的对象

    • 实现数据的更新与查询操作
  • 缺陷

    1. Statement 对象执行 SQL 语句,存在 SQL 注入风险
      • SQL注入:某些系统没有对用户输入的数据进行充分的检查,在用户输入数据中注入非法的 SQL 语句段或命令,恶意攻击数据库
      • 防范 SQL 注入只要用 PreparedStatement 取代 Statement 即可
        • PreparedStatementStatement 扩展而来
    2. 不能很好描述日期形式
    3. 需要进行 sql 语句拼接处理
      • 编写、维护麻烦
    4. 敏感字符无法合理拼凑
  • 使用场景

    • 业务实现要求使用允许 SQL 注入
      • 如:升、降序 需要输入 sql 关键字 desc、 asc
      • PreparedStatement 对关键字也会自动匹配为字符串类型,无法进行灵活排序
        • 会自动添加双引号
PreparedStatement
  • 预处理查询

    • 原理:预先对 sql 语句框架进行编译处理,然后再传入值
  • con.prepareStatement(sql);

    • 获取预处理对象并对 sql 框架进行预编译处理
  • 用户传入参数不会参与编译

    • 传入关键字也无法进行编译
      • 无法传入 descasc 等完成灵活操作
    • MySQL 本身就支持预编译,但是默认关闭
  • sql 语句中的值用占位符 来表示

    • setXxx(index, value) 方法来设置值
      • Xxx:值数据类型
      • index:占位符索引
      • value:值,自动根据数据类型匹配格式;例如字符串自动添加双引号
  • 好处

    1. 不再需要拼接 sql 语句,减少语法错误
    2. 有效解决 sql 注入的问题
    3. 减少编译次数,效率较高
      • sql 语句完全相同时不再编译直接执行
      • PreparedStatement sql 语句结构完全变化
        • 预编译后传入值直接执行
  • 常用方法

    • executeUptate()
      • 执行 dml 语句,返回受影响行数
    • executeQuery()
      • 执行查询语句,返回 ResultSet 结果集
    • execute()
      • 执行任意 SQL 语句,返回布尔值
    • setXxx(int index, Xxx x)
      • 有效解决 sql 注入问题
      • Xxx:传入字段的数据类型,例如:setInt(1, “字段”)
    • setObject(int index, Objcet o)
      • 当作对象传入

    在这里插入图片描述

  • 示例

    • try {
          Properties pro = new Properties();
          pro.load(new FileInputStream("src\\mysql.properties"));
          String user = pro.getProperty("user");
          String password = pro.getProperty("password");
          String driver = pro.getProperty("driver");
          String url = pro.getProperty("url");
          //加载驱动;可不写,
          Class.forName(driver); 
          Connection con = DriverManager.getConnection(url, user, password);
          String sql = "insert into table_name values()";
          String sql1 = "select * from table_name where name = ? and password = ?";
          // 执行dml语句,返回值为受影响行数
          // 直接填入 sql 语句,之后执行时不写
          int rows = con.prepareStatement(sql). executeUpdate();
          // 获取操作数据库对象
          PreparedStatement pre = con.prepareStatement( sql1);
          // 设置 ?值,1、2 表示索引,第几个?;传入对应值
          pre.setString(1, "用户名");
          pre.setString(2, "密码");
          // 执行查询语句; 返回ResultSet 对象 
          ResultSet set = pre.executeQuery();
          // 自定义处理结果集数据
          // 关闭释放资源
          set,close();
          pre.close();
          con.close();
      } catch (IOException | ClassNotFoundException | SQLException e) {
          e.printStackTrace();
      }
      
ResultSet
  • 结果集:表示数据库结果集的数据表

    • 通常通过执行查询数据库的语句生成
    • 查询结果保存在内存中,返回结果过大时可能会使程序崩溃
  • 类型

    • 创建 Statement 对象时决定
      • createStatement(int, int)
        • 第一个参数决定结果集是否滚动
        • 第二个参数决定结果集数据是否只读
    1. 是否可滚动
      • 默认可滚动
      • 设置第一个参数
        • ResultSet.TYPE_FORWORD_ONLY
          • 不滚动
        • ResultSet.TYPE_SCROLL_INSENSITIVE
          • 可滚动,不敏感
          • 结果集数据不随数据库再变化
        • ResultSet.TYPE_SCROLL_SENSITIVE
          • 可滚动,敏感
          • 结果集数据会随数据库变化
            • 数据库一般不实现该功能,正常不使用
      • 不可移动:只能使用 next() 方法移动光标
    2. 是否只读
      • 结果集只读数据
        • 不可通过修改结果集影响数据库数据
      • 设置第二个参数
        • ResultSet.CONCUR_READ_ONLY
          • 只读
        • ResultSet.CONCUR_UPDATABLE
          • 可更新,修改结果集会影响数据库数据
  • ResultSet 对象保持一个光标指向其当前的数据行

    • 最初光标在第一行之前

    • next() 方法将光标移动到下一行

      • 没有更多行时返回 false
        • while 循环中遍历结果集
    • previous():向上移动一行

    • getXxx() 方法获取对应字段数据

      • 传入 字段索引 或 字段名,得到对应字段值
        • 索引是查询语句中标注的字段的顺序
        • sql 中 通过 * 查询时按照表中字段顺序
        • sql 语句将字段取别名时使用别名获取数据
      • 例如
        • int id = getInt(1):id字段在第一个(查询 语句的顺序)
        • int id = get("id"):根据字段名 id 获取对应值(常用)
    • getMetaData():获取元数据

      • 返回值:ResultSetMetaData
    • getColumnCount()

      • 获取结果集列数
    • getColumnName(int colIndex)

      • 获取指定列列名
    • getObject()

      • 传入列名 或 索引,接受类型为Object
    • getDate()setDate() 日期类型都是 java.sql.Date

      • 但是使用 java.util.Date
        • 要转为 java.sql.Date
  • MySQL 和 Java 日期类型对应

    • datejava.sql.Date

    • timejava.sql.Time

    • timestampjava.Timestamp

    • 在 Java 中使用 java.util.Data 日期类型,对 java.sql.Data 类型需要转型

  • java.sql.Datajava.util.Date 的子类

    • 无需转型,可直接使用
  • java.util.Date 转为 sql 日期类型需要借助构造器

    • Date 类型转为毫秒数
      • long time = new java.util.Date().getTimes()
    • 用毫秒数创建 sql 包 Date、Time、Timestamp 对象
      • new java.sql.Date(time)

JDBC Utils

封装
  • 将JDBC对数据库的的连接、关闭 操作封装到JDBCUtils工具类

    package demo01;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.sql.*;
    import java.util.Properties;
    
    public class JDBCUtils {
        static String url;
        static String user;
        static String password;
        static String driver;
    
        // 静态代码块,在加载类时直接读取配置文件。只执行一次
        static {
            try {
                Properties pro = new Properties();
                pro.load(new FileInputStream("src\\mysql.properties"));
                url = pro.getProperty("url");
                user = pro.getProperty("user");
                password = pro.getProperty("password");
                driver = pro.getProperty("driver");
            } catch (IOException e) {
                // 在开发中将编译异常转成运行时异常抛出,调用者可以选择捕获或者默认处理
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 获取数据库连接
         * @return 返回已连接数据库的对象
         */
        public static Connection getConnection() {
            try {
                Class.forName(driver);
                return DriverManager.getConnection(url, user, password);
            } catch (SQLException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 关闭 JDBC 相关资源连接
         * 传参时可以传入null 表示该项不关闭
         * @param resultSet 结果集
         * @param statement 操作数据库
         * @param connection 数据库连接
         */
        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);
            }
        }
    
    }
    
使用
package demo01;

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

public class Test {

    /**
     * 执行dml语句示例
     * @return 返回受影响行数
     */
    public int dml() {
        int i = 0;													// DML 语句操作受影响行数
        Connection connection = null;								// 连接对象定义在异常块外,方便在 finally 块中使用
        PreparedStatement preparedStatement = null;
        try {
            connection = JDBCUtils.getConnection();					// 通过工具类获取连接
            String sql = "insert into tabel_naem values()";			// 编写 Sql 语句
            preparedStatement = connection.prepareStatement(sql);	// 预编译
            i = preparedStatement.executeUpdate();					// 执行 sql 
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCUtils.close(null, preparedStatement, connection);	// 关闭相关资源连接
        }
        return i;
    }

    /**
     * 示例执行查询语句
     */
    public void select(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "select * from table_name where id = ? and name = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,"id");
            preparedStatement.setString(2,"name");
            resultSet = preparedStatement.executeQuery();
            System.out.println("id\t\tname");
            while (resultSet.next()) {
                String id = resultSet.getString("id");
                String name = resultSet.getString("name");
                System.out.println(id + "\t\t" + name);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCUtils.close(resultSet, preparedStatement, connection);
        }
    }
}

事务

  • JDBC 程序中 Connection 对象创建时默认自动提交事务

    • 每次执行 SQL 语句成功时自动提交数据库,不能回滚
  • JDBC 中使用事务让多个 SQL 语句作为整体执行

    1. 创建 Connection 对象
      • 调用 setAutoCommit(false) 取消自动提交
        • setAutoCommit(boolean):默认开启自动提交
    2. 操作失败或异常时调用 rollback() 方法回滚
    3. 所有 SQL 语句都执行成功之后调用 commit() 方法提交事务
      • 不进行提交所有操作的结果都无法在数据库中可见
  • 同一事务需要使用同一个连接

    • 使用同一个 Connection 对象 进行操作

    • Connection con = null;
      PreparedStatement pre = null;
      ResultSet resultSet = null;
      try {
          con = JDBCUtils.getConnection();
          // 关闭自动提交,在 try 之后直接开启事务
          con.setAutoCommit(false);
          String sql = "select * from table_name where id = ? and name = ?";		// 编写 sql,两个参数占位符
          pre = con.prepareStatement(sql);										// 预编译 sql
          pre.setString(1, "id");													// 参数赋值
          pre.setString(2, "name");
          resultSet = pre.executeQuery();											// 执行 sql
          while (resultSet.next()) {												// 遍历结果集
              String id = resultSet.getString("id");
              String name = resultSet.getString("name");
              System.out.println(id + "\t\t" + name);
          }
          con.commit();												// 所有语句正常执行后提交事务,无异常时才执行到此处
      } catch (SQLException e) {
          try {														// 出现异常时回滚事务
              con.rollback();  										// 回退到开始状态,传入指定保存点回退到指定时刻
          } catch (SQLException throwables) {
              throwables.printStackTrace();
          }
          throw new RuntimeException(e);
      }finally {
          JDBCUtils.close(resultSet, pre, con);					    // 无论是否执行成功都要关闭连接
      }
      

批处理

  • 批量更新机制,允许多条语句一次性提交给数据库批量处理

    • 需要批量插入或更新记录时使用
      • 通常比单独提交处理更有效
  • JDBC 批处理方法

    • addBatch()
      • 添加需要批量处理的SQL语句或参数
      • 添加到集合临时保存
    • executeBatch()
      • 执行批处理语句
    • clearBatch()
      • 清空批处理包的语句
  • 使用批处理

    • MySQL 本身批处理机制默认关闭
      • 打开批处理功能
        • rewriteBatchedStatements=true
          • 追加在 url 后面,使用? 连接参数
          • jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true
  • 批处理和 PreparedStatement 一起使用

    • 既减少编译次数又减少运行次数
    • 效率很大提高
  • Connection connection = null;
    PreparedStatement preparedStatement = null;
    try {
        connection = JDBCUtils.getConnection();
        String sql = "insert into demo values(?,?)";
        preparedStatement = connection.prepareStatement(sql);
        int count = 0;
        // 添加5000条数据
        while (count < 5000){
            preparedStatement.setInt(1, count + 1);
            preparedStatement.setString(2, "name");
            // 添加批处理参数
            preparedStatement.addBatch();
            count++;
            // 满一千条SQL语句 执行一次,提交数据库
            if(count % 1000 == 0){
                // 批处理执行
                preparedStatement.executeBatch();
                // 执行完之后清除批处理包中的数据
                preparedStatement.clearBatch();
            }
        }
        /*
        // 添加完成后统一执行
        preparedStatement.executeBatch();
        // 执行完之后清除批处理包中的数据
        preparedStatement.clearBatch();
    	*/
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        JDBCUtils.close(null, preparedStatement, connection);
    }
    

连接池

传统连接

  • DriverManager 每次获取连接都要向数据库要求一个
    • 频繁进行数据库连接操作占用很多系统资源
      • 容易造成服务器崩溃
    • JDBC 获取连接都要先将 Connection 加载到内存
      • 再验证 IP、用户名 和 密码(0.05s ~ 1s)
    • 每次数据库连接用完之后都要断开
      • 若出现异常未能关闭,将导致数据库内存泄漏,最终导致数据库重启
  • 不能控制创建的连接数量
    • 连接过多可能导致内存泄漏、MySQL 崩溃
  • 采用数据库连接池技术(connection pool)解决数据库连接问题

连接池

介绍
  • 预先在缓冲池中放入一定数量的连接
    • 需要建立数据库连接时从缓冲池中取出一个
    • 使用完毕后再放回去
      • 不再引用该连接,而非关闭连接
  • 数据库连接池负责分配、管理 和 释放 数据库连接
    • 允许应用程序重复使用一个现有连接,而不是重新建立一个
  • 应用程序向连接池请求的连接数超过最大连接数量
    • 请求将被加入到等待队列
连接池种类
  • JDBC 的数据库连接池实现 javax.sql.DataSource
    • DateSource 是一个接口,通常由第三方提供实现
  • 常用数据库连接池
    • C3P0:速度相对较慢,稳定性不错
    • DBCP:速度相较 C3P0 快,但不稳定
    • Proxool:有监控连接池状态的功能,稳定性比 C3P0 差一点
    • BoneCP:速度快
    • Druid(德鲁伊):阿里提供的数据库连接池
      • 集 DBCP、C3P0、Proxccl 优点于一身
C3P0
  • 先导入 jar 包
使用方式1
try {
    // 读取配置文件
    Properties pro = new Properties();
    pro.load(new FileInputStream( "src\\mysql.properties"));
    String user = pro.getProperty("user");
    String password = pro.getProperty("password");
    String driver = pro.getProperty("driver");
    String url = pro.getProperty("url");

    // 创建连接池对象,并将 drive、url、user、password 写入
    ComboPooledDataSource combo = new ComboPooledDataSource();
    combo.setDriverClass(driver);
    combo.setUser(user);
    combo.setPassword(password);
    combo.setJdbcUrl(url);

    // 设置初始连接数为 10 个
    combo.setInitialPoolSize(10);
    // 最大连接数为 50 个,超过 50 个则进入排队等待
    combo.setMaxPoolSize(50);

    // 从连接池得到一个连接
    Connection connection = combo.getConnection();
    // 取消自动提交
    connection.setAutoCommit(false);
    String sql = "";
    PreparedStatement pre = connection.prepareStatement(sql);
    pre.executeUpdate();
    connection.commit();
    pre.close();
    connection.close();
} catch (IOException | PropertyVetoException | SQLException e) {
    e.printStackTrace();
}
使用方式2
  • 使用 xml 配置文件进行连接池配置

    • <c3p0-config>
          <!-- 配置文件名字 -->
          <named-config name="demo">
              <!-- 驱动类 -->
              <property name="driverClass">com.mysql.jdbc.Driver</property>
              <!-- url,修改为自己使用的数据库 url-->
              <property name="jdbcUrl">jdbc:mysql://192.168.137.1:3306/demo01</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">10</property>
      
              <!-- 可连接的最多的命令对象数 -->
              <property name="maxStatements">5</property>    
              <!-- 每个连接对象可连接的最多的命令对象数 -->
              <property name="maxStatementsPerConnection">2</property>
          </named-config>
      </c3p0-config>
      
  • 导入配置文件,创建连接池对象直接连接

    • try {
          // 创建连接池对象,传入配置文件名
          ComboPooledDataSource combo = new ComboPooledDataSource("demo");
          // 直接获取连接,加载 ComboPooledDataSource 时已读取配置文件,数据库连接已创建
          Connection con = combo.getConnection();
          String sql = "";
          PreparedStatement pre = con.prepareStatement(sql);
          pre.executeUpdate();
          pre.close();
          con.close();
      } catch ( SQLException e) {
          e.printStackTrace();
      }
      
Druid(建议)
使用
  • 导入jar包,导入配置文件到src目录

    • 修改配置文件

    • # key=value
      driverClassName=com.mysql.jdbc.Driver
      # url地址,开启批处理
      url=jdbc:mysql://localhost:3306/demo01?rewriteBatchedStatements=true
      # url=jdbc:mysql://localhost:3306/girls
      username=root
      password=123456
      # initial connection Size,初始连接数
      initialSize=10
      # min idle connection size,最小空闲
      minIdle=5
      # max active connection size,最大连接数
      maxActive=20
      # max wait time (5000 mil seconds),等待时间
      maxWait=5000
      
  • 得到连接

    • try {
          Properties pro = new Properties();
          // 加载配置文件
          pro.load(new FileInputStream("src\\druid.properties"));
          // 得到连接池对象
          DataSource dataS = DruidDataSourceFactory.createDataSource(pro);
          // 取出连接
          Connection connection = dataS.getConnection();
          connection.close();				// 关闭连接:将连接放回连接池,并非直接关闭
      } catch (Exception e) {
          e.printStackTrace();
      }
      
DruidUtils
  • 编写 Util 工具类,提供获取连接、关闭连接的方法
package demo01;

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 DruidUtils {

    private static DataSource dataSource ;

    // 静态加载配置文件,得到连接池对象,仅执行一次
    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从连接池得到连接
     * @return 返回连接
     * @throws SQLException 可能有SQL异常
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    // 关闭需要关闭的连接
    public static void close(ResultSet resultSet, Statement statement, Connection connection) {

        try {
            if (resultSet != null)
                resultSet.close();
            if (statement != null)
                statement.close();
            // connection 关闭只是将断掉连接引用放回连接池,而非彻底断掉连接
            if (connection != null)
                connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

}

Apache-DBUtils

作用
  • commons-dbutilsApache 组织提供的开源 JDBC 工具类库
    • 对 JDBC 封装,使用 dbutils 可以极大简化 jdbc 编码工作量
  • DBUtils
    • QueryRunner
      • 封装了 SQL 的执行,是线程安全的
        • 可实现增、删、改、查、批处理
      • public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params){}
        • 查询方法
        • conn:连接对象,可支持事务
        • sql:sql 语句
        • rsh:想要得到的结果集类型
        • params:sql 语句参数集
      • public int update(Connection conn, String sql, Object... params){}
        • dml 语句:增删改操作
        • 传入连接对象、sql 语句、sql 语句参数
    • ResultSetHandler 接口实现类
      • 处理 java.sql.ResultSet
        • 将数据按要求转化为另一种形式
      • ArrayHandler
        • 把结果集的第一行数据转换成对象数组
      • ArrayListHandler
        • 把结果集的每一行转换成对象数组,再存放到 List
      • BeanHandler
        • 将结果集的第一行数据封装到对应的 JavaBean 实例
      • BeanListHandler
        • 将结果集的每一行数据都封装到对应JavaBean 实例,存放到 List 中
      • ColumnListHandler
        • 将结果集的某一列数据存放到 List
      • KeyedHandler(name)
        • 将结果集中的每一行数据封装到 Map,根据指定 Key 将 Map 再存到一个 Map
      • MapHandler
        • 将结果集中第一行数据封装到一个Map,Key 是列名,value是对应的值
      • MapListHandler
        • 将结果集中每一行数据都封装到一个Map,再存放到 List
      • ScalarHandler
        • 单行单列结果,即 单个值
        • 用于 select count(*) from demo
        • 可能得到 Integer、Long、BigInteger 类型
          • 使用 Object 接收,强转为需求类型
示例
  • 执行常规查询

    • /**
      * 示例执行查询语句
      */
      public void select() {
          Connection connection = null;
          try {
              connection = JDBCUtils.getConnection();
              // 创建 QueryRunner
              QueryRunner runner = new QueryRunner();
              // 查询的 sql 语句,也可查询部分字段,其他字段在封装对象中为 null
              String sql = "select * from `user` where name = ? and age = ? ";
              //query() 方法执行 sql 语句,查询多条记录
              List<User> query = runner.query(connection, sql, new BeanListHandler<>(User.class), "smith", 15);
              //参数解释
              /*
              connection、sql: 传入连接和 sql 语句
              new BeanListHandler<User>(User.class)
                  得到 resultSet 取出每一行数据封装为 user 对象,再将 user 对象封装到集合
              User.class: 使用反射机制获取 User 类的属性进行封装        
              "smith", 15: 两个参数,给对应 ? 位置赋值的参数,(可变参数 Object...params)
              底层得到的 resultSet 会在query中关闭,PreparedStatement 也会在底层创建并关闭
              */
              for (User user : query){
                  System.out.println(user);
              }
          } catch (SQLException e) {
              throw new RuntimeException(e);
          } finally {
              //不参与事务使用,直接关闭连接
              JDBCUtils.close(null, null, connection);
          }
      }
      
  • 单条记录

    • // 查询单条记录使用: new BeanHandler<>(User.class)
      String sql = "select * from `user` where name = ?";
      User user = queryRunner.query(connection, sql, new BeanHandler<>(User.class), "smith");
      
      // 查询单行单列记录,返回 object 对象
      String sql = "select name from `user` where name = ?";
      Object obj = queryRunner.query(connection, sql, new ScalarHandler(), "smith"); 
      
  • DML 语句

    • // String sql = "insert into user values(?,?,?,?,?)";
      String sql = "delete from user where name = ?";
      // String sql = "update user set name = ? where name = ?";
      // update() 方法执行dml语句,返回受影响行数
      int rows = queryRunner.update(connection, sql, "smith");
      

BasicDao

  • 增删改查通用方工具类

    • 将执行sql 语句的操作封装到一个BasicDao类,让其他业务类继承该基础类使用这些通用基础方法
  • 基于泛型方法可以适配不同表的实例类

    • 各业务类自身还可以扩展自身需要的业务方法
package demo01;

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 {

    /**
     * 通用的 dml 方法
     * @param sql    传去sql 语句
     * @param params 传去sql 语句中要赋值的参数
     * @return 返回受影响行数
     */
    public int dml(String sql, Object... params) {
        Connection connection = null;
        try {
            connection = DruidUtils.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            return queryRunner.update(connesql, params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DruidUtils.close(null, null, connection);
        }

    }

    /**
     * 通用的查询多条记录方法
     * @param sql    传入sql 语句
     * @param clazz  传入一个类Class对象,User.class
     * @param params 传入为sql语句赋值的可变参数
     * @return 将查询的结果集,封装为List<T>返回,记录封装为对应类的对象
     */
    public <T>List<T> select(String sql, Class<T> clazz, Object... params) {
        Connection connection = null;
        try {
            connection = DruidUtils.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            return queryRunner.query(connection, sql, new BeanListHandler<>(clazz));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DruidUtils.close(null, null, connection);
        }
    }

    /**
     * 返回单行记录
     * @param sql    传入的sql语句
     * @param clazz  传入的类的Class对象
     * @param params 传入为sql语句赋值的可变参数
     * @return 返回单行记录
     */
    public <T>T selectOne(String sql, Class<T> clazz, Object... params) {
        Connection connection = null;
        try {
            connection = DruidUtils.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            return queryRunner.query(connection, sql, new BeanHandler<>(clazz), params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DruidUtils.close(null, null, connection);
        }
    }

    /**
     * 查询单行单列记录
     * @param sql    传入的sql语句
     * @param params 传入为sql语句赋值的可变参数
     * @return 返回单行单列记录
     */
    public Object selectOneOne(String sql, Object... params) {
        Connection connection = null;
        try {
            connection = DruidUtils.getConnection();
            QueryRunner queryRunner = new QueryRunner();
            return queryRunner.query(connection, sql, new ScalarHandler(), params);
        } catch (SQLException e) {
            throw ne w RuntimeException(e);
        } finally {
            DruidUtils.close(null, null, connection);
        }
    }
}

JNDI

  • Java Naming and Directory Interface:Java 命名和目录接口

  • 作用:在服务器配置资源,通过统一方式获取配置的资源

    • 这里配置数据库连接池

    • 项目通过统一方式获取连接池对象

TomCat 配置
<!-- 配置格式 -->
<?xml version="1.1 encoding="UTF-8?>
<Context>
    <Resource name=""      
        factory="org.apache.naming.factory.BeanFactory"
        tyoe=""   <!-- 前三项必须配置 -->
        ...
    />
</Context>
  • name:指定资源名称,获取资源时使用
  • factory:创建资源的工厂,基本固定
  • type:资源类型
    • 在此即是连接池类型
  • 还要配置资源必须的属性

例如:C3P0 连接池配置

<Context>
    <Resource name="jdbc/dataSource"
              type="com.mchange.v2.c3p0.ComboPooledDataSource"
              factory="org.apache.naming.factory.BeanFactory"
              user="root"
              password="123456"
              classDriver="com.mysql.jdbc.Driver"
              jdbcUrl="jdbc:mysql://127.0.0.1/mydb1"
              maxPoolSize.="20"
              minPoolSize ="5"
              initialPoolSize="10"
              acquirelncrement="2"
              />
</Context>
获取资源

启动 Tomcat 就可以在项目中通过 JNCI 方式获取资源

Tomcat 文档提供获取格式

  • 与上面 Tomcat 文档配置资源一套
//初始化上下文
Context initCtx = new InitialContext();
//查找资源入口(固定名称),得到上下文对象
Context envCtx = (Context)initCtx .lookup("java:comp/env");
//二次查找,查找配置文件资源,获取资源对象
MyBean bean = (MyBean)envCtx.lookup("bean/MyBeanFactory");
    
 /*
 MyBean bean = (MyBean)enCtx.lookup("java:comp/env/bean/MyBeanFactory")
 //可以直接查找配置文件
 */
  • Context:java.naming.Context
  • InitialContext:javax.naming.InitialContext
  • lookup(String):获取资源的方法
    • MyBean:资源对象类型,使用的资源类型
    • java:comp/env:资源入口,固定名称
      • 获取到 Context,再进一步获取
    • bean/MyBeanFactory
      • 对应配置中的 name
      • 获取资源对象

DAO 模式

  • DAO (DataAccessobjects 数据存取对象):位于业务逻辑和持久化数据之间实现对持久化数据的访问

    • 即将数据库操作都封装起来
  • 对外提供相应的接口

    • 提供访问关系型数据库系统所需操作的接口,将数据访问和业务逻辑分离对上层提供面向对象的数据访问接口

    • 在面向对象设计过程中,用于解决特定问题称为模式

优势

  • DAO 模式的优势就在于实现了两次隔离

    1. 隔离了数据访问代码和业务逻辑代码

      • 业务逻辑代码直接调用 DAO 方法即可,不用关心其他业务实现
        • 数据访问层代码变化不影响业务逻辑代码,符合单一职能原则,降低了藕合性,提高了可复用性
    2. 隔离了不同数据库实现

      • 采用面向接口编程
        • 底层数据库变化时,如:由 MySQL 变成 Oracle 只要增加 DAO 接口的新实现类即可,原有 MySQ 实现不用修改
        • 符合 开-闭 原则,降低了代码的藕合性,提高了代码扩展性和系统的可移植性

组成

  • 典型的 DAO 模式

  • DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现

    • public interface PetDao {
          List<Pet> findAllPets() throws Exception;			// 查询所有宠物
      }
      
  • DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现

    • public class PetDaoImpl extends BaseDao implements PetDao {
          // 查询所有宠物
          public List<Pet> findAllPets() throws Exception {
              Connection conn=BaseDao.getConnection();
              String sql="select * from pet";
              PreparedStatement stmt= conn.prepareStatement (sql);
              ResultSet rs= stmt.executeQuery();
              List<Pet> petList = new ArrayList<Pet>();
              while(rs.next()) {
                  Pet pet = new Pet(
                          rs.getInt("id"),
                          rs.getInt("owner_id"),
                          rs.getInt("store_id"),
                          rs.getString("name"),
                          rs.getString("type_name"),
                          rs.getInt("health"),
                          rs.getInt("love"),
                          rs.getDate("birthday")
                          );
                      petList.add(pet);
              }
              BaseDao.closeAll(conn, stmt, rs);
              return petList;
          }
      }
      
  • 实体类:用于存放与传输对象数据

    • public class Pet {
          private Integer id;    
          private Integer ownerId;    //主人ID
          private Integer storeId;    //商店ID
          private String name;    //姓名
          private String typeName;    //类型
          private int health;    //健康值
          private int love;    //爱心值
          private Date birthday;    //生日
          public int getId(){
              return id;
          }
          ......//get/set方法不再列出
          public void setBirthday(Date birthday){
              this.birthdat = birthday;
          }
      }
      
  • 数据库连接和关闭工具类

    • 避免了数据库连接和关闭代码的重复使用,方便修改

    • public class BaseDao {
          private static String driver="com.mysql.jdbc.Driver";
          private static String url="jdbc:mysql://127.0.0.1:3306/ epet";
          private static String user="root";
          private static String password="root";
              static {
                  try {
                      Class.forName(driver);
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }
              }
              
          public static Connection getConnection() throws SQLException {
              return DriverManager.getConnection(url, user, password);    
          }
          
          public static void closeAll(Connection conn,Statement stmt,ResultSet rs) throws SQLException {
              if(rs!=null) {rs.close();}
              if(stmt!=null) {stmt.close();}
              if(conn!=null) {conn.close();}
          }
          
          public int executeSQL(String preparedSql, Object[] param) throws ClassNotFoundException {
              Connection conn = null;
              PreparedStatement pstmt = null;
              /* 处理SQL,执行SQL */
              try {
                  conn = getConnection(); // 得到数据库连接
                  pstmt = conn.prepareStatement(preparedSql); // 得到PreparedStatement对象
                  if (param != null) {
                      for (int i = 0; i < param.length; i++) {
                          pstmt.setObject(i + 1, param[i]); // 为预编译sql设置参数
                      }
                  }
              ResultSet num = pstmt.executeQuery(); // 执行SQL语句
              } catch (SQLException e) {
                  e.printStackTrace(); // 处理SQLException异常
              } finally {
                  try {
                      BaseDao.closeAll(conn, pstmt, null);
                  } catch (SQLException e) {    
                      e.printStackTrace();
                  }
              }
              return 0;
          }
      }
      
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值