JDBC学习笔记
目录
第1章.JDBC概述
1.1持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别 是企业级应用, 数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现 过程大多通过各种关系数据库来完 成。
1.2JDBC的理解
1.3 图片理解
1.4 JDBC体系结构
1.5 数据库驱动
1.6 面向接口编程的思想
第2章 获取数据库连接
2.1 要素一:Driver接口实现类
2.1.1 Driver接口介绍
2.1.2 加载与注册JDBC驱动
2.2 要素二:URL
2.3 要素三:
2.4 配置文件说明:
2.5 操作数据库的工具类
第3章:使用PreparedStatement实现CRUD操作
3.1 操作和访问数据库
3.2 使用Statement操作数据表的弊端
3.3 PreparedStatement的使用
3.4 PreparedStatement的使用
3.4.1使用 PreparedStatement 进行增删改操作——version1.0
3.4.2 使用PreparedStatement实现查询操作——version1.0
3.5 ResultSet与ResultSetMetaData
3.5.1 ResultSet
3.5.2 ResultSetMetaData
3.5.3 两种思想 两种技术
3.6 资源的释放
第4章 操作BLOB类型字段
4.1 MySQL BLOB类型
4.2 向数据表中插入大数据类型
4.3 查询数据表中的Blob类型字段
第5章 批量插入
5.1 批量执行SQL语句
5.2 高效的批量插入
第6章 数据库事务
6.1数据库事务介绍
6.2 事务处理
6.3 事务ACID属性
6.3.1 数据库的并发问题
6.3.2 四种隔离级别
6.3.3 在MySql中设置隔离级别
第7章:DAO及相关实现类
第8章 数据库连接池
8.1 JDBC数据库连接池的必要性
8.2 数据库连接池技术
8.3 多种开源的数据库连接池
8.3.1 C3P0数据库连接池
8.3.2 DBCP数据库连接池
8.3.3 Druid(德鲁伊)数据库连接池
第9章 Apache-DBUtils实现CRUD操作
9.1 Apache-DBUtils简介
9.2 主要API的使用
9.2.1 DbUtils
9.2.2 QueryRunner类
第1章.JDBC概述
1.1持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别 是企业级应用, 数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现 过程大多通过各种关系数据库来完 成。
持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。
在Java中,数据库存取技术可分为如下几类: JDBC直接访问数据库 JDO (Java Data Object )技术 第三方O/R工具,如Hibernate, Mybatis 等 JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。
1.2JDBC的理解
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接 口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标 准的方法、方便地访问数据库资源。
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
简单理解:JDBC是SUN公司提供的一套API,可以实现对具体数据库的实现(获取连接,关闭连接,DML,DDL,DCL)
如果没有JDBC,那么Java程序访问数据库时是这样的:
1.3 图片理解
有了JDBC以后:
总结:
1.4 JDBC体系结构
JDBC接口(API)包括两个层次:
面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结 果)。
面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。
从开发者角度:不需要关注数据库具体细节
从数据库厂商:只需要提供具体的实现
1.5 数据库驱动
数据库厂商根据JDBC这套接口,提供具体实现类的集合
类似:
1.6 面向接口编程的思想
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。 不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。
第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
在module下新建一个lib文件夹——将jar包拖到lib——再点add as library
2.1.2 加载与注册JDBC驱动
加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名
Class.forName(“com.mysql.jdbc.Driver”);
注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序
使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动
通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口 的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法 来注册自身的一个实例。下图是MySQL的Driver实现类的源码:
2.2 要素二:URL
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到 数据库的连接。
JDBC URL的标准由三部分组成,各部分间用冒号分隔。
jdbc:子协议:子名称
协议:JDBC URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库 提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
举例:
2.3 要素三:
用户名和密码 user,password可以用“属性名=属性值”方式告诉数据库
可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接
数据库连接方式举例如下:
方式一:出现第三方数据库的API
//方式一: @Test public void testConnection1() throws SQLException{ Driver driver = new com.mysql.jdbc.Driver(); //jdbc:mysql:协议 //localhost:ip地址 //3306:默认mysql的端口号 //test:test数据库 String ur1 = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"; //将用户名和密码封装在Properties中 Properties info = new Properties(); info.setProperty("user","root"); info.setProperty("password","password"); Connection conn = driver.connect(ur1, info); System.out.println(conn); }
方式二:对方式一的迭代,这里使用反射实例化Driver,在如下的程序中不出现第三方的api,是的程序具有更好的可移植性,体现面向接口编程思想
//方式二:对方式一的迭代,这里使用反射实例化Driver,在如下的程序中不出现第三方的api,是的程序具有更好的可移植性,体现面向接口编程思想 @Test public void testConnection2() throws Exception { //1.获取Drive实现类对象,使用反射 Class clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); //2.提供要连接的数据库 String ur1 = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"; //3.提供连接需要的用户名和密码 Properties info = new Properties(); info.setProperty("user","root"); info.setProperty("password","password"); //4.获取连接 Connection conn = driver.connect(ur1, info); System.out.println(conn); }
方式三:使用DriverManager实现数据库的连接。体会获取连接必要的4个基本要素。
//方式三:使用DriverManager替换Driver @Test public void testConnection3() throws Exception{ //1.获取Drive实现类的对象 Class clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver =(Driver) clazz.newInstance(); //2.提供另外三个连接的基本信息 String ur1 ="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"; String user = "root"; String password = "password"; //注册驱动 DriverManager.registerDriver(driver); //获取连接 Connection conn = DriverManager.getConnection(ur1, user, password); System.out.println(conn); }
方式四:不必显式的注册驱动了。因为在DriverManager的源码中已经存在静态代码块,实现了驱动的注册。
@Test public void testConnection4() throws Exception{ //1.提供另外三个连接的基本信息 String ur1 ="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"; String user = "root"; String password = "password"; //2.获取Drive实现类的对象 Class.forName("com.mysql.jdbc.Driver"); //相较于方式三,可以省略如下的操作 //Driver driver =(Driver) clazz.newInstance(); //注册驱动 //DriverManager.registerDriver(driver); //为什么可以省略上述操作呢?因为在mysql的Driver实现类中,声明了如下的操作 //static { // try { // DriverManager.registerDriver(new Driver()); // } catch (SQLException var1) { // throw new RuntimeException("Can't register driver!"); // } // } //3获取连接 Connection conn = DriverManager.getConnection(ur1, user, password); System.out.println(conn); }
方式五:最终版:使用配置文件的方式保存配置信息,在代码中加载配置文件
//方式5:将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接 //idea中jdbc.properties要放到resources里 /* 这种方式的好处 1.实现了数据与代码的分离。实现了解耦 2.如果需要修改配置文件信息,可以避免程序重新打包 */ @Test public void getConnection5() throws Exception { //1.读取配置文件中的4个基本信息 InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); String user = pros.getProperty("user"); String password = pros.getProperty("password"); String ur1 = pros.getProperty("ur1"); String driverClass = pros.getProperty("driverClass"); //2.加载驱动 Class.forName(driverClass); //3.获取连接 Connection conn = DriverManager.getConnection(ur1, user, password); System.out.println(conn); }
配置文件好处:①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码 ②如果修改了 配置信息,省去重新编译的过程。
2.4 配置文件说明:
在new项目的时候:如果点的是java,配置文件要放在src下,如果是maven,要放在resources下
配置文件如下:
user=rootpassword=passwordur1= jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=truedriverClass=com.mysql.jdbc.Driver
2.5 操作数据库的工具类
package com.xuchengjing.util;import java.io.InputStream;import java.sql.*;import java.util.Properties;/ * * 操作数据库的工具类 */public class JDBCUtils { / * 连接的操作 * @return * @throws Exception */ public static Connection getConnection() throws Exception { //1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); String user = pros.getProperty("user"); String password = pros.getProperty("password"); String ur1 = pros.getProperty("ur1"); String driverClass = pros.getProperty("driverClass"); //2.加载驱动 Class.forName(driverClass); //3.获取连接 Connection conn = DriverManager.getConnection(ur1, user, password); return conn; } / * 关闭连接和Statement的操作 * @param conn * @param ps */ public static void closeResource(Connection conn, Statement ps){ try { if (ps != null){ ps.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null){ conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } / * 关闭资源的操作 * @param conn * @param ps * @param rs */ public static void closeResource(Connection conn, Statement ps, ResultSet rs){ try { if (ps != null){ ps.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null){ conn.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if (rs != null) rs.close(); }catch (SQLException e){ e.printStackTrace(); } }}
第3章:使用PreparedStatement实现CRUD操作
3.1 操作和访问数据库
数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连 接就是一个Socket连接。
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
CallableStatement:用于执行 SQL 存储过程
3.2 使用Statement操作数据表的弊端
使用Statement操作数据表存在弊端:
问题一:存在拼串操作,繁琐
问题二:存在SQL注入问题
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法。
Statement代码演示:
public class StatementTest {// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题//如何避免出现sql注入,只要用PreparedStatement(从Statement扩展而来) 取代Statement @Testpublic void testLogin () {Scanner scanner = new Scanner(System.in);System.out.print("请输入用户名:");String user = scanner.nextLine();System.out.print("请输入密码:");String password = scanner.nextLine();String sql = "SELECT user,password FROM user_table WHERE user = '" + user + "' AND password = '" + password + "'"; User returnUser = get(sql, User.class);if (returnUser != null) {System.out.println("登录成功");} else {System.out.println("用户名不存在或密码错误");}}// 使用Statement实现对数据表的查询操作public static T get(String sql, Class 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 ur1 = pros.getProperty("ur1");String driverClass = pros.getProperty("driverClass");// 3.加载驱动Class.forName(driverClass);// 4.获取连接conn = DriverManager.getConnection(ur1, 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的使用
PreparedStatement的理解:
①PreparedStatement是Statement的子接口
②是预编译过的sql语句
③可以解决sql注入问题,拼串问题
Java与SQL对应数据类型转换表
3.4 PreparedStatement的使用
3.4.1使用 PreparedStatement 进行增删改操作——version1.0
//通用的增删改操作 public void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同 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(conn,ps); } }
3.4.2 使用PreparedStatement实现查询操作——version1.0
/ * 针对不同的表的通用的查询操作,返回表中的一条记录 * @param clazz * @param sql * @param args * @param * @return */ public T getInstance(Class clazz,String sql,Object...args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 0;i < args.length;i++){ ps.setObject(i + 1,args[i]); } rs = ps.executeQuery(); //获取结果集的元数据:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //通过ResultSetMetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); if (rs.next()){ T t = clazz.newInstance(); //处理结果集一行数据中的每一个列 for (int i = 0;i < columnCount;i++){ //获取列值 Object columValue = rs.getObject(i + 1); //获取每个列的列名 String columnLabel = rsmd.getColumnLabel(i + 1); //给t对象指定的columnName属性,赋值为columValue:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t,columValue); } return t; } } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(conn,ps,rs); } return null; }
返回多个对象的构成的集合——version 1.0
public List getForList(Class clazz,String sql,Object...args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 0;i < args.length;i++){ ps.setObject(i + 1,args[i]); } rs = ps.executeQuery(); //获取结果集的元数据:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //通过ResultSetMetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); //创建集合对象 ArrayList list = new ArrayList(); while (rs.next()){ T t = clazz.newInstance(); //处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for (int i = 0;i < columnCount;i++){ //获取列值 Object columValue = rs.getObject(i + 1); //获取每个列的列名 String columnLabel = rsmd.getColumnLabel(i + 1); //给t对象指定的columnName属性,赋值为columValue:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t,columValue); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(conn,ps,rs); } return null; }
3.5 ResultSet与ResultSetMetaData
3.5.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开始。
3.5.2 ResultSetMetaData
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
getColumnName(int column):获取指定列的名称
getColumnLabel(int column):获取指定列的别名
getColumnCount():返回当前 ResultSet 对象中的列数。
getColumnTypeName(int column):检索指定列的数据库特定的类型名称。 getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
isNullable(int column):指示指定列中的值是否可以为 null。
isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
问题1:得到结果集后, 如何知道该结果集中有哪些列 ? 列名是什么?
需要使用一个描述 ResultSet 的对象, 即 ResultSetMetaData
问题2:关于ResultSetMetaData
1. 如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
2. 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
3. 获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的getColumnLabel() 方法
3.5.3 两种思想 两种技术
两种思想:
面向接口编程思想
ORM编程思想(object relational mapping) 一个数据表对应一个java类 表中的一条记录对应java类的一个对象 表中的一个字段对应java类的一个属性
两种技术:
JDBC结果集的元数据:ResultSetMetaData
获取列数:getColumnCount()
获取列的别名:getColumnLabel()
通过反射,创建指定类的对象,获取指定的属性并赋值
查询操作流程:
3.6 资源的释放
释放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 向数据表中插入大数据类型
/向数据库customers中插入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,"zhang@qq.com"); ps.setObject(3,"1992-09-08"); FileInputStream is = new FileInputStream(new File("微信图片_20211110225752.jpg")); ps.setObject(4,is); ps.execute(); JDBCUtils.closeResource(conn,ps); }
4.3 查询数据表中的Blob类型字段
//查询数据表customers中Blob类型的字段@Testpublic 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); is = null; fos = null; rs = ps.executeQuery(); if (rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); Date birth = rs.getDate("birth"); Customers cust = new Customers(id, name, email, birth); System.out.println(cust); //将Blob类型的字段下载下来,以文件的方式保存在本机 Blob photo = rs.getBlob("photo"); is = photo.getBinaryStream(); fos = new FileOutputStream("zhangyuhao.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(conn,ps,rs); }}
tips:
mysql8以上可以不用,用的话如下面路径(注意c盘可能被隐藏了)
第5章 批量插入
5.1 批量执行SQL语句
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处 理。
通常情况下比单独提交处理更有效率 JDBC的批量处理语句包括下面三个方法: addBatch(String):添加需要批量处理的SQL语句或是参数;
executeBatch():执行批量处理语句;
clearBatch():清空缓存的数据 通常我们会遇到两种批量执行SQL语句的情况: 多条SQL语句的批量处理; 一个SQL语句的批量传参;
5.2 高效的批量插入
举例:向数据表中插入20000条数据 数据库中提供一个goods表。创建如下:
CREATE TABLE goods(id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20));
层次一:
//方式一:使用StatementConnection conn = JDBCUtils.getConnection();Statement st = conn.createStatement();for(int i = 1;i <= 20000;i++){ String sql = "insert into goods(names)values('name_" + i + "')"; st.execute(sql);
层次二:使用PreparedStatement
//批量插入的方式二:使用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));//花费时间为:21176 } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(conn,ps); } }
层次三:
1.addBatch(),executeBatch(),clearBatch()2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持 ?rewriteBatchedStatements=true 写在配置文件的url后面3.使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
/* * 批量插入的方式三: * 1.addBatch(),executeBatch(),clearBatch() * 2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持 * ?rewriteBatchedStatements=true 写在配置文件的url后面 * 3.使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar * */ @Test public void testInsert2(){ Connection conn = null; PreparedStatement ps = null; try { long start = System.currentTimeMillis(); conn = JDBCUtils.getConnection(); String sql = "insert into goods(name)values(?)"; ps = conn.prepareStatement(sql); for (int i = 1;i <= 20000;i++){ ps.setObject(1,"name_" + i); //1."赞"sql ps.addBatch(); if (i % 500 == 0){ //2.执行batch ps.executeBatch(); //3.清空batch ps.clearBatch(); } } long end = System.currentTimeMillis(); System.out.println("花费时间为:" + (end - start));//花费时间为:21176 } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(conn,ps); } }
层次四:设置连接不允许自动提交数据
//再优化 @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 <= 20000;i++){ ps.setObject(1,"name_" + i); //1."赞"sql ps.addBatch(); if (i % 500 == 0){ //2.执行batch ps.executeBatch(); //3.清空batch ps.clearBatch(); } } //提交数据 conn.commit(); long end = System.currentTimeMillis(); System.out.println("花费时间为:" + (end - start));//花费时间为:21176 } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(conn,ps); } }
第6章 数据库事务
6.1数据库事务介绍
事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可 以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退 到开始状态。
6.2 事务处理
数据一旦提交,就不可回滚。
数据什么时候意味着提交?
当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会 向数据库自动提交,而不能回滚。
关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证 事务。即同一个事务的多个操作必须在同一个连接下。
JDBC程序中为了让多个 SQL 语句作为一个事务执行:
调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
在出现异常时,调用 rollback(); 方法回滚事务
若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状 态。
举例转账100
//考虑数据库事务后的转账操作 @Test public void testUpdateWithTx(){ Connection conn = null; try { conn = JDBCUtils.getConnection(); System.out.println(conn.getAutoCommit()); //1.取消数据的自动提交 conn.setAutoCommit(false); String sql1 = "update user_table set balance = balance - 100 where user = ?"; update(conn,sql1,"AA"); String sql2 ="update user_table set balance = balance + 100 where user = ?"; update(sql2,"BB"); System.out.println("转账成功"); //2.提交数据 conn.commit(); } catch (Exception e) { e.printStackTrace(); //3.回滚数据 try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } }finally { JDBCUtils.closeResource(conn,null); } }
考虑事务后,通用的查询操作——version2.0
/通用的增删改操作 verson 2.0 考虑事务 public int update(Connection conn,String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同 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.执行 return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally { //4.资源的关闭 JDBCUtils.closeResource(null,ps); } return 0; }
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, 表示当前的事务隔离级别。
查看当前的隔离级别:
SELECT @@tx_isolation;
设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
第7章:DAO及相关实现类
DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、 Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
作用:为了实现功能的模块化,更有利于代码的维护和升级。
BaseDAO:
/*DAO:data(base) access object封装了针对于数据库的通用的操作 */public abstract class BaseDAO { private Class clazz = null; { Type genericSuperclass = this.getClass().getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) genericSuperclass; Type[] typeArguments = paramType.getActualTypeArguments();// clazz = (Class) typeArguments[0];//泛型的第一个参数 } //通用的增删改操作 verson 2.0 考虑事务 public int update(Connection conn,String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同 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.执行 return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally { //4.资源的关闭 JDBCUtils.closeResource(null,ps); } return 0; } //通用的查询操作,用于返回数据库表中的一条数据(version 2.0 考虑上事务) public T getInstance(Connection conn, String sql, Object...args){ PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 0;i < args.length;i++){ ps.setObject(i + 1,args[i]); } rs = ps.executeQuery(); //获取结果集的元数据:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //通过ResultSetMetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); if (rs.next()){ T t = clazz.newInstance(); //处理结果集一行数据中的每一个列 for (int i = 0;i < columnCount;i++){ //获取列值 Object columValue = rs.getObject(i + 1); //获取每个列的列名 String columnLabel = rsmd.getColumnLabel(i + 1); //给t对象指定的columnName属性,赋值为columValue:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t,columValue); } return t; } } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(null,ps,rs); } return null; } //通用的查询操作,用于返回数据表中的多条记录构成的集合(version 2.0,考虑上事务) public List getForList(Connection conn, String sql, Object...args){ PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(sql); for (int i = 0;i < args.length;i++){ ps.setObject(i + 1,args[i]); } rs = ps.executeQuery(); //获取结果集的元数据:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //通过ResultSetMetaData获取结果集中的列数 int columnCount = rsmd.getColumnCount(); //创建集合对象 ArrayList list = new ArrayList(); while (rs.next()){ T t = clazz.newInstance(); //处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for (int i = 0;i < columnCount;i++){ //获取列值 Object columValue = rs.getObject(i + 1); //获取每个列的列名 String columnLabel = rsmd.getColumnLabel(i + 1); //给t对象指定的columnName属性,赋值为columValue:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t,columValue); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(null,ps,rs); } return null; } //用于查询特殊值的通用的方法 public 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 e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(null,ps,rs); }return null; }}
CustomerDAO:
/*此接口用于规范针对于customers表的常用操作 */public interface CustomerDAO { / * 将cust对象添加到数据库中 * @param conn * @param cust */ void insert(Connection conn, Customers cust); / * 针对于指定的id,删除表中的一条记录 * @param conn * @param id */ void deleteById(Connection conn,int id); / * 针对于内存中的cust对象,去修改数据库中指定的记录 * @param conn * @param cust */ void update(Connection conn,Customers cust); / * 针对指定的id查询得到对应的Customers对象 * @param conn * @param id * @return */ Customers getCustomerById(Connection conn, int id); / * 查询表中的所有记录构成的集合 * @param conn * @return */ List getAll(Connection conn); / * 返回数据表中的条目数 * @param conn * @return */ Long getCount(Connection conn); / * 返回数据表中最大的生日 * @param conn * @return */ Date getMaxBirth(Connection conn);}
CustomerDAOImpl:
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO{ @Override public void insert(Connection conn, Customers 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, Customers cust) { String sql = "update customers set name = ?,email = ?,birth = ? where id = ?"; update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId()); } @Override public Customers getCustomerById(Connection conn, int id) { String sql = "select id,name,email,birth from customers where id = ?"; Customers customers = getInstance(conn, sql, id); return customers; } @Override public List getAll(Connection conn) { String sql = "select id,name,email,birth from customers"; List list = getForList(conn, sql); return list; } @Override public Long getCount(Connection conn) { String sql = "select count(*) from customers"; return getValue(conn,sql); } @Override public Date getMaxBirth(Connection conn) { String sql = "select max(birth) from customers"; return getValue(conn,sql); }}
CustomerDAOImplTest:
public class CustomerDAOImplTest { private CustomerDAOImpl dao = new CustomerDAOImpl(); @Test void insert() { Connection conn = null; try { conn = JDBCUtils2.getConnection(); Customers cust = new Customers(1, "宋宏康", "shonghangk@126.com", new Date(46543646436L)); dao.insert(conn,cust); System.out.println("添加成功"); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } @Test void deleteById() { Connection conn = null; try { conn = JDBCUtils2.getConnection(); dao.deleteById(conn,13); System.out.println("删除成功"); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } @Test void update() { Connection conn = null; try { conn = JDBCUtils2.getConnection(); Customers cust = new Customers(18, "贝多芬", "beiduofen@126.com",new java.sql.Date(453465656L)); dao.update(conn,cust); System.out.println("修改成功"); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } @Test void getCustomerById() { Connection conn = null; try { conn = JDBCUtils2.getConnection2(); Customers cust = dao.getCustomerById(conn, 19); System.out.println(cust); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } @Test void getAll() { Connection conn = null; try { conn = JDBCUtils2.getConnection(); List list = dao.getAll(conn); System.out.println(list); list.forEach(System.out::println); System.out.println(""); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } @Test void getCount() { Connection conn = null; try { conn = JDBCUtils2.getConnection(); Long count = dao.getCount(conn); System.out.println("表中的记录数为:" + count); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } @Test void getMaxBirth() { Connection conn = null; try { conn = JDBCUtils2.getConnection(); Date maxBirth = dao.getMaxBirth(conn); System.out.println("最大的生日为:" + maxBirth); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } }}
考虑到事务以后的数据库操作(重点)
①获取数据库的连接 //方式一:手动获取链接 方式二:数据库连接池
Connection conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);//体现事务
②如下的多个DML操作,作为一个事务出现
需要使用通用的增删改查操作
//通用的增删改查操作如何实现//方式一:手动使用PreparedStatement实现 方式二:使用dbutils.jar中QueryRunner类
conn.commit();
③如果出现异常,则
conn.rollback();
④关闭资源
JDBCUtils.closeResource();//方式一:手动关闭资源 方式二:DBUtils类的关闭方法
第8章 数据库连接池
8.1 JDBC数据库连接池的必要性
这种模式开发,存在的问题:
在使用开发基于数据库的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数据库连接池
/ * 使用C3P0的数据库连接池技术 * @return * @throws SQLException */ //数据库连接池只需提供一个即可 private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0"); public static Connection getConnection1()throws SQLException{ Connection conn = cpds.getConnection(); return conn; }
配置文件如下:
rootpasswordjdbc:mysql:///testcom.mysql.jdbc.Driver510050100005
8.3.2 DBCP数据库连接池
DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Commonpool。如需使用该连接池实现,应在系统中增加如下两个 jar 文件:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程 序独立使用。
数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据 源即可。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但上面的代码并没有关闭数据库的 物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
配置属性说明
/ * 使用DBCP数据库连接池技术获取数据库连接 * @throws Exception */ private static DataSource source; static{ try { Properties pros = new 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 getConnection2()throws Exception{ Connection conn = source.getConnection(); return conn; }
配置文件如下:
driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8username=rootpassword=passwordinitialSize=10
8.3.3 Druid(德鲁伊)数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了 日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的 连接池之一。
/ * 使用Druid数据库连接池技术 */ private DataSource source1; static { try { Properties pros = new Properties(); InputStream in =JDBCUtils2.class.getClassLoader().getResourceAsStream("druid10.properties"); pros.load(in); source = DruidDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection3() throws SQLException { Connection conn = source.getConnection(); return conn; }
配置文件如下:
driverClassName=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8username=rootpassword=passwordinitialSize=10maxActive=15
详细配置参数:
第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)
9.2.2 QueryRunner类
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少 编码量。
QueryRunner类提供了两个构造器:
默认的构造器
需要一个 javax.sql.DataSource 来作参数的构造器
QueryRunner类的主要方法:
更新
public int update(Connection conn, String sql, Object... params) throws SQLException:用来执行 一个更新(插入、更新或删除)操作。
插入
public T insert(Connection conn,String sql,ResultSetHandler 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 insertBatch(Connection conn,String sql,ResultSetHandler 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 conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "insert into customers(name,email,birth)values(?,?,)"; int insertCount = runner.update(conn, sql, "蔡徐坤", "caixukun@126.com", "1977-09-08"); System.out.println("添加了" + insertCount + "条记录"); } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } }
该接口用于处理 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:查询单个值对象
测试查询:
//测试查询 /* BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录 */ @Test public void testQuery1() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "select id,name,email,birth from customers where id = ?"; BeanHandler handler = new BeanHandler(Customers.class); Customers customers = runner.query(conn, sql, handler, 18); System.out.println(customers); } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } /* BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合 */ @Test public void testQuery2() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "select id,name,email,birth from customers where id < ?"; BeanListHandler handler = new BeanListHandler(Customers.class); List list = runner.query(conn, sql, handler, 10); list.forEach(System.out::println); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } /* MapHander:是ResultSetHandler接口的实现类,对应表中的一条数据 将字段及相应字段的值作为map中的key和value */ @Test public void testQuery3(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "select id,name,email,birth from customers where id = ?"; MapHandler handler = new MapHandler(); Map map = runner.query(conn, sql, handler, 20); System.out.println(map); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } /* MapListHander:是ResultSetHanderler接口实现类,对应表中的多条记录 将字段及其相应字段的值作为map中的key和value,将这些map添加到List中 */ @Test public void testQuery4(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "select id,name,email,birth from customers where id < ?"; MapListHandler handler = new MapListHandler(); List<Map> list = runner.query(conn, sql, handler, 23); list.forEach(System.out::println); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } /* ScalarHandler:用于查询特殊值 */ @Test public void testQuery5(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "select count(*) from customers"; ScalarHandler handler = new ScalarHandler(); Long count = (Long) runner.query(conn, sql, handler); System.out.println(count); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } //最大生日 @Test public void testQuery6(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "select max(birth) from customers"; ScalarHandler handler = new ScalarHandler(); Date maxBirth = (Date) runner.query(conn, sql, handler); System.out.println(maxBirth); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } } /* 自定义ResultSetHandler的实现类 */ @Test public void testQuery7(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils2.getConnection3(); String sql = "select id,name,email,birth from customers where id = ?"; ResultSetHandler handler = new ResultSetHandler(){ public Customers handle(ResultSet rs) throws SQLException{ System.out.println("handle");//return new Customers(1,"Tom","tom@126.com",new Date(123323432L)); if(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); Date birth = rs.getDate("birth"); return new Customers(id, name, email, birth); } return null; } }; Customers customers = runner.query(conn, sql, handler, 20); System.out.println(customers); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils2.closeResource(conn,null); } }
使用dbutils.jar包中的dbutils工具类实现资源的关闭
/ * 使用dbutils.jar中提供的DBUtils工具类,实现资源的关闭 */ public static void closeResource1(Connection conn,Statement ps,ResultSet rs){ /* try { DbUtils.close(conn); } catch (SQLException e) { e.printStackTrace(); } try { DbUtils.close(ps); } catch (SQLException e) { e.printStackTrace(); } try { DbUtils.close(rs); } catch (SQLException e) { e.printStackTrace(); } */ DbUtils.closeQuietly(conn); DbUtils.closeQuietly(ps); DbUtils.closeQuietly(rs); }