JDBC核心技术[详细]

JDBC核心技术[跟随尚硅谷康师傅的步伐复习所做记录]

  • 01JDBC概述
    • 1-1数据的持久化
    • 1-2Java中的数据存储技术
    • 1-3JDBC介绍
    • 1-4JDBC体系结构
    • 1-5JDBC程序编写步骤
  • 02获取数据库连接
    • 2-1四要素
      • 2-1-1要素一:Driver接口的实现类
      • 2-1-2要素二:URL
      • 2-1-3要素三:用户名
      • 2-1-4要素四:密码
    • 2-2数据库连接方式举例【这些方式是逐一递进的,最终使用方式五获取数据库连接】
      • 2-2-1连接方式一
      • 2-2-2连接方式二【目的是不出现第三方API】
      • 2-2-3连接方式三【使用DriverManager替换Driver】
      • 2-2-4连接方式四【只是加载驱动,不用显示的注册驱动】
      • 2-2-5连接方式五【将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接】
  • 03使用PreparedStatement实现CRUD操作(Create,Retrieve,Update,Delete)
    • 3-1操作和访问数据库
    • 3-2使用Statement操作数据表的弊端
    • 3-3PreparedStatment的使用
      • 3-3-1PreparedStatment的介绍
      • 3-3-2 Java与SQL对应数据类型转换表
      • 3-3-3使用PreparedStatement实现增、删、改操作
      • 3-3-4PreparedStatement实现通用的增删改操作
      • 3-3-5实现对特定表(以customers表为例)的查询操作[通用的查询操作见3-3-8、3-3-9]
      • 3-3-6实现对特定表(customers)的通用查询操作【数据库表中的列名与属性名相同】[通用的查询操作见3-3-8、3-3-9]
      • 3-3-7实现对特定表(order)的通用查询操作【数据库中的列名与属性名不相同】[通用的查询操作见3-3-8、3-3-9]
      • 3-3-8实现对不同表的通用查询操作【获取一条记录】
      • 3-3-9实现对不同表的通用查询操作【获取多条记录】
    • 3-4ResultSet与ResultSetMetaData
      • 3-4-1ResultSet
      • 3-4-2ResultSetMetaData
    • 3-5PreparedStatement vs Statement
  • 04操作BLOB类型字段
    • 4-1MySQL BLOB类型
    • 4-2向数据表中插入Blob类型数据
    • 4-3从数据表中读取Blob类型数据
  • 05批量插入
    • 5-1批量执行sql语句
    • 5-2高效地批量插入【最终落地见实现层次4】
      • 5-2-1实现层次一:使用Statement
      • 5-2-2实现层次二:使用PreparedStatement
      • 5-2-3实现层次三【 使用addBatch()/executeBatch() / clearBatch()】
      • 5-2-4实现层次四[在三的基础上操作]
  • 06数据库事务
    • 6-1事务介绍【见文章MySQL基础】
    • 6-2JDBC事务处理
  • 07DAO及相关实现类(data(base) access object)
    • 7-1BaseDao
    • 7-2 interface CustomerDao
    • 7-3CustomerDaoImpl
    • 7-4层次结构
  • 08数据库连接池
    • 8-1 JDBC数据库连接池的必要性
    • 8-2数据库连接池技术
      • 8-2-1概述
      • 8-2-2工作原理
      • 8-2-3优点
    • 8-3多种开元的数据库连接池
    • 8-4数据库连接池的使用【可以只关注Druid数据库连接池】
      • 8-4-1C3P0数据库连接池
      • 8-4-2DBCP数据库连接池
      • 8-4-3Druid数据库连接池
  • 09Apache-DBUtils实现CRUD操作
    • 9-1Apache-DBUtils简介
    • 9-2主要API的使用
      • 9-2-1DbUtils
      • 9-2-2QueryRunner使用
      • 9-2-3ResultHandler接口及实现类
  • 10总结

01JDBC概述

1-1数据的持久化

、持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成
、持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

1-2Java中的数据存储技术

在Java中,数据库存取技术可分为如下几类:

  • JDBC直接访问数据库
  • JDO (Java Data Object )技术
  • 第三方O/R工具,如Hibernate, Mybatis 等
    JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC

1-3JDBC介绍

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标 准的方法、方便地访问数据库资源。
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题
  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
  • 如果没有JDBC,那么Java程序访问数据库时是这样的:
    JDBC核心技术[详细]_第1张图片
  • 有了JDBC,Java程序访问数据库时是这样的:
    JDBC核心技术[详细]_第2张图片
  • 总结如下:
    JDBC核心技术[详细]_第3张图片

1-4JDBC体系结构

JDBC接口(API)包括两个层次:

  • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
  • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

1-5JDBC程序编写步骤

JDBC核心技术[详细]_第4张图片
补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。
总结为如下几点:

  1. 导入java.sql包
  2. 加载驱动(这里用mysql驱动)
  3. 获取数据库连接
  4. 创建Statement对象用来操作数据库(增、删、改、查)

02获取数据库连接

2-1四要素

2-1-1要素一:Driver接口的实现类

一、Driver接口介绍

  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
  • 在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
  1. Oracle的驱动:oracle.jdbc.driver.OracleDriver
  2. mySql的驱动: com.mysql.jdbc.Driver

二、加载与注册JDBC驱动
1、加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名

  • Class.forName(“com.mysql.jdbc.Driver”);

2、注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序

  • 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动
  • 通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。
    JDBC核心技术[详细]_第5张图片

2-1-2要素二:URL

  • JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。

  • JDBC URL的标准由三部分组成,各部分间用冒号分隔。
    jdbc:子协议:子名称
    JDBC核心技术[详细]_第6张图片

  1. 协议:JDBC URL中的协议总是jdbc
  2. 子协议:子协议用于标识一个数据库驱动程序
  3. 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名

几种常用数据库的 JDBC URL:

1. MySQL的连接URL编写方式:
①jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
②jdbc:mysql://localhost:3306/test
③jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
④- jdbc:mysql://localhost:3306/test?user=root&password=123456
2. Oracle 9i的连接URL编写方式:
①jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
②jdbc:oracle:thin:@localhost:1521:test
3. SQLServer的连接URL编写方式:
①jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称
②jdbc:sqlserver://localhost:1433:DatabaseName=test

2-1-3要素三:用户名

可以用“属性名=属性值”方式告诉数据库
可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接

2-1-4要素四:密码

可以用“属性名=属性值”方式告诉数据库
可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接

2-2数据库连接方式举例【这些方式是逐一递进的,最终使用方式五获取数据库连接】

2-2-1连接方式一

public class GetConnection {
    public static void main(String[] args) throws SQLException {
    	//一、获取Driver的实现类对象
        //①导入java.sql(包下的类Driver)
        //②加载Mysql驱动(提供Driver的实现类)【需要手动导入jar包】
        Driver driver = new com.mysql.jdbc.Driver();
        //jdbc:mysql    协议
        //localhost:3306    ip:mysql默认的端口号
        //test  test数据库
        String url = "jdbc:mysql://localhost:3306/test";//提供要连接的数据库
        //将用户名和密码封装在Properties中
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","zzw123");
        Connection conn = driver.connect(url,info);//需要传入两个参数String url、Properties info
        System.out.println(conn);//打印,看获取成功与否
    }

}

2-2-2连接方式二【目的是不出现第三方API】

方式二与方式一的不同在于使用反射获取Driver的实现类对象,目的是不出现第三方API,使得程序具有更好的可移植性

public class GetConnection {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        //一、获取Driver的实现类对象(通过反射的方式)
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver)clazz.newInstance();
        //二、提供要连接的数据库
        String url = "jdbc:mysql://localhost:3306/test";
        //三、提供需要的用户名和密码
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","zzw123");
        //四、获取连接
        Connection conn = driver.connect(url,info);
        System.out.println(conn);
    }

}

2-2-3连接方式三【使用DriverManager替换Driver】

public class GetConnection {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        //1、获取Driver实现类的对象(注册驱动时作为参数传入)
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();
        //2、提供另外三个连接的基本信息
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "zzw123";
        //注册驱动
        DriverManager.registerDriver(driver);
        //获取连接
        Connection conn = DriverManager.getConnection(url,user,password);
        System.out.println(conn);
    }

}

2-2-4连接方式四【只是加载驱动,不用显示的注册驱动】

public class GetConnection {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        //1、提供另外三个连接的基本信息
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "zzw123";
        //2、加载Driver
        Class.forName("com.mysql.jdbc.Driver");

        /*Driver driver = (Driver) clazz.newInstance();
        注册驱动
        DriverManager.registerDriver(driver);*/

        //获取连接
        Connection conn = DriverManager.getConnection(url,user,password);
        System.out.println(conn);
    }

}

不用注册驱动的原因是因为加载类Driver实现类时,Driver实现类中静态代码块部分已经帮我们注册了驱动
JDBC核心技术[详细]_第7张图片

2-2-5连接方式五【将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接】

为了web阶段的使用,最好将配置文件放到src下,以免之后部署到Tomcat服务器下后配置文件缺失
JDBC核心技术[详细]_第8张图片
【注意】:jdbc配置文件内容不要带双引号后

public class GetConnection {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException, IOException {
        //读取配置文件的4个基本信息
        //InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream();
        InputStream is = GetConnection.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties pro = new Properties();
        pro.load(is);
        String driverClass = pro.getProperty("driverClass");
        String url = pro.getProperty("url");
        String user = pro.getProperty("user");
        String password = pro.getProperty("password");
        //1、加载Driver(注册了Driver(因为Driver实现类中静态代码块中有注册驱动功能))
        Class.forName(driverClass);
        //2、获取数据库连接
        Connection conn = DriverManager.getConnection(url,user,password);
        System.out.println(conn);
    }
}

使用配置文件的好处
①实现了代码和数据的分离,解耦,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
②如果修改了配置信息,省去重新编译的过程,避免程序重新打包

03使用PreparedStatement实现CRUD操作(Create,Retrieve,Update,Delete)

3-1操作和访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接
  • 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
  1. Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
  2. PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
  3. CallableStatement:用于执行 SQL 存储过程
    JDBC核心技术[详细]_第9张图片

3-2使用Statement操作数据表的弊端

JDBC核心技术[详细]_第10张图片
JDBC核心技术[详细]_第11张图片

3-3PreparedStatment的使用

3-3-1PreparedStatment的介绍

一、可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象
二、PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
三、PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1
开始),第二个是设置的 SQL 语句中的参数的值

3-3-2 Java与SQL对应数据类型转换表

JDBC核心技术[详细]_第12张图片

3-3-3使用PreparedStatement实现增、删、改操作

由于操作数据库的通用步骤都是先获取数据库连接、操作数据库、关闭资源
所以想到创建一个操作数据库的工具类,将获取数据库连接、关闭资源封装到其中。
操作数据库的工具类

public class JDBCUtils {
    //获取数据库的连接
    public static Connection getConnection() throws Exception {
        //1、获取配置文件中的4个基本信息
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties pro = new Properties();
        pro.load(is);
        //2、加载Driver
        Class.forName(pro.getProperty("driverClass"));
        //3、获取数据库连接
        Connection conn = DriverManager.getConnection(pro.getProperty("url"),
                pro.getProperty("user"),pro.getProperty("password"));
        return  conn;
    }
    //关闭资源
    public static void closeResource(Connection conn, Statement ps){
        try {
            if(ps != null){
                ps.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if(conn != null){
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

修改customers表的一条记录

public class GetConnection {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //获取数据库连接
            conn = JDBCUtils.getConnection();
            //提供预编译的sql语句,返回PreparedStatement实例
            String sql = "update customers set name=? where id=?";
            ps = conn.prepareStatement(sql);
            //填充占位符
            ps.setObject(1,"张一");
            ps.setObject(2,19);
            //执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //关闭资源
            JDBCUtils.closeResource(conn,ps);
        }
    }

}

3-3-4PreparedStatement实现通用的增删改操作

JDBC核心技术[详细]_第13张图片

public class GetConnection {
    public static void main(String[] args) {
        String sql = "delete from customers where id = ?";
        update(sql,"20");
    }
    //实现通用的增、删、改操作
    public static void update(String sql,Object ... args){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //获取数据库连接
            conn = JDBCUtils.getConnection();
            //提供预编译的sql语句,返回PreparedStatement实例
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[i]);
            }
            //执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //关闭资源
            JDBCUtils.closeResource(conn,ps);
        }
    }
}

3-3-5实现对特定表(以customers表为例)的查询操作[通用的查询操作见3-3-8、3-3-9]

查询操作会返回一个ResultSet实例,其中有next()方法,返回值是boolean[拥有了Iterator当中hasNext()判断下一个是否有元素的功能,也拥有next()方法指针下移的功能]
ORM编程思想(Object relational mapping):
1、一个数据表对应一个类
2、表中的一条记录对应java类的一个对象
3、表中的一个字段对应java类的一个属性

public class Test {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //获取数据库连接
            conn = JDBCUtils.getConnection();
            //预编译sql语句返回PreparedStatement实例
            String sql = "select * from customers where id BETWEEN ? and ?";
            ps = conn.prepareStatement(sql);
            ps.setObject(1,1);
            ps.setObject(2,10);
            //执行并返回结果集
            resultSet = ps.executeQuery();
            //处理结果集
            while(resultSet.next()){//判断结果集的下一条是否有数据,如果有数据返回true ,并指针下移;如果返回false,指针不会下移。
                //获取当前结果集各个字段的值
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                java.sql.Date birth = resultSet.getDate(4);
                System.out.println(new Customer(id,name,email,birth));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //关闭资源
            JDBCUtils.closeResource(conn,ps,resultSet);
        }
    }
}

3-3-6实现对特定表(customers)的通用查询操作【数据库表中的列名与属性名相同】[通用的查询操作见3-3-8、3-3-9]

JDBC核心技术[详细]_第14张图片

public class Customer {
    private int id;
    private String name;
    private String email;
    private java.sql.Date birth;

    public Customer() {
    }

    public Customer(int id, String name, String email, Date date) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.birth = date;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public Date getBirth() {
        return birth;
    }

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

    public void setName(String name) {
        this.name = name;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setDate(Date date) {
        this.birth = date;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birth=" + birth +
                '}';
    }
}

public class CustomerForQuery {
    public static void main(String[] args) {
        String sql = "select id,name,email,birth from customers where name = ?";
        Customer cust1 = queryForCustomers(sql, "周杰伦");
        System.out.println(cust1);
    }
    public static Customer queryForCustomers(String sql,Object ... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //获取数据库连接
            conn = JDBCUtils.getConnection();
            //预编译sql语句返回PreparedStatement对象
            ps = conn.prepareStatement(sql);
            //将执行后的结果保存到结果集中
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //处理结果集
            if(rs.next()){
                Customer cust = new Customer();
                //通过结果集获取列数
                int columnCount = rsmd.getColumnCount();
                //为Customer对象的属性赋值
                for (int i = 0; i < columnCount; i++) {
                    //获取列名
                    String columnName = rsmd.getColumnName(i + 1);
                    //获取值
                    Object columnValue = rs.getObject(i + 1);
                    //通过反射获取属性名并赋值
                    Field fieldName = Customer.class.getDeclaredField(columnName);
                    fieldName.setAccessible(true);
                    fieldName.set(cust,columnValue);
                }
                return cust;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;
    }
}

3-3-7实现对特定表(order)的通用查询操作【数据库中的列名与属性名不相同】[通用的查询操作见3-3-8、3-3-9]

JDBC核心技术[详细]_第15张图片
代码部分通过反射获取到属性之后,不要忘记setAccessible(true);

public class OrderForQuery {
    public static void main(String[] args) {
        String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
        Order order1 = queryForOrders(sql, 2);
        System.out.println(order1);
    }
    public static Order queryForOrders(String sql,Object ... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //获取数据库连接
            conn = JDBCUtils.getConnection();
            //预编译sql语句返回PreparedStatement实例
            ps = conn.prepareStatement(sql);
            //给占位符赋值
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[i]);
            }
            //执行并获取结果集
            rs = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数
            int columnCount = rsmd.getColumnCount();
            if(rs.next()){
                Order order = new Order();
                for (int i = 0; i < columnCount; i++) {
                    //获取列名(或者别名)
                    String columnName = rsmd.getColumnLabel(i + 1);
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
                    //通过反射对属性名赋值
                    Field fieldName = Order.class.getDeclaredField(columnName);
                    fieldName.setAccessible(true);
                    fieldName.set(order,columnValue);
                }
                return order;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;
    }
}

3-3-8实现对不同表的通用查询操作【获取一条记录】

public class OrderForQuery {
    public static void main(String[] args) {
        String sql = "select id,name,birth from customers where id=?";
        Customer cust = getInstance(Customer.class, sql, 10);
        System.out.println(cust);
    }
    public static <T>T getInstance(Class<T> clazz,String sql,Object ... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[i]);
            }
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            if(rs.next()){
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    String columnName = rsmd.getColumnLabel(i + 1);
                    Object columnValue = rs.getObject(i + 1);
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.close(conn,ps,rs);
        }
        return null;
    }
}

3-3-9实现对不同表的通用查询操作【获取多条记录】

public class OrderForQuery {
    public static void main(String[] args) {
        String sql = "select id,name,birth from customers where id between ? and ?";
        List<Customer> list = getForList(Customer.class, sql, 1, 10);
        /*for(Customer cust : list){
            System.out.println(cust);
        }*/
        //用jdk8新特性Lambda表达式
        list.forEach(System.out::println);
    }
    public static <T> List<T> getForList(Class<T> clazz, String sql, Object ... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //获取数据库连接诶
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            List<T> list = new ArrayList<>();
            for (int i = 0; i < args.length; i++) {
                //赋值占位符
                ps.setObject(i + 1,args[i]);
            }
            //获取结果集
            rs = ps.executeQuery();
            //获取元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //通过元数据获取结果集的列数
            int columnCount = rsmd.getColumnCount();
            while(rs.next()){
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取列名
                    String columnName = rsmd.getColumnLabel(i + 1);
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
                    //通过反射获取属性并赋值
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.close(conn,ps,rs);
        }
        return null;
    }
}

3-4ResultSet与ResultSetMetaData

3-4-1ResultSet

JDBC核心技术[详细]_第16张图片
JDBC核心技术[详细]_第17张图片

3-4-2ResultSetMetaData

  • 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
  • getColumnName(int column):获取指定列的名称
  • getColumnLabel(int column):获取指定列的别名
  • getColumnCount():返回当前 ResultSet 对象中的列数。
  • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
  • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
  • isNullable(int column):指示指定列中的值是否可以为 null。
  • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

3-5PreparedStatement vs Statement

  • PreparedStatement 可以防止SQL注入
  • PreparedStatement操作Blob的数据,而Statement做不到
  • PreparedStatement可以实现更高效的批量操作
  1. DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行
  2. 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次
  3. (语法检查,语义检查,翻译成二进制命令,缓存)

04操作BLOB类型字段

4-1MySQL BLOB类型

  • MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据
  • 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的
  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
    JDBC核心技术[详细]_第18张图片
  • 实际使用中根据需要存入的数据大小定义不同的BLOB类型
  • 需要注意的是:如果存储的文件过大,数据库的性能会下降
  • 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务

4-2向数据表中插入Blob类型数据

public class TestInsert {
    public static void main(String[] args) throws Exception{
        String sql = "insert into customers(name,email,birth,photo) values(?,?,?,?)";
        Connection conn = JDBCUtils.getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1,"李一");
        ps.setString(2,"[email protected]");
        ps.setDate(3,new java.sql.Date(new java.util.Date().getTime()));
        FileInputStream fis = new FileInputStream(new File("G://html_code//img//py3.jpg"));
        ps.setBlob(4,fis);
        ps.execute();
        JDBCUtils.closeResource(conn,ps);
    }
}

4-3从数据表中读取Blob类型数据

public class TestInsert {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        InputStream is = null;
        try {
            String sql = "select photo from customers where id = ?";
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            ps.setInt(1,23);
            ResultSet rs = ps.executeQuery();
            if(rs.next()){
                //将Blob类型字段下载下来,以文件的形式保存到本地
                Blob photo = rs.getBlob(1);
                //photo存取的是二进制数据,以下操作获取二进制流
                is = photo.getBinaryStream();
                //保存到本地
                FileOutputStream fos = new FileOutputStream(new File("C://Users//18369//Desktop//copypy3.jpg"));
                byte[] buf = new byte[1024];
                int len;
                while((len = is.read(buf)) != -1){
                    fos.write(buf,0,len);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(is != null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            JDBCUtils.closeResource(conn,ps);
        }
    }
}

05批量插入

5-1批量执行sql语句

当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
JDBC的批量处理语句包括下面三个方法:

  • addBatch(String):添加需要批量处理的SQL语句或是参数
  • executeBatch():执行批量处理语句
  • clearBatch():清空缓存的数据

通常我们会遇到两种批量执行SQL语句的情况:

  • 多条SQL语句的批量处理
  • 一个SQL语句的批量传参

5-2高效地批量插入【最终落地见实现层次4】

数据库中提供一个goods表。创建如下:

create table goods(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME varchar(20)
);

举例:向数据表中插入20000条数据

5-2-1实现层次一:使用Statement

JDBC核心技术[详细]_第19张图片

5-2-2实现层次二:使用PreparedStatement

JDBC核心技术[详细]_第20张图片

5-2-3实现层次三【 使用addBatch()/executeBatch() / clearBatch()】

在实现该层次之前需要做一些修改:

  • mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。?rewriteBatchedStatements=true 写在配置文件的url后面
    JDBC核心技术[详细]_第21张图片
  • 使用更新的mysql驱动:mysql-connector-java-5.1.37-bin.jar
public class TestInsert {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "insert into goods(name) VALUES(?)";
            ps = conn.prepareStatement(sql);
            long start = System.currentTimeMillis();
            for (int i = 1; i <= 20000; i++) {
                ps.setObject(1,"name" + i);
                //"攒"sql
                ps.addBatch();
                if(i % 500 == 0){
                    //执行
                    ps.executeBatch();
                    //清空
                    ps.clearBatch();
                }
            }
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.closeResource(conn,ps);
        }
    }
}

5-2-4实现层次四[在三的基础上操作]

使用Connection 的 setAutoCommit(false) / commit()

public class TestInsert {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCUtils.getConnection();
            //1.设置为不自动提交数据
            conn.setAutoCommit(false);
            String sql = "insert into goods(name) VALUES(?)";
            ps = conn.prepareStatement(sql);
            long start = System.currentTimeMillis();
            for (int i = 1; i <= 1000000; i++) {
                ps.setObject(1,"name" + i);
                //"攒"sql
                ps.addBatch();
                if(i % 500 == 0){
                    //执行
                    ps.executeBatch();
                    //清空
                    ps.clearBatch();
                }
            }
            //提交数据
            conn.commit();
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.closeResource(conn,ps);
        }
    }
}

06数据库事务

6-1事务介绍【见文章MySQL基础】

6-2JDBC事务处理

  • 数据一旦提交就不可以回滚
  • 数据什么时候意味着提交?
  1. 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
    在这里插入图片描述
  2. 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下
  • JDBC程序中为了让多个 SQL 语句作为一个事务执行:
  1. 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
  2. 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
  3. 在出现异常时,调用 rollback(); 方法回滚事务

若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态
setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

模拟AA对BB转账的操作
JDBC核心技术[详细]_第22张图片
其中对数据库操作的方法为
JDBC核心技术[详细]_第23张图片
补充操作
JDBC核心技术[详细]_第24张图片

07DAO及相关实现类(data(base) access object)

7-1BaseDao

//封装了针对于数据表的通用操作
public abstract class BaseDao<E> {
    private Class<E> clazz = null;
    {
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType pt = (ParameterizedType)type;
        Type[] actualTypeArguments = pt.getActualTypeArguments();
        clazz = (Class<E>) actualTypeArguments[0];
    }
    //通用的增、删、改操作(考虑到事务)
    public void update(Connection conn, String sql, Object ... args){
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[i]);
            }
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
        	try {
        		//6.恢复每次DML操作的自动提交功能
        		conn.setAutoCommit(true); 
        	} catch (SQLException e) {
        	 	e.printStackTrace();
        	}
            JDBCUtils.closeResource(null,ps);
        }
    }
    //通用的查询操作(考虑到事务),返回 1 条记录
    public E queryForOne(Connection conn,String sql,Object ... args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            if(rs.next()){
                E e = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    String columnName = rsmd.getColumnLabel(i + 1);
                    Object columnValue = rs.getObject(i + 1);
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(e,columnValue);
                }
                return e;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
        	try {
        		//6.恢复每次DML操作的自动提交功能
        		conn.setAutoCommit(true); 
        	} catch (SQLException e) {
        	 	e.printStackTrace();
        	}
        	JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }
    //通用的查询操作(考虑到事务),返回多条记录
    public List<E> queryForList(Connection conn, String sql, Object ... args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[i]);
            }
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            ArrayList<E> list = new ArrayList<>();
            while(rs.next()){
                E e = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    String columnName = rsmd.getColumnLabel(i + 1);
                    Object columnValue = rs.getObject(i + 1);
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(e,columnValue);
                }
                list.add(e);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
        	try {
        		//6.恢复每次DML操作的自动提交功能
        		conn.setAutoCommit(true); 
        	} catch (SQLException e) {
        	 	e.printStackTrace();
        	}
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }
    //对组函数使用的返回结果获取
    public <T> T getValue(Connection conn,String sql,Object ... args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[i]);
            }
            rs = ps.executeQuery();
            if(rs.next()){
                return (T) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
        	try {
        		//6.恢复每次DML操作的自动提交功能
        		conn.setAutoCommit(true); 
        	} catch (SQLException e) {
        	 	e.printStackTrace();
        	}
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }
}

7-2 interface CustomerDao

public interface CustomerDao {
    //将Customer实例添加到数据库中
    void insert(Connection conn, Customer cust);
    //针对指定id删除数据库中一条记录
    void delete(Connection conn,int id);
    //根据内存中的Customer实例,修改表中的一条记录
    void updateC(Connection conn, Customer cust);
    //根据指定id查询数据库中的一条记录
    Customer getCustomerById(Connection conn,int id);
    //查询表中的所有记录
    List<Customer> getAllCustomers(Connection conn);
    //返回表中的记录数量
    long getCount(Connection conn);
}

7-3CustomerDaoImpl

public class CustomerDaoImpl extends BaseDao<Customer> implements CustomerDao{

    @Override
    public void insert(Connection conn, Customer cust) {
        String sql = "insert into customers(name,email,birth) values(?,?,?)";
        update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth());
    }

    @Override
    public void delete(Connection conn, int id) {
        String sql = "delete from customers where id=?";
        update(conn,sql,id);
    }

    @Override
    public void updateC(Connection conn, Customer cust) {
        String sql = "update customers set name=?,email=?,birth=? where id=?";
        update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
    }

    @Override
    public Customer getCustomerById(Connection conn, int id) {
        String sql = "select id,name,email,birth from customers where id=?";
        return queryForOne(conn,sql,id);
    }

    @Override
    public List<Customer> getAllCustomers(Connection conn) {
        String sql = "select id,name,email,birth from customers";
        return queryForList(conn,sql);
    }

    @Override
    public long getCount(Connection conn) {
        String sql = "select count(*) from customers";
        return getValue(conn,sql);
    }
}

7-4层次结构

JDBC核心技术[详细]_第25张图片

08数据库连接池

8-1 JDBC数据库连接池的必要性

JDBC核心技术[详细]_第26张图片

8-2数据库连接池技术

8-2-1概述

JDBC核心技术[详细]_第27张图片

8-2-2工作原理

JDBC核心技术[详细]_第28张图片

8-2-3优点

JDBC核心技术[详细]_第29张图片

8-3多种开元的数据库连接池

JDBC核心技术[详细]_第30张图片

8-4数据库连接池的使用【可以只关注Druid数据库连接池】

8-4-1C3P0数据库连接池

  • 两种获取连接方式
    JDBC核心技术[详细]_第31张图片
  • src下的配置文件为:【c3p0-config.xml】
    JDBC核心技术[详细]_第32张图片

8-4-2DBCP数据库连接池

JDBC核心技术[详细]_第33张图片

  • 配置属性说明
    JDBC核心技术[详细]_第34张图片
  • 获取连接的两种方式
    JDBC核心技术[详细]_第35张图片
  • src下的配置文件为:【dbcp.properties】
    JDBC核心技术[详细]_第36张图片

8-4-3Druid数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,是目前最好的连接池之一
JDBC核心技术[详细]_第37张图片

  • src下的配置文件为:【druid.properties】
    JDBC核心技术[详细]_第38张图片
  • 详细的配置参数
    JDBC核心技术[详细]_第39张图片
    JDBC核心技术[详细]_第40张图片

09Apache-DBUtils实现CRUD操作

9-1Apache-DBUtils简介

JDBC核心技术[详细]_第41张图片
JDBC核心技术[详细]_第42张图片

9-2主要API的使用

9-2-1DbUtils

JDBC核心技术[详细]_第43张图片

9-2-2QueryRunner使用

JDBC核心技术[详细]_第44张图片
测试
JDBC核心技术[详细]_第45张图片

9-2-3ResultHandler接口及实现类

JDBC核心技术[详细]_第46张图片

  • 测试查询【一条记录】
    JDBC核心技术[详细]_第47张图片
  • 测试查询【多条记录】
    JDBC核心技术[详细]_第48张图片
  • 测试【自定义ResultHandler实现类】
    JDBC核心技术[详细]_第49张图片
  • 测试【查询类似于最大的,最小的,平均的,总和,个数相关的数据】使用ScalarHandler
    JDBC核心技术[详细]_第50张图片

10总结

JDBC核心技术[详细]_第51张图片

你可能感兴趣的:(JDBC核心技术[详细])