JDBC-

一、JDBC概述

1、JDBD介绍

1)JDBC是一个独立于特定数据库管理系统,通用的SQL数据库存取和操作的公共接口(一组API),定义用来访问数据库的标准Java库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
JDBC-_第1张图片
2)JDBC体系结构

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

面向应用的API:

java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。

面向数据库的API

java Driver API,供开发商开发数据库驱动程序用


2、JDBD程序编写步骤

JDBC-_第2张图片


二、获取数据库连接

1、URL

JDBC-_第3张图片

2、五种实现方式

特点:直接获取Driver(驱动)对象

// 方式一:
    @Test
    public void testConnection1() throws SQLException {
        //1、获取Driver(驱动)对象
        Driver driver =new com.mysql.jdbc.Driver();

        //2、提供要连接的数据库
        /*
            url:http://lovalhost:8080/gmall/keyboard.jpg
            jdbc:mysql:  协议
            localhost:   ip地址
            3306:       默认端口号
            test:       名test的数据库
        */
        String url ="jdbc:mysql://localhost:3306/bjpowernode";


        //3、提供连接需要的用户名和密码
        //将用户名和密码封装在Properties中
        Properties info =new Properties();
        info.setProperty("user","root");
        info.setProperty("password","4680123");

        //4、获取连接
        java.sql.Connection conn = driver.connect(url,info);
        System.out.println(conn);
    }

特点:对方式一的迭代—如下程序不出现第三方的api,使程序具有更好的可移植性。

//方式二:对方式一的迭代---如下程序不出现第三方的api,使程序具有更好的可移植性。
    @Test
    public void testConnection2() throws Exception {
        //1、获取Driver实现类对象:---使用反射
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver=(Driver) clazz.getDeclaredConstructor().newInstance();

        //2、提供要连接的数据库
        String url ="jdbc:mysql://localhost:3306/bjpowernode";

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

        //4、获取连接
        java.sql.Connection conn = driver.connect(url,info);
        System.out.println(conn);
    }

特点:在方式二的基础上,使用DriverManager替换Driver

// 方式三:使用DriverManager替换Driver
    @Test
    public void testConnection3() throws Exception{
        //1、提供三个连接的基本信息
        String url ="jdbc:mysql://localhost:3306/bjpowernode";
        String user ="root";
        String password ="4680123";

        //2、获取Driver的实现类对象
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver=(Driver) clazz.getDeclaredConstructor().newInstance();

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

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

特点:优化方式三—可以只是加载驱动,不用显示的注册驱动了

// 方式四:优化方式三---可以只是加载驱动,不用显示的注册驱动了
    @Test
    public void testConnection4() throws Exception{
        //1、提供三个连接的基本信息
        String url ="jdbc:mysql://localhost:3306/bjpowernode";
        String user ="root";
        String password ="4680123";

        //2、加载Driver
        Class.forName("com.mysql.jdbc.Driver");
        //相较于方式三,可以省略如下的操作

        //Driver driver=(Driver) clazz.getDeclaredConstructor().newInstance();

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

        // 为什么可以省略上述操作
        // 在mysql的Driver实现类中,存在一个静态代码块,
        // 随着类的加载,内存实现了上述省略的操作

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

⑤- - -最终版 特点:将数据库连接需要的4个基本信息声明在配置文件中,读取配置文件jdbc.properties,获取连接

此方法的好处

* 1、实现了数据与代码的分离;实现了解耦
* 2、如果需要修改配置文件信息,可以避免程序重新打包
@Test
    public void testConnection5() throws Exception {
        //1、读取配置文件中的4个基本信息  系统类的加载器
        InputStream is = Connection.class.getClassLoader().getResourceAsStream("jdbc.properties");

        Properties pros = new Properties(); //Hashtable子类,key和value都是String类型
        pros.load(is);

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

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

        //3、获取链接
        java.sql.Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);
    }


三、使用PreparedStatement实现CURD操作

JDBC-_第4张图片

1、CURD的含义

代表创建(Create)、更新(Update)、读取(Retrieve)和删除(Delete)操作。

2、操作和访问数据库

1)数据库连接的含义

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

2)java.sql包中不同接口对数据库的调用

① Statement:用于执行静态SQL语句并返回它所生成结果的对象

PrepatedStatement:SQL语句预编译并存储在此对象中,可以使用此对象多次高效的执行该语句

③ CallableStatement:用于执行SQL存储过程

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

① 存在拼串操作,繁琐

② 存在SQL注入问题

SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令(如:select user,password from user_table where user=‘a’ or 1= ’ and password = ’ or ‘1’ = ‘1’),从而利用系统的SQL引擎完成恶意行为的做法。

对Java而言,要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以了。


4、PreparedStatement的使用

JDBC-_第5张图片


4.1.1增删改操作

例如:向customers表中添加一条记录

注意:
要操作的表是在读取配置文件(jdbc.properties)中对应的数据库里的

操作步骤如下:
	1、读取配置文件中的4个基本信息  系统类的加载器
	2、加载驱动
	3、获取连接
	4、预编译sql语句,返回PreparedStatement的实例
	5、填充占位符
	6、执行操作
	7、资源关闭

代码如下:

@Test
    public void testInsert() {
        java.sql.Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1、读取配置文件中的4个基本信息  系统类的加载器
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");

            Properties pros = new Properties(); //Hashtable子类,key和value都是String类型
            pros.load(is);

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

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

            //3、获取链接
            conn = DriverManager.getConnection(url, user, password);
            System.out.println(conn);


            //4、预编译sql语句,返回PreparedStatement的实例
            String sql="insert into customers(name,email,birth) values(?,?,?)"; // ?表示占位符

            ps = conn.prepareStatement(sql);

            //5、填充占位符
            ps.setString(1,"哪吒");
            ps.setString(2,"[email protected]");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse("1000-01-01");
            ps.setDate(3,new java.sql.Date(date.getTime()));

            //6、执行操作
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7、资源关闭
            try {
                if (ps!=null)
                    ps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            try {
                if (conn!=null)
                    conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

4.1.2 将共有操作(获取数据库连接、关闭资源)写进数据库工具类

1) 获取数据库连接包含:① 读取配置文件中的4个基本信息 系统类的加载器、② 加载驱动、③ 获取连接

2)关闭资源–① 关闭Statement的操作、② 关闭连接

代码如下

public class JDBCUtils {

    // 获取数据库连接
    public static Connection getConnection() throws Exception {
        //1、读取配置文件中的4个基本信息  系统类的加载器
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");

        Properties pros = new Properties(); //Hashtable子类,key和value都是String类型
        pros.load(is);

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

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

        //3、获取连接
        java.sql.Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }

    // 关闭资源--连接和Statement的操作
    public static void closeResource(Statement ps,Connection conn){
        try {
            if (ps!=null)
                ps.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if (conn!=null)
                conn.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

4.1.3 利用数据库工具类修改一条记录

操作步骤如下:
	1、获取连接
	2、预编译sql语句,返回PreparedStatement的实例
	3、填充占位符
	4、执行操作
	5、资源关闭

代码如下

  //修改customers表中一条记录
    @Test
    public void testUpdate(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1、获取数据库连接
            conn = JDBCUtils.getConnection();
            //2、预编译sql语句,返回PreparedStatement的实例
            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(ps,conn);
        }
    }

4.1.4 将增删改sql语句作为变量参数,占位符作为可变参数- - -写出通用增删改操作同时使用数据库工具类

String sql 存储增删改sql语句、Object…args存储占位符, 写出通用增删改操作

代码如下:

//通用的增删改操作
    public void update(String sql,Object...args){  //用可变参数存占位符
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1、获取数据库连接
            conn = JDBCUtils.getConnection();
            //2、预编译sql语句,返回PreparedStatement的实例
            ps = conn.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(ps,conn);
        }
    }

测试通用增删改,实现对customers表、order表的数据进行修改

注意:order是与关键字相同的表名,在对此表进行操作时要使用 `` 符号括起来。

// 测试通用增删改操作
    @Test
    public void testCommonUpdate(){
        //String sql="delete from customers where id =?";
        //update(sql,3);

        String sql ="update `order` set order_name =? where order_id =?";
        update(sql,"DD","2");
    }


4.2.1对Customers表查询操作

注意
1) select与增删改不同的是执行操作后需要显示出来,故执行操作时采用executeQuery方法,返回一个结果集resultSet。

2)if (resultSet.next()),next指针在结果集初始的上方,故用来判断结果集下一条是否有数据

3) 三种方式输出select查询到的结果

推荐方式三(将数据封装为一个对象)

此方法体现了 ORM编程思想(object relational mapping)

① 一个数据表对应一个java类
② 表中的一条记录对应java类的一个对象
③ 表中的一个字段对象java类的一个属性

代码如下:

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 birth) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.birth = birth;
    }

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

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

4)当在关闭资源时,也要结果集resultSet资源进行关闭,故需要在*数据库工具类JDBCUtils中重载closeResource,增加结果集的关闭 - 代码省略。

5)综上1-4分析写出查询Customer表的代码如下:

@Test
    public void testQuery1(){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //1、获取数据库连接
            conn = JDBCUtils.getConnection();
            //2、预编译sql语句,返回PreparedStatement的实例
            String sql="select customers.* from customers where id=?";
            ps = conn.prepareStatement(sql);

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

            //4、执行,并返回结果集
            resultSet = ps.executeQuery();
            //5、处理结果集
            if (resultSet.next()){  //判断结果集下一条是否有数据,
                        // 如果有数据返回true,指针下移,如果没有数据返回flase
                // 获取当前这条数据的各个字段值
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                Date birth = resultSet.getDate(4);

                //方式一:
                //System.out.println("id = "+ id +",name ="+ name +",email ="+ email +",birth ="+ birth);

                //方式二:
                //Object[] data = {id, name, email, birth};

                //方式三:将数据封装为一个对象(推荐)
                Customer customer = new Customer(id, name, email, birth);
                System.out.println(customer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6、关闭资源
            JDBCUtils.closeResource(ps,conn,resultSet);
        }
    }


4.2.2对customers表的通用查询操作

注意
1) String sql 存储增删改sql语句、Object…args存储占位符, 写出通用查询操作

2)重点在于结果集的处理上,

① 根据结果集获取它的元数据ResultSetMetaData
② 利用元数据获取结果集的列数
③ if (rs.next()),next指针在结果集初始的上方,故用来判断结果集下一条是否有数据,创建数据封装的对象Customer cust = new Customer();
④ 获取结果集每一行对应的列的值
⑤ 获取每个列的列名
⑥ 给cust对象指定的columnName属性,赋值为columValue,通过反射

代码如下:

/*
    * 针对customers表的通用的查询操作
    * */
    public Customer queryForCustomers(String sql,Object...args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1、获取数据库连接
            conn = JDBCUtils.getConnection();
            //2、返回PreparedStatement的实例
            ps = conn.prepareStatement(sql);
            //3、填充占位符
            for (int i=0;i<args.length;i++){
                ps.setObject(i+1,args[i]);
            }
            //4、执行,并返回结果集
            rs = ps.executeQuery();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            // 通过ResultSetMetaData获取结果集的列数
            int columnCount = rsmd.getColumnCount();

            if (rs.next()){ //if (rs.next()),next指针在结果集初始的上方,故用来判断结果集下一条是否有数据
                Customer cust = new Customer();

                //处理结果集一行数据中的每一列
                for (int i=0;i<columnCount;i++){
                    //获取每列对应的值
                    Object columnValue = rs.getObject(i + 1);

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

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = Customer.class.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(cust,columnValue);
                }
                return cust;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.closeResource(ps,conn,rs);
        }
        return null;
    }

测试customers表的通用的查询操作

注意没被查询的字段会被默认赋值,因为在处理结果集时, Customer cust = new Customer();**

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

        // 没被查询的字段会被默认赋值,因为在处理结果集时, Customer cust = new Customer();
        sql="select name,email from customers where name= ?";
        Customer customer1 = queryForCustomers(sql, "周杰伦");
        System.out.println(customer1);
    }



4.3.1Order表的查询操作(与Customer不同)

不同点、带来的问题以及处理方法如下:

在Order类中的属性名与Order数据表中的字段名不同

不同点带来的问题:

反射机制去用字段名查找类属性时,就会报错

处理方法:

需要在反射机制中用rsmd.getColumnLabel方法获取别名(没有别名,默认获取字段名),,并在sql语句中对查询到的字段起别名,使结果集中的字段名与Order类属性名相同


Order类代码如下: 属性orderId与字段名order_id不同

public class Order {
    private int orderId;
    private String orderName;
    private Date orderDate;

    public Order(){
        super();
    }

    public Order(int orderId, String orderName, Date orderDate) {
        this.orderId = orderId;
        this.orderName = orderName;
        this.orderDate = orderDate;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", orderName='" + orderName + '\'' +
                ", orderDate=" + orderDate +
                '}';
    }

    public int getOrderId() {
        return orderId;
    }

    public void setOrderId(int orderId) {
        this.orderId = orderId;
    }

    public String getOrderName() {
        return orderName;
    }

    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }

    public Date getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }
}


普通的一条sql语句的操作同Customer表一样
代码如下:

@Test
    public void tesetQuery1() {

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1、获取连接
            conn = JDBCUtils.getConnection();
            //2、预编译sql语句,返回prepareStatement实例
            String sql="select order_id,order_name,order_date from `order` where order_id =?";
            ps = conn.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 = new Order(id, name, date);
                System.out.println(order);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6、关闭资源
            JDBCUtils.closeResource(ps,conn,rs);
        }
    }


4.3.2对order表的通用查询操作

① 注意
在Order类中的属性名与Order数据表中的字段名不同

反射机制去用字段名查找类属性时,就会报错,故需要在反射机制中用rsmd.getColumnLabel方法获取别名(没有别名,默认获取字段名),,并在sql语句中对查询到的字段起别名,使结果集中的字段名与Order类属性名相同

代码如下:

/*
     * 针对order表的通用的查询操作
     * */
    public Order orderForQuery(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();

            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            if (rs.next()){
                Order order = new Order();
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //获取列的列名getColumnName   不推荐使用
                    //获取列的别名getColumnLabel  没有别名,默认列名
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = Order.class.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(order,columnValue);
                }
                return order;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(ps,conn,rs);
        }
        return null;
    }

测试order表的通用的查询操作

注意没被查询的字段会被默认赋值,因为在处理结果集时, Order order = new Order();

 //测试通用order查询方法
    @Test
    public void testOrderForQuery(){
        // 在sql语句中对查询到的字段起别名,使结果集中的字段名与Order类属性名相同
        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);
    }


③ 总结

针对表的字段名与类的属性名不相同的情况:

① 必须声明sql时,使用类的属性名来命名字段的别名

②  使用ResultSetMetaData时,需要使用getColumnLabel()来替换getColumnName()来获取列的别名。
注意:如果sql中没有给字段起别名,getColumnLabel()获取的就是字段名(列名)。

查询过程的流程

JDBC-_第6张图片


4.4.1对不同表的通用查询操作 - - -返回表中的一条记录

不知道是哪个表,故需要此表的类作为参数传入进来

其中要通过反射创建运行时类的对象

代码如下:

// 针对不同表的通用的查询操作,返回表中的一条记录
    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();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            if (rs.next()){
                //创建运行时类的对象
                T t = clazz.getDeclaredConstructor().newInstance();
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(ps,conn,rs);
        }
        return null;
    }


4.4.2对不同表的通用查询操作 - - -返回表中的多条记录(sql语句中查询到多条记录,如where id<12

实现不同表通用查询多条记录

原来是返回一个对象,此时需要利用循环,把每条记录(每行)创建的对象,添加到一个集合,返回对象集合。

代码如下:

public <T> List<T> getForList(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();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            // 创建集合对象
            ArrayList<T> list = new ArrayList<>();
            while (rs.next()){
                //创建运行时类的对象
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集每一行数据中的每一列,给t对象指定的属性赋值
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;

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

测试不同表通用查询多条记录

由于返回的是个集合,因此需要遍历输出多条记录(利用forEach方法)

代码如下:

@Test
    public void testGetForLit(){
        String sql ="select id,name,email from customers where id < ?";
        List<Customer> list = getForList(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> orderList = getForList(Order.class, sql1, 5);
        orderList.forEach(System.out::println);
    }

③ 总结

PreparedStatement替换Statement

除了解决Statement的拼串、sql注入问题之外,PreparedStatement还有好处如下:

1)PreparedStatement操作Blob的数据,而Statement做不到。
2)PreparedStatement可以实现更高效的批量操作。


四、PreparedStatement操作BLOB类型字段

PreparedStatement操作Blob的数据,而Statement做不到

1、MySQL BLOB类型

JDBC-_第7张图片

2、向数据表中插入Blob类型(图片/声音文件)的字段(要使用进行传输)

举例说明

FileInputStream is = new FileInputStream(new File("ss.jpg"));
ps.setObject(4,is);

代码如下:

//向数据表custpmers中插入Blob类型的字段
    @Test
    public void testInsert() throws Exception{
        Connection conn = JDBCUtils.getConnection();
        String sql="insert into customers(name,email,birth,photo) values(?,?,?,?)";
        PreparedStatement ps = conn.prepareStatement(sql);

        ps.setObject(1,"赵武");
        ps.setObject(2,"[email protected]");
        ps.setObject(3,"1992-08-07");

        FileInputStream is = new FileInputStream(new File("ss.jpg"));
        ps.setObject(4,is);

        ps.execute();

        JDBCUtils.closeResource(ps,conn);
    }

3、查询Blob类型字段(图片/声音文件)(要使用进行传输)

举例
实现查询Blob类型字段,下载下来,以文件的方式保存在本地。

实现方法
当查询到Blob类型的字段photo后,调用方法getBinaryStream()返回此文件的输入流,再创建一个输出流,来完成文件的读取和下载(关闭资源时,也要关闭输入流和输出流)。

代码如下:

//查询数据表customers中Blob类型的字段
    @Test
    public void testQuery(){
        Connection conn = null;
        PreparedStatement ps = null;
        InputStream is= null;
        FileOutputStream fos = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth,photo from customers where id=?";
            ps = conn.prepareStatement(sql);

            ps.setInt(1,21);

            rs = ps.executeQuery();
            if (rs.next()){

                // 方式一:索引
                /*int id = rs.getInt(1);
                String name = rs.getString(2);
                String email = rs.getString(3);
                Date birth = rs.getDate(4);
                */
                // 方式二:列的别名
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                Date birth = rs.getDate("birth");

                Customer cust = new Customer(id, name, email, birth);
                System.out.println(cust);

                //查询Blob类型字段,下载下来,以文件的方式保存在本地
                Blob photo = rs.getBlob("photo");
                is = photo.getBinaryStream(); //调用方法获取输入流
                fos = new FileOutputStream("kakaxi.jpg");
                byte[] buffer = new byte[1024];
                int len;
                while ((len=is.read(buffer))!=-1){
                    fos.write(buffer,0,len);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is!=null)
                    is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fos!=null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            JDBCUtils.closeResource(ps,conn,rs);
        }
    }


五、批量插入数据

update、deltete本身就具有批量操作的效果(不加条件where…)

PreparedStatement可以实现更高效的批量操作


题目:向goods表中插入20000条数据

CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(25)
);


批量插入方式 使用Statement

缺点:由于没有预编译sql语句,导致sql在循环中生成,即每循环一次,生成一个sql语句变量,执行一句。浪费时间。

Connection conn = JDBCUtils.getConnection();
Statement st = conn.createStatement();
for(int i=0;i<=20000;i++){
	String sql = "insert into goods(name)values('name_" + i + "')";
	st.execute(sql);
}


批量插入方式 使用PreparedStatement

优点存在预编译sql语句变量只生成一次,在循环中给占位符传值即可。
缺点:依然是见一条sql语句,执行一条,并且提交一次,浪费时间

代码如下:

//批量插入方式二:PreparedStatement
    @Test
    public void testInsert1(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            long start = System.currentTimeMillis();

            conn = JDBCUtils.getConnection();
            String sql ="insert into goods(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));//51290

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


批量插入方式 优化之- - - “攒sql语句” 使用PreparedStatement

1、使用addBatch()、executeBatch()、clearBatch() “攒sql语句”,到一定数量之后,一并执行,节约时间。 - - - 要使用此方法

2、mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。

url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true

driverClass = com.mysql.cj.jdbc.Driver

3、使用更新的mysql驱动:mysql-connector-java-8.0.11-bin.jar


优点:使用addBatch()、executeBatch()、clearBatch() “攒sql语句”,到一定数量之后,一并执行,节约时间。 - - -例如:“攒”500个sql语句后,再执行一次
缺点每执行一条sql语句,就会提交(写入数据库) 一次,在提交过程中也浪费时间

代码如下:

@Test
    public void testInsert2() {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            long start = System.currentTimeMillis();

            conn = JDBCUtils.getConnection();
            String sql = "insert into goods(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();

                    //3、清空batch
                    ps.clearBatch();
                }
            }

            long end = System.currentTimeMillis();

            System.out.println("花费的时间为: " + (end - start));// 20000用时579
                                                            // 1000000用时14160
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(ps, conn);
        }
    }


批量插入方式 优化之- - - “攒sql语句”、设置不允许自动提交数据 使用PreparedStatement

在方式三的基础上,设置不允许自动提交数据每执行一次不会提交数据,最后同一提交

优点“攒sql语句”、设置不允许自动提交数据减少了执行次数和提交次数,从而节约了时间

代码如下:

//批量插入方式四:PreparedStatement优化方式三
    @Test
    public void testInsert3() {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            long start = System.currentTimeMillis();

            conn = JDBCUtils.getConnection();
            // 设置不允许自动提交数据
            conn.setAutoCommit(false);

            String sql = "insert into goods(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();  //每执行一次就会提交一次,也就写死到数据库中一次,浪费了时间
                    // 设置不允许自动提交,可以节约时间
                    //3、清空batch
                    ps.clearBatch();
                }
            }
            //最后统一的提交数据
            conn.commit();

            long end = System.currentTimeMillis();

            System.out.println("花费的时间为: " + (end - start));// 1000000用时7297

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


六、数据库事务

1、什么叫数据库事务?

事务:一组逻辑操作单元,是数据从一种状态变换到另一种状态。

一组逻辑操作单元:一个或多个DML操作

2、事务处理的原则

保证所有事务都作为一个工作单元来执行,即出现了故障,都不能改变这种执行方式。

一个事务中执行多个操作时要么所有的事务都被提交commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所做的所有修改,整个事务回滚rollback)到最初的状态。

注意数据一旦提交,就不可回滚。

3、哪些操作会导致数据的自动提交?

① DDL操作一旦执行,都会自动提交。
	set autocommit = false 对DDL操作失效
	
② DML默认情况下,一旦执行们就会自动提交。
	我们可以通过set autocommit = false方式取消DML操作的自动提交。
	
③ 默认在关闭连接的时,会自动提交数据

在未考虑数据库事务情况下的转账操作

当出现异常时,会导致出现异常之前的DML操作已经提交,不能在回滚,导致事务在没有完成的条件下,改变了数据错误!!!

代码如下:

//***************未考虑数据库事务情况下的转账操作******************
	@Test
    public void testUpdate(){
        String sql1="update user_table set balance=balance - 100 where user=?";
        update(sql1,"AA");

        //模拟网络异常
        System.out.println(10/0);  //此时就会出现只有AA减了100

        String sql2="update user_table set balance=balance + 100 where user=?";
        update(sql1,"BB");

        System.out.println("转账成功");
    }

    //通用的增删改操作 - - -version 1.0
    public void update(String sql, Object... args) {  //用可变参数存占位符
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1、获取数据库连接
            conn = JDBCUtils.getConnection();
            //2、预编译sql语句,返回PreparedStatement的实例
            ps = conn.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(ps, conn);
        }
    }

考虑数据库事务情况下的转账操作

即:
通过set autocommit = false方式取消DML操作的自动提交,执行完所有sql语句后一并提交

② 在执行完事务的所有sql语句后,一并关闭资源(即连接conn在调用函数的外部创建和关闭

代码如下:

//***************考虑数据库事务情况下的转账操作******************
    @Test
    public void testUpdateWithTx(){
        Connection conn = null;
        try {
            //获取连接
            conn = JDBCUtils.getConnection();

            //1、取消数据的自动提交
            conn.setAutoCommit(false);

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

            //模拟网络异常
            //System.out.println(10/0);  //此时就会出现回滚数据

            String sql2="update user_table set balance=balance + 100 where user=?";
            update(conn,sql2,"BB");

            System.out.println("转账成功");

            //2、提交数据
            conn.commit();

        } catch (Exception e) {
            e.printStackTrace();
            // 3、出现异常,回滚数据
            try {
                conn.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        } finally {
            //关闭资源
            JDBCUtils.closeResource(null,conn); //ps已经在内部关闭
        }

    }

    //重载- - - 通用的增删改操作 - - -version 2.0(考虑上事务)
    public void update(Connection conn,String sql, Object... args) {  //用可变参数存占位符
        PreparedStatement ps = null;
        try {
            //1、预编译sql语句,返回PreparedStatement的实例
            ps = conn.prepareStatement(sql);
            //2、填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]); // 小心参数声明错误
            }
            //3、执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4、资源的关闭
            JDBCUtils.closeResource(ps, null); //因为是外部传的连接,这里也不要关闭
        }
    }

4、总结

JDBC程序中为了让多个SQL语句作为一个事务执行

① 调用Connection对象的setAutoCommit(false);取消自动提交事务;

② 在所有的SQL语句都执行成功后,调用commit(); 方法提交事务;

③ 在出现异常时,调用rollback(); 方法回滚事务。

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

5、事务的ACID属性

A:原子性(Atomicity)
	说明事务是最小的工作单元。不可再分。

C:一致性(Consistency)
	所有事务要求,在同一个事务当中,所有操作必须同时成功,或者同时失败,以保证数据的一致性。

I:隔离性(Isolation)
	A事务和B事务之间具有一定的隔离。
	教室A和教室B之间有一道墙,这道墙就是隔离性。
	A事务在操作一张表的时候,另一个事务B也操作这张表会那样???

D:持久性(Durability)
	事务最终结束的一个保障。事务提交,就相当于将没有保存到硬盘上的数据
	保存到硬盘上!

6、事务和事务之间的隔离级别有哪些呢?4个级别

读未提交:read uncommitted(最低的隔离级别)《没有提交就读到了》- - -脏读现象!(Dirty Read)

		什么是读未提交?
			事务A可以读取到事务B未提交的数据。
		这种隔离级别存在的问题就是:
			脏读现象!(Dirty Read)
			我们称读到了脏数据。
		这种隔离级别一般都是理论上的,大多数的数据库隔离级别都是二档起步!

读已提交:read committed《提交之后才能读到》- - -不可重复读取数据,但解决了脏读的现象。

		什么是读已提交?
			事务A只能读取到事务B提交之后的数据。
		这种隔离级别解决了什么问题?
			解决了脏读的现象。
		这种隔离级别存在什么问题?
			不可重复读取数据。
			什么是不可重复读取数据呢?
				在事务开启之后,第一次读到的数据是3条,当前事务还没有
				结束,可能第二次再读取的时候,读到的数据是4条,3不等于4
				称为不可重复读取。

		这种隔离级别是比较真实的数据,每一次读到的数据是绝对的真实。
		oracle数据库默认的隔离级别是:read committed

可重复读:repeatable read《提交之后也读不到,永远读取的都是刚开启事务时的数据》 - - - 解决不可重复读取数据,但会出现幻读

		什么是可重复读取?
			事务A开启之后,不管是多久,每一次在事务A中读取到的数据
			都是一致的。即使事务B将数据已经修改,并且提交了,事务A
			读取到的数据还是没有发生改变,这就是可重复读。
		可重复读解决了什么问题?
			解决了不可重复读取数据。
		可重复读存在的问题是什么?
			可以会出现幻读。
			每一次读取到的数据都是幻象。不真实!
		
		早晨9点开始开启了事务,只要事务不结束,到晚上9点,读到的数据还是那样!
		读到的是假象。不够绝对的真实。

		mysql中默认的事务隔离级别就是这个!!!!!!!!!!!

序列化/串行化:serializable(最高的隔离级别)

		这是最高隔离级别,效率最低。解决了所有的问题。
		这种隔离级别表示事务排队,不能并发!
		synchronized,线程同步(事务同步)
		每一次读取到的数据都是最真实的,并且效率是最低的。

考虑数据库事务情况下的查询操作

在外部实现连接,且在外部conn.getTransactionIsolation()获取当前连接的隔离级别conn.setTransactionIsolation(Connection.*)设置数据库的隔离级别

	@Test
    public void testTransactionSelect() throws  Exception{
        Connection conn = JDBCUtils.getConnection();
        //获取当前连接的隔离级别
        System.out.println(conn.getTransactionIsolation());

        //设置数据库的隔离级别
        conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

        //取消自动提交数据
        conn.setAutoCommit(false);

        String sql="select user,password,balance from user_table where user=?";
        List<User> userList = getForList(conn, User.class, sql, "CC");

        userList.forEach(System.out::println);
    }

    @Test
    public void testTransactionUpdate() throws Exception{
        Connection conn = JDBCUtils.getConnection();

        //取消自动提交数据
        conn.setAutoCommit(false);

        String sql="update user_table set balance=? where user=?";
        update(conn,sql,5000,"CC");

        Thread.sleep(15000);
        System.out.println("修改结束");
    }


	//  对任意对象的通用查询操作 - - -version 2.0(考虑上事务)
    public <T> List<T> getForList(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();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            // 创建集合对象
            ArrayList<T> list = new ArrayList<>();
            while (rs.next()){
                //创建运行时类的对象
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集每一行数据中的每一列,给t对象指定的属性赋值
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(ps,null,rs);// 不需要在内部关conn
        }
        return null;
    }


七、DAO及其实现类

DAO:data(base) accsee object 是一个数据访问接口

四部分

① BaseDao类包装针对于数据表通用操作(增删改、查询一条记录/多条记录/特殊值) - - -考虑到事务

代码如下:

 * DAO:data(base) access object
 * 封装了针对于数据表的通用的操作
 */
public abstract class BaseDao {  //表示是一个抽象类,不能实例化

    //--- 通用的增删改操作 ---version 2.0(考虑上事务,即连接从外部传入)
    public void update(Connection conn, String sql, Object... args) {  //用可变参数存占位符
        PreparedStatement ps = null;
        try {
            //1、预编译sql语句,返回PreparedStatement的实例
            ps = conn.prepareStatement(sql);
            //2、填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]); // 小心参数声明错误
            }
            //3、执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4、资源的关闭
            JDBCUtils.closeResource(ps, null); //因为是外部传的连接,这里也不要关闭
        }
    }

    //---通用查询操作,返回任意表的一条记录---version 2.0(考虑上事务,即连接从外部传入)
    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();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            if (rs.next()){
                //创建运行时类的对象
                T t = clazz.getDeclaredConstructor().newInstance();
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(ps,null,rs);
        }
        return null;
    }

    //---通用查询操作,返回任意表的多条记录的集合---version 2.0(考虑上事务,即连接从外部传入)
    public <T> List<T> getForList(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();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            // 创建集合对象
            ArrayList<T> list = new ArrayList<>();
            while (rs.next()){
                //创建运行时类的对象
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集每一行数据中的每一列,给t对象指定的属性赋值
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(ps,null,rs);// 不需要在内部关conn
        }
        return null;
    }

    //用于查询特殊值的通用方法   考虑到了事务
    public <E> E getValue(Connection conn,String sql,Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i=0;i<args.length;i++){
                ps.setObject(i+1,args[i]);
            }

            rs = ps.executeQuery();
            if (rs.next()){
                return (E) rs.getObject(1);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JDBCUtils.closeResource(ps,conn,rs);
        }
        return null;
    }

}

② 创建Customer类

ORM编程思想(object relational mapping)

  • 一个数据表对应一个java类
  • 表中的一条记录对应java类的一个对象
  • 表中的一个字段对应Java类的一个属性

代码如下:

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 birth) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.birth = birth;
    }

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

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

③ 创建CustomerDAO接口

此接口用于规范针对于customers表的常用操作

内容回顾:

① 接口中所有方法都是抽象方法;

② 无论加不加abstract,所有实现指定接口的类都必须重写并实现该接口的所有方法,否则这个类就是抽象类。

代码如下:

public interface CustomerDAO {
    //将cust对象添加到数据库中
    void insert(Connection conn, Customer cust);

    //根据指定的id,删除表中的一条记录
    void deleteById(Connection conn, int id);

    //针对内存中的cust对象,去修改数据表中的指定的记录
    void update(Connection conn,Customer cust);

    // 针对指定的id查询得到对象的Customer对象
    Customer getCustomerById(Connection conn,int id);

    //查询表中的所有记录构成的集合
    List<Customer> getAll(Connection conn);

    //返回数据表中的数据的条目数
    Long getCount(Connection conn);

    //返回数据表中最大的生日
    Date getMaxBirth(Connection conn);
}

④ 创建 继承对表操作的BaseDao类,以及实现CustomerDAO接口的类CustomerDAOImpl

CustomerDAOImpl要重写CustomerDAO接口的所有方法,在重写方法中对表进行操作要调用从BaseDao类中继承来的方法

public class CustomerDAOImpl extends BaseDao implements CustomerDAO{

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

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

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

    @Override
    public Customer getCustomerById(Connection conn, int id) {
        String sql ="select id,name,email,birth from customers where id=?";
        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> customerList = getForList(conn, Customer.class, sql);
        return customerList;
    }

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

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


对于已经知道对customers表的操作,当子类继承父类时包含泛型即可。故对BaseDao类、CustomerDAOImpl类进行简单修改。

BaseDao类修改后代码如下:

public abstract class BaseDao<T> {  //表示是一个抽象类,不能实例化

    private Class<T> clazz=null; // clazz的类取决于CustomerDAOImpl继承BaseDao泛型中的类

    //显式赋值  定义时、构造器中、代码块中

    {   //获取当前BaseDao的子类继承的父类的泛型  this.getClass()是CustomerDAOImpl类
        // 对象.getClass返回的是创建这个对象的类(即运行时类 CustomerDAOImpl)
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        ParameterizedType paramType =(ParameterizedType) genericSuperclass;

        Type[] typeArguments = paramType.getActualTypeArguments();//获取了父类的泛型参数
        clazz=(Class<T>) typeArguments[0];//泛型的第一个参数Customer
    }


    //--- 通用的增删改操作 ---version 2.0(考虑上事务,即连接从外部传入)
    public void update(Connection conn, String sql, Object... args) {  //用可变参数存占位符
        PreparedStatement ps = null;
        try {
            //1、预编译sql语句,返回PreparedStatement的实例
            ps = conn.prepareStatement(sql);
            //2、填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]); // 小心参数声明错误
            }
            //3、执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4、资源的关闭
            JDBCUtils.closeResource(ps, null); //因为是外部传的连接,这里也不要关闭
        }
    }

    //---通用查询操作,返回任意表的一条记录---version 2.0(考虑上事务,即连接从外部传入)
    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();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            if (rs.next()){
                //创建运行时类的对象
                T t = clazz.getDeclaredConstructor().newInstance();
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(ps,null,rs);
        }
        return null;
    }

    //---通用查询操作,返回任意表的多条记录的集合---version 2.0(考虑上事务,即连接从外部传入)
    public  List<T> getForList(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();
            // 获取结果集的元数据 ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数,通过元数据 ResultSetMetaData获得
            int columnCount = rsmd.getColumnCount();

            // 创建集合对象
            ArrayList<T> list = new ArrayList<>();
            while (rs.next()){
                //创建运行时类的对象
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集每一行数据中的每一列,给t对象指定的属性赋值
                for (int i=0;i<columnCount;i++){
                    //获取每个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名:通过ResultSetMetaData
                    //String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    //给cust对象指定的columnName属性,赋值为columValue,通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(ps,null,rs);// 不需要在内部关conn
        }
        return null;
    }

    //用于查询特殊值的通用方法   考虑到了事务
    public <E> E getValue(Connection conn,String sql,Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i=0;i<args.length;i++){
                ps.setObject(i+1,args[i]);
            }

            rs = ps.executeQuery();
            if (rs.next()){
                return (E) rs.getObject(1);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JDBCUtils.closeResource(ps,conn,rs);
        }
        return null;
    }

}

CustomerDAOImpl类修改后代码如下:

public class CustomerDAOImpl extends BaseDao<Customer> implements CustomerDAO {

    //获取当前对象的父类的泛型

    /*{
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        ParameterizedType paramType =(ParameterizedType) genericSuperclass;

        Type[] typeArguments = paramType.getActualTypeArguments();//获取了父类的泛型参数
        clazz=(Class) typeArguments[0];//泛型的第一个参数Customer
    }*/


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

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

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

    @Override
    public Customer getCustomerById(Connection conn, int id) {
        String sql ="select id,name,email,birth from customers where id=?";
        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> customerList = getForList(conn,sql);
        return customerList;
    }

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

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


八、数据库连接池

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

在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:

① 在主程序(如servlet、beans)中建立数据库连接
② 进行sql操作
③ 断开数据库连接 

这种模式开发,存在的问题

① 普通的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证用户名和密码(得花费0.05s-1s时间)。导致数据库的连接资源并没有得到很好的重复利用。
② 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄露,最终将导致重启数据库。
③ 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾忌地分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

2、数据库连接池技术

数据库连接池的基本思想

就是为数据库连接建立一个“缓冲池”。与现在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需要冲“缓冲池”中取一个,使用完毕之后再放回去。

JDBC-_第8张图片

3、多种开源的数据库连接池

JDBC-_第9张图片

3.1、C3P0连接池

两种方式使用此连接池

1)方式一:硬编码

//方式一:硬编码
    @Test
    public void testGetConnection() throws Exception{
        //获取C3P0数据库连接池
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        cpds.setDriverClass( "com.mysql.cj.jdbc.Driver" ); //loads the jdbc driver
        cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true" );
        cpds.setUser("root");
        cpds.setPassword("4680123");

        //通过设置相关的参数,对数据库连接池进行管理
        //设置初始的数据库连接池中的连接数
        cpds.setInitialPoolSize(10);

        Connection conn = cpds.getConnection(); //获取其中一个连接
        System.out.println(conn);

        //销毁c3p0数据库连接池  一般不用
        //DataSources.destroy(cpds);
    }

2)方式二:使用配置文件

// 方式二:使用配置文件
    @Test
    public void testGetConnection1() throws Exception{
        ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3P0");
        Connection conn = cpds.getConnection();
        System.out.println(conn);
    }

配置文件写在XML文件

代码如下:



<c3p0-config>

    
    <named-config name="helloC3P0">
        
        <property name="driverClass">com.mysql.cj.jdbc.Driverproperty>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=trueproperty>
        <property name="user">rootproperty>
        <property name="password">4680123property>

        
        
        <property name="acquireIncrement">5property>
        
        <property name="initialPoolSize">10property>
        
        <property name="minPoolSize">10property>
        
        <property name="maxPoolSize">1000property>

        
        <property name="maxStatements">50property>
        
        <property name="maxStatementsPerConnection">2property>

    named-config>
c3p0-config>

3.2、DBCP连接池

两种方式使用此连接池

1)方式一:硬编码

//方式一:硬编码,不推荐
    @Test
    public void testGetConnection() throws Exception{

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

        //设置基本信息
        source.setDriverClassName("com.mysql.cj.jdbc.Driver");
        source.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true");
        source.setUsername("root");
        source.setPassword("4680123");

        //还可以设置其他涉及数据库连接池管理的相关属性:
        source.setInitialSize(10);
        source.setMaxActive(10);
        //...

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

2)方式二:使用配置文件

	@Test
    //方式二:推荐- - -使用配置文件
    public void testGetConnection1() throws Exception{
        Properties pros =new Properties();

        //获取流的方式一:
        //InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("DBCP.properties");
        //方式二:
        FileInputStream is = new FileInputStream(new File("src/DBCP.properties"));

        //读取配置文件
        pros.load(is);

        //创建一个DBCP数据库连接池
        DataSource source = BasicDataSourceFactory.createDataSource(pros);

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

配置文件写在File文件

代码如下:

username=root
password=4680123
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true
driverClassName = com.mysql.cj.jdbc.Driver

initialSize=10

3.3、Druid连接池 - - -主流

两种方式使用此连接池

1)方式一:同DBCP方式一类似,硬编码,不再写出

2)方式二:使用配置文件,也同DBCP类似

	@Test
    public void testGetConnection() throws Exception{
        Properties pros = new Properties();

        //获取流
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("Druid.properties");
        //读取配置文件
        pros.load(is);

        //创建一个DBCP数据库连接池
        DataSource source = DruidDataSourceFactory.createDataSource(pros);

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

配置文件写在File文件

代码如下:

url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true
username=root
password=4680123
driverClassName=com.mysql.cj.jdbc.Driver

initialSize=10
maxActive=10

3.4、总结- - -将三种连接池的连接写入JDBCUtils2工具类

注意事项

要把创建连接池对象这行代码放到获取连接方法getConnection()的外部

当把DBCP、Druid连接池对象放到外部过程中,由于两者的配置文件在File文件中,当执行pros.load(is);读取配置文件时,在外部不能实现,故要放入静态代码块执行。


三种连接池在工具类中的代码如下:

public class JDBCUtils2 {

    // 使用C3P0的数据库连接池技术
    // cpsd放里面每次创建连接就会创建一个,放在外面是只有一个连接池对象,只需要一个连接池对象
    private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3P0");
    public static Connection getConnection() throws Exception{
        Connection conn = cpds.getConnection();

        return conn;
    }


    //使用DBCP数据库连接池技术获取数据库连接
    //把连接池对象的创建也放在外部
    //由于在外部pros.load(is);不能实现,故放入静态代码块中执行
    private static DataSource source; //source要在下面方法中使用,故定义为静态
    static {
        try {
            Properties pros =new Properties();
            //获取流的方式一:
            //InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("DBCP.properties");
            //方式二:
            FileInputStream is = new FileInputStream(new File("src/DBCP.properties"));
            pros.load(is);
            source = BasicDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection1() throws Exception{

        Connection conn = source.getConnection();

        return conn;
    }


    //使用Druid数据库连接池技术获取数据库连接
    //把连接池对象的创建也放在外部
    //由于在外部pros.load(is);不能实现,故放入静态代码块中执行
    private static DataSource source1; //source1要在下面方法中使用,故定义为静态
    static {
        try {
            Properties pros =new Properties();
            //获取流的方式一:
            //InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("Druid.properties");
            //方式二:
            FileInputStream is = new FileInputStream(new File("src/Druid.properties"));
            pros.load(is);
            source1 = DruidDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection2() throws Exception{

        Connection conn = source1.getConnection();

        return conn;
    }

}


九、Apache-DBUtils实现CURD操作

1、Apache-DBUtils简介

commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的简单封装。


2、DbUtils 中的核心类/接口为 QueryRunnerResultSetHandler


1) QreryRunner

它包含以下几个方法

① query(Connection conn, String sql, Object[] params, ResultSetHandler rsh):执行选择查询,在查询中,对象阵列的值被用来作为查询的置换参数。

② query(String sql, Object[] params, ResultSetHandler rsh):方法本身不提供数据库连接,执行选择查询,在查询中,对象阵列的值被用来作为查询的置换参数。

③ query(Connection conn, String sql, ResultSetHandler rsh):执行无需参数的选择查询。

④ update(Connection conn, String sql, Object[] params):被用来执行插入、更新或删除(DML)操作。

示例代码如下:

//测试插入  ---删除和修改不一一测试了
    @Test
    public void testInsert(){
        Connection conn = null; //创建Druid连接池连接
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();
            String sql="insert into customers(name,email,birth)values(?,?,?)";

            int insertCount = runner.update(conn, sql, "鸡你太美", "ji@126,com", "1997-03-30");
            System.out.println("添加了" +insertCount + "条记录");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }
    }


2) ResultSetHandler接口

ResultSetHandler接口执行处理一个结果集对象,将数据转变并处理为任何一种形式,供其他应用使用。

ResultSetHandler是接口,负责处理两件事

1)处理Statement执行后产生的结果集,生成结果列表

2)处理存储过程执行后的输出参数

  • 提供了两个函数分别用来处理普通操作和存储过程的结果集

     ArrayHandler:把结果集中的第一行数据转成对象数组。
     
     ArrayListHandler:把结果集中的每一行数据都转成一个对象数组,再存放到List中。
     
     BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
     
     BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。//重点
     
     MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。//重点
     
     MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
     
     ColumnListHandler:将结果集中某一列的数据存放到List中。
     
     KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里(List),再把这些map再存到一个map里,其key为指定的列。
     
     ScalarHandler:将结果集第一行的某一列放到某个对象中。//重点
    

示例代码如下:

//测试查询操作
    //BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录
    @Test
    public void testQuery1(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();
            String sql="select id,name,email,birth from customers where id=?";

            //查询返回一个对象一条记录
            BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
            Customer customer = runner.query(conn, sql, handler, 23);
            System.out.println(customer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }

    }

    //BeanListHander:是ResultSetHandler接口的实现类,用于封装表中的多条记录的集合
    @Test
    public void testQuery2(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();
            String sql="select id,name,email,birth from customers where id;

            //查询返回一个对象的多条记录
            BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
            List<Customer> customerList = runner.query(conn, sql, handler, 12);
            customerList.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }
    }

    //MapHander:是ResultSetHandler接口的实现类,对应表中的一条记录
    //          将字段及相应字段的值作为map中的key和value
    @Test
    public void testQuery3(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();
            String sql="select id,name,email,birth from customers where id=?";

            //查询返回一个以Map(键值对存储)形式的对象的一条记录
            MapHandler handler = new MapHandler();
            Map<String, Object> map = runner.query(conn, sql, handler, 23);
            System.out.println(map);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }
    }


    //MapListHander:是ResultSetHandler接口的实现类,对应表中的多条记录
    //      将字段及相应字段的值作为map中的key和value,将这些map添加到list中
    @Test
    public void testQuery4(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();
            String sql="select id,name,email,birth from customers where id;

            //查询返回一个以Map(键值对存储)形式的对象的多条记录
            MapListHandler handler = new MapListHandler();
            List<Map<String, Object>> mapList = runner.query(conn, sql, handler, 12);
            mapList.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }
    }

    //查询特殊特殊值的方法  - - -统计记录数
    //  ScalarHandler是ResultSetHandler接口的实现类
    @Test
    public void testQuery5(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();
            String sql="select count(*) from customers";

            ScalarHandler<Object> handler = new ScalarHandler<>();

            Long count = (Long) runner.query(conn, sql, handler);
            System.out.println(count);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }
    }

    //查询特殊特殊值的方法 - - -查询最大生日
    // ScalarHandler是ResultSetHandler接口的实现类
    @Test
    public void testQuery6(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();
            String sql="select max(birth) from customers";

            ScalarHandler<Object> handler = new ScalarHandler<>();

            Date maxbirth = (Date) runner.query(conn, sql, handler);
            System.out.println(maxbirth);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }
    }

注意

可以自定义ResultSetHandler的实现类

通过匿名实现类实现ResultSetHandler接口重写内部的handle()方法,重写后方法的返回值就作为runner.query()方法的返回值

示例代码如下:

//自定义ResultSetHandler的实现类
    @Test
    public void testQuery7(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();

            conn = JDBCUtils2.getConnection2();

            String sql="select id,name,email,birth from customers where id=?";

            //匿名实现类   ResultSetHandler是接口,以上的方法也是此接口的实现类
            ResultSetHandler<Customer> handler=new ResultSetHandler<Customer>() {
                @Override
                public Customer handle(ResultSet rs) throws SQLException {

                    if (rs.next()){
                        int id=rs.getInt("id");
                        String name = rs.getString("name");
                        String email = rs.getString("email");
                        Date birth = rs.getDate("birth");
                        Customer customer = new Customer(id, name, email, birth);

                        return customer;//其返回值就作为runner.query()方法的返回值
                    }
                    return null;
                }
            };
            Customer customer = runner.query(conn, sql, handler, 23);
            System.out.println(customer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,conn);
        }
    }


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


DbUtils.close()方法,会报异常,要自己try-catch。

DbUtils.closeQuietly()方法,已经在其方法内把异常处理了。

把代码写入JDBCUtils工具类中:

//使用dbutils.jar中提供的DbUtils工具类,实现资源的关闭
    public static void closeResource1(Statement ps, Connection conn, ResultSet rs){
        //方式一:close()报异常,自己try-catch

        /* try {
            DbUtils.close(conn);
            DbUtils.close(ps);
            DbUtils.close(rs);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }*/

        //方式二:closeQuietly()已经在其方法内把异常处理了
        DbUtils.closeQuietly(conn);
        DbUtils.closeQuietly(ps);
        DbUtils.closeQuietly(rs);
    }

你可能感兴趣的:(JDBC,数据库,jdbc)