JDBC

JDBC

1.获取数据库连接

1.获取数据库连接方式一

1.Driver接口的实现类

image-20220420110835677

image-20220420110740665

补充:Uniform Resource Locator,统一资源定位器

image-20220420111503755

2.获取连接image-20220420112652984

image-20220420113501863

3.代码参考:

package com.xurong.connection;

import org.junit.Test;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @auther xu
 * @date 2022/4/20 - 10:35
 */
public class ConnectionTest {
    @Test
    public void testConnection1() throws SQLException {

        Driver drive = new com.mysql.jdbc.Driver();//1.获取mysql所实现的驱动类(多态:引用类型为原始的接口)

        String url = "jdbc:mysql://localhost:3306/test";//获取对应目的主机所要访问的数据库
        //封装数据库连接必要的信息(用户名和密码)
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","draf19");

        //2.获取连接对象
        Connection connect = drive.connect(url, info);//多态:com.mysql.jdbc.Driver重写了的Driver接口中的connect()方法
        System.out.println(connect);//调用实现类中的重写的toString()方法
    }
}

2.获取数据库连接方式二

new com.mysql.jdbc.Driver();--第3方的API,为了使程序具有更好的可移植性(也就是说,换成Oracle数据库时,不需要通过改源代码也能够获取连接),我们尽量不要出现第3方的API.

  @Test
    /**
     * new com.mysql.jdbc.Driver();--第3方的API,为了使程序具有更好的可移植性(也就是说,换成Oracle数据库时,
     * 不需要通过改源代码也能够获取连接),我们尽量不要出现第3方的API.
     */
    public void testConnection2() throws Exception {

        //1.获取mysql所实现的驱动类,采用反射机制
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();

        //2.指明所要连接的数据库
        String url = "jdbc:mysql://localhost:3306/test";

        //3.提供连接所需要的用户名和密码
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","draf19");

        //4.获取连接
        Connection connect = driver.connect(url, info);

        System.out.println(connect);
    }

3.获取数据库连接方式三---使用DriverManager替换Driver

image-20220422162528554

image-20220422162300823

image-20220422163444104

4.获取数据库连接方式四

类的加载:

我们都知道 Java 源文件通过编译器 javac 命令能够编译生成相应的 class 文件,即二进制字节码文件。Java 虚拟机将描述类或接口的 class 文件(准确地说,应该是类的二进制字节流)加载到内存,对数据进行校验、转换解析和初始化,最终形成能够被虚拟机直接使用的 Java 类型,真正能够执行字节码的操作才刚刚开始。这个过程就是虚拟机的类加载机制。

静态代码块:随着类的加载而执行。

image-20220422165837209

  @Test
    /**
     * mysql的Driver实现类自动完成了Driver的注册功能
     */
    public void testConnection4() throws Exception {

        //1.获取另外三个连接的基本信息
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "draf19";
        
        //2.获取mysql所实现的驱动类,采用反射机制
        //Class clazz = Class.forName("com.mysql.jdbc.Driver");
        //Driver driver = (Driver) clazz.newInstance();

       
        //3.注册驱动
//        DriverManager.registerDriver(driver);
        
        //加载驱动,并完成了注册
        Class.forName("com.mysql.jdbc.Driver");

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

    }

输出结果:

image-20220422170637073

image-20220422172207600

5.获取数据库连接方式五

image-20220423095106335

image-20220423093559347

image-20220423094500243

 /**
     * 获取连接的最终版本,将基本信息写入配置文件中
     * @throws Exception
     */
    @Test
    public void testConnection5() throws Exception {
        //1.读取配置文件中的基本信息
        InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);

        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");

        //2.加载驱动
        Class.forName(driverClass);

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

    }

image-20220424103505089

总代码参考:

package com.xurong1.connection;

import org.junit.Test;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @auther xu
 * @date 2022/4/20 - 10:35
 */
public class ConnectionTest {
    @Test
    public void testConnection1() throws SQLException {

        Driver drive = new com.mysql.jdbc.Driver();//1.获取mysql所实现的驱动类(多态:引用类型为原始的接口)

        String url = "jdbc:mysql://localhost:3306/test";//获取对应目的主机所要访问的数据库
        //封装数据库连接必要的信息(用户名和密码)
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","draf19");

        //2.获取连接对象
        Connection connect = drive.connect(url, info);//多态:com.mysql.jdbc.Driver重写了的Driver接口中的connect()方法
        System.out.println(connect);//调用实现类中的重写的toString()方法
    }
    @Test
    /**
     * new com.mysql.jdbc.Driver();--第3方的API,为了使程序具有更好的可移植性(也就是说,换成Oracle数据库时,
     * 不需要通过改源代码也能够获取连接),我们尽量不要出现第3方的API.
     */
    public void testConnection2() throws Exception {

        //1.获取mysql所实现的驱动类,采用反射机制
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();

        //2.指明所要连接的数据库
        String url = "jdbc:mysql://localhost:3306/test";

        //3.提供连接所需要的用户名和密码
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","draf19");

        //4.获取连接
        Connection connect = driver.connect(url, info);

        System.out.println(connect);
    }
    @Test
    /**
     * 使用DriverManager替换Driver
     */
    public void testConnection3() throws Exception {
        //1.获取mysql所实现的驱动类,采用反射机制
        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 = "draf19";

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

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

    }
    @Test
    /**
     * mysql的Driver实现类自动完成了Driver的注册功能
     */
    public void testConnection4() throws Exception {

        //1.获取另外三个连接的基本信息
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "draf19";

        //2.获取mysql所实现的驱动类,采用反射机制
        //Class clazz = Class.forName("com.mysql.jdbc.Driver");
        //Driver driver = (Driver) clazz.newInstance();


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

        //加载驱动,并完成了注册
//        Class.forName("com.mysql.jdbc.Driver");

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

    /**
     * 获取连接的最终版本,将基本信息写入配置文件中
     * @throws Exception
     */
    @Test
    public void testConnection5() throws Exception {
        //1.读取配置文件中的基本信息
        InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);

        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");

        //2.加载驱动
        Class.forName(driverClass);

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

    }

}

2.使用PreparedStatemenet-进行增删改

image-20220420100435887

image-20220420101107393大概访问数据库的思路:

将所使用厂商的数据库驱动加载进来,通过获取的驱动得到数据库连接对象,通过数据库连接对象获取Statement对象,通过Statement对象操作sql语句,对于增,删,改是直接对数据库进行的操作,而对于查就需要返回结果集。

2.1 操作和访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。

  • 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

    • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    • CallableStatement:用于执行 SQL 存储过程

image-20220424104415872

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

1.弊端:

image-20220423102042549

1.SQL注入问题:

image-20220423100836424

2-3 PrepareSatement简介

image-20220423102746783

image-20220423102854706

image-20220423103003374

image-20220423103059920

image-20220423103152001

2.3.1 PreparedStatement介绍

  • 可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象

  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句

  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

2.3.2 PreparedStatement vs Statement

  • 代码的可读性和可维护性。

  • PreparedStatement 能最大可能提高性能:

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

2.3.3 Java与SQL对应数据类型转换表

Java类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY , VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP

2-4对数据库的增删改操作

2-4-1 向customer表中插入一条数据

image-20220423105337474

 /**
     * 向customer表中插入一条数据
     * @throws Exception
     */
    @Test
    public void testInsert() throws Exception {

        InputStream is = null;
        Connection connection = null;
        PreparedStatement ps = null;

        try {
            //1.读取配置文件中的基本信息
            is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");//该方式也可以获取类的加载器
            Properties properties = new Properties();
            properties.load(is);

            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String url = properties.getProperty("url");
            String driverClass = properties.getProperty("driverClass");

            //2.加载驱动
            Class.forName(driverClass);

            //3.获取连接
            connection = DriverManager.getConnection(url, user, password);

            //4预编译SQL语句,返回PrepareStatement对象
            String sql = "insert customers(name,email,birth)values(?,?,?)";//?是占位符,解决了Statement弊端
            ps = connection.prepareStatement(sql);

            //5.填充占位符
            ps.setString(1,"孙悟空");//注意:数据库表的的下标是从1开始的
            ps.setString(2,"sunwukong@gmail.com");

            //填充日期
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date parseDate = simpleDateFormat.parse("2000-01-01");//import java.util.Date;
            ps.setDate(3,new java.sql.Date(parseDate.getTime()));

            //6对数据库进行操作
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7.资源的关闭

            try {
                if (ps != null)
                    ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if (connection != null)
                    connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    }

image-20220423111514031

2-4-2 修改customer表中的一条数据---封装了数据库连接操作和关闭资源的操作

package com.xurong3.util;

import com.xurong1.connection.ConnectionTest;

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

/**
 * @auther xu
 * @date 2022/4/23 - 11:20
 */
public class JDBCUtils {

    /**
     * 获取数据库的连接
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //1.读取配置文件中的基本信息
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");//该方式也可以获取类的加载器
        Properties properties = new Properties();
        properties.load(is);

        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");

        //2.加载驱动
        Class.forName(driverClass);

        //3.获取连接
        Connection connection = DriverManager.getConnection(url, user, password);

        return connection;
    }

    /**
     * 关闭连接数据库的底层资源
     * @param conn
     * @param ps
     */
    public static void closeResource(Connection conn, Statement ps) {//java.sql public interface Statement
        //7.资源的关闭

        try {
            if (ps != null)
                ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if (conn != null)
                conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
/**
     * 修改customer表中的一条数据
     */
    @Test
    public void testUpdate() throws Exception {

        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1.获取数据库的连接
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象
            String sql = "update customers set name = ? where id = ?";
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            ps.setObject(1,"莫扎特");
            ps.setObject(2,18);

            //4.执行操作
            ps.execute();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.关闭资源
            JDBCUtils.closeResource(conn,ps);
        }
    }

image-20220423114951996

2-4-3 通用的增删改操作

/**
 * 通用的增删改操作
 * @param sql
 * @param args 相当于占位符的不定长度的数组
 * @throws Exception
 */
public void update(String sql,Object...args) {
    Connection connection = null;
    PreparedStatement ps = null;
    try {
        //1.获取数据库的连接
        connection = JDBCUtils.getConnection();

        //2.预编译SQL语句,返回一个PrepareStatement对象
        ps = connection.prepareStatement(sql);

        //3.填充占位符
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i+1,args[i]);
        }

        //4.执行操作
        ps.execute();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //5.资源的关闭
       JDBCUtils.closeResource(connection,ps);
    }

}

测试结果:

  @Test
    public void testCommonUpdate() {

//        //删除customerb编号为3的顾客
//        String sql = "delete from customers where id = ?";
//        update(sql,3);
        String sql = "update order set order_name = ? where order_id = ?";
        update(sql,"CCC",4);
    }

image-20220424110433920

image-20220424111233176

2-5 对数据库进行查询操作

结果集中运用next()与迭代器中的hasNext(),next()不同:

结果集中的next():结果集中下一个位置有元素,该方法会返回true,并自动将指针下移。否则,返回false,指针不下移。

迭代器中的hasNext(),next()不同:

hasNext():就只有一个作用,返回ture表示下一个位置有元素,返回false表示下一个位置没有元素。

next():两个作用:将指针下移,并且将将下移位置的元素给返回了。

2-5-1:正针对customers表写了一个查询操作

customer表映射的实体类:

package com.xurong3.bean;

import java.sql.Date;

/**
 *
 *
 * ORM编程思想(object relational mapping)
 * 一个数据表对应一个java类
 * 表中的一条记录对应java类的一个对象
 * 表中的一个字段对应java中的一个属性
 * @auther xu
 * @date 2022/4/24 - 15:16
 */
public class Customer {
    private int id;
    private String name;
    private String email;
    private 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 void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

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

    public Date getBirth() {
        return birth;
    }

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

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

对顾客表格的查询操作:

package com.xurong3.PrepareStatement.crud;

import com.xurong3.bean.Customer;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;

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

/**
 *
 * 对顾客表格的查询操作
 * @auther xu
 * @date 2022/4/24 - 14:36
 */
public class CustomerForQuery {

    @Test
    public void testQuery(){

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //1.获取连接
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象,并填充占位符
            String sql = "select id,name,email,birth,photo from customers where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setObject(1,1);

            //3.执行操作并返回一个结果集对象
            resultSet = ps.executeQuery();

            //4.将结果集中的每一个元素进行封装
            if (resultSet.next()) {
                //获取当前字段的各个数据
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                Date birth = resultSet.getDate(4);

                //显示数据
                //方式1:
    //            System.out.println("id = "+id+",name="+name+",email="+email+",birth="+birth);
                //方式2
    //            Object[] data = new Object[]{id,name,email,birth};
                //方式3:将数据封装为一个对象
                Customer customer = new Customer(id, name, email, birth);
                System.out.println(customer);

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.关闭资源对象
            JDBCUtils.closeResource(conn,ps,resultSet);
        }
    }
}

返回结果:

image-20220424154126635

image-20220424154104047

2-5-2:针对customer写一个通用查询操作:

此处着重利用反射完成数据属性封装的任务:

package com.xurong3.PrepareStatement.crud;

import com.xurong3.bean.Customer;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.*;

/**
 *
 * 对customers表格的查询操作
 * @auther xu
 * @date 2022/4/24 - 14:36
 */
public class CustomerForQuery {

    @Test
    public void testQueryForCustomers(){
        String sql1 = "select id,name,email,birth from customers where id = ?";
        Customer customer1 = queryForCustomers(sql1, 1);
        System.out.println(customer1);


        String sql2 = "select name,email from customers where id = ?";
        Customer customer2 = queryForCustomers(sql2, 10);
        System.out.println(customer2);
    }

    /**
     * 对customers的通用查询操作
     * @param sql sql语句
     * @param args 占位符所需要填充的数据
     * @return 实体对象--customer
     */
    public Customer queryForCustomers(String sql,Object...args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.获取连接对象
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //6.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //7.将数据封装到结果集当中
            if (rs.next()) {
                Customer customer = new Customer();
                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
                    String columnName = metaData.getColumnName(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = customer.getClass().getDeclaredField(columnName);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(customer,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                return customer;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //8.关闭资源
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;//结果集中没有数据返回null
    }


    /**
     * 对customer基本的查询操作
     */
    @Test
    public void testCustomers(){

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //1.获取连接
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象,并填充占位符
            String sql = "select id,name,email,birth,photo from customers where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setObject(1,1);

            //3.执行操作并返回一个结果集对象
            resultSet = ps.executeQuery();

            //4.将结果集中的每一个元素进行封装
            if (resultSet.next()) {
                //获取当前字段的各个数据
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                Date birth = resultSet.getDate(4);

                //显示数据
                //方式1:
    //            System.out.println("id = "+id+",name="+name+",email="+email+",birth="+birth);
                //方式2
    //            Object[] data = new Object[]{id,name,email,birth};
                //方式3:将数据封装为一个对象
                Customer customer = new Customer(id, name, email, birth);
                System.out.println(customer);

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.关闭资源对象
            JDBCUtils.closeResource(conn,ps,resultSet);
        }


    }


}

结果:image-20220425072540622

2-5-2:针对order写一个通用查询操作:

异常1:

由于表中的列名于实体类中的属性名不一致所导致。

image-20220425092458209

解决:

使用getColumnLabel 替换 getColumnName方法,并且使用实体类中的表名作为表的别名:

getColumnLabel :起了别名就使用别名,没有起别名就与getColumnName方法一样。

Gets the designated(指定的) column's suggested title for use in printouts and displays.
The suggested title is usually specified by the SQL AS clause. If a SQL AS is not specified,
the value returned from getColumnLabel will be the same as the value returned by the getColumnName method.

package com.xurong3.PrepareStatement.crud;

import com.xurong3.bean.Order;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.*;

/**
 * @auther xu
 * @date 2022/4/25 - 7:59
 */
public class OrderForQuery {

    @Test
    public void TestOrderForQuery() {
//        String sql = "select order_id,order_name,order_date from `order` where order_id = ?";
        String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
        Order order = orderForQuery(sql, 1);
        System.out.println(order);
    }

    /**
     * 对order表的通用查询操作
     * @param sql
     * @param args
     * @return
     * @throws Exception
     */
    public Order orderForQuery(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 metaData = rs.getMetaData();

            //根据元数据获取该表的列的数目
            int columnCount = metaData.getColumnCount();

            //封装数据
            if (rs.next()) {

                //造一个Order对象,用以封装数据
                Order order = new Order();

                for (int i = 0; i < columnCount; i++) {
                    //获取对应行的对应列的某一个数据
                    Object columnValue = rs.getObject(i + 1);

                    //获取对应行的对应列的名字
//                    String columnName = metaData.getColumnName(i + 1);

                    //Gets the designated(指定的) column's suggested title for use in printouts and displays.
                    // The suggested title is usually specified by the SQL AS clause. If a SQL AS is not specified,
                    // the value returned from getColumnLabel will be the same as the value returned by the getColumnName method.
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //通过反射将对应属性赋值
//                    Field declaredField = order.getClass().getDeclaredField(columnName);
                    Field declaredField = order.getClass().getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//权限问题
                    declaredField.set(order,columnValue);
                }
                return order;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;
    }
    @Test
    public void testQuery1(){

        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            //1.获取连接
            connection = JDBCUtils.getConnection();

            //2.预编译sql语句
            String sql = "select order_id,order_name,order_date from `order` where order_id = ?";
            ps = connection.prepareStatement(sql);

            //3.填充占位符
            ps.setObject(1,1);//填充占位符

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.封装数据
            if (rs.next()) {
                //获取第一行的每一列数据
                int id = (int)rs.getObject(1);
                String name = (String)rs.getObject(2);
                Date date = (Date)rs.getObject(3);

                //创建一个order对象,对数据进行封装
                Order order = new Order(id, name, date);
                System.out.println(order);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6.关闭资源
            JDBCUtils.closeResource(connection,ps,rs);
        }


    }
}

结果:

image-20220425093726524

2-5-3图解查询的操作流程:

image-20220425103336328

2-6:使用PrepareStatement实现不同表的查询操作---传入实体类的字节码文件对象,利用该对象动态实例化一个类

2-6-1:查询的是某一个表中的一条数据

image-20220425105700029

代码参考:

package com.xurong3.PrepareStatement.crud;

import com.xurong3.bean.Customer;
import com.xurong3.bean.Order;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

/**
 *
 *
 *
 * 使用PrepareStatement实现针对不同表的一个查询操作
 *
 * @auther xu
 * @date 2022/4/25 - 10:37
 */
public class PrepareStatementForQuery {

    @Test
    public void testgetInstance() {

        String sql = "select id,name,email,birth from customers where id = ?";
        Customer customer = getInstance(Customer.class, sql, 1);
        System.out.println(customer);

        String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
        Order order = getInstance(Order.class, sql1, 1);
        System.out.println(order);


    }
 /**
     * 针对不同表的一个通用查询操作,返回表的一条数据
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> T getInstance(Class<T> clazz,String sql,Object...args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.获取连接对象
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //6.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //7.将数据封装到结果集当中
            if (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //8.关闭资源
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;//结果集中没有数据返回null
    }
}

输出结果:

image-20220425105753532

2-6-2 查询的是某一个表中的多条数据

image-20220425150139389

 @Test
    public void testGetForList() {
        String sql = "select id,name,email,birth from customers where id < ?";
        List<Customer> list = getForListInstance(Customer.class,sql,12);
        list.forEach(System.out::println);

        String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id < ?";
        List<Order> list1 = getForListInstance(Order.class,sql1,4);
        list1.forEach(System.out::println);

    }

 /**
     * 查询的是某一个表中的多条数据
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> List<T> getForListInstance(Class<T> clazz, String sql, Object...args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.获取连接对象
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //6.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //7.创建一个集合对象
            ArrayList<T> list = new ArrayList<>();

            //8.将数据封装到结果集当中
            while (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //9.关闭资源
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;//结果集中没有数据返回null
    }

结果:

image-20220425151920465

总的代码参考:

package com.xurong3.PrepareStatement.crud;

import com.xurong3.bean.Customer;
import com.xurong3.bean.Order;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 *
 *
 *
 * 使用PrepareStatement实现针对不同表的一个查询操作
 *
 * @auther xu
 * @date 2022/4/25 - 10:37
 */
public class PrepareStatementForQuery {

    @Test
    public void testGetForList() {
        String sql = "select id,name,email,birth from customers where id < ?";
        List<Customer> list = getForListInstance(Customer.class,sql,12);
        list.forEach(System.out::println);

        String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id < ?";
        List<Order> list1 = getForListInstance(Order.class,sql1,4);
        list1.forEach(System.out::println);

    }

    public <T> List<T> getForListInstance(Class<T> clazz, String sql, Object...args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.获取连接对象
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //6.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //7.创建一个集合对象
            ArrayList<T> list = new ArrayList<>();

            //8.将数据封装到结果集当中
            while (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //9.关闭资源
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;//结果集中没有数据返回null
    }

    @Test
    public void testgetInstance() {

        String sql = "select id,name,email,birth from customers where id = ?";
        Customer customer = getInstance(Customer.class, sql, 1);
        System.out.println(customer);

        String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
        Order order = getInstance(Order.class, sql1, 1);
        System.out.println(order);


    }

    public <T> T getInstance(Class<T> clazz,String sql,Object...args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.获取连接对象
            conn = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //6.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //7.将数据封装到结果集当中
            if (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //8.关闭资源
            JDBCUtils.closeResource(conn,ps,rs);
        }
        return null;//结果集中没有数据返回null
    }
}

2-7:使用PrepareStatement解决了SQl注入问题以及其他优点(不知道我的代码为什么运行不出来)

image-20220425154810016

image-20220425155539593

2-8 小结:

image-20220425155433786

疑惑:

1.在2-4-2中关闭底层资源时,为什么没有关闭流对象?

2.SQL语句中使用关键字所导致至的异常

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order set order_name = 'CCC' where order_id = 4' at line 1

解决:

image-20220424110712746

将该关键字使用反单引号---``即可。

image-20220424110925633

3.在2-7 中自己的代码运行不出来(没有解决)

3.Blob数据类型

3-1 从表中读取Blob数据

/**
 * 向数据表customers中插入Blob类型的字段
 */
@Test
public void testInsert() throws Exception {
    //获取连接
    Connection connection = JDBCUtils.getConnection();
    //预编译sql语句
    String sql = "insert into customers(name,email,birth,photo) value(?,?,?,?)";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //填充占位符

    preparedStatement.setObject(1,"lixintin");
    preparedStatement.setObject(2,"li@qq.com");
    preparedStatement.setObject(3,"2001-01-01");

    //采用流的方式将照片文件以流的方式插入到数据库当中
    FileInputStream fis = new FileInputStream(new File("lib/butifulGirl.jpg"));
    preparedStatement.setBlob(4,fis);

    //执行对数据库的操作
    preparedStatement.execute();

    //关闭资源
    JDBCUtils.closeResource(connection,preparedStatement);

}

结果:(已经插入成功(从下面的读取数据也可验证),但是sqlyog没法显示)

image-20220425171243185

3-2 从表中读取Blob数据

package com.xurong5.biob;

import com.xurong3.bean.Customer;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;

import java.io.*;
import java.sql.*;

/**
 *
 * 测试使用PrepareStatement操作Blob类型的数据
 * @auther xu
 * @date 2022/4/25 - 16:06
 */
public class BlobTest {

    /**
     * 向数据表customers中插入Blob类型的字段
     */
    @Test
    public void testInsert() throws Exception {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //获取连接
            connection = JDBCUtils.getConnection();
            //预编译sql语句
            String sql = "insert into customers(name,email,birth,photo) value(?,?,?,?)";
            preparedStatement = connection.prepareStatement(sql);

            //填充占位符

            preparedStatement.setObject(1,"lixintin");
            preparedStatement.setObject(2,"li@qq.com");
            preparedStatement.setObject(3,"2001-01-01");

            //采用流的方式将照片文件以流的方式插入到数据库当中
            FileInputStream fis = new FileInputStream(new File("lib/butifulGirl.jpg"));
            preparedStatement.setBlob(4,fis);

            //执行对数据库的操作
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.closeResource(connection,preparedStatement);
        }


    }

    /**
     * 查询表中的Blob数据(照片)
     * @throws Exception
     */
    @Test
    public void testQuery() {
        Connection conn = null;//连接
        PreparedStatement ps = null;//预编译sql语句
        ResultSet resultSet = null;

        InputStream binaryStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth,photo from customers where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setObject(1,23);//填充占位符
            resultSet = ps.executeQuery();

            if (resultSet.next()) {

                //获取改行属性值的方法1:
    //            int id = resultSet.getInt(1);
    //            String name = resultSet.getString(2);
    //            String email = resultSet.getString(3);
    //            Date birth = resultSet.getDate(4);

                //获取改行属性值的方法2:
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String email = resultSet.getString("email");
                Date birth = resultSet.getDate("birth");
                Customer customer = new Customer(id,name,email,birth);
                System.out.println(customer);

                //将Blob类型的字段下载下来并保存到本地
                Blob photo = resultSet.getBlob("photo");
                binaryStream = photo.getBinaryStream();
                fileOutputStream = new FileOutputStream(new File("lib/butifulGirlCopy.jpg"));
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = binaryStream.read(buffer)) != -1) {
                    fileOutputStream.write(buffer,0,len);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关流
            try {
                if (binaryStream != null)
                    binaryStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (fileOutputStream != null)
                    fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            JDBCUtils.closeResource(conn,ps,resultSet);
        }
    }
}

输出结果:

image-20220425172225967

image-20220425172131301

3-3 插入Blob字段特殊情况的说明---插入文件过大解决方案

image-20220425212413955

解决:设置max_allowed_packet=16M

image-20220425212919597

结果:image-20220425213530355

image-20220425213430786

4.批量插入数据

1.验证批量插入数据时PrepareStatement性能优于Statement

image-20220426051207337

2.批量数据的插入

2-1: 方式1:进行数据批处理

image-20220426051801864

2-2: 方式2:进行数据批处理

 /**
     * 方式2:进行数据批处理
     * 批量插入数据时PrepareStatement性能优与Statement
     * @throws Exception
     */
    @Test
    public void testInsert1(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {

            long start = System.currentTimeMillis();
            //获取连接
            conn = JDBCUtils.getConnection();
            //预编译sql语句,返回PrepareStatement对象
            String sql = "insert into gooods(name) values(?)";
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 1; i <= 20000; i++) {
                ps.setObject(1,"name_"+i);
                //执行操作
                ps.execute();
            }
            long end = System.currentTimeMillis();
            System.out.println("所花费的时间:"+(end-start));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,ps);
        }
    }

结果:执行插入20000行时的结果

image-20220426055601608

2-3: 方式3:进行数据批处理

  • 方式3:进行数据批处理
    • 修改1: 使用 addBatch() / executeBatch() / clearBatch()----类似于流中的缓冲数组
    • 修改2:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
    •   ?rewriteBatchedStatements=true 写在配置文件的url后面
      
    • 修改3:使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
  /**

     *
     * 方式3:进行数据批处理
     * 修改1: 使用 addBatch() / executeBatch() / clearBatch()
     * 修改2:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
     * 		 ?rewriteBatchedStatements=true 写在配置文件的url后面
     * 修改3:使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
     */
    @Test
    public void testInsert2(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {

            long start = System.currentTimeMillis();
            //获取连接
            conn = JDBCUtils.getConnection();
            //预编译sql语句,返回PrepareStatement对象
            String sql = "insert into gooods(name) values(?)";
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 1; i <= 20000; i++) {
                ps.setObject(1,"name_"+i);

                //1.攒,sql
                ps.addBatch();
                if (i % 500 == 0) {//每500次执行一次插入操作
                    //2.执行batch(批)
                    ps.executeBatch();

                    //清空batch
                    ps.clearBatch();

                }

            }
            long end = System.currentTimeMillis();
            System.out.println("所花费的时间:"+(end-start));//所花费的时间:33472
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,ps);
        }
    }

结果:执行插入20000行的结果

image-20220426055747089

image-20220426060721875

2-4: 方式4:进行数据批处理

  /**
     * 通过限制数据的事务提交减少时间花费
     */
    @Test
    public void testInsert4(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {

            long start = System.currentTimeMillis();
            //获取连接
            conn = JDBCUtils.getConnection();
            //将事务的自动提交提交关闭,因为一次事务提交都需要花费时间,不如,将所有数据插入完成之后再进行事务提交
            conn.setAutoCommit(false);

            //预编译sql语句,返回PrepareStatement对象
            String sql = "insert into gooods(name) values(?)";
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 1; i <= 1000000; i++) {
                ps.setObject(1,"name_"+i);

                //1.攒,sql
                ps.addBatch();
                if (i % 500 == 0) {//每500次执行一次插入操作
                    //2.执行batch(批)
                    ps.executeBatch();

                    //清空batch
                    ps.clearBatch();

                }

            }

            //提交数据
            conn.commit();
            
            long end = System.currentTimeMillis();
            System.out.println("所花费的时间:"+(end-start));//所花费的时间:33472
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,ps);
        }
    }

结果:执行插入1000000行的结果

image-20220426061013222

5. 数据库事务

关于异常抛出与捕捉异常:

image-20220426092637385

image-20220426092852368

5-1:利用AA给BB转账来引出事务

1.正常转账:

image-20220426110841681

转账结果:

image-20220426111017499

2.不正常转账(用户名不正确或卡号不正确)----该如何处理

image-20220426111314093

image-20220426111609394

代码参考:

 //------------------------------未考虑数据库事务的转账操作-------------------------------

    /**
     * 针对数据表uer_table来说
     * AA用户给BB用户转账100
     *
     * update user_table set balance = balance - 100 where user = "AA";
     * update user_table set balance = balance + 100 where user = "BB";
     */
    @Test
    public void testUpdate() {
        String sql1 = "update user_table set balance = balance - 100 where user = ? ";
        update(sql1,"AA");

        //模拟网络异常(eg:卡号写错了)
        System.out.println(10 / 0);

        String sql2 = "update user_table set balance = balance + 100 where user = ? ";
        update(sql2,"BB");
        System.out.println("转账成功");
    }

    /**
     * 通用的增删改操作
     * @param sql
     * @param args 相当于占位符的不定长度的数组
     * @throws Exception
     */
    public int update(String sql,Object...args) {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1.获取数据库的连接
            connection = JDBCUtils.getConnection();

            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = connection.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作
            //Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
            //either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
            //that return nothing
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.资源的关闭
            JDBCUtils.closeResource(connection,ps);
        }
        return 0;
    }

5-2. 什么是事务

image-20220426112104796

image-20220426113955233

补充:在处理一个事务的时候,不要关闭连接,也就是不要执行资源关闭的操作。

总结:要想达到事务的回滚,必须保证数据没有提交

5-3:考虑事务以后的代码实现

1.正常转账

    @Test
    public void testUpdateWithTx(){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            System.out.println(connection.getAutoCommit());//默认是自动提交
            //1.取消数据的自动提交
            connection.setAutoCommit(false);

            String sql1 = "update user_table set balance = balance - 100 where user = ? ";
            update(connection,sql1,"AA");

            //模拟网络异常(eg:卡号写错了)
//        System.out.println(10 / 0);

            String sql2 = "update user_table set balance = balance + 100 where user = ? ";
            update(connection,sql2,"BB");
            System.out.println("转账成功");

            //2.提交数据
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();

            //3.解决中途出现异常,需要对数据进行回滚
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

        } finally {
            //修改其为自动提交
            //主要正对数据库连接池的使用
            try {
                connection.setAutoCommit(true);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            JDBCUtils.closeResource(connection,null);
        }
    }

    public int update(Connection conn,String sql,Object...args) {
        PreparedStatement ps = null;
        try {
            //1.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //3.执行操作
            //Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
            //either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
            //that return nothing
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭
            JDBCUtils.closeResource(null,ps);
        }
        return 0;
    }

结果:

image-20220426203937075

image-20220426204018917

2.不正常转账--进行了事务处理就会进行数据回滚(也就是说数据库表中的数据不会变化)

@Test
public void testUpdateWithTx(){
    Connection connection = null;
    try {
        connection = JDBCUtils.getConnection();
        System.out.println(connection.getAutoCommit());//默认是自动提交
        //1.取消数据的自动提交
        connection.setAutoCommit(false);

        String sql1 = "update user_table set balance = balance - 100 where user = ? ";
        update(connection,sql1,"AA");

        //模拟网络异常(eg:卡号写错了)
        System.out.println(10 / 0);

        String sql2 = "update user_table set balance = balance + 100 where user = ? ";
        update(connection,sql2,"BB");
        System.out.println("转账成功");

        //2.提交数据
        connection.commit();
    } catch (Exception e) {
        e.printStackTrace();

        //3.解决中途出现异常,需要对数据进行回滚
        try {
            connection.rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }

    } finally {
        //修改其为自动提交
        //主要正对数据库连接池的使用
        try {
            connection.setAutoCommit(true);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        JDBCUtils.closeResource(connection,null);
    }
}

public int update(Connection conn,String sql,Object...args) {
    PreparedStatement ps = null;
    try {
        //1.预编译SQL语句,返回一个PrepareStatement对象
        ps = conn.prepareStatement(sql);

        //2.填充占位符
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i+1,args[i]);
        }

        //3.执行操作
        //Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
        //either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
        //that return nothing
        return ps.executeUpdate();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //4.资源的关闭
        JDBCUtils.closeResource(null,ps);
    }
    return 0;
}

image-20220426204340286

image-20220426204507024

3.小结一波

1.从连接对象入手--预防数据自动提交

image-20220426205126918

2.通过设置参数--预防增删改等操作的自动提交功能

image-20220426205445799

3.通过以上两部起到预防数据自动提交,而此操作针对出现异常时才起到作用,也就是说,出现异常时,数据就不会自动提交。数据没有

提交成功,还需要对数据进行回滚,使数据保持原样。

image-20220426205634516

5.4 事务的ACID属性

  1. 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

5.4.1 数据库的并发问题

  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

5.4.2 四种隔离级别

  • 数据库提供的4种事务隔离级别:

    image-20220427052555613

  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED

  • Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

5-4-3:命令行验证mysql的隔离级别

1.创建两个mysql用户代表两个事务:

image-20220427065746381

image-20220427070116377

2.对用户2授权可以操作的数据库:

image-20220427071006533

3.防止数据自动提交,并查看mysql事务默认的隔离级别:

image-20220427072457961

4.可重复读:

image-20220427073736378

image-20220427074057657

image-20220427074322174

疑惑:如果Tom提交了数据,root仍然访问的是原来的重复数据,但是在下文的java代码实现中,如果更新事务提交了数据,查询事务查询到的是更新的数据,因为查询事务并没有关闭,应该查询到的数据是重复的数据。

image-20220427103242706

5.将隔离级别设置为 READ-COMMITTED--读已提交的数据

root事务中设置隔离级别:

Microsoft Windows [版本 10.0.19044.1645]
(c) Microsoft Corporation。保留所有权利。

C:\Users\Administrator>mysql -u root -p
Enter password: ******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.5.15 MySQL Community Server (GPL)

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> set transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> Ctrl-C -- exit!
Bye

C:\Users\Administrator>mysql -u root -p
Enter password: ******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 16
Server version: 5.5.15 MySQL Community Server (GPL)

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.00 sec)

mysql>

Tom事务中查看隔离级别:

Microsoft Windows [版本 10.0.19044.1645]
(c) Microsoft Corporation。保留所有权利。

C:\Users\Administrator>mysql -u Tom -pabc123
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.5.15 MySQL Community Server (GPL)

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.00 sec)

mysql>

能够读取可以提交的数据(Oracle采用这种隔离级别)-----没有解决可重复读问题

image-20220427084303397

6.READ UNCOMMITTED--见视频(40节,后面部分)

5-4-4 在没有设置隔离级别的数据库当中,需要用java代码设置隔离级别()

image-20220427095801571

代码参考:

package com.xu.transaction;

import com.xu.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.*;

/**
 * @auther xu
 * @date 2022/4/27 - 9:12
 */
public class TransationTest {




    //---------------------------下面是考虑事务的数据库转账操作-----------------------------

    @Test
    public void testUpdateWithTx(){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            System.out.println(connection.getAutoCommit());//默认是自动提交
            //1.取消数据的自动提交
            connection.setAutoCommit(false);

            String sql1 = "update user_table set balance = balance - 100 where user = ? ";
            update(connection,sql1,"AA");

            //模拟网络异常(eg:卡号写错了)
            System.out.println(10 / 0);

            String sql2 = "update user_table set balance = balance + 100 where user = ? ";
            update(connection,sql2,"BB");
            System.out.println("转账成功");

            //2.提交数据
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();

            //3.解决中途出现异常,需要对数据进行回滚
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

        } finally {

            //修改其为自动提交
            //主要正对数据库连接池的使用
            try {
                connection.setAutoCommit(true);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            JDBCUtils.closeResource(connection,null);
        }
    }

    public int update(Connection conn,String sql,Object...args) {
        PreparedStatement ps = null;
        try {
            //1.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //3.执行操作
            //Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
            //either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
            //that return nothing
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭
            JDBCUtils.closeResource(null,ps);
        }
        return 0;
    }



    //---------------------考虑隔离级别的版本----------------------------------

    @Test
    public void testTransactionSelect() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        //打印当前的隔离级别
        System.out.println(connection.getTransactionIsolation());
        //设置级别为--读以提交
//        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);//mysql默认的隔离级别
        //取消数据的自动提交
        connection.setAutoCommit(false);

        String sql = "select user,password,balance from user_table where user = ?";
        User cc = getInstance(connection, User.class, sql, "CC");
        System.out.println(cc);

    }
    @Test
    public void testTransactionUpdate() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        //取消数据的自动提交
        connection.setAutoCommit(false);

        String sql = "update user_table set balance = ? where user = ?";
        update(connection,sql,7000,"CC");
        Thread.sleep(10000);//15s
        System.out.println("修改结束");
    }
    /**
     * 获取某一个表的一条数据,查询数据库表中的某一个对象
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object...args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //3.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //4.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //5.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //6.将数据封装到结果集当中
            if (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7.关闭资源
            JDBCUtils.closeResource(null,ps,rs);//连接对象是从外面通过参数传进来的,关闭的时候不要在此方法中关闭
        }
        return null;//结果集中没有数据返回null
    }

}

结果探索:

image-20220427100348671

image-20220427101836364

image-20220427102047511

未解决的问题:

1.我想将mysql的隔离级别改为但是没有成功:

image-20220428075836292

 connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);//读已提交

6.DAO及相关实现类

6-1. 提供操作数据表的BaseDAO

image-20220429173454095

package com.xu.dao;

import com.xu.util.JDBCUtils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * 封装了数据库表的通用的操作--增删改查
 * @auther xu
 * @date 2022/4/27 - 10:41
 */
public abstract class BaseDAO {//将此类抽象化是为了避免实例化,改抽象类只提供对数据库的增删改查

    /**
     * 通用的增删改操作----version 2.0(考虑上事务)
     * @param conn
     * @param sql
     * @param args
     * @return
     */
    public int update(Connection conn, String sql, Object...args) {
        PreparedStatement ps = null;
        try {
            //1.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //3.执行操作
            //Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
            //either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
            //that return nothing
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭
            JDBCUtils.closeResource(null,ps);
        }
        return 0;
    }

    /**
     *
     * 查询--返回一个对象--version 2.0(考虑上事务)
     * 获取某一个表的一条数据,查询数据库表中的某一个对象
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object...args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //3.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //4.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //5.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //6.将数据封装到结果集当中
            if (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7.关闭资源
            JDBCUtils.closeResource(null,ps,rs);//连接对象是从外面通过参数传进来的,关闭的时候不要在此方法中关闭
        }
        return null;//结果集中没有数据返回null
    }


    /**
     * 查询--返回表中的多个对象---需要考虑事务,将连接对象以参数的方式传递--version 2.0(考虑上事务)
     * 查询的是某一个表中的多条数据
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> List<T> getForListInstance(Connection conn,Class<T> clazz, String sql, Object...args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //6.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //7.创建一个集合对象
            ArrayList<T> list = new ArrayList<>();

            //8.将数据封装到结果集当中
            while (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //9.关闭资源
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;//结果集中没有数据返回null
    }



    /**
     * SELECT COUNT(*) FROM user_table;
     * 此方法对应如上sql语句的特殊查询(结果集只有一列数据)
     * @param conn
     * @param sql
     * @param args
     * @param <F>
     * @return
     * @throws SQLException
     */
    public <F> F getValue(Connection conn,String sql,Object...args) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.预编译sql语句,并返回PrepareStatement对象
            ps = conn.prepareStatement(sql);
            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }
            //执行操作,返回结果集
            rs = ps.executeQuery();

            if (rs.next()) {
                return (F)rs.getObject(1);//特殊:只有一列数据
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
        JDBCUtils.closeResource(null,ps,rs);

        }
        return null;
    }
}

补充:对特殊sql语句定义方法的补充说明

image-20220427110627515

6-2:CustomerDAO以及CustomerDAOImpl实现

image-20220429173522594

CustomerDAO:

package com.xu2.dao;

import com.xu2.bean.Customer;

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

/**
 * 接口体现具体的规范,具体实现类实现接口就可以了
 * 此接口用于规范对customers表的常用操作
 * @auther xu
 * @date 2022/4/27 - 11:26
 */
public interface CustomerDAO {

    /**
     * @Description 将Customer对象添加到数据库中
     * @Author xu
     * @param conn :
     * @param customer :
     * @return
     * @throws
     * @Date
     */
    void insert(Connection conn, Customer customer);

    /**
     * @Description  针对指定的id,删除表中的一条记录
     * @Author xu
     * @param conn :
     * @param id :
     * @return
     * @throws
     * @Date
     */
    void deleteById(Connection conn, int id);

    /**
     * @Description 针对内存中的customer对象,去修改数据表中指定的记录
     * @Author xu
     * @param conn :
     * @param customer :
     * @return
     * @throws
     * @Date
     */
    void update(Connection conn,Customer customer);

    /**
     * @Description 针对指定的id查询得到对应的Customer对象
     * @Author xu
     * @param conn :
     * @param id :
     * @return
     * @throws
     * @Date
     */
    Customer getCustomerById(Connection conn,int id);

    /**
     * @Description 查询到表中的所有记录构成的集合
     * @Author xu
     * @param conn :
     * @return
     * @throws
     * @Date
     */
    List<Customer> getAll(Connection conn);

    /**
     * @Description 返回数据表中的数据的条目数(注意:数据表中的数据条目可能特别多,所以需要用到long类型作为返回值)
     * @Author xu
     * @param conn :
     * @return
     * @throws
     * @Date
     */
    long getCount(Connection conn);

    /**
     * @Description 返回数据表中的最大生日
     * @Author xu
     * @param conn :
     * @return
     * @throws
     * @Date
     */
    Date getMaxBirth(Connection conn);
}



CustomerDAOImpl:

image-20220429173549703

package com.xu2.dao;

import com.xu2.bean.Customer;

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

/**
 * @auther xu
 * @Description
 * @date 2022/4/28 - 20:06
 * @Modified By:
 */
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {

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

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

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

    }

    @Override
    public Customer getCustomerById(Connection conn, int id) {

        String sql = "select id,name,email,birth from customers where id = ?";
        Customer customer = getInstance(conn, Customer.class, sql, id);
        return customer;
    }

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

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

    @Override
    public Date getMaxBirth(Connection conn) {
        String sql = "select max(birth) from customers";
        return  getValue(conn,sql);
    }
}

6-3. CustomerDAOImpl测试类

image-20220429173609870

import com.xu1.util.JDBCUtils;
import com.xu2.bean.Customer;
import com.xu2.dao.CustomerDAOImpl;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Test;

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

/**
 * @auther xu
 * @Description
 * @date 2022/4/29 - 16:55
 * @Modified By:
 */
public class CustomerDAOImplTest extends Assert {

    private CustomerDAOImpl dao = new CustomerDAOImpl();

    @Test
    public void testInsert() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = new Customer(1,"婷欣","abc@bca.com", new Date(122789609L));
            dao.insert(conn,customer);
            System.out.println("添加成功");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testDeleteById() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            dao.deleteById(conn,23);
            System.out.println("删除成功");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testUpdate() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = new Customer(13, "张学友", "zhangxueyou@123.com", new Date(1234213L));
            dao.update(conn,customer);
            System.out.println("修改成功");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetCustomerById() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = dao.getCustomerById(conn, 29);
            System.out.println(customer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetAll() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            List<Customer> customers = dao.getAll(conn);
            customers.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetCount() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            long count = dao.getCount(conn);
            System.out.println("customers表中数据条数为:"+count);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetMaxBirth() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Date maxBirth = dao.getMaxBirth(conn);
            System.out.println("customers表中的最大生日为:"+maxBirth);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }
}

6-4. 优化后的DAO及其实现类的测试


1.对BaseDAO进行优化:

image-20220429164825131

2.修改之处:

image-20220429162213375

image-20220429162257564

image-20220429162331803

image-20220429164413504

image-20220429154843751

image-20220429164453964

3.相关java类

1.BaseDAO类:

image-20220429173256327

package com.xu3.dao;

import com.xu1.util.JDBCUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * 数据访问对象模式(Data Access Object Pattern)或 DAO 模式。
 * 是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。
 * 封装了数据库表的通用的操作--增删改查
 * @auther xu
 * @date 2022/4/27 - 10:41
 */
public abstract class BaseDAO<T> {//将此类抽象化是为了避免实例化,改抽象类只提供对数据库的增删改查

    private Class<T> clazz = null;
    {
        //获取当前BaseDAO的子类继承的父类中的泛型
        Type genericSuperclass = this.getClass().getGenericSuperclass();//获取带泛型的父类
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;//获取带参数的泛型
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//对于该类泛型参数只有一个Customer,所以该数组只有一个参数Customer
        clazz = (Class<T>) actualTypeArguments[0];//获取泛型的第一个参数,也就是得到了T实体(eg:Customer)
    }

    /**
     * 通用的增删改操作----version 2.0(考虑上事务)
     * @param conn
     * @param sql
     * @param args
     * @return
     */
    public int update(Connection conn, String sql, Object...args) {
        PreparedStatement ps = null;
        try {
            //1.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //3.执行操作
            //Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
            //either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
            //that return nothing
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭
            JDBCUtils.closeResource(null,ps);
        }
        return 0;
    }

    /**
     *
     * 查询--返回一个对象--version 2.0(考虑上事务)
     * 获取某一个表的一条数据,查询数据库表中的某一个对象
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public T getInstance(Connection conn, String sql, Object...args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //3.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //4.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //5.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //6.将数据封装到结果集当中
            if (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7.关闭资源
            JDBCUtils.closeResource(null,ps,rs);//连接对象是从外面通过参数传进来的,关闭的时候不要在此方法中关闭
        }
        return null;//结果集中没有数据返回null
    }


    /**
     * 查询--返回表中的多个对象---需要考虑事务,将连接对象以参数的方式传递--version 2.0(考虑上事务)
     * 查询的是某一个表中的多条数据
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public List<T> getForListInstance(Connection conn, String sql, Object...args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //2.预编译SQL语句,返回一个PrepareStatement对象
            ps = conn.prepareStatement(sql);

            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }

            //4.执行操作并返回一个结果集对象
            rs = ps.executeQuery();

            //5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
            ResultSetMetaData metaData = rs.getMetaData();

            //6.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();

            //7.创建一个集合对象
            ArrayList<T> list = new ArrayList<>();

            //8.将数据封装到结果集当中
            while (rs.next()) {
                T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类

                for (int i = 0; i < columnCount; i++) {
                    //获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
                    Object columnVale = rs.getObject(i + 1);

                    /*
                    将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
                    造对象两种方式:
                    1.通过含参构造器的方式完成对属性的赋值;
                    2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
                    此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
                    参数的构造器
                     */

                    //获取每一个列的列名
//                    String columnName = metaData.getColumnName(i + 1);
                    String columnLabel = metaData.getColumnLabel(i + 1);

                    //给customer对象指定的columnName属性,赋值为columValue,通过反射
                    Field declaredField = clazz.getDeclaredField(columnLabel);
                    declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
                    declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //9.关闭资源
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;//结果集中没有数据返回null
    }



    /**
     * SELECT COUNT(*) FROM user_table;
     * 此方法对应如上sql语句的特殊查询(结果集只有一列数据)
     * @param conn
     * @param sql
     * @param args
     * @param <F>
     * @return
     * @throws SQLException
     */
    public <F> F getValue(Connection conn,String sql,Object...args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1.预编译sql语句,并返回PrepareStatement对象
            ps = conn.prepareStatement(sql);
            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }
            //执行操作,返回结果集
            rs = ps.executeQuery();

            if (rs.next()) {
                return (F)rs.getObject(1);//特殊:只有一列数据
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
        JDBCUtils.closeResource(null,ps,rs);

        }
        return null;
    }
}

2.CustomerDAO接口:

image-20220429173323420

package com.xu3.dao;

import com.xu2.bean.Customer;

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

/**
 * 接口体现具体的规范,具体实现类实现接口就可以了
 * 此接口用于规范对customers表的常用操作
 * @auther xu
 * @date 2022/4/27 - 11:26
 */
public interface CustomerDAO {

    /**
     * @Description 将Customer对象添加到数据库中
     * @Author xu
     * @param conn :
     * @param customer :
     * @return
     * @throws
     * @Date
     */
    void insert(Connection conn, Customer customer);

    /**
     * @Description  针对指定的id,删除表中的一条记录
     * @Author xu
     * @param conn :
     * @param id :
     * @return
     * @throws
     * @Date
     */
    void deleteById(Connection conn, int id);

    /**
     * @Description 针对内存中的customer对象,去修改数据表中指定的记录
     * @Author xu
     * @param conn :
     * @param customer :
     * @return
     * @throws
     * @Date
     */
    void update(Connection conn,Customer customer);

    /**
     * @Description 针对指定的id查询得到对应的Customer对象
     * @Author xu
     * @param conn :
     * @param id :
     * @return
     * @throws
     * @Date
     */
    Customer getCustomerById(Connection conn,int id);

    /**
     * @Description 查询到表中的所有记录构成的集合
     * @Author xu
     * @param conn :
     * @return
     * @throws
     * @Date
     */
    List<Customer> getAll(Connection conn);

    /**
     * @Description 返回数据表中的数据的条目数(注意:数据表中的数据条目可能特别多,所以需要用到long类型作为返回值)
     * @Author xu
     * @param conn :
     * @return
     * @throws
     * @Date
     */
    long getCount(Connection conn);

    /**
     * @Description 返回数据表中的最大生日
     * @Author xu
     * @param conn :
     * @return
     * @throws
     * @Date
     */
    Date getMaxBirth(Connection conn);
}
3.CustomerDAOImpl实现类:

image-20220429173354374

package com.xu3.dao;

import com.xu2.bean.Customer;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;

/**
 * @auther xu
 * @Description
 * @date 2022/4/28 - 20:06
 * @Modified By:
 */
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO {

//    {
//        Type genericSuperclass = this.getClass().getGenericSuperclass();//获取带泛型的父类
//        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;//获取带参数的泛型
//        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//对于该类泛型参数只有一个Customer,所以该数组只有一个参数Customer
//        clazz = actualTypeArguments[0];//获取泛型的第一个参数,也就是得到了Customer
//    }
//    注释的代码就是为了获取Customer,但是每创建一个实体DAOImple对象都需要进行该操作,未免麻烦,直接放到父类BaseDAO<Customer>继承即可,注意this句柄的使用
    @Override
    public void insert(Connection conn, Customer customer) {
        String sql = "insert into customers(name,email,birth)values(?,?,?)";
        update(conn,sql,customer.getName(),customer.getEmail(),customer.getBirth());
    }

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

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

    }

    @Override
    public Customer getCustomerById(Connection conn, int id) {

        String sql = "select id,name,email,birth from customers where id = ?";
        Customer customer = getInstance(conn, sql, id);
        return customer;
    }

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

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

    @Override
    public Date getMaxBirth(Connection conn) {
        String sql = "select max(birth) from customers";
        return  getValue(conn,sql);
    }
}

4.CustomerDAOImpl测试类

image-20220429173418752

package com.xu3.dao.junit;

import com.xu1.util.JDBCUtils;
import com.xu2.bean.Customer;
import com.xu3.dao.CustomerDAOImpl;
import org.junit.Assert;
import org.junit.Test;

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

/**
 * @auther xu
 * @Description
 * @date 2022/4/29 - 16:55
 * @Modified By:
 */
public class CustomerDAOImplTest extends Assert {

    private CustomerDAOImpl dao = new CustomerDAOImpl();

    @Test
    public void testInsert() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = new Customer(1,"婷欣","abc@bca.com", new Date(122789609L));
            dao.insert(conn,customer);
            System.out.println("添加成功");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testDeleteById() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            dao.deleteById(conn,23);
            System.out.println("删除成功");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testUpdate() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = new Customer(13, "张学友", "zhangxueyou@123.com", new Date(1234213L));
            dao.update(conn,customer);
            System.out.println("修改成功");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetCustomerById() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = dao.getCustomerById(conn, 10);
            System.out.println(customer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetAll() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            List<Customer> customers = dao.getAll(conn);
            customers.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetCount() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            long count = dao.getCount(conn);
            System.out.println("customers表中数据条数为:"+count);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void testGetMaxBirth() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Date maxBirth = dao.getMaxBirth(conn);
            System.out.println("customers表中的最大生日为:"+maxBirth);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }
}

7.数据库连接池

7-1 数据库连接池技术概述

7-2 数据库连接池两种实现方式

7-2-1 根据需要设置数据库连接池的基本配置属性

image-20220501162014562

7-2-2 从连接池中获取连接对象方式--c3p0

方式1--c3p0:

image-20220501203543436

/**
 * @Description 从连接池中获取连接对象方式1
 * @Author xu

 * @return 
 * @throws
 * @Date 2022/5/1 16:23
 */
@Test
public void testGetConnection() throws Exception {
    ComboPooledDataSource cpds = new ComboPooledDataSource();//厂商的实现类接口
    cpds.setDriverClass( "com.mysql.jdbc.Driver" );//数据库驱动
    cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
    cpds.setUser("root");
    cpds.setPassword("draf19");

    //设置初始时数据库连接池中的连接对象
    cpds.setInitialPoolSize(10);//设置连接对象为10个
    Connection connection = cpds.getConnection();
    System.out.println(connection);
}

结论:

image-20220501162547368

方式2--c3p0:

image-20220501170149558

image-20220501170240419

c3p0-connection.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<c3p0-config>

    <!-- 提供获取连接的4个基本信息-->
    <named-config name="c3p0Conn">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="user">root</property>
        <property name="password">draf19</property>

        <!-- 进行数据库管理的基本信息 -->

<!--        当数据库连接池中的连接数不够时,c3p0每一次性向数据库服务器申请的连接数-->
        <property name="acquireIncrement">5</property>
<!--        c3p0数据库连接池中初始化时的连接数-->
        <property name="initialPoolSize">10</property>
<!--        c3p0数据库连接池维护的最少连接数-->
        <property name="minPoolSize">10</property>
<!--        c3p0数据库连接池维护的最多连接数-->
        <property name="maxPoolSize">1000</property>

<!--        c3p0数据库连接池最多维护的Statements的个数-->
        <property name="maxStatements">0</property>
<!--        每一个连接中可以最多使用的Statement的个数-->
        <property name="maxStatementsPerConnection">5</property>

    </named-config>
</c3p0-config>
 /**
     * @Description 使用配置文件
     * @Author xu

     * @return
     * @throws
     * @Date 2022/5/1 16:57
     */
    @Test
    public void testGetConnection2() throws Exception {
        ComboPooledDataSource cpds = new ComboPooledDataSource("c3p0Conn");//自动读取配置文件信息初始化
        Connection connection = cpds.getConnection();
        System.out.println(connection);
    }

7-3-2:

使用c3p0数据库连接池获取连接测试:

image-20220501202904512

image-20220501202715762

改进:

image-20220501203358553

7-2-3--使用JBCP连接池

1.导入的jar包:

image-20220501203846893

2.相关类及其方法:

image-20220502170300688

3.dbcp连接池常用基本配置属性:
dbcp连接池常用基本配置属性

1.initialSize :连接池启动时创建的初始化连接数量(默认值为0)

2.maxActive :连接池中可同时连接的最大的连接数(默认值为8,调整为20,高峰单机器在20并发左右,自己根据应用场景定)

3.maxIdle:连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制(默认为8个,maxIdle不能设置太小,因为假如在高负载的情况下,连接的打开时间比关闭的时间快,会引起连接池中idle的个数 上升超过maxIdle,而造成频繁的连接销毁和创建,类似于jvm参数中的Xmx设置)

4.minIdle:连接池中最小的空闲的连接数,低于这个数量会被创建新的连接(默认为0,调整为5,该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大,因为在机器很空闲的时候,也会创建低于minidle个数的连接,类似于jvm参数中的Xmn设置)

5.maxWait  :最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待(默认为无限,调整为60000ms,避免因线程池不够用,而导致请求被无限制挂起)

6.poolPreparedStatements:开启池的prepared(默认是false,未调整,经过测试,开启后的性能没有关闭的好。)

7.maxOpenPreparedStatements:开启池的prepared 后的同时最大连接数(默认无限制,同上,未配置)

8.minEvictableIdleTimeMillis  :连接池中连接,在时间段内一直空闲, 被逐出连接池的时间

9.removeAbandonedTimeout  :超过时间限制,回收没有用(废弃)的连接(默认为 300秒,调整为180)

10.removeAbandoned  :超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收(默认为false,调整为true)
4.代码实现获取连接对象及其属性设置:
方式1:
 /**
     * @Description 测试DBCP数据库连接技术
     * @Author xu

     * @return
     * @throws
     * @Date 2022/5/2 17:04
     */
    @Test
    public void testGetConnection() throws SQLException {

        //创建DBCP数据库连接池
        BasicDataSource source = new BasicDataSource();

        //设置获取连接的基本信息
        source.setDriverClassName("com.mysql.jdbc.Driver");
        source.setUrl("jdbc:mysql://localhost:3306/test");
        source.setUsername("root");
        source.setPassword("draf19");

        //设置其他设计数据库连接池管理的相关属性
        source.setInitialSize(10);
        source.setMaxActive(10);

        Connection connection = source.getConnection();
        System.out.println(connection);
    }

输出结果:

image-20220502170747318

方式2:使用加载配置文件的 方式

dbcp.properties

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=draf19


initialSize=10

  /**
     * @Description 使用配置文件获取连接对象
     * @Author xu

     * @return
     * @throws
     * @Date 2022/5/2 17:11
     */
    @Test
    public void testGetConnection2() throws Exception {

        Properties properties = new Properties();

        //导入文件流方式1:
//        FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));

        //导入文件流方式2:使用类的系统加载器
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
//        InputStream is = ClassLoader.getSystemResourceAsStream("dbcp.properties");
        properties.load(is);

        DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

    }

结果:

image-20220502172633613

改进:

image-20220502173559002

 private static DataSource dataSource = null;
    static {
        try {
            Properties properties = new Properties();

            //导入文件流方式1:
//        FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));

            //导入文件流方式2:使用类的系统加载器
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
//        InputStream is = ClassLoader.getSystemResourceAsStream("dbcp.properties");
            properties.load(is);

            dataSource = BasicDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * @Description 使用dbcp数据库连接池技术获取数据库连接对象
     * @Author xu

     * @return
     * @throws
     * @Date 2022/5/2 17:28
     */
    public static Connection getConnection2() throws Exception {

        Connection connection = dataSource.getConnection();
        return connection;

    }

测试:

image-20220502174037625

7-2-4--使用Druid

1.入门

image-20220502193230380

  • 详细配置参数:
配置 缺省 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
2.使用Druid获取数据库连接对象:

image-20220502194639516

3.代码实现
/**
 * @Description 使用Druid数据库连接池
 * @Author xu
 * @param null : 
 * @return 
 * @throws
 * @Date 2022/5/2 19:53
 */
private static DataSource dataSourceForDruid = null;
static {
    try {
        //创建配置文件对象
        Properties properties = new Properties();

        //读取配置文件,以流的形式载入到配置文件对象中
        InputStream is = ClassLoader.getSystemResourceAsStream("druid.properties");
        properties.load(is);

        //直接使用配置文件获取连接对象
        dataSourceForDruid = DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public static  Connection getConnection3() throws SQLException {

    Connection connection = dataSourceForDruid.getConnection();
    return connection;
}

测试结果:

image-20220502195722294

8.Apache-DBUtils实现CRUD操作

1.增删改操作的实现:

package com.xu5.dbutil;

import com.xu4.util.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;

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

/**
 * @auther xu
 * @Description
 * @date 2022/5/2 - 20:02
 * @Modified By:
 */
public class QueryRunnerTest {

    /**
     * @Description 利用dbutils实现数据的插入操作(删除与更改就是sql语句不同)
     * @Author xu

     * @return
     * @throws
     * @Date 2022/5/2 20:50
     */
    @Test
    public void testInsert(){
        Connection conn = null;
        try {
            QueryRunner queryRunner = new QueryRunner();

            //获取连接对象
            conn = JDBCUtils.getConnection3();//com.xu4.util.JDBCUtils

            //执行操作
            String sql = "insert into customers(name,email,birth)values(?,?,?)";
            int insertCount = queryRunner.update(conn, sql, "黄家驹", "huangjiaju@123.com", "1993-06-24");
            System.out.println("插入操作成功了"+insertCount+"条数据");
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);//com.xu4.util.JDBCUtils
        }
    }
}

结果:

image-20220502210000033

image-20220502210025696

2.查询操作

1.查询一条记录:

image-20220502210734435

image-20220502210828054

image-20220502211259632

2.查询多条记录

1.BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
  /**
     * BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
     * @Description 查询多条记录
     * @Author xu
     * @return
     * @throws
     * @Date 2022/5/2 21:16
     */
    @Test
    public void testQuery2() throws Exception {
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = JDBCUtils.getConnection();
        String sql = "select id,name,email,birth from customers where id < ?";

        BeanListHandler<Customer> listHandler = new BeanListHandler<>(Customer.class);//结果集处理器

        List<Customer> query = queryRunner.query(connection, sql, listHandler, 30);
        query.forEach(System.out::println);
    }

结果:

image-20220502212131593

2.BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
  /**
     * BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
     * @Description 查询多条记录
     * @Author xu
     * @return
     * @throws
     * @Date 2022/5/2 21:16
     */
    @Test
    public void testQuery2() throws Exception {
        Connection connection = null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id < ?";

            BeanListHandler<Customer> listHandler = new BeanListHandler<>(Customer.class);//结果集处理器

            List<Customer> query = queryRunner.query(connection, sql, listHandler, 30);
            query.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

image-20220502214553285

3.BeanHander:是ResultSetHandler接口的实现类,对应表中的一条记录,将字段及相应字段的值作为map中的key和value
  /**
     * BeanHander:是ResultSetHandler接口的实现类,对应表中的一条记录
     * 将字段及相应字段的值作为map中的key和value
     * @Description 
     * @Author xu
     * @return 
     * @throws
     * @Date 2022/5/2 21:25
     *
     */
    @Test
    public void testQuery3() throws Exception {
        Connection connection = null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id = ?";

            MapHandler handler = new MapHandler();//结果集处理器

            Map<String, Object> customer = queryRunner.query(connection, sql, handler, 32);
            System.out.println(customer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

image-20220502214513785

4.BeanHander:是ResultSetHandler接口的实现类,对应表中的多条记录
  /**
     * BeanHander:是ResultSetHandler接口的实现类,对应表中的多条记录
     * 将字段及相应字段的值作为map中的key和value
     * @Description
     * @Author xu
     * @return
     * @throws
     * @Date 2022/5/2 21:25
     *
     */
    @Test
    public void testQuery4() throws Exception {
        Connection connection = null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id < ?";

            MapListHandler handler = new MapListHandler();//结果集处理器

            List<Map<String, Object>> customerList = queryRunner.query(connection, sql, handler, 32);
            customerList.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

结果:

image-20220502214350353

5.特殊查询--ScalarHandler用于查询特殊值
1.总条数

image-20220502215945141

@Test
public void testQuery5() throws Exception {
    Connection connection = null;
    try {
        QueryRunner queryRunner = new QueryRunner();
        connection = JDBCUtils.getConnection();

        String sql = "select count(*) from customers";
        ScalarHandler handler = new ScalarHandler();
        Long count = (Long)queryRunner.query(connection, sql, handler);
        System.out.println("customers数据条数为:"+count);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(connection,null);
    }
}

image-20220502220357260

2.最大生日
/**
     *
     * @Description 特殊查询---查询最大的生日为
     * @Author xu
     * @return
     * @throws
     * @Date 2022/5/2 22:06
     *
     */
    @Test
    public void testQuery6() throws Exception {
        Connection connection = null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection = JDBCUtils.getConnection();

            String sql = "select max(birth) from customers";
            ScalarHandler handler = new ScalarHandler();
            Date maxBirth = (Date)queryRunner.query(connection, sql, handler);
            System.out.println("最大的生日为:"+maxBirth);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

结果:

image-20220502220853649

6.自己实现一个匿名实现类,通过重写handler方法达到自己对结果集处理的目的
 /**
     *
     * @Description 自己实现一个匿名实现类,通过重写handler方法达到自己对结果集处理的目的
     * @Author xu
     * @return
     * @throws
     * @Date 2022/5/2 22:13
     *
     */
    @Test
    public void testQuery7() throws Exception {
        Connection connection = null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id = ?";

            ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
                @Override
                public Customer handle(ResultSet resultSet) throws SQLException {
                    System.out.println("重写了handle方法");
                    return null;
                }
            };//结果集处理器

            Customer customer = queryRunner.query(connection, sql, handler, 32);
            System.out.println(customer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

image-20220502221948474

image-20220502222526631

3.使用dbutils.jar中提供的DBUtils工具类,实现资源的关闭

 /**
     *
     * @Description 使用dbutils.jar中提供的DBUtils工具类,实现资源的关闭
     * @Author xu
     * @return 
     * @throws
     * @Date 2022/5/2 22:28
     *
     */
    public static void closeResource1(Connection conn, Statement ps, ResultSet rs) {//java.sql public interface Statement
        //8.资源的关闭
//        try {
//            DbUtils.close(rs);
//        } catch (SQLException throwables) {
//            throwables.printStackTrace();
//        }
//        try {
//            DbUtils.close(ps);
//        } catch (SQLException throwables) {
//            throwables.printStackTrace();
//        }
//        try {
//            DbUtils.close(conn);
//        } catch (SQLException throwables) {
//            throwables.printStackTrace();
//        }
        DbUtils.closeQuietly(rs);
        DbUtils.closeQuietly(ps);
        DbUtils.closeQuietly(conn);

    }
posted @ 2022-11-14 17:24  远道而重任  阅读(5)  评论(0编辑  收藏  举报