typora-copy-images-to: img

连接池和DBUtils

学习目标

  • 能够理解连接池解决现状问题的原理
  • 能够使用C3P0连接池
  • 能够使用DRUID连接池
  • 能够编写C3P0连接池工具类
  • 能够使用DBUtils完成CRUD
  • 能够理解元数据
  • 能够自定义DBUtils

第一章-自定义连接池

知识点-连接池概念

1.目标

  • 能够理解连接池解决现状问题的原理

2.讲解

2.1为什么要使用连接池

  1. Connection对象在JDBC使用的时候就会去创建一个对象,使用结束以后就会将这个对象给销毁了(close).每次创建和销毁对象都是耗时操作.需要使用连接池对其进行优化.
  2. 程序初始化的时候,初始化多个连接,将多个连接放入到池(集合)中.每次获取的时候,都可以直接从连接池中进行获取.使用结束以后,将连接归还到池中.

2.2.生活里面的连接池例子

  • 老方式:
    下了地铁需要骑车, 跑去生产一个, 然后骑完之后,直接把车销毁了.

  • 连接池方式 摩拜单车:
    骑之前, 有一个公司生产了很多的自行车, 下了地铁需要骑车, 直接扫码使用就好了, 然后骑完之后, 还回去

连接池_DbUtils - 图1

2.3连接池原理【重点】

连接池_DbUtils - 图2

  1. 程序一开始就创建一定数量的连接,放在一个容器(集合)中,这个容器称为连接池。
  2. 使用的时候直接从连接池中取一个已经创建好的连接对象, 使用完成之后 归还到池子
  3. 如果池子里面的连接使用完了, 还有程序需要使用连接, 先等待一段时间(eg: 3s), 如果在这段时间之内有连接归还, 就拿去使用; 如果还没有连接归还, 新创建一个, 但是新创建的这一个不会归还了
  4. 集合选择LinkedList

    • 增删比较快
    • LinkedList里面的removeFirst()和addLast()方法和连接池的原理吻合

3.小结

  1. 使用连接池的目的: 可以让连接得到复用, 避免浪费

自定义连接池-初级版本

1.目标

  1. 根据连接池的原理, 使用LinkedList自定义连接池

2.分析

  1. 创建一个类MyDataSource, 定义一个集合LinkedList
  2. 程序初始化的时候, 创建5个连接 存到LinkedList
  3. 定义getConnection() 从LinkedList取出Connection返回
  4. 定义addBack()方法归还Connection到LinkedList

3.实现

  1. /**
  2. * 包名:com.itheima.customer.datasource
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 09:37
  6. * 自定义连接池的第一个版本
  7. * 1. 创建一个容器,存放连接
  8. * 2. 默认往容器中存放5个连接
  9. * 在构造函数中编写代码
  10. * 3. 提供一个方法,让调用者获取连接
  11. * 4. 提供一个方法,让调用者归还连接
  12. *
  13. * 当前第一个版本存在的问题:
  14. * 1. 新创建的连接(原本没有在连接池中的连接)也会归还回连接池
  15. * 2. 连接池使用的耦合性太高了,不便于以后项目切换连接池
  16. */
  17. public class MyDataSource {
  18. private LinkedList<Connection> connectionPool = new LinkedList<>();
  19. public MyDataSource() {
  20. //初始化往connectionPool中存放5个连接
  21. for (int i = 0; i < 5; i++) {
  22. try {
  23. //创建一个连接
  24. Connection conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
  25. //将连接添加到connectionPool中
  26. connectionPool.add(conn);
  27. } catch (SQLException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. /**
  33. * 获取连接的方法
  34. * @return
  35. */
  36. public Connection getConn() throws SQLException {
  37. //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
  38. Connection conn = null;
  39. if (connectionPool.size() > 0){
  40. //从容器中获取连接
  41. conn = connectionPool.removeFirst();
  42. }else {
  43. //则新创建连接
  44. conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
  45. }
  46. return conn;
  47. }
  48. public void addBack(Connection connection){
  49. //归还连接:其实就是将要归还的那个连接,添加到容器的尾部
  50. connectionPool.addLast(connection);
  51. }
  52. }

4.小结

  1. 创建一个类MyDataSource, 定义一个集合LinkedList
  2. 程序初始化(静态代码块)里面 创建5个连接存到LinkedList
  3. 定义提供Connection的方法
  4. 定义归还Connection的方法

自定义连接池-进阶版本

1.目标

  1. 实现datasource完成自定义连接池

2.分析

  1. 在初级版本版本中, 我们定义的方法是getConnection(). 因为是自定义的.如果改用李四的自定义的连接池,李四定义的方法是getAbc(), 那么我们的源码就需要修改, 这样不方便维护. 所以sun公司定义了一个接口datasource,让自定义连接池有了规范

3.讲解

3.1datasource接口概述

  1. Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商(用户)需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!

连接池_DbUtils - 图3

3.2代码实现

  1. /**
  2. * 包名:com.itheima.customer.datasource
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 10:01
  6. * 自定义连接池的第二版:
  7. * 编写连接池,并且实现官方的DataSource接口
  8. *
  9. * 存在的问题:DataSource中并没有提供归还连接的方法
  10. */
  11. public class MyDataSource2 implements DataSource{
  12. private LinkedList<Connection> connectionPool = new LinkedList<>();
  13. public MyDataSource2() {
  14. //初始化往connectionPool中存放5个连接
  15. for (int i = 0; i < 5; i++) {
  16. try {
  17. //创建一个连接
  18. Connection conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
  19. //将连接添加到connectionPool中
  20. connectionPool.add(conn);
  21. } catch (SQLException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }
  26. @Override
  27. public Connection getConnection() throws SQLException {
  28. //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
  29. Connection conn = null;
  30. if (connectionPool.size() > 0){
  31. //从容器中获取连接
  32. conn = connectionPool.removeFirst();
  33. }else {
  34. //则新创建连接
  35. conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
  36. }
  37. return conn;
  38. }
  39. @Override
  40. public Connection getConnection(String username, String password) throws SQLException {
  41. return null;
  42. }
  43. @Override
  44. public <T> T unwrap(Class<T> iface) throws SQLException {
  45. return null;
  46. }
  47. @Override
  48. public boolean isWrapperFor(Class<?> iface) throws SQLException {
  49. return false;
  50. }
  51. @Override
  52. public PrintWriter getLogWriter() throws SQLException {
  53. return null;
  54. }
  55. @Override
  56. public void setLogWriter(PrintWriter out) throws SQLException {
  57. }
  58. @Override
  59. public void setLoginTimeout(int seconds) throws SQLException {
  60. }
  61. @Override
  62. public int getLoginTimeout() throws SQLException {
  63. return 0;
  64. }
  65. @Override
  66. public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  67. return null;
  68. }
  69. }

4.小结

4.1编写连接池遇到的问题

  • 实现DataSource接口后,addBack()不能调用了.
  • 能不能不引入新的api,直接调用之前的connection.close(),但是这个close不是关闭,是归还

4.2解决办法

  • 继承

    • 条件:可以控制父类, 最起码知道父类的名字
  • 装饰者模式

    • 作用:改写已存在的类的某个方法或某些方法
    • 条件:

      • 增强类和被增强类实现的是同一个接口
      • 增强类里面要拿到被增强类的引用
  • 动态代理

自定义连接池-终极版本

1.目标

使用装饰者模式改写connection的close()方法, 让connection归还

2.讲解

2.1自定义连接池终极版本

2.1.1分析
  1. 增强connectionclose()方法, 其它的方法逻辑不改

连接池_DbUtils - 图4

  1. 创建WrapperConnection实现Connection
  2. 在WrapperConnection里面需要得到被增强的connection对象(通过构造方法传进去)
  3. 改写close()的逻辑, 变成归还
  4. 其它方法的逻辑, 还是调用被增强connection对象之前的逻辑

2.2.2实现
  • WrapperConnection
  1. /**
  2. * 包名:com.itheima.customer.connection
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 10:16
  6. * 依赖倒置原则:
  7. * 尽量依赖抽象,不依赖具体
  8. */
  9. public class WrapperConnection implements Connection{
  10. private Connection connection;
  11. private LinkedList<Connection> connectionPool;
  12. public WrapperConnection(Connection connection,LinkedList<Connection> connectionPool) {
  13. this.connection = connection;
  14. this.connectionPool = connectionPool;
  15. }
  16. @Override
  17. public void close() throws SQLException {
  18. //将当前这个连接归还回原来那个连接池容器
  19. connectionPool.addLast(this);
  20. }
  21. @Override
  22. public Statement createStatement() throws SQLException {
  23. return connection.createStatement();
  24. }
  25. @Override
  26. public PreparedStatement prepareStatement(String sql) throws SQLException {
  27. return connection.prepareStatement(sql);
  28. }
  29. @Override
  30. public CallableStatement prepareCall(String sql) throws SQLException {
  31. return null;
  32. }
  33. @Override
  34. public String nativeSQL(String sql) throws SQLException {
  35. return null;
  36. }
  37. @Override
  38. public void setAutoCommit(boolean autoCommit) throws SQLException {
  39. connection.setAutoCommit(autoCommit);
  40. }
  41. @Override
  42. public boolean getAutoCommit() throws SQLException {
  43. return false;
  44. }
  45. @Override
  46. public void commit() throws SQLException {
  47. connection.commit();
  48. }
  49. @Override
  50. public void rollback() throws SQLException {
  51. connection.rollback();
  52. }
  53. //....其它方法省略
  54. }
  • MyDataSource03
  1. /**
  2. * 包名:com.itheima.customer.datasource
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 10:01
  6. * 自定义连接池的第三版:
  7. * 1. 可以归还连接
  8. * 2. 如果是原本在连接池中的连接,就归还;如果是新创建的连接用完后就销毁
  9. *
  10. * 如果是原本在连接池中的连接,调用close()方法不销毁,而是将其归还回连接池
  11. * 如果是新创建的连接,调用close()方法,就销毁(执行原本的close)
  12. *
  13. * 要在不修改类的源码的基础之上,改变类的方法
  14. * 1. 继承(在这里没法用)
  15. * 2. 动态代理(代理模式)
  16. * 3. 装饰者模式
  17. * 1. 装饰者和被装饰者要实现相同的接口
  18. * 2. 将被装饰者的对象传入装饰者中
  19. * 3. 不需要修改的方法,直接调用被装饰者的方法;需要修改的方法,由装饰者重写
  20. */
  21. public class MyDataSource3 implements DataSource{
  22. private LinkedList<Connection> connectionPool = new LinkedList<>();
  23. public MyDataSource3() {
  24. //初始化往connectionPool中存放5个连接
  25. for (int i = 0; i < 5; i++) {
  26. try {
  27. //创建一个装饰后的连接
  28. Connection conn = new WrapperConnection(DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8","root","123"),connectionPool);
  29. //将连接添加到connectionPool中
  30. connectionPool.add(conn);
  31. } catch (SQLException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }
  36. @Override
  37. public Connection getConnection() throws SQLException {
  38. //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
  39. Connection conn = null;
  40. if (connectionPool.size() > 0){
  41. //从容器中获取连接
  42. conn = connectionPool.removeFirst();
  43. }else {
  44. //则新创建连接
  45. conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
  46. }
  47. return conn;
  48. }
  49. @Override
  50. public Connection getConnection(String username, String password) throws SQLException {
  51. return null;
  52. }
  53. @Override
  54. public <T> T unwrap(Class<T> iface) throws SQLException {
  55. return null;
  56. }
  57. @Override
  58. public boolean isWrapperFor(Class<?> iface) throws SQLException {
  59. return false;
  60. }
  61. @Override
  62. public PrintWriter getLogWriter() throws SQLException {
  63. return null;
  64. }
  65. @Override
  66. public void setLogWriter(PrintWriter out) throws SQLException {
  67. }
  68. @Override
  69. public void setLoginTimeout(int seconds) throws SQLException {
  70. }
  71. @Override
  72. public int getLoginTimeout() throws SQLException {
  73. return 0;
  74. }
  75. @Override
  76. public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  77. return null;
  78. }
  79. }

3.小结

  1. 创建一个MyConnection实现Connection
  2. 在MyConnection得到被增强的connection对象
  3. 改写MyConnection里面的close()方法的逻辑为归还
  4. MyConnection里面的其它方法 调用被增强的connection对象之前的逻辑
  5. 在MyDataSource03的getConnection()方法里面 返回了myConnection

自定义连接池扩展版本-使用动态代理

使用动态代理创建Connection对象的代理对象,增强Connection的close方法

  1. /**
  2. * 包名:com.itheima.customer.datasource
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 10:01
  6. * 自定义连接池的第四版:
  7. * 1. 可以归还连接
  8. * 2. 如果是原本在连接池中的连接,就归还;如果是新创建的连接用完后就销毁
  9. *
  10. * 使用动态代理技术,增强connection的close方法
  11. */
  12. public class MyDataSource4 implements DataSource{
  13. private LinkedList<Connection> connectionPool = new LinkedList<>();
  14. public MyDataSource4() {
  15. //初始化往connectionPool中存放5个连接
  16. for (int i = 0; i < 5; i++) {
  17. try {
  18. Connection connection = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
  19. ClassLoader classLoader = connection.getClass().getClassLoader();
  20. //创建动态代理对象
  21. Connection connectionProxy = (Connection) Proxy.newProxyInstance(classLoader, new Class[]{Connection.class}, new InvocationHandler() {
  22. @Override
  23. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  24. //判断执行方法是否是close方法,如果是,则增强,如果不是则执行被代理者原本的方法
  25. if (method.getName().equals("close")) {
  26. //增强close
  27. //将当前这个连接对象,添加到容器中
  28. connectionPool.addLast((Connection) proxy);
  29. return null;
  30. }
  31. //不需要增强的方法,就执行原本的方法
  32. return method.invoke(connection,args);
  33. }
  34. });
  35. //将连接添加到connectionPool中
  36. connectionPool.add(connectionProxy);
  37. } catch (Exception e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. }
  42. @Override
  43. public Connection getConnection() throws SQLException {
  44. //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
  45. Connection conn = null;
  46. if (connectionPool.size() > 0){
  47. //从容器中获取连接
  48. conn = connectionPool.removeFirst();
  49. }else {
  50. //则新创建连接
  51. conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
  52. }
  53. return conn;
  54. }
  55. @Override
  56. public Connection getConnection(String username, String password) throws SQLException {
  57. return null;
  58. }
  59. @Override
  60. public <T> T unwrap(Class<T> iface) throws SQLException {
  61. return null;
  62. }
  63. @Override
  64. public boolean isWrapperFor(Class<?> iface) throws SQLException {
  65. return false;
  66. }
  67. @Override
  68. public PrintWriter getLogWriter() throws SQLException {
  69. return null;
  70. }
  71. @Override
  72. public void setLogWriter(PrintWriter out) throws SQLException {
  73. }
  74. @Override
  75. public void setLoginTimeout(int seconds) throws SQLException {
  76. }
  77. @Override
  78. public int getLoginTimeout() throws SQLException {
  79. return 0;
  80. }
  81. @Override
  82. public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  83. return null;
  84. }
  85. }

第二章-第三方连接池

知识点-常用连接池

1.目标

  • 常用连接池

2.分析

  1. 通过前面的学习,我们已经能够使用所学的基础知识构建自定义的连接池了。其目的是锻炼大家的基本功,帮助大家更好的理解连接池的原理, 但现实是残酷的,我们所定义的 连接池 和第三方的连接池相比,还是显得渺小. 工作里面都会用第三方连接池.

3.讲解

常见的第三方连接池如下:

  • C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。C3P0是异步操作的,所以一些操作时间过长的JDBC通过其它的辅助线程完成。目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能
  • 阿里巴巴-德鲁伊druid连接池:Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求。
  • DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。dbcp没有自动回收空闲连接的功能。

4.小结

我们工作里面用的比较多的是:

  • C3P0
  • druid
  • 光连接池

知识点-C3P0

1.目标

  • 掌握C3P0的使用

2.路径

  1. c3p0介绍
  2. c3p0的使用(硬编码)
  3. c3p0的使用(配置文件)
  4. 编写C3P0Util工具类

3.讲解

3.1 c3p0介绍

连接池_DbUtils - 图5

  • C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件c3p0-config.xml.
  • 使用C3P0需要添加c3p0-0.9.1.2.jar

3.2c3p0的使用

3.2.1通过硬编码来编写【了解】

步骤

  1. 拷贝jar
  2. 创建C3P0连接池对象
  3. 从C3P0连接池对象里面获得connection

实现:

  1. ComboPooledDataSource cpds = new ComboPooledDataSource();
  2. cpds.setDriverClass("com.mysql.jdbc.Driver");
  3. cpds.setJdbcUrl("jdbc:mysql://localhost:3306/web10");
  4. cpds.setUser("root");
  5. cpds.setPassword("123456");
  6. Connection connection = cpds.getConnection();

3.2.2 通过配置文件来编写【重点】

步骤:

  1. 拷贝jar
  2. 拷贝配置文件(c3p0-config.xml)到src目录【名字不要改】
  3. 创建C3P0连接池对象【自动的读取】
  4. 从池子里面获得连接

实现:

  • 编写配置文件c3p0-config.xml,放在src目录下(注:文件名一定不要改)
  1. <c3p0-config>
  2. <default-config>
  3. <property name="driverClass">com.mysql.jdbc.Driver</property>
  4. <property name="jdbcUrl">jdbc:mysql://localhost:3306/web11</property>
  5. <property name="user">root</property>
  6. <property name="password">123</property>
  7. <property name="initialPoolSize">5</property>
  8. </default-config>
  9. </c3p0-config>
  • 编写Java代码 (会自动读取resources目录下的c3p0-config.xml,所以不需要我们解析配置文件)
  1. DataSource ds = new ComboPooledDataSource();

3.3使用c3p0改写工具类【重点】

编写C3P0Util工具类,提供DataSource对象,保证整个项目只有一个DataSource对象

  1. /**
  2. * 包名:com.itheima.utils
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 11:43
  6. * 这个工具类就负责,提供C3P0连接池对象
  7. */
  8. public class C3P0Util {
  9. private static DataSource dataSource;
  10. static {
  11. dataSource = new ComboPooledDataSource();
  12. }
  13. /**
  14. * 获取连接池
  15. * @return
  16. */
  17. public static DataSource getDataSource(){
  18. return dataSource;
  19. }
  20. }

4.小结

  1. C3P0 配置文件方式使用

    • 拷贝jar
    • 拷贝配置文件到resources【配置文件的名字不要改】
    • 创建C3P0连接池对象
  2. C3P0工具类

    • 保证DataSource连接池只有一个【static】

知识点-DRUID

1.目标

  • 掌握DRUID连接池的使用

2.路径

  1. DRUID的介绍
  2. DRUID的使用(硬编码方式)
  3. DRUID的使用(配置文件方式)
  4. DRUID抽取成工具类

3.讲解

3.1DRUID介绍

  1. Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是国内目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过很久生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票

Druid的下载地址:https://github.com/alibaba/druid

DRUID连接池使用的jar包:druid-1.0.9.jar

连接池_DbUtils - 图6

3.2DRUID的使用

3.2.1通过硬编码方式【了解】

步骤:

  1. 导入DRUID jar 包
  2. 创建Druid连接池对象, 配置4个基本参数
  3. 从Druid连接池对象获得Connection

实现:

  1. //1. 创建DataSource
  2. DruidDataSource dataSource = new DruidDataSource();
  3. dataSource.setDriverClassName("com.mysql.jdbc.Driver");//设置驱动
  4. dataSource.setUrl("jdbc:mysql://localhost:3306/web17");//设置数据库路径
  5. dataSource.setUsername("root");//设置用户名
  6. dataSource.setPassword("123");//设置密码
  7. dataSource.setInitialSize(5);//设置初始化连接的数量
  8. //2. 从数据源里面获得Connection
  9. Connection connection = dataSource.getConnection();

3.2.2 通过配置文件方式【重点】

步骤:

  1. 导入DRUID jar 包
  2. 拷贝配置文件到src目录
  3. 根据配置文件创建Druid连接池对象
  4. 从Druid连接池对象获得Connection

实现:

  • 创建druid.properties, 放在src目录下
  1. url=jdbc:mysql:///day20
  2. username=root
  3. password=123
  4. driverClassName=com.mysql.jdbc.Driver
  • 编写Java代码
  1. //0 根据druid.properties创建配置文件对象
  2. Properties properties = new Properties();
  3. // 关联druid.properties文件
  4. InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
  5. properties.load(is);
  6. //1. 创建DataSource
  7. DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
  8. //2. 从数据源(连接池)获得连接对象
  9. Connection connection = dataSource.getConnection();

3.3 Druid工具类

  1. /**
  2. * 包名:com.itheima.utils
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 11:45
  6. */
  7. public class DruidUtil {
  8. private static DataSource dataSource;
  9. static {
  10. try {
  11. //1. 创建Properties对象
  12. Properties properties = new Properties();
  13. //2. 将配置文件转换成字节输入流
  14. InputStream is = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
  15. //3. 使用properties对象加载is
  16. properties.load(is);
  17. //druid底层是使用的工厂设计模式,去加载配置文件,创建DruidDataSource对象
  18. dataSource = DruidDataSourceFactory.createDataSource(properties);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. public static DataSource getDataSource(){
  24. return dataSource;
  25. }
  26. }

4.小结

  1. Druid配置文件使用

    • 拷贝jar
    • 拷贝配置文件到src
    • 读取配置文件成properties对象
    • 使用工厂根据properties创建DataSource
    • 从DataSource获得Connection

第三章-DBUtils

知识点-DBUtils的介绍

1.目标

  • 知道什么是DBUtils

2.路径

  1. DBUtils的概述
  2. DBUtils的常用API介绍

3.讲解

3.1 DBUtils的概述

  1. DbUtilsApache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能

3.2 DBUtils的常用API介绍

  1. 创建QueryRunner对象的API
    QueryRunner(DataSource ds) ,提供数据源(连接池),DBUtils底层自动维护连接connection

  2. 执行增删改的SQL语句的API
    int update(String sql, Object… params),执行增删改的SQL语句, params参数就是可变参数,参数个数取决于语句中问号的个数

  3. 执行查询的SQL语句的API
    query(String sql, ResultSetHandler rsh, Object… params),其中ResultSetHandler是一个接口,表示结果集处理者

4.小结

  1. DBUtils: Apache开发的一个数据库工具包, 用来简化JDBC操作数据库的步骤

知识点-JavaBean(POJO)

1.目标

  • 理解JavaBean的字段和属性

2.讲解

  1. JavaBean说白了就是一个类, 用来封装数据用的

  2. JavaBean要求

    • 私有字段
    • 提供公共的get/set方法
    • 无参构造
    • 实现Serializable
    1. 字段和属性
    • 字段: 全局/成员变量 eg: private String username
    • 属性: 去掉get或者set首字母变小写 eg: setUsername-去掉set->Username-首字母变小写->username

      一般情况下,我们通过IDEA直接生成的set/get 习惯把字段和属性搞成一样而言

3.小结

  1. JavaBean用来封装数据用的
  2. 字段和属性

    • 字段: 全局/成员变量 eg: private String username
    • 属性: 去掉get或者set首字母变小写 eg: setUsername-去掉set->Username-首字母变小写->username

知识点-使用DBUtils完成增删改

1.目标

  • 掌握使用DBUtils完成增删改

2.步骤

  1. 拷贝jar包
  2. 创建QueryRunner()对象,传入dataSource
  3. 调用update()方法

3.实现

  1. /**
  2. * 包名:com.itheima
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 11:50
  6. * DBUtils是一个JDBC的简单的框架(它存在的价值,仅仅是简化这一阶段的JDBC的代码)
  7. * 1. 引入jar包:dbutils的jar、mysql驱动的jar
  8. * 2. 创建QueryRunner对象,并且往它的构造函数中传入一个DataSource对象
  9. * 3. 调用queryRunner对象的方法执行SQL语句
  10. * 1. update(sql)执行增删改的SQL语句
  11. * 2. query(sql)执行查询的SQL语句
  12. */
  13. public class TestDBUtils {
  14. @Test
  15. public void test01() throws SQLException {
  16. //目标:执行添加数据的SQL语句
  17. String sql = "insert into user values (null,?,?,?)";
  18. //1. 创建QueryRunner对象
  19. QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
  20. //2. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句
  21. queryRunner.update(sql,"aobama","666666","圣枪游侠");
  22. }
  23. @Test
  24. public void test02() throws SQLException {
  25. //目标:执行修改数据的SQL语句
  26. String sql = "update user set username=?,password=?,nickname=? where id=?";
  27. QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
  28. queryRunner.update(sql,"threezhang","654321","小张",1);
  29. }
  30. }

4.小结

  1. 创建QueryRuner()对象, 传入DataSource
  2. 调用update(String sql, Object…params)

知识点-使用DBUtils完成查询

1.目标

  • 掌握使用DBUtils完成查询

2.步骤

  1. 拷贝jar包
  2. 创建QueryRunner()对象 传入DataSource
  3. 调用query(sql, resultSetHandler,params)方法

3.实现

3.1ResultSetHandler结果集处理类介绍

连接池_DbUtils - 图7

3.2代码实现

3.2.1 查询一条数据封装到JavaBean对象中(使用BeanHandler)
  1. @Test
  2. public void test04() throws SQLException {
  3. //1. 查询一条数据,封装到POJO对象中
  4. String sql = "select * from user where id=?";
  5. QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
  6. //执行SQL语句
  7. U user = queryRunner.query(sql, new BeanHandler<>(U.class), 3);
  8. System.out.println(user);
  9. }

3.2.2 查询多条数据封装到List中(使用BeanListHandler)
  1. @Test
  2. public void test05() throws SQLException {
  3. //1. 查询多行数据,封装到List<POJO>对象中
  4. String sql = "select * from user where id>?";
  5. QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
  6. List<U> uList = queryRunner.query(sql, new BeanListHandler<>(U.class), 1);
  7. for (U u : uList) {
  8. System.out.println(u);
  9. }
  10. }

3.2.3 查询一条数据,封装到Map对象中(使用MapHandler)
  1. @Test
  2. public void test06() throws SQLException {
  3. //1. 查询一条数据,封装到Map中: 结果集的字段名就是map的key,结果集的字段值就是map的value
  4. String sql = "select * from user where id=?";
  5. QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
  6. Map<String, Object> map = queryRunner.query(sql, new MapHandler(), 3);
  7. System.out.println(map);
  8. }

3.2.4 查询多条数据,封装到List<Map>对象中(使用MapListHandler)
  1. @Test
  2. public void test07() throws SQLException {
  3. //1. 查询多条数据,封装到List<Map>中
  4. String sql = "select * from user where id>?";
  5. QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
  6. List<Map<String, Object>> mapList = queryRunner.query(sql, new MapListHandler(), 1);
  7. for (Map<String, Object> map : mapList) {
  8. System.out.println(map);
  9. }
  10. }

3.2.5 查询单个数据(使用ScalarHandler())
  1. @Test
  2. public void test03() throws SQLException {
  3. //1. 查询单个数据:比如查询用户的总数
  4. String sql = "select count(*) from user";
  5. QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
  6. //执行查询,调用query()方法
  7. long count = (long) queryRunner.query(sql,new ScalarHandler());
  8. System.out.println(count);
  9. }

4. 小结

  1. 步骤

    • 创建QueryRunner() 对象传入DataSource
    • 调用query(sql,ResultSetHandler, params…)
  2. ResultSetHandler

    • BeanHandler() 查询一条记录封装到JavaBean对象
    • BeanListHandler() 查询多条记录封装到List list
    • MapHandler() 查询一条记录封装到Map对象
    • MapListHandler() 查询多条记录封装到List list
    • ScalarHandler() 封装单个记录的 eg:统计数量
  3. 注意实现
    封装到JavaBean条件, 查询出来的数据的列名必须和JavaBean属性一致

第四章-自定义DBUtils

知识点-元数据

1.目标

  • 能够说出什么是数据库元数据

2.分析

  1. 我们要自定义DBUtils, 就需要知道列名, 参数个数等, 这些可以通过数据库的元数据库进行获得.元数据在建立框架和架构方面是特别重要的知识,我们可以使用数据库的元数据来创建自定义JDBC工具包, 模仿DBUtils.

3.讲解

3.1什么是元数据

  1. 元数据(MetaData),即定义数据的数据。打个比方,就好像我们要想搜索一首歌(歌本身是数据),而我们可以通过歌名,作者,专辑等信息来搜索,那么这些歌名,作者,专辑等等就是这首歌的元数据。因此数据库的元数据就是一些注明数据库信息的数据。

歌曲:凉凉

作词:刘畅

演唱: 杨宗纬 张碧晨

时长: 3分28秒

  1. 简单来说: 数据库的元数据就是 数据库、表、列的定义信息。
  2. PreparedStatement对象的getParameterMetaData ()方法获取的是ParameterMetaData对象。

② 由ResultSet对象的getMetaData()方法获取的是ResultSetMetaData对象。

3.2ParameterMetaData

3.2.1概述
  1. ParameterMetaData是由preparedStatement对象通过getParameterMetaData方法获取而来,`ParameterMetaData`可用于获取有关`PreparedStatement`对象和`其预编译sql语句` 中的一些信息. eg:参数个数,获取指定位置占位符的SQL类型
  2. ![](https://gitee.com/tgl_bug/typora-table/raw/master/img/20210121131838.png#alt=img)
  3. 获得ParameterMetaData:
  1. `ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData ()`

3.2.2ParameterMetaData相关的API
  • int getParameterCount(); 获得参数个数
  • int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)

3.2.3实例代码
  1. //3. 获得参数的元数据
  2. ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
  3. // 获得sql语句里面参数的个数
  4. int parameterCount = parameterMetaData.getParameterCount();
  5. System.out.println("parameterCount="+parameterCount);

3.3ResultSetMetaData

3.3.1概述
  1. ResultSetMetaData是由ResultSet对象通过getMetaData方法获取而来,`ResultSetMetaData`可用于获取有关`ResultSet`对象中列的类型和属性的信息。

连接池_DbUtils - 图8

  1. 获得ResultSetMetaData:
  1. `ResultSetMetaData resultSetMetaData = resultSet.getMetaData()`

3.3.2resultSetMetaData 相关的API
  • getColumnCount(); 获取结果集中列项目的个数
  • getColumnName(int column); 获得数据指定列的列名
  • getColumnTypeName();获取指定列的SQL类型
  • getColumnClassName();获取指定列SQL类型对应于Java的类型

3.2.3实例代码
  1. @Test
  2. public void test03() throws SQLException {
  3. DataSource dataSource = DruidUtil.getDataSource();
  4. Connection connection = dataSource.getConnection();
  5. String sql = "select * from user";
  6. PreparedStatement pstm = connection.prepareStatement(sql);
  7. //获取结果集元数据
  8. ResultSetMetaData resultSetMetaData = pstm.getMetaData();
  9. int columnCount = resultSetMetaData.getColumnCount();
  10. System.out.println(columnCount);
  11. for (int i = 1; i <= columnCount; i++) {
  12. //获取每列的列名
  13. String columnName = resultSetMetaData.getColumnName(i);
  14. System.out.println(columnName);
  15. }
  16. }

4.小结

  1. 元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型…
  2. mysql元数据:

    • ParameterMetaData
    • ResultSetMetaData

案例-自定义DBUtils增删改

1.需求

  • 模仿DBUtils, 完成增删改的功能

2.分析

  1. 创建MyQueryRunner类, 定义dataSource, 提供有参构造方法
  2. 定义int update(String sql, Object…params)方法
  1. //1.从dataSource里面获得connection
  2. //2.根据sql语句创建预编译sql语句对象
  3. //3.获得参数元数据对象, 获得参数的个数
  4. //4.遍历, 从params取出值, 依次给参数? 赋值
  5. //5.执行
  6. //6.释放资源

3.实现

  1. public class MyQueryRunner {
  2. private DataSource dataSource;
  3. public MyQueryRunner(DataSource dataSource) {
  4. this.dataSource = dataSource;
  5. }
  6. /**
  7. * 执行增删改的SQL语句的方法
  8. * @param sql
  9. * @param params
  10. * @return
  11. * @throws SQLException
  12. */
  13. public int update(String sql,Object... params) throws SQLException {
  14. //1. 获取连接对象
  15. Connection connection = dataSource.getConnection();
  16. //2. 预编译SQL语句
  17. PreparedStatement pstm = connection.prepareStatement(sql);
  18. //3. 设置参数
  19. //3.1 获取参数的个数
  20. ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
  21. int parameterCount = parameterMetaData.getParameterCount();
  22. for (int i = 1; i <= parameterCount; i++) {
  23. pstm.setObject(i,params[i-1]);
  24. }
  25. //4. 执行SQL语句
  26. int num = pstm.executeUpdate();
  27. //5. 关闭资源
  28. pstm.close();
  29. connection.close();
  30. return num;
  31. }
  32. }

4.小结

  1. 先模拟DBUtils写出架子
  2. update()

    • 封装了PrepareStatement
    • 用到了参数元数据

案例-自定义DBUtils查询多条数据封装到List

  1. /**
  2. * 包名:com.itheima.framework
  3. *
  4. * @author Leevi
  5. * 日期2020-07-06 15:03
  6. */
  7. public class MyQueryRunner {
  8. private DataSource dataSource;
  9. public MyQueryRunner(DataSource dataSource) {
  10. this.dataSource = dataSource;
  11. }
  12. /**
  13. * 执行增删改的SQL语句的方法
  14. * @param sql
  15. * @param params
  16. * @return
  17. * @throws SQLException
  18. */
  19. public int update(String sql,Object... params) throws SQLException {
  20. //1. 获取连接对象
  21. Connection connection = dataSource.getConnection();
  22. //2. 预编译SQL语句
  23. PreparedStatement pstm = connection.prepareStatement(sql);
  24. //3. 设置参数
  25. //3.1 获取参数的个数
  26. ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
  27. int parameterCount = parameterMetaData.getParameterCount();
  28. for (int i = 1; i <= parameterCount; i++) {
  29. pstm.setObject(i,params[i-1]);
  30. }
  31. //4. 执行SQL语句
  32. int num = pstm.executeUpdate();
  33. //5. 关闭资源
  34. pstm.close();
  35. connection.close();
  36. return num;
  37. }
  38. /**
  39. * 查询到多条数据封装到List<T>
  40. * @param sql
  41. * @param clazz
  42. * @param params
  43. * @param <T>
  44. * @return
  45. * @throws Exception
  46. */
  47. public <T> List<T> queryList(String sql, Class<T> clazz, Object... params) throws Exception {
  48. //1. 获取连接对象
  49. Connection connection = dataSource.getConnection();
  50. //2. 预编译SQL语句
  51. PreparedStatement pstm = connection.prepareStatement(sql);
  52. //3. 设置参数
  53. //3.1 获取参数的个数
  54. ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
  55. int parameterCount = parameterMetaData.getParameterCount();
  56. for (int i = 1; i <= parameterCount; i++) {
  57. pstm.setObject(i,params[i-1]);
  58. }
  59. //4. 执行SQL语句
  60. ResultSet rst = pstm.executeQuery();
  61. //获取结果集元数据
  62. ResultSetMetaData rstMetaData = rst.getMetaData();
  63. //获取结果集的列数
  64. int columnCount = rstMetaData.getColumnCount();
  65. //1. 获取POJO中的所有方法
  66. Method[] methods = clazz.getMethods();
  67. //声明一个集合存储t
  68. List<T> tList = new ArrayList<>();
  69. while (rst.next()) {
  70. //创建出封装数据的POJO对象
  71. T t = clazz.newInstance();
  72. for (int i = 1; i <= columnCount; i++) {
  73. //获取每列的名字(字段名)
  74. String columnName = rstMetaData.getColumnName(i);
  75. //1. 获取每一行数据的各个字段的数据
  76. Object columnValue = rst.getObject(columnName);
  77. //2. 调用对象的对应的set方法,往对象中设置数据
  78. //遍历出POJO的每一个方法
  79. for (Method method : methods) {
  80. //获取方法名
  81. String methodName = method.getName();
  82. //匹配方法名
  83. if (methodName.equalsIgnoreCase("set"+columnName)) {
  84. //说明匹配到了对应的set方法,那么就调用这个set方法,设置columnValue
  85. method.invoke(t,columnValue);
  86. }
  87. }
  88. }
  89. tList.add(t);
  90. }
  91. //关闭资源
  92. rst.close();
  93. pstm.close();
  94. connection.close();
  95. return tList;
  96. }
  97. }