JDBC学习笔记

目录

1、JDBC概述

1.1、数据的持久化

1.2、Java中的数据存储计数 

1.3、Jdbc介绍

 1.4、Jdbc体系结构

1.5、Jdbc程序编写步骤

2、获取数据库连接

2.1、Driver接口实现类

2.1.1、Driver接口介绍及导入驱动

2.1.2、加载与注册驱动

2.2、URL

 2.3、用户名和密码

2.4、数据库连接方式举例

2.4.1、连接方式1(选学)

2.4.2、连接方式2(选学)

2.4.3、连接方式3(选学)

2.4.4、连接方式4(选学)

2.4.5、连接方式5(最终版)

3、使用PreparedStatement实现CRUD操作

3.1、操作和访问数据库

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

3.3、PreparedStatement的使用

3.3.1、PreparedStatement介绍

3.3.2、PreparedStatement 与 Statement 的对比

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

3.3.4、使用PreparedStatement实现增删改操作

3.3.4.1、准备工作(创建JDBCUtils)

3.3.4.2、 插入操作

3.3.4.3、修改

3.3.4.4、通用的增删改操作

3.3.4.5、使用PreparedStatement实现查询操作

        返回一条记录

        返回多条记录(List)

3.4、ResultSet与ResultSetMetaData

3.4.1、ResultSet

 3.4.2、ResultSetMetaDate

3.5、资源的释放

4、操作BLOB类型字段

4.1、MySQL BLOB类型

4.2、向数据表查询Blob类型 

4.3、向数据表插入Blob类型

5、批量插入

5.1、批量执行sql语句

5.2、批量插入的方法

5.2.1、方法1:使用Statement

5.2.2、方法2:使用PreparedStatement

5.2.3、方法3:使用Batch

5.2.4、方法4:数据处理完后再统一提交

6、数据库事务

6.1、数据库事务介绍

6.2、JDBC事务处理

6.2.1、举例

6.3、事务的ACID属性

6.3.1、数据库的并发问题

6.3.2、四种隔离级别

6.3.3、在MySQL设置隔离级别

6.3.4、在Java中设置隔离级别

7、DAO及其实现类

8、数据库连接池

8.1、数据库连接池的必要性

8.2、数据库连接池技术

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

8.3.1、C3P0数据库连接池

方式一:

方式二:使用配置文件

         8.3.2、DBCP数据库连接池

 方法一

方法二:使用配置文件

 ​​​​​​​        ​​​​​​​8.3.3、Drui(德鲁伊)数据库连接池

9、Apache-DBUtils实现CRUD操作

9.1、Apache-DBUtils简介

9.2、主要API的使用 

9.2.1、DbUtils

9.2.2、QueryRunner类

9.2.3、ResultSetHandler接口及其实现类


学习B站尚硅谷的JDBC视频的笔记:尚硅谷JDBC核心技术(新版jdbc)_哔哩哔哩_bilibili

1、JDBC概述

1.1、数据的持久化

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

  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

1.2、Java中的数据存储计数 

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

    • JDBC直接访问数据库

    • JDO (Java Data Object )技术

    • 第三方O/R工具,如Hibernate, Mybatis 等

  • JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。

1.3、Jdbc介绍

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

  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。

  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

  • 如果没有JDBC,那么Java程序访问数据库时是这样的:

  • 有了JDBC,Java程序访问数据库时是这样的:

  • 总结如下:

 1.4、Jdbc体系结构

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

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

    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。

不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程

1.5、Jdbc程序编写步骤

 补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。

2、获取数据库连接

2.1、Driver接口实现类

2.1.1、Driver接口介绍及导入驱动

  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。

  • 在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。

    • Oracle的驱动:oracle.jdbc.driver.OracleDriver

    • mySql的驱动: com.mysql.jdbc.Driver

将对应的jar包拷贝到Java工程的一个目录中,习惯上新建一个lib文件夹。

在驱动jar上右键-->(IDEA)添加为库(add as library)

2.1.2、加载与注册驱动

  • 加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名

    • Class.forName(“类名”);

注意,类名不同版本的驱动不同,可以在IDEA中将光标移到Driver上按ctrl+H 查看,结果如下图

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

  • 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动

通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。

2.2、URL

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

  • JDBC URL的标准由三部分组成,各部分间用冒号分隔。

    • jdbc:子协议:子名称

    • 协议:JDBC URL中的协议总是jdbc

    • 子协议:子协议用于标识一个数据库驱动程序

    • 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名

  • 举例:

  • 几种常用数据库的 JDBC URL

    • MySQL的连接URL编写方式:

      • jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值

      • jdbc:mysql://localhost:3306/atguigu

      • jdbc:mysql://localhost:3306/atguigu?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)

      • jdbc:mysql://localhost:3306/atguigu?user=root&password=123456

    • Oracle 9i的连接URL编写方式:

      • jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称

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

    • SQLServer的连接URL编写方式:

      • jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称

      • jdbc:sqlserver://localhost:1433:DatabaseName=atguigu

 2.3、用户名和密码

  • user,password可以用“属性名=属性值”方式告诉数据库(可以封装在Properties中)

Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "zyj123");
  • 可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接

2.4、数据库连接方式举例

在连接时不需要 DriverManager.registerDriver(driver) 的原因是在mysql的Driver类中已经声明了:

注意:若出现 java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) 错误,则为密码写错了 

2.4.1、连接方式1(选学)

使用DriverManager实现数据库的连接。

//获取数据库连接方式1
    @Test
    public void test1() throws SQLException, ClassNotFoundException {
        Driver driver = new com.mysql.cj.jdbc.Driver();

        String url = "jdbc:mysql://localhost:13306/";
        String user = "root";
        String password = "zyj123";

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

        System.out.println(connection);
    }

2.4.2、连接方式2(选学)

使用反射实例化Driver,不在代码中体现第三方数据库的API。体现了面向接口编程思想。

//获取数据库连接方式2
    @Test
    public void test2() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        //实例化Driver
        String classname = "com.mysql.cj.jdbc.Driver";
        Class clazz = Class.forName(classname);
        Driver driver = (Driver) clazz.newInstance();

        String url = "jdbc:mysql://localhost:13306/test";

        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password", "zyj123");

        Connection connection = driver.connect(url, info);
        System.out.println(connection);
    }

2.4.3、连接方式3(选学)

不必显式的注册驱动了。因为在DriverManager的源码中已经存在静态代码块,实现了驱动的注册。

//获取数据库连接方式3
    @Test
    public void test3() throws ClassNotFoundException, SQLException {
        String classname = "com.mysql.cj.jdbc.Driver";
        Class.forName(classname);

        String url = "jdbc:mysql://localhost:13306/";
        String user = "root";
        String password = "zyj123";

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

        System.out.println(connection);
    }

2.4.4、连接方式4(选学)

代码中显式出现了第三方数据库的API

//获取数据库连接方式4
    @Test
    public void test4() throws SQLException {
        Driver driver = null;
        driver = new com.mysql.cj.jdbc.Driver();

        String url = "jdbc:mysql://localhost:13306/test";

        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password", "zyj123");

        Connection connection = driver.connect(url, info);
        System.out.println(connection);
    }

2.4.5、连接方式5(最终版)

在对应的src下创建一个配置文件jdbc.properties其中的内容如下:

className=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:13306/test
user=root
password=zyj123

使用配置文件的方式保存配置信息,在代码中加载配置文件

使用配置文件的好处:

①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码 ,解耦

②如果修改了配置信息,省去重新编译的过程。  

③编写的java程序在部署到服务器上时,需要打包。如果java代码修改过,就需要重新打包。使用配置文件的方式,如果配置信息修改,并没有导致代码的修改。所以不需要重写打包。

//获取数据库连接方式5:将数据库连接的基本信息声明在配置文件中
    @Test
    public void test5() throws IOException, ClassNotFoundException, SQLException {
        //读取配置文件中的四个基本信息
        Properties properties = new Properties();
        //加载资源的路径:默认为src下
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        properties.load(is);

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

        //加载驱动
        Class.forName(className);

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


        System.out.println(connection);
    }

3、使用PreparedStatement实现CRUD操作

3.1、操作和访问数据库

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

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

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

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

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

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

  • 通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。

  • Statement 接口中定义了下列方法用于执行 SQL 语句:

int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):执行查询操作SELECT
  • 但是使用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 就可以了。

  • 代码演示:

public class StatementTest {

	// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
	@Test
	public void testLogin() {
		Scanner scan = new Scanner(System.in);

		System.out.print("用户名:");
		String userName = scan.nextLine();
		System.out.print("密   码:");
		String password = scan.nextLine();

		// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
		String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
				+ "'";
		User user = get(sql, User.class);
		if (user != null) {
			System.out.println("登陆成功!");
		} else {
			System.out.println("用户名或密码错误!");
		}
	}

	// 使用Statement实现对数据表的查询操作
	public <T> T get(String sql, Class<T> clazz) {
		T t = null;

		Connection conn = null;
		Statement st = null;
		ResultSet rs = null;
		try {
			// 1.加载配置文件
			InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
			Properties pros = new Properties();
			pros.load(is);

			// 2.读取配置信息
			String user = pros.getProperty("user");
			String password = pros.getProperty("password");
			String url = pros.getProperty("url");
			String driverClass = pros.getProperty("driverClass");

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

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

			st = conn.createStatement();

			rs = st.executeQuery(sql);

			// 获取结果集的元数据
			ResultSetMetaData rsmd = rs.getMetaData();

			// 获取结果集的列数
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {

				t = clazz.newInstance();

				for (int i = 0; i < columnCount; i++) {
					// //1. 获取列的名称
					// String columnName = rsmd.getColumnName(i+1);

					// 1. 获取列的别名
					String columnName = rsmd.getColumnLabel(i + 1);

					// 2. 根据列名获取对应数据表中的数据
					Object columnVal = rs.getObject(columnName);

					// 3. 将数据表中得到的数据,封装进对象
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnVal);
				}
				return t;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 关闭资源
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if (st != null) {
				try {
					st.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}

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

		return null;
	}
}

3.3、PreparedStatement的使用

3.3.1、PreparedStatement介绍

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

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

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

3.3.2、PreparedStatement 与 Statement 的对比

  • 代码的可读性和可维护性,不用拼串

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

    • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。

    • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。

    • (语法检查,语义检查,翻译成二进制命令,缓存)

  • PreparedStatement 可以防止 SQL 注入

  • PreparedStatement可以操作Blob的数据,而Statement不能

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

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

3.3.4、使用PreparedStatement实现增删改操作

exeuce():如果执行的是查询操作,有返回值,则返回true,如果执行的是增删改操作,没有返回结果,返回false

executeUpdate():返回结果影响的行数

3.3.4.1、准备工作(创建JDBCUtils)

在实现之前,先创建一个JDBCUtils类,里面写了获取数据库的连接、关闭资源等方法,代码如下:

public class JDBCUtils {

    //获取数据库的连接
    public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException {
        //1.读取配置文件
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");

        Properties properties = new Properties();
        properties.load(is);

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

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

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

        return connection;
    }

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

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

3.3.4.2、 插入操作

//向customers添加一条记录(没有使用JDBCUtils)
    @Test
    public void testInsert() {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1.读取配置文件的4个基本信息
            InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");

            Properties properties = new Properties();
            properties.load(is);

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

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

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

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

            //5.填充占位符
            ps.setString(1, "张三");
            ps.setString(2, "zhangsan@qq.com");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            java.util.Date date = simpleDateFormat.parse("2050-05-11");
            ps.setDate(3, new Date(date.getTime()));

            //6.执行操作
            ps.execute();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ParseException 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();
            }
        }
    }

3.3.4.3、修改

//修改customers表的一条记录
    @Test
    public void testUpdate() {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1.获取数据库的连接
            connection = JDBCUtils.getConnection();
            //2.预编译SQL语句,返回PreparedStatement的实例
            String sql = "update customers set email = ? where name = ?";
            ps = connection.prepareStatement(sql);
            //3.填充占位符
            ps.setString(1, "zhangsan@126.com");
            ps.setString(2, "张三");
            //4.执行
            ps.execute();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5.资源的关闭
            JDBCUtils.closeResource(connection, ps);
        }
    }

3.3.4.4、通用的增删改操作

//通用的增删改操作
    public void update(String sql, Object... args){//sql语句中的占位符个数与可变形参args的长度相同
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1.获取数据库的连接
            connection = JDBCUtils.getConnection();
            //2.预编译SQL语句,返回PreparedStatement的实例
            ps = connection.prepareStatement(sql);
            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //4.执行
            ps.execute();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5.关闭资源
            JDBCUtils.closeResource(connection, ps);
        }
    }

    //测试通用代码的删除
    @Test
    public void TestCommonUpdate(){
        String sql = "delete from customers where name = ?";
        update(sql, "张三");
    }

3.3.4.5、使用PreparedStatement实现查询操作

注意:

getColumnName(): 获取列名

getColumnLabel():获取列的别名,没有别名则为列名

返回一条记录

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

            ps = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取元数据的列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
//                    String columnName = rsmd.getColumnName(i + 1);
                    //获取别名或列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给t对象指定的columnName属性赋值为columnValue
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps, rs);
        }
        return null;
    }

    @Test
    public void testGetInstance() {
        String sql = "select id,name,email from customers where id = ?";
        Customer instance = getInstance(Customer.class, sql, 12);
        System.out.println(instance);

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

返回多条记录(List)

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

            ps = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取元数据的列数
            int columnCount = rsmd.getColumnCount();
            //创建集合对象
            ArrayList<T> list = new ArrayList<>();
            while (rs.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
//                    String columnName = rsmd.getColumnName(i + 1);
                    //获取别名或列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给t对象指定的columnName属性赋值为columnValue
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps, rs);
        }
        return null;
    }

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

3.4、ResultSet与ResultSetMetaData

3.4.1、ResultSet

  • 查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象

  • ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现

  • ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。

  • ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext() 和 next() 方法的结合体。

  • 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。

    • 例如: getInt(1), getString("name")

    • 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。

  • ResultSet 接口的常用方法:

    • boolean next()

    • getString()

 3.4.2、ResultSetMetaDate

  • 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象

  • ResultSetMetaData meta = rs.getMetaData();

    • getColumnName(int column):获取指定列的名称

    • getColumnLabel(int column):获取指定列的别名

    • getColumnCount():返回当前 ResultSet 对象中的列数。

    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。

    • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。

    • isNullable(int column):指示指定列中的值是否可以为 null。

    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

3.5、资源的释放

  • 释放ResultSet, Statement,Connection。

  • 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

  • 可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭。

4、操作BLOB类型字段

4.1、MySQL BLOB类型

  • MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。

  • 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)

  • 实际使用中根据需要存入的数据大小定义不同的BLOB类型。

  • 需要注意的是:如果存储的文件过大,数据库的性能会下降。

  • 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

4.2、向数据表查询Blob类型 

@Test
    public void testBlobQuery() {
        InputStream is = null;
        FileOutputStream fos = null;
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth,photo from customers where id = ?";
            ps = connection.prepareStatement(sql);
            ps.setObject(1, 22);
            ResultSet rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                //方式1
                //            int id = rs.getInt(1);
                //            String name = rs.getString(2);
                //            String email = rs.getString(3);
                //            Date birth = rs.getDate(4);
                //方式2
                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);
                System.out.println(customer);

                //将Blob类型的字段保存下来,以文件的方式保存在本地
                Blob photo = rs.getBlob("photo");
                is = photo.getBinaryStream();
                fos = new FileOutputStream("C:\\Users\\zhang\\Desktop\\2\\b.jpg");
                byte[] bytes = new byte[1024];
                int len;
                while ((len = is.read(bytes)) != -1) {
                    fos.write(bytes, 0, len);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException 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(connection, ps);
        }


    }

4.3、向数据表插入Blob类型

//向数据表customers插入Blob类型的字段
    @Test
    public void testBlobInsert() throws SQLException, IOException, ClassNotFoundException {
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into customers (name,email,birth,photo) values(?,?,?,?)";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setObject(1, "张三");
        ps.setObject(2, "zhangsan@126.com");
        ps.setObject(3, "2002-08-14");
        FileInputStream fis = new FileInputStream("C:\\Users\\zhang\\Desktop\\1\\a.jpg");
        ps.setBlob(4, fis);
        ps.execute();
        JDBCUtils.closeResource(connection, ps);
    }

5、批量插入

5.1、批量执行sql语句

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

JDBC的批量处理语句包括下面三个方法:

  • addBatch(String):添加需要批量处理的SQL语句或是参数;

  • executeBatch():执行批量处理语句;

  • clearBatch():清空缓存的数据

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

  • 多条SQL语句的批量处理;

  • 一个SQL语句的批量传参;

mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
?rewriteBatchedStatements=true 写在配置文件的url后面

例如:在JDBC.properties中

className=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:13306/test?rewriteBatchedStatements=true
user=root
password=zyj123

Connection.setAutoCommit(false):设置不允许自动提交数据

Connection.commit():提交数据

5.2、批量插入的方法

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

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

CREATE TABLE goods(
    id int PRIMARY KEY AUTO_INCREMENT,
    name varchar(25)
)

5.2.1、方法1:使用Statement

时间:29342

//方式1:使用Statement
    @Test
    public void testStatement() throws SQLException, IOException, ClassNotFoundException {
        Connection connection = JDBCUtils.getConnection();
        Statement st = connection.createStatement();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 20000; i++) {
            String sql = "insert into goods(name) values('name_" + i + "')";
            st.execute(sql);
        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为:" + (end - start));
    }

5.2.2、方法2:使用PreparedStatement

时间:32395

//方式2:使用PreparedStatement
    @Test
    public void testPreparedStatement() {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            long start = System.currentTimeMillis();
            connection = JDBCUtils.getConnection();
            String sql = "insert into goods(name) values(?)";
            ps = connection.prepareStatement(sql);
            for (int i = 0; i < 20000; i++) {
                ps.setObject(1, "name_" + i);
                ps.execute();
            }
            long end = System.currentTimeMillis();
            System.out.println("花费的时间为:" + (end - start));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps);
        }
    }

5.2.3、方法3:使用Batch

时间:1229

/**
     * 方式3:使用batch
     * 注意:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
     * ?rewriteBatchedStatements=true 写在配置文件的url后面
     */
    @Test
    public void test3() {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            long start = System.currentTimeMillis();
            connection = JDBCUtils.getConnection();
            String sql = "insert into goods(name) values(?)";
            ps = connection.prepareStatement(sql);
            for (int i = 1; i <= 1000000; i++) {
                ps.setObject(1, "name_" + i);
                ps.addBatch();
                if(i % 500 ==0){
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("花费的时间为:" + (end - start)); //1229
                                                               //1000000:16582
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps);
        }
    }

5.2.4、方法4:数据处理完后再统一提交

时间:1311

//方式4:先设置不自动提交数据,等数据缓存完后再统一提交
    @Test
    public void test4(){
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            long start = System.currentTimeMillis();
            connection = JDBCUtils.getConnection();
            //设置不允许自动提交数据
            connection.setAutoCommit(false);
            String sql = "insert into goods(name) values(?)";
            ps = connection.prepareStatement(sql);
            for (int i = 1; i <= 1000000; i++) {
                ps.setObject(1, "name_" + i);
                ps.addBatch();
                if(i % 500 ==0){
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }
            //提交数据
            connection.commit();
            long end = System.currentTimeMillis();
            System.out.println("花费的时间为:" + (end - start)); //1311
                                                               //1000000:12469
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps);
        }
    }

6、数据库事务

6.1、数据库事务介绍

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

  • 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

6.2、JDBC事务处理

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

  • 数据什么时候意味着提交?

    • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。

    • 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。

    • DDL默认情况下,一旦执行会自动提交。set autocommit = false 对DDL操作无效。

    • DML默认情况下,一旦执行自动提交。可以通过set autocommit = false 取消DML操作的自动提交。

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

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

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

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

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

6.2.1、举例

举例:用户AA向用户BB转账100

1、在没有考虑数据库事务的情况下:

//没有考虑数据库事务的情况下
    @Test
    public void testUpdate() throws SQLException, IOException, ClassNotFoundException {
        Connection connection = JDBCUtils.getConnection();

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

        //模拟网络异常
        System.out.println(10 / 0);

        String sql2 = "Update user_table set balance = balance + 100 where user = ?";
        JDBCUtils.update(connection, sql2, "BB");

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

 该情况下AA减100,而BB不变

2、考虑数据库事务的情况下:

通过 Connection.setAutocommit( true/false );可以设置是否自动提交

由于要在所有sql语句执行完后再提交,而原本的增删改通用方法在执行完后关闭连接,所以增加一个重构方法:

//通用的增删改操作(版本2------考虑事务)
    public static int update(Connection connection, String sql, Object... args) {//sql语句中的占位符个数与可变形参args的长度相同
        PreparedStatement ps = null;
        try {
            //1.预编译SQL语句,返回PreparedStatement的实例
            ps = connection.prepareStatement(sql);
            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //3.执行
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            JDBCUtils.closeResource(null, ps);
        }
        return 0;
    }

模拟的代码:

//考虑数据库事务的情况
    @Test
    public void testUpdateWithTransaction() {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();

            //1.取消数据的自动提交
            connection.setAutoCommit(false);
            System.out.println(connection.getAutoCommit());

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

            //模拟网络异常
            System.out.println(10 / 0);

            String sql2 = "Update user_table set balance = balance + 100 where user = ?";
            JDBCUtils.update(connection, sql2, "BB");

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

            //2.提交数据
            connection.commit();

        } catch (Exception e) {
            try {
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            //修改为自动提交
            //主要针对于使用数据库连接池的使用
            try {
                connection.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }

            JDBCUtils.closeResource(connection, null);
        }
    }

该情况下,由于回滚,AA和BB的钱都不变

6.3、事务的ACID属性

1.原子性(Atomicity)

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

 2.一致性(Consistency)

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

3.隔离性(Isolation)

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

4.持久性(Durability)

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

6.3.1、数据库的并发问题

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

    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。

    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。

  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

  • 说明:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

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

6.3.2、四种隔离级别

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

  • 表格从上至下,一致性越来越好,但是并发性越来越差。

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

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

6.3.3、在MySQL设置隔离级别

  • 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别。

  • 查看当前的隔离级别:

#5.7版本
SELECT @@tx_isolation;
#8.0版本
select @@transaction_isolation;

设置当前 mySQL 连接的隔离级别:

set  transaction isolation level read committed;

设置数据库系统的全局的隔离级别:

set global transaction isolation level read committed;

补充操作:

  • 创建mysql数据库用户:

    create user tom identified by 'abc123';
  • 授予权限

    #授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123.
    grant all privileges on *.* to tom@'%'  identified by 'abc123'; 
    
    #给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。
    grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123';
    #8.0版本下的语句为
    grant select,insert,update,delete on test.* to 'tom'@'%';

6.3.4、在Java中设置隔离级别

在Java中,通过 Connection.getTransactionIsolation() 可以得到当前连接的隔离级别,得到的数字可以可以在Connection.java的380行查看意义。

int TRANSACTION_NONE             = 0;
int TRANSACTION_READ_UNCOMMITTED = 1;
int TRANSACTION_READ_COMMITTED   = 2;
int TRANSACTION_REPEATABLE_READ  = 4;
int TRANSACTION_SERIALIZABLE     = 8;
//用于查询
    @Test
    public void testTransactionSelect() throws SQLException, IOException, ClassNotFoundException {
        Connection connection = JDBCUtils.getConnection();
        //获取当前的隔离级别
        System.out.println(connection.getTransactionIsolation());
        //设置数据库的隔离级别
        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        //取消自动提交数据
        connection.setAutoCommit(false);

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

    //用于修改
    @Test
    public void testTransactionUpdate() throws SQLException, IOException, ClassNotFoundException, InterruptedException {
        Connection connection = JDBCUtils.getConnection();
        connection.setAutoCommit(false);
        String sql = "update user_table set balance = ? where user = ?";
        JDBCUtils.update(connection,sql,4000,"CC");

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

7、DAO及其实现类

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO

  • 作用:为了实现功能的模块化,更有利于代码的维护和升级。

BaseDAO.java

package DAO;

import JDBCUtils.JDBCUtils;

import java.io.IOException;
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;

/**
 * @Author zhang
 * @Date 2021/12/14 17:11
 * @Version 1.0
 */

//DAO:data(base) access object
//封装了针对于数据表的通用操作
public abstract class BaseDAO<T> {

    private Class<T> clazz = null;

    //获取当前BaseDAO的子类继承的父类的泛型
    {
        //子类创建时,此代码块会先执行,this指的是子类
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;

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

    //通用的增删改操作(版本2------考虑事务)
    public static int update(Connection connection, String sql, Object... args) {//sql语句中的占位符个数与可变形参args的长度相同
        PreparedStatement ps = null;
        try {
            //1.预编译SQL语句,返回PreparedStatement的实例
            ps = connection.prepareStatement(sql);
            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //3.执行
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            JDBCUtils.closeResource(null, ps);
        }
        return 0;
    }

    //通用的查询操作,返回数据表的一条记录(版本2-----考虑事务)
    public T getInstance(Connection connection, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取元数据的列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
//                    String columnName = rsmd.getColumnName(i + 1);
                    //获取别名或列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给t对象指定的columnName属性赋值为columnValue
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, ps, rs);
        }
        return null;
    }

    //通用的查询操作,返回数据表的多条记录(版本2-----考虑事务)
    public List<T> getForList(Connection connection, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取元数据的列数
            int columnCount = rsmd.getColumnCount();
            //创建集合对象
            ArrayList<T> list = new ArrayList<>();
            while (rs.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
//                    String columnName = rsmd.getColumnName(i + 1);
                    //获取别名或列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给t对象指定的columnName属性赋值为columnValue
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, ps, rs);
        }
        return null;
    }

    //用于返回不是数据表记录的查询,如使用count(*)
    public <E> E  getValue(Connection connection, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = connection.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 e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }
}

CustomerDAO.java

package DAO;

import bean.Customer;

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

/**
 * @Author zhang
 * @Date 2021/12/14 17:24
 * @Version 1.0
 */

//此接口用于规范针对于customers表的常用操作
public interface CustomerDAO {
    //将customer对象添加到数据库中
    void insert(Connection connection, Customer customer);

    //针对指定的id,删除一条记录
    void deleteById(Connection connection, int id);

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

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

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

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

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

CustomerDAOImpl.java

package DAO;

import bean.Customer;

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

/**
 * @Author zhang
 * @Date 2021/12/14 17:34
 * @Version 1.0
 */
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO {

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

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

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

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

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

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

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

8、数据库连接池

8.1、数据库连接池的必要性

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

    • 在主程序(如servlet、beans)中建立数据库连接

    • 进行sql操作

    • 断开数据库连接

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

    • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。

    • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)

    • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

8.2、数据库连接池技术

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。

  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。

  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个

  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

  • 工作原理

  • 数据库连接池技术的优点

    1. 资源重用

    由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

    2. 更快的系统反应速度

    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

    3. 新的资源分配手段

    对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

    4. 统一的连接管理,避免数据库连接泄漏

    在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

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

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:

    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。

    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用

    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点

    • BoneCP 是一个开源组织提供的数据库连接池,速度快

    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快

  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池

  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。

  • 特别注意:

    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。

    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

8.3.1、C3P0数据库连接池

方式一:

//方式一
    @Test
    public void testGetConnection1() throws PropertyVetoException, SQLException {
        //获取c3p0数据库连接池
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        cpds.setDriverClass("com.mysql.cj.jdbc.Driver");
        cpds.setJdbcUrl("jdbc:mysql://localhost:13306/test");
        cpds.setUser("root");
        cpds.setPassword("zyj123");
        //通过设置相关的参数,对数据库连接池进行管理
        //设置初始时数据库连接池中的连接数
        cpds.setInitialPoolSize(10);
        Connection connection = cpds.getConnection();
        System.out.println(connection);

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

方式二:使用配置文件

在src下新建c3p0-config.xml,内容如下:

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

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

        <!-- 进行数据库连接池管理的基本信息 -->
        <!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数 -->
        <property name="acquireIncrement">5</property>
        <!-- c3p0数据库连接池中初始化时的连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- c3p0数据库连接池维护的最少连接数 -->
        <property name="minPoolSize">10</property>
        <!-- c3p0数据库连接池维护的最多连接数 -->
        <property name="maxPoolSize">100</property>
        <!-- c3p0数据库连接池最多维护的Statement的个数 -->
        <property name="maxStatements">50</property>
        <!-- 每个连接中最多可以使用的Statement的个数 -->
        <property name="maxStatementsPerConnection">2</property>

    </named-config>
</c3p0-config>

连接代码如下

//方式二:使用配置文件
    @Test
    public void testGetConnection2() throws SQLException {
        ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");//文件名是配置文件内定义的名字,不是文件的名字
        Connection connection = cpds.getConnection();
        System.out.println(connection);
    }

将其放到JDBCUtils.java,

//通过c3p0获取数据库连接池
    private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");//文件名是配置文件内定义的名字,不是文件的名字
    public static Connection getConnection1() throws SQLException {
        Connection connection = cpds.getConnection();
        return connection;
    }

8.3.2、DBCP数据库连接池

  • DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool。如需使用该连接池实现,应在系统中增加如下两个 jar 文件:

    • Commons-dbcp.jar:连接池的实现

    • Commons-pool.jar:连接池实现的依赖库

  • Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

  • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。

  • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

  • 配置属性说明

DBCP需要导入两个jar包:commons-pool-1.5.5.jar 和 commons-dbcp-1.4.jar,导入后将光标移到DataSource按ctrl+T可以查看下图

 方法一

//连接方式一:不推荐
    @Test
    public void testGetConnection1() throws SQLException {
        //创建DBCP数据库连接池
        BasicDataSource source = new BasicDataSource();

        //设置四个基本信息
        source.setDriverClassName("com.mysql.cj.jdbc.Driver");
        source.setUrl("jdbc:mysql://localhost:13306/test");
        source.setUsername("root");
        source.setPassword("zyj123");

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

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

方法二:使用配置文件

在src下新建dbcp.properties,在里面可以对四个基本信息及其他信息进行定义,如:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:13306/test
username=root
password=zyj123

initialSize=10

连接代码如下:

//连接方式二:使用配置文件
    @Test
    public void testGetConnection2() throws Exception {
        Properties pros = new Properties();

        //方式1
//        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
        //方式2
        FileInputStream is = new FileInputStream("jdbc/src/dbcp.properties");

        pros.load(is);
        DataSource source = BasicDataSourceFactory.createDataSource(pros);
        Connection connection = source.getConnection();
        System.out.println(connection);
    }

将其添加到JDBCUtils.java中:

//通过DBCP获取数据库连接池
    private static DataSource source;
    //随着类的加载而执行,而且只执行一次
    static {
        try {
            Properties pros = new Properties();

            //方式1
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
            //方式2
//        FileInputStream is = new FileInputStream("jdbc/src/dbcp.properties");

            pros.load(is);
            source = BasicDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection2() throws Exception {
        Connection connection = source.getConnection();
        return connection;
    }

8.3.3、Drui(德鲁伊)数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

按照惯例先导包,导完包后在DataSource用ctrl+H查看

  • 详细配置参数:

配置缺省说明
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
url连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为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,是组合关系,并非替换关系

在src下新建druid.properties,内容如下:

url=jdbc:mysql://localhost:13306/test
username=root
password=zyj123
driverClassName=com.mysql.cj.jdbc.Driver

initialSize=10
maxActive=10

连接代码如下:

//使用配置文件
    @Test
    public void getConnection() throws Exception {
        Properties pros = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
        pros.load(is);
        DataSource source = DruidDataSourceFactory.createDataSource(pros);
        Connection connection = source.getConnection();
        System.out.println(connection);
    }

将其添加到JDBCUtils.java中:

//通过Druid获取数据库连接池
    private static DataSource source1;
    static {
        try {
            Properties pros = new Properties();
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
            pros.load(is);
            source1 = DruidDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection3() throws SQLException {
        Connection connection = source.getConnection();
        return connection;
    }

9、Apache-DBUtils实现CRUD操作

9.1、Apache-DBUtils简介

  • commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

  • API介绍:

    • org.apache.commons.dbutils.QueryRunner

    • org.apache.commons.dbutils.ResultSetHandler

    • 工具类:org.apache.commons.dbutils.DbUtils

9.2、主要API的使用 

9.2.1、DbUtils

  • DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:

    • public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。

    • public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。

    • public static void commitAndClose(Connection conn)throws SQLException: 用来提交连接的事务,然后关闭连接

    • public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。

    • public static void rollback(Connection conn)throws SQLException:允许conn为null,因为方法内部做了判断

    • public static void rollbackAndClose(Connection conn)throws SQLException

    • rollbackAndCloseQuietly(Connection)

    • public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

9.2.2、QueryRunner类

  • 该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

  • QueryRunner类提供了两个构造器:

    • 默认的构造器

    • 需要一个 javax.sql.DataSource 来作参数的构造器

  • QueryRunner类的主要方法:

    • 更新

      • public int update(Connection conn, String sql, Object... params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。

      • ......

    • 插入

      • public <T> T insert(Connection conn,String sql,ResultSetHandler<T> rsh, Object... params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值

      • ....

    • 批处理

      • public int[] batch(Connection conn,String sql,Object params)throws SQLException: INSERT, UPDATE, or DELETE语句

      • public <T> T insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object params)throws SQLException:只支持INSERT语句

      • .....

    • 查询

      • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

      • ......

//测试插入
    @Test
    public void testInsert() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "insert into customers(name,email,birth) values(?,?,?)";
            int insertCount = runner.update(connection, sql, "李四", "lisi@126.com", "2000-01-02");
            System.out.println("添加了 " + insertCount + " 条记录");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

9.2.3、ResultSetHandler接口及其实现类

  • 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

  • ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。

  • 接口的主要实现类:

    • ArrayHandler:把结果集中的第一行数据转成对象数组。

    • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。

    • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。

    • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。

    • ColumnListHandler:将结果集中某一列的数据存放到List中。

    • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。

    • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。

    • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List

    • ScalarHandler:查询单个值对象

测试使用:

//测试插入
    @Test
    public void testInsert() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "insert into customers(name,email,birth) values(?,?,?)";
            int insertCount = runner.update(connection, sql, "李四", "lisi@126.com", "2000-01-02");
            System.out.println("添加了 " + insertCount + " 条记录");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, null);
        }
    }

    //BeanHandler测试
    @Test
    public void testQuery1() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "select id,name,email,birth from customers where id = ?";
            //BeanHandle是ResultSetHandler接口的实现类,用于封装表中的一条记录
            BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
            Customer customer = runner.query(connection, sql, handler, 26);
            System.out.println(customer);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, null);
        }
    }

    //BeanListHandler测试
    @Test
    public void testQuery2() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "select id,name,email,birth from customers where id < ?";

            //BeanListHandler是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
            BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);

            List<Customer> list = runner.query(connection, sql, handler, 26);
            list.forEach(System.out::println);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

    //MapHandler测试
    @Test
    public void testQuery3() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "select id,name,email,birth from customers where id = ?";

            //MapHandler是ResultSetHandler接口的实现类,对应表中的一条记录
            //将字段及其对应的value作为map中的key和value
            MapHandler handler = new MapHandler();

            Map<String, Object> map = runner.query(connection, sql, handler, 26);
            System.out.println(map);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

    //MapListHandler测试
    @Test
    public void testQuery4() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "select id,name,email,birth from customers where id < ?";

            //MapListHandler是ResultSetHandler接口的实现类,对应表中的多条记录
            //将字段及其对应的value作为map中的key和value,将这些map添加到list中
            MapListHandler handler = new MapListHandler();

            List<Map<String, Object>> list = runner.query(connection, sql, handler, 26);
            list.forEach(System.out::println);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

    //ScalarHandler用于查询特殊值
    //求表中的行数
    @Test
    public void testQuery5() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "select count(*) from customers";

            ScalarHandler handler = new ScalarHandler();

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

    //求表中最大生日
    @Test
    public void testQuery6() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();
            String sql = "select max(birth) from customers";

            ScalarHandler handler = new ScalarHandler();

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

    //自定义ResultSetHandler
    @Test
    public void testQuery7() {
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection3();

            String sql = "select id,name,email,birth from customers where id =?";
            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;
                    }
                    return null;
                }
            };

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值