学习JDBC这一篇就够了 痛定思痛。 2022-11-18 01:42 368阅读 0赞 ### 文章目录 ### * 一、JDBC简介 * 二、获取数据库连接 * * 准备工作 * * 1、导入驱动jar包 * 2、加载与注册JDBC驱动 * 3、URL * 4、用户名和密码 * 数据库连接方式一——使用Properties文件获取☆☆☆ * 数据库连接方式二——使用XML文件获取数据库连接☆☆☆ * 三、使用PreparedStatement实现CRUD操作 * * 1.增、删、改操作 * 2.查询操作 * * 返回单个结果 * 返回所有结果 * 3.操作批量数据 * 四、数据库事务☆ * * 1. 数据库事务介绍 * 2. JDBC事务处理 * 3. 事务的ACID属性 * 4. 数据库的并发问题 * 5. 四种隔离级别 * 6. 在MySql中设置隔离级别 * 五、数据库连接池☆☆☆ * * 1. 数据库连接池技术 * 2. Druid(德鲁伊)连接池(☆☆☆☆☆) * 六、Apache-DBUtils实现CRUD操作☆☆☆ * * 1. Apache-DBUtils简介 * 2. 主要API的使用 * * DbUtils * QueryRunner类 * ResultSetHandler接口及实现类 * JDBC总结 -------------------- 读完大概需要20分钟 -------------------- 绪: 需要掌握有以下基础: * java基础 * [MySQL简单的DML语言][MySQL_DML] * [可扩展标记语言XML][XML] -------------------- # 一、JDBC简介 # JDBC(Java Database Connectivity)是一个**独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口**(一组API),定义了用来访问数据库的标准Java类库,(**java.sql,javax.sql**)使用这些类库可以以一种**标准**的方法、方便地访问数据库资源。 JDBC的目标是使Java程序员使用JDBC可以连接任何**提供了JDBC驱动程序**的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。 ![image-20210411083305414][] 使用`JDBC`的步骤如下: **加载数据库驱动 → 建立数据库连接(`Connection`) → 创建执行`SQL`语句的`Statement`对象 → 处理执行结果(`ResultSet`) → 释放资源** -------------------- # 二、获取数据库连接 # ## 准备工作 ## ### 1、导入驱动jar包 ### java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。 * Oracle的驱动:**oracle.jdbc.driver.OracleDriver** * mySql的驱动: **com.mysql.jdbc.Driver** JDBC驱动下载地址:[https://blog.csdn.net/qq\_20185737/article/details/115599254][https_blog.csdn.net_qq_20185737_article_details_115599254] 1. 数据库驱动的jar包 ![image-20210411084932946][] 2. 将上述jar包拷贝到Java工程的一个目录中,习惯上新建一个lib文件夹。 ![image-20210411085154536][] 3. 在驱动jar上右键–>【Build Path】–>【Add to Build Path】 ![image-20210411085703285][] ### 2、加载与注册JDBC驱动 ### * 加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名 Class.forName(“com.mysql.jdbc.Driver”); * 注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序 * **使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动** * 通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类**都**包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。 -------------------- ### 3、URL ### * JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。 * JDBC URL的标准由三部分组成,各部分间用冒号分隔。 * **jdbc:子协议:子名称(主机:端口/数据库名)** * **协议**:JDBC URL中的协议总是jdbc * **子协议**:子协议用于标识一个数据库驱动程序 * **子名称**:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了**定位数据库**提供足够的信息。包含**主机名**(对应服务端的ip地址)**,端口号,数据库名** ![image-20210411092342865][] * **常用数据库的 JDBC URL** * MySQL的连接URL编写方式: * jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值 * jdbc:mysql://localhost:3306/atguigu * jdbc:mysql://localhost:3306/atguigu\*\*?useUnicode=true&characterEncoding=utf8\*\*(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集) * 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 -------------------- ### 4、用户名和密码 ### * user,password可以用“属性名=属性值”方式告诉数据库 * 可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接 ## 数据库连接方式一——使用Properties文件获取☆☆☆ ## 将jdbc的配置信息保存到【properties】文件中,读取其中的配置信息 public void getConnection() throws Exception { //1.加载配置文件 InputStream is = JDBCUtils.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.获取连接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); } 其中,配置文件声明在工程的src目录下:【jdbc.properties】 user=root password=123456 url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 driverClass=com.mysql.cj.jdbc.Driver > 注: > > 这里用的mysql驱动是8.0.22版本,`com.mysql.jdbc.Driver`替换成`com.mysql.cj.jdbc.Driver`,还需要在url中设置时区,不然会报异常 > > 1. `com.mysql.jdbc.Driver`未替换成`com.mysql.cj.jdbc.Driver` > > Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary. > 2. url中未设置时区 > > The server time zone value '?й???????' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specific time zone value if you want to utilize time zone support. 说明:使用配置文件的方式保存配置信息,在代码中加载配置文件 **使用配置文件的好处:** ①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码 ②如果修改了配置信息,省去重新编译的过程。 -------------------- ## 数据库连接方式二——使用XML文件获取数据库连接☆☆☆ ## 在此之前,我们可以先通过另一篇文章来了解一下什么是XML? [可扩展标记语言XML][XML] -------------------- 在学习XML之后,我们就可以把jdbc的配置信息存储在xml文件中,通过对xml文件进行解析获取其中的配置信息,来建立连接 【jdbcconfig.xml】 <?xml version="1.0" encoding="utf-8" ?> <!-- jdbc配置信息 --> <config-info> <driverClassName>com.mysql.cj.jdbc.Driver</driverClassName> <url>jdbc:mysql://localhost:3306?serverTimezone=GMT%2B8</url> <username>root</username> <password>123456</password> </config-info> 【JDBCInfo.java】 /** * jdbc配置信息的javaBean类 * */ public class JDBCInfo { private String driverClassName; private String url; private String username; private String password; public JDBCInfo(String driverClassName, String url, String username, String password) { super(); this.driverClassName = driverClassName; this.url = url; this.username = username; this.password = password; } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } 【JDBCXML.java】 import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; /** * xml解析类 * */ public class JDBCXML { /** * 对xml文件进行解析 * * @return JDBCInfo对象,包含jdbc的配置信息 */ public static JDBCInfo getJDBCInfo() { SAXReader reader = new SAXReader(); try { Document doc = reader.read("src\\jdbcconfig.xml"); //获取xml文件的根元素 Element rootElement = doc.getRootElement(); //获取根元素的子元素信息 String driverClassName = rootElement.elementText("driverClassName"); String url = rootElement.elementText("url"); String username = rootElement.elementText("username"); String password = rootElement.elementText("password"); //返回一个包含jdbc配置信息的javaBean对象 return new JDBCInfo(driverClassName, url, username, password); } catch (DocumentException e) { e.printStackTrace(); } return null; } } 【JDBCUtils.java】 import java.sql.Connection; import java.sql.DriverManager; /** * jdbc工具类 * */ public class JDBCUtils { /** * 建立jdbc连接 * * @return Connection */ public static Connection getConnection() { // 调用JDBCXML对xml进行解析,通过getJDBCInfo()方法返回的JDBCInfo对象建立连接 JDBCInfo jdbcInfo = JDBCXML.getJDBCInfo(); try { Class.forName(jdbcInfo.getDriverClassName()); Connection connection = DriverManager.getConnection(jdbcInfo.getUrl(), jdbcInfo.getUsername(), jdbcInfo.getPassword()); return connection; } catch (Exception e) { e.printStackTrace(); } return null; } } -------------------- # 三、使用PreparedStatement实现CRUD操作 # -------------------- JDBCUtils类,包含 * 获取连接方法`getConnection()` * 关闭连接方法`closeConnection(Connection conn, PreparedStatement ps, ResultSet rs)` public class JDBCUtils { /** * 获取连接 * @return * @throws Exception */ public static Connection getConnection() throws Exception { Connection conn = null; InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); String user = pros.getProperty("user"); String password = pros.getProperty("password"); String url = pros.getProperty("url"); String driverClass = pros.getProperty("driverClass"); Class.forName(driverClass); conn = DriverManager.getConnection(url, user, password); return conn; } /** * 关闭连接 * @param con * @param ps */ public static void closeConnection(Connection con, PreparedStatement ps) { try { if (con != null) con.close(); } catch (SQLException e1) { e1.printStackTrace(); } try { if (ps != null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } } /** * 关闭连接 * @param conn * @param ps * @param rs */ public static void closeConnection(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps != null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } } } -------------------- ## 1.增、删、改操作 ## // 针对于不同的表,通用的增、删、改操作 public static void update(String sql, Object... orgs) { Connection con = null; PreparedStatement ps = null; try { // 1.获取数据库的连接 con = JDBCUtils.getConnection(); // 2.获取PreparedStatement的实例 (或:预编译sql语句) ps = con.prepareStatement(sql); // 3.填充占位符 for (int i = 0; i < orgs.length; i++) { ps.setObject(i + 1, orgs[i]); } // 4.执行sql语句 ps.execute(); } catch (SQLException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } finally { // 5.关闭资源 JDBCUtils.closeConnection(con, ps); } } 注:增、删、改无返回值 -------------------- ## 2.查询操作 ## -------------------- ### 返回单个结果 ### // 返回一个结果 public static <T> T selectOne(Class<T> clazz, String sql, Object... args) { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { // 1.获取数据库连接 con = JDBCUtils.getConnection(); // 2.预编译sql语句,得到PreparedStatement对象 ps = con.prepareStatement(sql); // 3.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 4.执行executeQuery(),得到结果集:ResultSet rs = ps.executeQuery(); // 5.得到结果集的元数据:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); // 6.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值 int columnCount = rsmd.getColumnCount(); if (rs.next()) { T t = clazz.newInstance(); // 遍历每一个列 for (int i = 0; i < columnCount; i++) { // 获取列值 Object columnVal = rs.getObject(i + 1); // 获取列的别名:列的别名,使用类的属性名充当 String columnLabel = rsmd.getColumnLabel(i + 1); // 6.2使用反射,给对象的相应属性赋值 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columnVal); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { // 7.关闭资源 JDBCUtils.closeConnection(con, ps); } return null; } > 说明:使用PreparedStatement实现的查询操作可以替换Statement实现的查询操作,解决Statement拼串和SQL注入问题。 -------------------- ### 返回所有结果 ### // 获取所有结果 public static <T> List<T> selectAll(Class<T> clazz, String sql, Object... args) { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; List list = new ArrayList(); try { // 1.获取数据库连接 con = JDBCUtils.getConnection(); // 2.预编译sql语句,得到PreparedStatement对象 ps = con.prepareStatement(sql); // 3.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 4.执行executeQuery(),得到结果集:ResultSet rs = ps.executeQuery(); // 获取结果集的元数据 ResultSetMetaData rsmd = rs.getMetaData(); // 获取结果集列数 int columnCount = rsmd.getColumnCount(); while (rs.next()) { T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { // 6.使用反射,给对象的相应属性赋值 Field field = clazz.getDeclaredField(rsmd.getColumnLabel(i + 1)); field.setAccessible(true); field.set(t, rs.getObject(i + 1)); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeConnection(con, ps, rs); } return null; } -------------------- ## 3.操作批量数据 ## -------------------- 1. 使用`addBatch() / executeBatch() / clearBatch()` 2. 修改mysql配置文件,添加`?rewriteBatchedStatements=true`使mysql开启批处理的支持 user=root password=123456 url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&serverTimezone=GMT%2B8 driverClass=com.mysql.cj.jdbc.Driver 3. 使用高版本的mysql驱动 //向表中插入50W条数据 public static void main(String[] args) { Connection con = null; PreparedStatement ps = null; try { long start = System.currentTimeMillis(); String sql = "insert into goods(name) values(?)"; // 获取连接 con = JDBCUtils.getConnection(); // 关闭自动提交数据 con.setAutoCommit(false); ps = con.prepareStatement(sql); //插入50W条数据 for (int i = 1; i <= 500000; i++) { ps.setObject(1, "name_" + i); // 1."攒"sql ps.addBatch(); //攒够1000sql执行一次 if (i % 1000 == 0) { // 2.执行batch批量 ps.executeBatch(); // 3.清空batch ps.clearBatch(); } } // 手动提交数据 con.commit(); long end = System.currentTimeMillis(); System.out.println(end - start); } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭连接 JDBCUtils.closeConnection(con, ps); } } -------------------- # 四、数据库事务☆ # -------------------- ## 1. 数据库事务介绍 ## * **事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。** * **事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit)**,那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务\*\*回滚(rollback)\*\*到最初状态。 * 为确保数据库中数据的**一致性**,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。 -------------------- ## 2. JDBC事务处理 ## * 数据一旦提交,就不可回滚。 * 数据什么时候意味着提交? * **当一个连接对象被创建时,默认情况下是自动提交事务**:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。 * \*\*关闭数据库连接,数据就会自动的提交。\*\*如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。 * **JDBC程序中为了让多个 SQL 语句作为一个事务执行:** * 调用 Connection 对象的 **setAutoCommit(false);** 以取消自动提交事务 * 在所有的 SQL 语句都成功执行后,调用 **commit();** 方法提交事务 * 在出现异常时,调用 **rollback();** 方法回滚事务 > 若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。 【案例:用户AA向用户BB转账100】 public void testJDBCTransaction() { Connection conn = null; try { // 1.获取数据库连接 conn = JDBCUtils.getConnection(); // 2.开启事务,关闭自动提交 conn.setAutoCommit(false); // 3.进行数据库操作 String sql1 = "update user_table set balance = balance - 100 where user = ?"; update(conn, sql1, "AA"); // 模拟网络异常 //System.out.println(10 / 0); String sql2 = "update user_table set balance = balance + 100 where user = ?"; update(conn, sql2, "BB"); // 4.若没有异常,则提交事务 conn.commit(); } catch (Exception e) { e.printStackTrace(); // 5.若有异常,则回滚事务 try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { //6.恢复每次DML操作的自动提交功能 conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } //7.关闭连接 JDBCUtils.closeResource(conn, null, null); } } 其中,对数据库操作的方法为: //使用事务以后的通用的增删改操作 public void update(Connection conn ,String sql, Object... args) { PreparedStatement ps = null; try { // 1.获取PreparedStatement的实例 (或:预编译sql语句) ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 3.执行sql语句 ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { // 4.关闭资源 JDBCUtils.closeResource(null, ps); } } > 注:为了在出现故障时,数据能回滚到上一次提交之前,所以**必须保持同一个事务的多个操作必须在同一个连接下**,即让Connection连接从外传进来,方法内不关闭连接,当所有事务都执行完时,再关闭连接。**需要关闭自动提交,改为手动提交** -------------------- ## 3. 事务的ACID属性 ## 1. **原子性(Atomicity)** 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 2. **一致性(Consistency)** 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 3. **隔离性(Isolation)** 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 4. **持久性(Durability)** 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。 -------------------- ## 4. 数据库的并发问题 ## * 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题: * **脏读**: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还**没有被提交**的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。 * **不可重复读**: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 **更新**了该字段。之后, T1再次读取同一个字段, 值就不同了。 * **幻读**: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中**插入**了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。 * **数据库事务的隔离性**: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。 * 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, **隔离级别越高, 数据一致性就越好, 但并发性越弱。** -------------------- ## 5. 四种隔离级别 ## * 数据库提供的4种事务隔离级别: ![1555586275271][] * Oracle 支持的 2 种事务隔离级别:**READ COMMITED**, SERIALIZABLE。 Oracle 默认的事务隔离级别为: **READ COMMITED** 。 * Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: **REPEATABLE READ。** -------------------- ## 6. 在MySql中设置隔离级别 ## * 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx\_isolation, 表示当前的事务隔离级别。 * 查看当前的隔离级别: SELECT @@tx_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'; -------------------- # 五、数据库连接池☆☆☆ # -------------------- ## 1. 数据库连接池技术 ## * **数据库连接池的基本思想**:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。 * **数据库连接池**负责分配、管理和释放数据库连接,它**允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个**。 * 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由**最小数据库连接数来设定**的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的**最大数据库连接数量**限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。 -------------------- * JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现 * DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池 * **DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。** > 注: > > * 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此**整个应用只需要一个数据源即可。** > * 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。 -------------------- ## 2. Druid(德鲁伊)连接池(☆☆☆☆☆) ## -------------------- 将Druid驱动的jar包拷贝到lib目录下 ![image-20210411112652938][] 在驱动jar上右键–>【Build Path】–>【Add to Build Path】 ![image-20210411112844408][] -------------------- Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,**可以说是目前最好的连接池之一。** package com.atguigu.druid; import java.sql.Connection; import java.util.Properties; import javax.sql.DataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; public class TestDruid { public static void main(String[] args) throws Exception { InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"); Properties pro = new Properties(); pro.load(is); DataSource ds = DruidDataSourceFactory.createDataSource(pro); Connection conn = ds.getConnection(); System.out.println(conn); } } 其中,src下的配置文件为:【druid.properties】 url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 username=root password=123456 driverClassName=com.mysql.cj.jdbc.Driver * 详细配置参数: <table> <thead> <tr> <th><strong>配置</strong></th> <th><strong>缺省</strong></th> <th><strong>说明</strong></th> </tr> </thead> <tbody> <tr> <td>name</td> <td></td> <td>配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)</td> </tr> <tr> <td>url</td> <td></td> <td>连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto</td> </tr> <tr> <td>username</td> <td></td> <td>连接数据库的用户名</td> </tr> <tr> <td>password</td> <td></td> <td>连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:<a href="https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter" rel="nofollow">https://github.com/alibaba/druid/wiki/使用ConfigFilter</a></td> </tr> <tr> <td>driverClassName</td> <td></td> <td>根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)</td> </tr> <tr> <td>initialSize</td> <td>0</td> <td>初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时</td> </tr> <tr> <td>maxActive</td> <td>8</td> <td>最大连接池数量</td> </tr> <tr> <td>maxIdle</td> <td>8</td> <td>已经不再使用,配置了也没效果</td> </tr> <tr> <td>minIdle</td> <td></td> <td>最小连接池数量</td> </tr> <tr> <td>maxWait</td> <td></td> <td>获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。</td> </tr> <tr> <td>poolPreparedStatements</td> <td>false</td> <td>是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。</td> </tr> <tr> <td>maxOpenPreparedStatements</td> <td>-1</td> <td>要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100</td> </tr> <tr> <td>validationQuery</td> <td></td> <td>用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。</td> </tr> <tr> <td>testOnBorrow</td> <td>true</td> <td>申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。</td> </tr> <tr> <td>testOnReturn</td> <td>false</td> <td>归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能</td> </tr> <tr> <td>testWhileIdle</td> <td>false</td> <td>建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。</td> </tr> <tr> <td>timeBetweenEvictionRunsMillis</td> <td></td> <td>有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明</td> </tr> <tr> <td>numTestsPerEvictionRun</td> <td></td> <td>不再使用,一个DruidDataSource只支持一个EvictionRun</td> </tr> <tr> <td>minEvictableIdleTimeMillis</td> <td></td> <td></td> </tr> <tr> <td>connectionInitSqls</td> <td></td> <td>物理连接初始化的时候执行的sql</td> </tr> <tr> <td>exceptionSorter</td> <td></td> <td>根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接</td> </tr> <tr> <td>filters</td> <td></td> <td>属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall</td> </tr> <tr> <td>proxyFilters</td> <td></td> <td>类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系</td> </tr> </tbody> </table> -------------------- # 六、Apache-DBUtils实现CRUD操作☆☆☆ # -------------------- ## 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 * API包说明: ![image-20210411111919266][] 将Dbutils的jar包拷贝到lib目录下 ![image-20210411112415835][] 在驱动jar上右键–>【Build Path】–>【Add to Build Path】 -------------------- ## 2. 主要API的使用 ## -------------------- ### 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。 -------------------- ### 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 的创建和关闭。 * … 示例 package jdbc; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.junit.Test; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; public class QueryRunnerTest { // 创建Druid连接池 private static DataSource source = null; static { try { InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"); Properties pros = new Properties(); pros.load(is); source = DruidDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } } // 测试添加 @Test public void testInsert() { Connection con = null; try { // 获取连接池连接 con = source.getConnection(); String sql = "insert into customers(name,email,birth) values(?,?,?)"; // 调用QueryRunner提供的update方法,返回修改了几条数据 QueryRunner runner = new QueryRunner(); int update = runner.update(con, sql, "迪迦", "迪迦@qq.com", "2020-04-18"); } catch (SQLException e) { e.printStackTrace(); } finally { // 调用DbUtils类关闭释放连接 DbUtils.closeQuietly(con); } } // 测试删除 @Test public void testdelete() { Connection con = null; try { // 获取连接池连接 con = source.getConnection(); String sql = "delete from customers where id=?"; // 调用QueryRunner提供的update方法,返回修改了几条数据 QueryRunner runner = new QueryRunner(); int update = runner.update(con, sql, 21); } catch (SQLException e) { e.printStackTrace(); } finally { // 调用DbUtils类关闭释放连接 DbUtils.closeQuietly(con); } } } -------------------- ### 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:\*\*查询单个值对象 package jdbc; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Iterator; import java.util.List; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.ResultSetHandler; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import org.junit.Test; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; public class QueryRunnerTest { // 创建Druid连接池 private static DataSource source = null; static { try { InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"); Properties pros = new Properties(); pros.load(is); source = DruidDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } } // 测试返回一条数据 @Test public void testQueryInstance() { Connection con = null; try { // 获取线程池连接 con = source.getConnection(); String sql = "select id,name,email,birth from customers where id=?"; // 将结果集中的第一行数据封装到一个对应的JavaBean实例 BeanHandler<Customers> bh = new BeanHandler<Customers>(Customers.class); // 调用QueryRunner类的query()方法,返回结果 QueryRunner runner = new QueryRunner(); Customers customers = runner.query(con, sql, bh, 2); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.closeQuietly(con); } } // 测试返回多个对象 @Test public void testQueryInstance1() { Connection con = null; try { // 获取线程池连接 con = source.getConnection(); String sql = "select * from user"; // 将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里 BeanListHandler<User> bh = new BeanListHandler<User>(User.class); // 调用QueryRunner类的query()方法,返回结果的集合 QueryRunner runner = new QueryRunner(); List<User> list = runner.query(con, sql, bh); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.closeQuietly(con); } } // 测试自定义ResultSetHandler的实现类 @Test public void testQueryInstance2() { // 实现ResultSetHandler的内部接口 ResultSetHandler<Customers> rsh = new ResultSetHandler<Customers>() { @Override public Customers handle(ResultSet arg0) throws SQLException { if (arg0.next()) { int id = arg0.getInt("id"); String name = arg0.getString("name"); String email = arg0.getString("email"); Date birth = arg0.getDate("birth"); // 结果返回 return new Customers(id, name, email, birth); } return null; } }; Connection con = null; try { // 获取Druid连接池连接 con = source.getConnection(); String sql = "select id,name,email,birth from customers where id=?"; QueryRunner runner = new QueryRunner(); Customers customers = runner.query(con, sql, rsh, 2); System.out.println(customers); } catch (SQLException e) { e.printStackTrace(); } finally { DbUtils.closeQuietly(con); } } // ScalarHandler类 查询特殊的数据 @Test public void testQueryValue() { Connection con = null; try { con = source.getConnection(); QueryRunner runner = new QueryRunner(); // 测试一 /* * String sql = "select count(*) from customers"; ScalarHandler sh = new * ScalarHandler(); long count = (long) runner.query(con, sql, sh); * System.out.println(count); */ // 测试二: String sql = "select max(birth) from customers"; ScalarHandler handler = new ScalarHandler(); Date birth = (Date) runner.query(con, sql, handler); System.out.println(birth); } catch (SQLException e) { e.printStackTrace(); } finally { DbUtils.closeQuietly(con); } } } # JDBC总结 # 总结 @Test public void testUpdateWithTx() { Connection conn = null; try { /* * 获取数据库连接两种方法 * ①手写的连接:JDBCUtils.getConnection(); * ②数据库连接池:Druid */ /* *对数据表进行一系列CRUD操作的两种方法 * ①自写PreparedStatement实现通用的增删改、查询操作 * ②使用dbutils提供的jar包中提供的QueryRunner类 */ //提交数据 conn.commit(); } catch (Exception e) { e.printStackTrace(); try { //回滚数据 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } }finally{ //3.关闭连接等操作 //① JDBCUtils.closeResource(); //② 使用dbutils提供的jar包中提供的DbUtils类提供了关闭的相关操作 } } [MySQL_DML]: https://blog.csdn.net/qq_20185737/article/details/114853793 [XML]: https://blog.csdn.net/qq_20185737/article/details/116405106 [image-20210411083305414]: /images/20221022/71177f25e7af40619e7e6802290421e0.png [https_blog.csdn.net_qq_20185737_article_details_115599254]: https://blog.csdn.net/qq_20185737/article/details/115599254 [image-20210411084932946]: /images/20221022/2b32b1bd32f745c484b3e4bbc78649da.png [image-20210411085154536]: /images/20221022/3951e902c02b47a192fae74ee632eb56.png [image-20210411085703285]: /images/20221022/be7c174bb1914d1c82e8b556b94d3fb8.png [image-20210411092342865]: /images/20221022/6cc2ef26a2904b8d8da2a25fdacb43be.png [1555586275271]: /images/20221022/f0647bb1a61f479bb0fb8009690c21ad.png [image-20210411112652938]: /images/20221022/b8cda3dbe56c491ea5e633b5f7402c70.png [image-20210411112844408]: /images/20221022/0773444feb31476fa63fbc7ed631095e.png [image-20210411111919266]: /images/20221022/bad9f594d7f64c9f8508c2b58b6c3013.png [image-20210411112415835]: /images/20221022/e28c4b68b9d8418a8bceb9b923aa8df1.png
相关 学习JDBC这一篇就够了 目录 第一章 概述 第二章 数据库连接 第三章 数据库事务 第四章 数据库连接池 4.1、DBC 一时失言乱红尘/ 2023年03月03日 14:29/ 0 赞/ 42 阅读
相关 学习Maven这一篇就够了 目录 第一章 Maven简介 1.1、Maven概述 1.2、Maven特点 1.3、Maven官网 1.4、 ╰+哭是因爲堅強的太久メ/ 2022年12月28日 05:28/ 0 赞/ 707 阅读
相关 学习JavaScript这一篇就够了 目录 第一章 JavaScript简介 1.1、JavaScript的起源 1.2、JavaScript的组成 1.3、Ja 痛定思痛。/ 2022年12月17日 15:55/ 0 赞/ 283 阅读
相关 学习JavaWeb这一篇就够了 目录 第一章 开发工具 1.1、JDK安装 1.2、Tomcat安装 1.3、IDEA安装 1.4、IDEA集 忘是亡心i/ 2022年12月06日 04:14/ 0 赞/ 203 阅读
相关 学习Tomcat这一篇就够了 目录 第一章 Tomcat概述 1.1、Tomcat概述 1.2、Tomcat历史 1.3、T 短命女/ 2022年12月04日 08:37/ 0 赞/ 472 阅读
相关 学习FastDFS这一篇就够了 目录 第一章 FastDFS简介 1.1、FastDFS的简介 1.2、FastDFS的发展历史 ╰+攻爆jí腚メ/ 2022年12月04日 07:40/ 0 赞/ 677 阅读
相关 学习Nginx这一篇就够了 目录 第一章 Nginx概述 1.1、Nginx概述 1.2、Nginx官网 1.3、Ngin 叁歲伎倆/ 2022年12月02日 04:24/ 0 赞/ 416 阅读
相关 学习Oracle这一篇就够了 目录 第一章 数据库概述 1.1、数据库的好处 1.2、数据库的常见概念 1.3、数据库的存储 缺乏、安全感/ 2022年11月27日 03:13/ 0 赞/ 437 阅读
相关 学习JavaScript这一篇就够了 [https://blog.csdn.net/qq\_38490457/article/details/109257751?utm\_medium=distribute.pc\ 左手的ㄟ右手/ 2022年11月21日 03:34/ 0 赞/ 320 阅读
相关 学习JDBC这一篇就够了 文章目录 一、JDBC简介 二、获取数据库连接 准备工作 1、导入驱动jar包 2、加载与注册JDBC驱 痛定思痛。/ 2022年11月18日 01:42/ 0 赞/ 369 阅读
还没有评论,来说两句吧...