尚硅谷JDBC学习笔记

尚硅谷JDBC学习笔记

写在前面

学习链接:Java JDBC 视频教程全集

特别注意:该内容全是我自学时,一边听课一边做的笔记,本文的初衷是希望每一个学习时有疑问的同学能够在这篇文章里找到答案;里面关于吐槽的内容仅代表个人看法,白嫖党不会针对任何老师或书籍发表不合适的看法,如有冒犯,请见谅。所有传播知识的行为都应该受到尊重,该内容涉及的一切只为学习交流使用,希望每个求知进来的读者都可以满载而归。有任何问题或侵权行为,请联系本人李英俊小朋友,我会回应并删除;另外,做笔记真的不容易,希望大家转载时标明作者,若非个人使用的转载,请联系本人,否则必究。

学习体会:

  • 先说优点:
  1. 细致细节细心,总之超级无敌细,对有耐心的初学者很友好。
  2. 在讲解各种底层的同时,还会介绍常用的工具。这个超级无敌棒!不是一味地为了学习而去学习,在实战上面并不会重复造轮子,所以这点也很重要。
  • 再说缺点:
  1. 我觉得废话有点多,同一个知识点翻来覆去的讲,并且有的疑点还没解决。比如说:比如说:利用Driver和DriverManager都能用不同的数据库,为什么DriverManager更好。视频中说的两个优点我觉得Driver也能做到啊,没搞懂为什么。
  2. 由于本人已经学过了高琪老师的java300集,其中包括了JDBC的内容,因此仅以此视频作为查漏补缺。
  3. 视频中涉及到的练习,进行了简化学习。
  4. 学到第8集的时候发现,很多知识点都是基于作业进行的,而作业我并没有做,只是简单的跟着老师敲代码,老师怎么敲我就怎么敲。因此当老师在用Student举例的时候,我用的还是高琪老师java300集中的t_user跟着学,并且很多知识点结构较差,不知道怎么就突然学到这点了,觉得跨越的幅度很大。因为我在java300集中学过,所以还可以接受,但是看到弹幕里面说整个人都蒙了,我表示理解。
  5. 在利用反射进行查询的时候,涉及到数据库的列名跟类的属性不一致的问题。
    • 高琪老师的java300集中:类是根据数据库创建,并自动生成的。
    • 该视频中:类是自己建的,数据库也是自己的,名字不一致就给数据库的的列名起别名解决
    • 我觉得该视频的方式不好,既不如上面简单,又不如上面方便。
    • 另外,该视频中在用map内容创建反射对象的时候,直接调用了以前写过的ReflectionUtils工具,我知道是什么意思和原理(根据列名和属性名对应,获取属性名对应的set方法,利用反射调用set方法将map中对应属性的值进行赋值,这样就将map的内容赋给了object对象,即clazz.newInstance(),最后得到了目标对象),但是没学过的人就会一脸懵逼。(看到最后几集的时候,我又回来吐槽一波这个ReflectionUtils工具,又去网上查了查getSuperClassGenricType这个函数,补充了一下才能跑)
  6. 由于本视频讲解的结构很乱(不是指大纲乱,而是老师在讲的时候,东一下,西一下,一会这个文件一会儿那个文件,还常用复制粘贴,特别是多个知识点在一个类中通过各种函数、测试函数讲解,这点我真的吐了),因此我的笔记相对于较乱,请读者轻喷。
  7. 讲的太啰嗦了,一个知识点在讲的时候翻来覆去的讲,注释写了一遍又一遍,并且最后还会回过头来小结。(细致和啰嗦的度不好把握,看个人吧,勿喷,比如PreparedStatement就是一遍又一遍的讲)
  8. 老师还是讲的很细致的,比如讲ResultSetMetaData的时候,就会讲为什么要用,这个是什么,怎么用。我觉得这一点还是很不错的,比高琪老师要好,但是讲一遍就行了,我印象里是讲了至少3遍。
  9. 学到12集重构DAO的时候,就觉得老师的讲课逻辑真的不行,前面写了最后重构竟然是在原来的基础上改的,这个行为真的不好;其次就是感觉想到哪儿写到哪儿,指哪儿改哪儿,查询多条记录写完了才想起来查询一条记录可以直接简化,好像没有备课的感觉。

1. 通过Driver接口获取数据库连接

  • 数据持久化

  • 数据库存取技术分类

    • JDBC直接访问数据库
    • JDO技术
    • 第三方O/R工具,如Hibernate,ibatis等
  • JDBC是java访问数据库的基石

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口。

    img

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

    • 面向应用的API:java api,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
    • 面向数据库的API:java driver api,供开发商开发数据库驱动程序用。
  • JDBC试验,Driver是一个接口:数据库厂商必须提供实现的接口,能从其中获取数据库连接。

  • 可以通过Driver的实现类对象获取数据库连接

    • 加入mysql驱动
      • 解压mysql-connector-java.zip
      • 在挡墙项目下新建lib目录
      • 吧.jar文件复制到lib目录下
      • 右键buildpath,add to buildpath加入到类路径下
    • JDBC的URL的标准由三部分组成,各部分间用冒号分隔。
      • jdbc:<子协议>:<子名称>
      • 协议:JDBC URL中的协议总是jdbc
      • 子协议:子协议用于标识一个数据库驱动程序
      • 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。
    • 遇到的问题:
      • 进不去,各种报错:可能是密码错误了。参考
      • time zone错误:参考
    package com.litian.jdbc;
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.SQLException;
    import java.util.Properties;
    
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: JDBCTest.java
     * @time: 2019/12/15 18:56
     * @desc: JDBC试验,Driver是一个接口:数据库厂商必须提供实现的接口,能从其中获取数据库连接。
     */
    
    public class JDBCTest {
        public static void main(String[] args) throws SQLException {
            // 1. 创建一个Driver实现类的对象
            Driver driver = new com.mysql.jdbc.Driver();
            // 2. 准备连接数据库的基本信息:url,user,password
            String url = "jdbc:mysql://localhost:3306/girls";
            Properties info = new Properties();
            info.put("user", "root");
            info.put("password", "tian19951103");
    
            // 3. 调用Driver接口的connect(url, info)获取数据库连接
            Connection connection = driver.connect(url, info);
            System.out.println(connection);
        }
    }
    
  • 编写一个通用的方法,在不修改源程序的情况下,可以获取任何数据库的连接

  • 解决方案:把数据库驱动Driver 实现类的全类名、url、user、password放入一个配置文件中,通过修改配置文件的方式实现和具体的数据库解耦。

    • jdbc.properties

      driver=com.mysql.jdbc.Driver
      jdbcUrl=jdbc:mysql://localhost:3306/girls
      user=root
      password=tian19951103
      
    • JDBCTest

      package com.litian.jdbc;
      
      import java.io.InputStream;
      import java.sql.Connection;
      import java.sql.Driver;
      import java.sql.SQLException;
      import java.util.Properties;
      
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: JDBCTest.java
       * @time: 2019/12/15 18:56
       * @desc: JDBC试验,Driver是一个接口:数据库厂商必须提供实现的接口,能从其中获取数据库连接。
       */
      
      public class JDBCTest {
      
          public void test1() throws SQLException {
              // 1. 创建一个Driver实现类的对象
              Driver driver = new com.mysql.jdbc.Driver();
              // 2. 准备连接数据库的基本信息:url,user,password
              String url = "jdbc:mysql://localhost:3306/girls";
              Properties info = new Properties();
              info.put("user", "root");
              info.put("password", "tian19951103");
      
              // 3. 调用Driver接口的connect(url, info)获取数据库连接
              Connection connection = driver.connect(url, info);
              System.out.println(connection);
          }
      
          // 编写一个通用的方法,在不修改源程序的情况下,可以获取任何数据库的连接
          public Connection getConnection() throws Exception {
              String driverClass = null;
              String jdbcUrl = null;
              String user = null;
              String password = null;
      
              // 读取类路径下的jdbc.propertites 文件
              InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
              Properties properties = new Properties();
              properties.load(in);
              driverClass = properties.getProperty("driver");
              jdbcUrl = properties.getProperty("jdbcUrl");
              user = properties.getProperty("user");
              password = properties.getProperty("password");
      
              Driver driver = (Driver) Class.forName(driverClass).newInstance();
      
              Properties info = new Properties();
              info.put("user", user);
              info.put("password", password);
              Connection connection = driver.connect(jdbcUrl, info);
      
              return connection;
          }
      
          public void testGetConnection() throws Exception {
              System.out.println(getConnection());
          }
      
          public static void main(String[] args) throws Exception {
              new JDBCTest().testGetConnection();
          }
      }
      

2. 通过DriverManager获取数据库连接

  • 修改一下配置文件

    driver=com.mysql.cj.jdbc.Driver
    jdbcUrl=jdbc:mysql://localhost:3306/testjdbc?serverTimezone=GMT%2B8
    user=root
    password=123456
    
  • 代码(我觉得废话有点多,同一个知识点翻来覆去的讲,并且有的疑点还没解决)

  • 比如说:利用Driver和DriverManager都能用不同的数据库,为什么DriverManager更好

    package com.litian.jdbc;
    
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.Properties;
    
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: JDBCTest.java
     * @time: 2019/12/15 18:56
     * @desc: JDBC试验,Driver是一个接口:数据库厂商必须提供实现的接口,能从其中获取数据库连接。
     */
    
    public class JDBCTest {
    
        public Connection getConnection2() throws Exception {
            // 1. 准备连接数据库的4个字符串。
            // 1.1 创建Properties对象
            Properties properties = new Properties();
            // 1.2 获取jdbc.properties对应的输入流
            InputStream in = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
            // 1.3 加载1.2对应的输入流
            properties.load(in);
            // 1.4 具体决定user,password等4个字符串。
            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String jdbcUrl = properties.getProperty("jdbcUrl");
            String driver = properties.getProperty("driver");
            // 2. 加载数据库驱动程序
            Class.forName(driver);
            // 3. 通过DriverManager的getConnection()方法获取数据库连接。
            return DriverManager.getConnection(jdbcUrl, user, password);
        }
    
        /**
         * DriverManager是驱动的管理类
         * 1. 可以通过重载的getConnection()方法获取数据库连接。较为方便
         * 2. 可以同时管理多个驱动程序:若注册了多个数据库连接,则调动getConnection()方法时
         *    传入的参数不同,则返回不同的数据库连接
         */
        public void testDriverManager() throws Exception {
            // 1. 准备连接数据库的4个字符串
    
            // 驱动的全类名
            String driverClass = null;
            String jdbcUrl = null;
            String user = null;
            String password = null;
    
            // 读取类路径下的jdbc.propertites 文件
            InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(in);
            driverClass = properties.getProperty("driver");
            jdbcUrl = properties.getProperty("jdbcUrl");
            user = properties.getProperty("user");
            password = properties.getProperty("password");
    
            // 2. 加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块程序)
            // 下面的注册程序已经写好了,不需要自己写
            // DriverManager.registerDriver((Driver) Class.forName(driverClass).newInstance());
            Class.forName(driverClass);
    
            // 3. 通过DriverManager的getConnection()方法获取数据库连接
            Connection connection = DriverManager.getConnection(jdbcUrl, user, password);
            System.out.println(connection);
    
        }
    
        public void test1() throws SQLException {
            // 1. 创建一个Driver实现类的对象
            Driver driver = new com.mysql.jdbc.Driver();
            // 2. 准备连接数据库的基本信息:url,user,password
            String url = "jdbc:mysql://localhost:3306/girls";
            Properties info = new Properties();
            info.put("user", "root");
            info.put("password", "tian19951103");
    
            // 3. 调用Driver接口的connect(url, info)获取数据库连接
            Connection connection = driver.connect(url, info);
            System.out.println(connection);
        }
    
        // 编写一个通用的方法,在不修改源程序的情况下,可以获取任何数据库的连接
        public Connection getConnection() throws Exception {
            String driverClass = null;
            String jdbcUrl = null;
            String user = null;
            String password = null;
    
            // 读取类路径下的jdbc.propertites 文件
            InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(in);
            driverClass = properties.getProperty("driver");
            jdbcUrl = properties.getProperty("jdbcUrl");
            user = properties.getProperty("user");
            password = properties.getProperty("password");
    
            Driver driver = (Driver) Class.forName(driverClass).newInstance();
    
            Properties info = new Properties();
            info.put("user", user);
            info.put("password", password);
            Connection connection = driver.connect(jdbcUrl, info);
    
            return connection;
        }
    
        public void testGetConnection() throws Exception {
            System.out.println(getConnection());
        }
    
        public static void main(String[] args) throws Exception {
            // new JDBCTest().testGetConnection();
            // new JDBCTest().testDriverManager();
            Connection conn = new JDBCTest().getConnection2();
            System.out.println(conn);
        }
    }
    

3. 通过Statement执行更新操作

  • Statement测试

    /**
     * 通过JDBC向指定的数据表中插入一条记录
     * 1. Statement:用于执行sql语句的对象
     * 1.1 通过Connection的createStatement()方法来获取
     * 1.2 通过executeUpdate(sql)可以执行SQL语句
     * 1.3 传入的sql可以是insert, update或delete,但不能是select
     * 2. Connection、Statement都是应用程序和数据库服务器的连接资源。使用后一定要关闭。
     * 2.1 需要再finally中关闭
     * 3. 关闭顺序:先获取的后关,后获取的先关
     */
    public void testStatement() {
        Connection conn = null;
        Statement statement = null;
        try {
            // 1. 获取数据库连接
            conn = getConnection2();
            // 2. 准备插入的SQL语句
            String sql = "insert into t_user (username, pwd) values('测试', 3352)";
            String sql2 = "update t_user set username='傻瓜' where id = 20017";
            // 3. 执行插入
            // 3.1 获取操作sql语句的Statement对象
            statement = conn.createStatement();
            // 3.2 调用Statement对象的executeUpdate(sql)执行SQL语句
            statement.executeUpdate(sql);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (statement != null) {
                    // 4. 关闭Statement对象
                    statement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (statement != null) {
                    // 5. 关闭Connection对象
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
  • insert/update/delete封装

    /**
     * 通用的更新的方法:insert/update/delete
     * 版本1
     */
    public void update(String sql){
        Connection conn = null;
        Statement statement = null;
    
        try {
            conn = getConnection2();
            statement = conn.createStatement();
            statement.executeUpdate(sql);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (statement != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 创建JDBC的工具类,封装方法

    • 工具类

      package com.litian.jdbc;
      
      import java.io.InputStream;
      import java.sql.*;
      import java.util.Properties;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: JDBCUtils.java
       * @time: 2020/3/21 15:23
       * @desc: |操作JDBC的工具类,其中封装了一些工具方法
       * Version1
       */
      
      public class JDBCTools {
      
          /**
           * 关闭Statement和Connection的方法
           */
          public static void release(Statement statement, Connection conn) {
              try {
                  if (statement != null) {
                      statement.close();
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              try {
                  if (statement != null) {
                      conn.close();
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
          public static void release(ResultSet rs, Statement statement, Connection conn) {
              try {
                  if (rs != null) {
                      rs.close();
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              try {
                  if (statement != null) {
                      statement.close();
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              }
              try {
                  if (statement != null) {
                      conn.close();
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 1. 获取连接的方法
           * 通过读取配置文件从数据库服务器获取一个连接。
           *
           * @return
           */
          public static Connection getConnection() throws Exception {
              // 1. 准备连接数据库的4个字符串。
              // 1.1 创建Properties对象
              Properties properties = new Properties();
              // 1.2 获取jdbc.properties对应的输入流
              InputStream in = JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");
              // 1.3 加载1.2对应的输入流
              properties.load(in);
              // 1.4 具体决定user,password等4个字符串。
              String user = properties.getProperty("user");
              String password = properties.getProperty("password");
              String jdbcUrl = properties.getProperty("jdbcUrl");
              String driver = properties.getProperty("driver");
              // 2. 加载数据库驱动程序
              Class.forName(driver);
              // 3. 通过DriverManager的getConnection()方法获取数据库连接。
              return DriverManager.getConnection(jdbcUrl, user, password);
          }
      
      }
      
    • 修改后的insert/update/delete封装

      public void update(String sql) {
          Connection conn = null;
          Statement statement = null;
      
          try {
              conn = getConnection2();
              statement = conn.createStatement();
              statement.executeUpdate(sql);
          } catch (Exception e) {
              e.printStackTrace();
          } finally {
              JDBCTools.release(statement, conn);
          }
      }
      

4. 通过ResultSet执行查询操作

/**
 * ResultSet:结果集。封装了使用JDBC进行查询的结果。
 * 1. 调用Statement对象的executeQuery(sql)方法
 * 2. ResultSet返回的实际上就是一张数据表。有一个指针指向数据表的第一行的前面。
 * 可以调用next()方法检测下一行是否有效。若有效,该方法返回true,且指针下移。
 * 相当于Iterator对象的hasNext()和next()方法的结合体
 * 3. 当指针对应到一行时,可以通过嗲用getXXX(index)或getXXX(columnName)获取
 * 每一列的值。如:getInt(1),getString("name")
 * 4. 关闭ResultSet
 */
public void testResultSet(){
    // 获取各项记录,并打印
    Connection conn = null;
    Statement statement = null;
    ResultSet rs = null;
    try {
        // 1. 获取Connection
        conn = JDBCTools.getConnection();
        // 2. 获取Statement
        statement = conn.createStatement();
        // 3. 准备SQL
        String sql = "select id, username, pwd, regTime, lastLoginTime from t_user";
        // 4. 执行查询,得到ResultSet
        rs = statement.executeQuery(sql);
        // 5. 处理ResultSet
        while(rs.next()){
            int id = rs.getInt(1);
            String username = rs.getString(2);
            String pwd = rs.getString(3);
            Date regTime = rs.getDate(4);
            Timestamp lastLoginTime = rs.getTimestamp(5);
            System.out.println(id + "-->" + username + "-->" + pwd + "-->" + regTime + "-->" + lastLoginTime);
        }
        // 6. 关闭数据库资源
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCTools.release(rs, statement, conn);
    }
}
  • 以面向对象的思想编写JDBC程序
    • 将数据表中的属性封装为一个类,增删改变为从类到数据库,查变为从数据库到类。

5. PreparedStatement

  • 是Statement的子接口,可以传入带占位符的sql语句,并且提供了补充占位符变量的方法。

  • 使用Statement需要进行拼写SQL语句,很辛苦,很容易出错。

  • 引号的问题处理很复杂,不利于维护。

  • 可以有效的禁止sql注入。(通过用户输入非法的sql命令)

  • 代码的可读性和可维护性,最大可能的提高性能(批量插入)

  • 代码测试

    public void testPreparedStatement() {
        Connection connection = null;
        PreparedStatement ps = null;
    
        try {
            connection = JDBCTools.getConnection();
            String sql = "insert into t_user (id, username, pwd, regTime, lastLoginTime) values(?,?,?,?,?)";
            ps = connection.prepareStatement(sql);
            ps.setInt(1, 2);
            ps.setString(2, "狗贼");
            ps.setString(3, "123456");
            ps.setDate(4, new Date(System.currentTimeMillis()));
            ps.setTimestamp(5, new Timestamp(System.currentTimeMillis()));
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.release(ps, connection);
        }
    }
    

6. 利用反射及JDBC元数据编写通用的查询

  1. 先利用SQL进行查询,得到结果集
  2. 利用反射创建实体类的对象:创建Student对象
  3. 获取结果集的列的别名:idCard、studentName
  4. 再获取结果集的每一列的值,结合3得到一个Map键值对。键:列的别名;值:列的值。
  5. 再利用反射为2对应的属性赋值,属性即为Map的键,值即为Map的值。
  • ResultSetMetaData

    • 是什么:是描述ResultSet的元数据对象。即从中可以获取到结果集中有多少列,列名是什么...
    • 如何用:
      • 得到ResultSetMetaData对象:调用ResultSet的getMetaData()方法
      • 常用好用的方法:
        • int getColumnCount():SQL语句中包含了哪些列
        • String getColumnLabel(int column):获取指定的列的别名,从1开始。
  • ResultSetMetaData测试

    public void testResultSetMetaData() {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            String sql = "select id, username, pwd, regTime, lastLoginTime from t_user where id = ?";
            conn = JDBCTools.getConnection();
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 1);
            rs = ps.executeQuery();
    
            // 1. 得到ResultSetMetaData对象
            ResultSetMetaData rsmd = rs.getMetaData();
            // 2. 打印每一列的列名
            for (int i = 0; i < rsmd.getColumnCount(); i++) {
                String columnLabel = rsmd.getColumnLabel(i + 1);
                System.out.println(columnLabel);
            }
    
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.release(rs, ps, conn);
        }
    }
    
  • 取出列的别名和值

    public void testResultSetMetaData() {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            String sql = "select id, username, pwd, regTime, lastLoginTime from t_user where id = ?";
            conn = JDBCTools.getConnection();
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 1);
            rs = ps.executeQuery();
            Map<String, Object> values = new HashMap<>();
    
            // 1. 得到ResultSetMetaData对象
            ResultSetMetaData rsmd = rs.getMetaData();
    
            while(rs.next()){
                // 2. 打印每一列的列名
                for (int i = 0; i < rsmd.getColumnCount(); i++) {
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    Object columnValue = rs.getObject(columnLabel);
                    values.put(columnLabel, columnValue);
                }
            }
            System.out.println(values);
            Class clazz = User.class;
            Object object = clazz.newInstance();
            for (Map.Entry<String, Object> entry: values.entrySet()){
                String fieldName = entry.getKey();
                Object fieldValue = entry.getValue();
                System.out.println(fieldName + ": " + fieldValue);
            }
    
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.release(rs, ps, conn);
        }
    }
    
  • 通用的查询方法代码

    public <T> T get(Class<T> clazz, String sql, Object... args) {
        T entity = null;
    
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCTools.getConnection();
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            Map<String, Object> values = new HashMap<>();
            ResultSetMetaData rsmd = rs.getMetaData();
    
            if (rs.next()) {
                // 利用反射创建对象
                entity = clazz.newInstance();
                // 通过解析sql语句来判断到底选择了哪些列,以及需要为entity对象的哪些属性赋值
                for (int i = 0; i < rsmd.getColumnCount(); i++) {
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    Object columnValue = rs.getObject(columnLabel);
                    values.put(columnLabel, columnValue);
                }
            }
            for (Map.Entry<String, Object> entry : values.entrySet()) {
                String fieldName = entry.getKey();
                Object fieldValue = entry.getValue();
                System.out.println(fieldName + ": " + fieldValue);
            }
    
            // 这里要加入ReflectionUtils方法,将map的内容写入entity中,并返回entity
    
            // 6. 关闭数据库资源
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.release(rs, ps, conn);
        }
        return entity;
    }
    

7. DAO设计模式

  • Data Access Object,数据访问对象

  • what:访问数据信息的类。包含了对数据的CRUD(create、read、update、delete,增删改查)操作,而不包含任何业务相关的信息。

  • why:实现功能的模块化。更有利于代码的维护和升级。DAO可以被子类集成或直接使用。

  • how:使用JDBC编写DAO可能会包含的方法:

    • void update()

      // insert, update, delete 操作都可以包含在其中
      void update(String sql, Object ... args)
      
    • 查询

      // 查询一条记录,返回对应的对象
      <T> T get(Class<T> clazz, String sql, Object ... args)
      // 查询多条记录,返回对应的对象的集合
      <T> List<T> getForList(Class<T> clazz, String sql, Object ... args)
      // 返回某条记录的某一个字段的值或一个统计的值(一共有多少记录等。)
      <E> E getForValue(String sql, Object ... args)
      
  • 代码实现

    • DAO

      package com.litian.jdbc;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.ResultSetMetaData;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DAO.java
       * @time: 2020/3/26 18:37
       * @desc: |
       */
      
      public class DAO {
          // insert, update, delete 操作都可以包含在其中
          void update(String sql, Object... args) {
              Connection conn = null;
              PreparedStatement ps = null;
      
              try {
                  conn = JDBCTools.getConnection();
                  ps = conn.prepareStatement(sql);
      
                  for (int i = 0; i < args.length; i++) {
                      ps.setObject(i + 1, args[i]);
                  }
                  ps.executeUpdate();
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(null, ps, conn);
              }
      
          }
      
          // 查询一条记录,返回对应的对象
          <T> T get(Class<T> clazz, String sql, Object... args) {
              T entity = null;
              Connection conn = null;
              PreparedStatement ps = null;
              ResultSet rs = null;
              try {
                  // 1. 获取Connection
                  conn = JDBCTools.getConnection();
                  // 2. 获取PreparedStatement
                  ps = conn.prepareStatement(sql);
                  // 3. 填充占位符
                  for (int i = 0; i < args.length; i++) {
                      ps.setObject(i + 1, args[i]);
                  }
                  // 4. 进行查询,得到ResultSet
                  rs = ps.executeQuery();
                  // 5. 若ResultSet中有记录,准备一个Map<String, Object>: 键:存放列的别名;值:存放列的值
                  if (rs.next()) {
                      Map<String, Object> values = new HashMap<>();
                      // 6. 得到ResultSetMetaData对象
                      ResultSetMetaData rsmd = rs.getMetaData();
                      // 7. 处理ResultSet,把指针向下移动一个单位
                      // 8. 由ResultSetMetaData对象得到结果集中有多少列
                      int columnCount = rsmd.getColumnCount();
                      // 9. 由ResultSetMetaData得到每一列的别名,由ResultSet得到具体每一列的值
                      for (int i = 0; i < columnCount; i++) {
                          String columnLabel = rsmd.getColumnLabel(i + 1);
                          Object columnValue = rs.getObject(columnLabel);
      
                          // 10. 填充Map对象
                          values.put(columnLabel, columnValue);
                      }
                      // 11. 用反射创建Class对象的对象
                      entity = clazz.newInstance();
                      // 12. 遍历Map对象,用反射填充对象的属性值:属性名为Map中的Key,属性值为Map中的Value
                      for (Map.Entry<String, Object> entry : values.entrySet()) {
                          String propertyName = entry.getKey();
                          Object value = entry.getValue();
                          ReflectionUtils.setFieldValue(entity, propertyName, value);
                      }
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(rs, ps, conn);
              }
              return entity;
          }
      
          // 查询多条记录,返回对应的对象的集合
          <T> List<T> getForList(Class<T> clazz, String sql, Object... args) {
              return null;
          }
      
          // 返回某条记录的某一个字段的值或一个统计的值(一共有多少记录等。)
          <E> E getForValue(String sql, Object... args) {
              return null;
          }
      }
      
    • DAO测试

      package com.litian.jdbc;
      
      import java.sql.Date;
      import java.sql.Timestamp;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DAOTest.java
       * @time: 2020/3/26 18:59
       * @desc: |
       */
      
      public class DAOTest {
          public static void main(String[] args) {
              DAO dao = new DAO();
              // 测试update
              // String sql = "insert into t_user(id, username, pwd, regTime, lastLoginTime) values(?,?,?,?,?)";
              // dao.update(sql, 4, "李英俊", "123456", new Date(System.currentTimeMillis()), new Timestamp(System.currentTimeMillis()));
      
              // 测试get
              String sql = "select id, username, pwd, regTime, lastLoginTime from t_user where id=?";
              User u = dao.get(User.class, sql, 4);
              System.out.println(u);
          }
      }
      
    • 这里在网上找了一份ReflectionUtils

      package com.litian.jdbc;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ReflectionUtils.java
       * @time: 2020/3/26 18:57
       * @desc: |JDBC 查询得到属性字段 反射机制返回到 JavaBean中相同类属性名的对象中
       */
      
      import java.lang.reflect.*;
      
      
      public class ReflectionUtils {
      
          /**
           * 使 filed 变为可访问
           *
           * @param field
           */
          public static void makeAccessible(Field field) {
              if (!Modifier.isPublic(field.getModifiers())) {
                  field.setAccessible(true);
              }
          }
      
          /**
           * 直接设置对象的属性,忽略 private/protected 修饰符, 也不经过 setter
           *
           * @param object
           * @param fieldName 属性名称
           * @param value
           */
          public static void setFieldValue(Object object, String fieldName, Object value) {
      
              Field field = getDeclaredField(object, fieldName);
              if (field == null)
                  throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
      
              // 让 private 元素变得可以访问,field.setAccessible();
              makeAccessible(field);
      
              try {
                  field.set(object, value);
              } catch (IllegalArgumentException | IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
          }
      
          /**
           * 循环向上转型, 获取对象的 DeclaredField
           *
           * @param object
           * @param filedName
           * @return
           */
          public static Field getDeclaredField(Object object, String filedName) {
              // 一步步的循环得到 获取声明对象的祖宗类
              for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
                  try {
                      return superClass.getDeclaredField(filedName);
                  } catch (NoSuchFieldException | SecurityException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
              }
              return null;
          }
      
          /**
           * 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
           *
           * @param object
           * @param fieldName
           * @return
           */
          public static Object getFieldValue(Object object, String fieldName) {
              Field field = getDeclaredField(object, fieldName);
      
              if (field == null)
                  throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
      
              makeAccessible(field);
              Object result = null;
      
              try {
                  result = field.get(object);
              } catch (IllegalArgumentException | IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              return result;
          }
      
          /**
           * 循环向上转型, 获取对象的 DeclaredMethod
           *
           * @param object
           * @param methodName
           * @param parameterTypes: 指定特定成员方法有重载可能性,必须指定特定的类型变量
           * @return
           */
          public static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes) {
              for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
                  try {
                      //superClass.getMethod(methodName, parameterTypes);
                      return superClass.getDeclaredMethod(methodName, parameterTypes);
                  } catch (NoSuchMethodException e) {
                      //Method 不在当前类定义, 继续向上转型
                  }
                  //..
              }
      
              return null;
          }
      
          /**
           * 直接调用对象方法, 而忽略修饰符(private, protected)
           *
           * @param object
           * @param methodName
           * @param parameterTypes
           * @param parameters
           * @return
           * @throws InvocationTargetException
           * @throws IllegalArgumentException
           */
          public static Object invokeMethod(Object object, String methodName, Class<?>[] parameterTypes,
                                            Object[] parameters) throws InvocationTargetException {
              Method method = getDeclaredMethod(object, methodName, parameterTypes);
      
              if (method == null)
                  throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + object + "]");
              method.setAccessible(true);
      
              // 使用 method.invoke()方法进行运行
              try {
                  method.invoke(object, parameters);
              } catch (IllegalAccessException | IllegalArgumentException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              return method;
          }
      
          /**
           * 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型
           * 如: public EmployeeDao extends BaseDao<Employee, String>
           *
           * @param clazz
           * @return
           */
          public static Class getSuperClassGenricType(Class clazz, int index) {
              Type genType = clazz.getGenericSuperclass();
      
              // 判定s是否是ParameterType相对应的
              if (!(genType instanceof ParameterizedType))
                  return Object.class;
      
              // 强制转换 获取超类泛型参数实际类型,返回genType 是类的接口,基本类型或者void
              Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
      
              if (index >= params.length || index < 0)
                  return Object.class;
      
              if (!(params[index] instanceof Class))
                  return Object.class;
              return (Class) params[index];
          }
      
          /**
           * 通过反射,获得Class定义中声明的父类的泛型参数的类型.
           * eg.
           * public UserDao extends HibernateDao<User>
           *
           * @param clazz The class to introspect
           * @return the first generic declaration, or Object.class if cannot be determined
           */
          @SuppressWarnings("unchecked")
          public static <T> Class<T> getSuperClassGenricType(final Class clazz) {
              return getSuperClassGenricType(clazz, 0);
          }
      }
      
  • JAVA类属性

    • 在JAVAEE中,JAVA类的属性通过getter,setter来定义:get(set)方法:去除get(或set)后,首字母小写即为该类的属性。

    • 而之前的属性,即成员变量称之为字段。

    • 操作java类的属性有一个工具包:beanutils(需要结合logging来用)

      • BeanUtils.setProperty()
      • BeanUtils.getProperty()
    • 一般情况下,字段名和属性名都一致

    • 通过该工具包可以将之前的ReflectionUtil替换为:BeanUtils.setProperty(entity, propertyName, value);

    • BeanUtils测试代码

      package com.litian.jdbc;
      
      import org.apache.commons.beanutils.BeanUtils;
      
      import java.lang.reflect.InvocationTargetException;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: BeanUtilsTest.java
       * @time: 2020/3/27 15:37
       * @desc: |测试工具包beanutils
       */
      
      public class BeanUtilsTest {
          public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
              // 测试赋值操作
              Object obj = new User();
              System.out.println(obj);
      
              BeanUtils.setProperty(obj, "username", "二哈");
              System.out.println(obj);
      
              // 测试获取操作
              Object val = BeanUtils.getProperty(obj, "username");
              System.out.println(val);
      
          }
      }
      
  • DAO的补充和重构

    • DAO重构后的代码

      package com.litian.jdbc;
      
      import org.apache.commons.beanutils.BeanUtils;
      
      import java.sql.*;
      import java.util.ArrayList;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DAO.java
       * @time: 2020/3/26 18:37
       * @desc: |
       */
      
      public class DAO {
          // insert, update, delete 操作都可以包含在其中
          void update(String sql, Object... args) {
              Connection conn = null;
              PreparedStatement ps = null;
      
              try {
                  conn = JDBCTools.getConnection();
                  ps = conn.prepareStatement(sql);
      
                  for (int i = 0; i < args.length; i++) {
                      ps.setObject(i + 1, args[i]);
                  }
                  ps.executeUpdate();
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(null, ps, conn);
              }
      
          }
      
          // 查询一条记录,返回对应的对象
          <T> T get(Class<T> clazz, String sql, Object... args) {
              List<T> result = getForList(clazz, sql, args);
              if (result.size() > 0) {
                  return result.get(0);
              }
              return null;
      
              /* 根据下面的方法进行了改写
              T entity = null;
              Connection conn = null;
              PreparedStatement ps = null;
              ResultSet rs = null;
              try {
                  // 1. 获取Connection
                  conn = JDBCTools.getConnection();
                  // 2. 获取PreparedStatement
                  ps = conn.prepareStatement(sql);
                  // 3. 填充占位符
                  for (int i = 0; i < args.length; i++) {
                      ps.setObject(i + 1, args[i]);
                  }
                  // 4. 进行查询,得到ResultSet
                  rs = ps.executeQuery();
      
                  // 5. 若ResultSet中有记录,准备一个Map<String, Object>: 键:存放列的别名;值:存放列的值
                  if (rs.next()) {
                      Map<String, Object> values = new HashMap<>();
                      // 6. 得到ResultSetMetaData对象
                      ResultSetMetaData rsmd = rs.getMetaData();
                      // 7. 处理ResultSet,把指针向下移动一个单位
                      // 8. 由ResultSetMetaData对象得到结果集中有多少列
                      int columnCount = rsmd.getColumnCount();
                      // 9. 由ResultSetMetaData得到每一列的别名,由ResultSet得到具体每一列的值
                      for (int i = 0; i < columnCount; i++) {
                          String columnLabel = rsmd.getColumnLabel(i + 1);
                          Object columnValue = rs.getObject(columnLabel);
      
                          // 10. 填充Map对象
                          values.put(columnLabel, columnValue);
                      }
                      // 11. 用反射创建Class对象的对象
                      entity = clazz.newInstance();
                      // 12. 遍历Map对象,用反射填充对象的属性值:属性名为Map中的Key,属性值为Map中的Value
                      for (Map.Entry<String, Object> entry : values.entrySet()) {
                          String propertyName = entry.getKey();
                          Object value = entry.getValue();
                          BeanUtils.setProperty(entity, propertyName, value);
                      }
                  }
      
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(rs, ps, conn);
              }
              return entity;
      
               */
          }
      
          // 查询多条记录,返回对应的对象的集合
          <T> List<T> getForList(Class<T> clazz, String sql, Object... args) {
              List<T> list = new ArrayList<>();
      
              Connection conn = null;
              PreparedStatement ps = null;
              ResultSet rs = null;
      
              try {
                  // 1. 得到结果集
                  conn = JDBCTools.getConnection();
                  ps = conn.prepareStatement(sql);
                  for (int i = 0; i < args.length; i++) {
                      ps.setObject(i + 1, args[i]);
                  }
                  rs = ps.executeQuery();
      
                  // 2. 处理结果集,得到一个Map对应的List,其中一个Map对象就是一条记录,Map的Key为rs中列的别名,Value为列的值
                  List<Map<String, Object>> values = handleResultSetToMapList(rs);
                  // 3. 把Map的List转为clazz对应的List,其中Map的key即clazz对应的对象的propertyName,value即为clazz对应对象的propertyValue
                  list = transferMapListToBeanList(clazz, values);
      
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(rs, ps, conn);
              }
              return list;
          }
      
          public <T> List<T> transferMapListToBeanList(Class<T> clazz, List<Map<String, Object>> values) throws Exception {
              // 12. 判断List是否为空集合,若不为空,则遍历List,得到一个个Map对象,再把一个Map对象转为一个Class参数对应的Object对象。
              List<T> result = new ArrayList<T>();
              T bean = null;
              if (values.size() > 0) {
                  for (Map<String, Object> m : values) {
                      bean = clazz.newInstance();
                      for (Map.Entry<String, Object> entry : m.entrySet()) {
                          String propertyName = entry.getKey();
                          Object value = entry.getValue();
      
                          BeanUtils.setProperty(bean, propertyName, value);
                      }
                      result.add(bean);
                  }
              }
              return result;
          }
      
          /**
           * 处理结果集,得到Map的一个List,其中一个Map对象对应一条记录
           *
           * @param rs
           * @return
           * @throws SQLException
           */
          private List<Map<String, Object>> handleResultSetToMapList(ResultSet rs) throws SQLException {
              // 5. 若ResultSet中有记录,准备一个List<Map<String, Object>>
              // 键:存放列的别名;值:存放列的值,其中一个Map对象对应着一条记录
              List<Map<String, Object>> values = new ArrayList<>();
              List<String> columnLabels = getColumnLabels(rs);
              // 7. 处理ResultSet,使用while循环
              Map<String, Object> maps = null;
              while (rs.next()) {
                  maps = new HashMap<>();
                  for (String columnLabel : columnLabels) {
                      Object value = rs.getObject(columnLabel);
      
                      maps.put(columnLabel, value);
                  }
                  // 11. 把一条记录的填充好的Map对象放入5准备的List中
                  values.add(maps);
              }
              return values;
          }
      
          /**
           * 获取结果集ColumnLabel对应的结果集
           */
          private List<String> getColumnLabels(ResultSet rs) throws SQLException {
              List<String> labels = new ArrayList<>();
              ResultSetMetaData rsmd = rs.getMetaData();
              for (int i = 0; i < rsmd.getColumnCount(); i++) {
                  String columnLabel = rsmd.getColumnLabel(i + 1);
                  labels.add(columnLabel);
              }
              return labels;
          }
      
          // 返回某条记录的某一个字段的值或一个统计的值(一共有多少记录等。)
          <E> E getForValue(String sql, Object... args) {
              Connection conn = null;
              PreparedStatement ps = null;
              ResultSet rs = null;
      
              try {
                  // 1. 得到结果集
                  conn = JDBCTools.getConnection();
                  ps = conn.prepareStatement(sql);
                  for (int i = 0; i < args.length; i++) {
                      ps.setObject(i + 1, args[i]);
                  }
                  rs = ps.executeQuery();
      
                  // 2. 取得结果
                  if (rs.next()){
                      return (E) rs.getObject(1);
                  }
      
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(rs, ps, conn);
              }
              return null;
          }
      }
      
    • 测试

      package com.litian.jdbc;
      
      import java.sql.Date;
      import java.sql.Timestamp;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DAOTest.java
       * @time: 2020/3/26 18:59
       * @desc: |
       */
      
      public class DAOTest {
          public static void main(String[] args) {
              DAO dao = new DAO();
              // 测试update
              // String sql = "insert into t_user(id, username, pwd, regTime, lastLoginTime) values(?,?,?,?,?)";
              // dao.update(sql, 4, "李英俊", "123456", new Date(System.currentTimeMillis()), new Timestamp(System.currentTimeMillis()));
      
              // 测试get
              // String sql = "select id, username, pwd, regTime, lastLoginTime from t_user where id=?";
              // User u = dao.get(User.class, sql, 4);
              // System.out.println(u);
      
              // 测试getForList
              // String sql2 = "select id, username, pwd, regTime, lastLoginTime from t_user where id<?";
              // List<User> us = dao.getForList(User.class, sql2, 10);
              // System.out.println(us);
      
              // 测试getForValue
              String sql3 = "select username from t_user where id = ?";
              String cc = dao.getForValue(sql3, 1);
              System.out.println(cc);
          }
      }
      

8. JDBC的元数据

  • 可以从Connection对象中获得有关数据库管理系统的各种信息

  • 获取这些信息的方法都是在DatabaseMetaData类中。

  • DatabaseMetaData:描述数据库的元数据对象

  • ResultSetMetaData:描述结果集的元数据对象

    package com.litian.jdbc;
    
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: MetaDataTest.java
     * @time: 2020/3/29 15:12
     * @desc: |
     */
    
    public class MetaDataTest {
    
        public static void main(String[] args){
            testDatabaseMetaData();
            testResultSetMetaData();
        }
    
        /**
         * ResultSetMetaData:描述结果集的元数据对象
         * 可以得到结果集中的基本信息:结果集中有哪些列,列名、列的别名等。
         * 结合反射可以写出通用的查询方法
         */
        public static void testResultSetMetaData(){
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
    
            try {
                conn = JDBCTools.getConnection();
                String sql = "select id, username 姓名, pwd from t_user";
                ps = conn.prepareStatement(sql);
                rs = ps.executeQuery();
    
                // 1. 得到ResultSetMetaData对象
                ResultSetMetaData rsmd = rs.getMetaData();
    
                // 2. 得到列的个数
                int columnCount = rsmd.getColumnCount();
                System.out.println(columnCount);
    
                for (int i = 0; i < columnCount; i++) {
                    // 3. 得到列名
                    String columnName = rsmd.getColumnName(i + 1);
                    // 4. 得到列的别名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    System.out.println(columnName + "-->" + columnLabel);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(rs, ps, conn);
            }
        }
    
        /**
         * DatabaseMetaData是描述数据库的元数据对象
         * 可以由Connection得到
         */
        public static void testDatabaseMetaData(){
            Connection conn = null;
            ResultSet rs = null;
    
            try {
                conn = JDBCTools.getConnection();
                DatabaseMetaData data = conn.getMetaData();
    
                // 可以得到数据库本身的一些基本信息
                // 1. 得到数据库的版本号
                int version = data.getDatabaseMajorVersion();
                System.out.println(version);
    
                // 2. 得到连接数据库的用户名
                String user = data.getUserName();
                System.out.println(user);
    
                // 3. 得到MySQL中有哪些数据库
                rs = data.getCatalogs();
                while (rs.next()) {
                    System.out.println(rs.getString(1));
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(rs, null, conn);
            }
        }
    }
    

9. JDBC获取插入记录的主键值

  • 取得数据库自动生成的主键值

    package com.litian.jdbc;
    
    import javax.swing.plaf.nimbus.State;
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: JDBCTest3.java
     * @time: 2020/3/29 15:27
     * @desc: |取得数据库自动生成的主键值
     */
    
    public class JDBCTest3 {
        public static void main(String[] args){
            Connection conn = null;
            PreparedStatement ps = null;
    
            try {
                conn = JDBCTools.getConnection();
                String sql = "insert into t_user(username, pwd) values(?,?)";
                // ps = conn.prepareStatement(sql);
    
                // 使用重载的ps方法来生成ps对象
                ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                ps.setString(1, "pika");
                ps.setString(2, "123456");
                ps.executeUpdate();
    
                // 通过getGeneratedKeys方法获取包含了新生成的主键的ResultSet对象
                // 在ResultSet中只有一列GENERATED_KEYS,用于存放新生成的主键值
                ResultSet rs = ps.getGeneratedKeys();
                if(rs.next()){
                    System.out.println(rs.getInt(1));
                }
    
                ResultSetMetaData rsmd = rs.getMetaData();
                for (int i = 0; i < rsmd.getColumnCount(); i++) {
                    System.out.println(rsmd.getColumnName(i + 1));
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, ps, conn);
            }
        }
    }
    

10. 处理Blob

  • LOB,即Large Objects(大对象),是用来存储大量的二进制和文本数据的一种数据类型

  • LOB分为两种内省:内部LOB和外部LOB

    • 内部LOB将数据以字节流的形式存储在数据库的内部。因而内部LOB的许多操作都可以参与事务,也可以像处理普通数据一样对其进行备份和恢复操作。
    • Oracle支持三种类型的内部LOB:
      • BLOB:二进制数据
      • CLOB:单字节字符数据
      • NCLOB:多字节字符数据
    • CLOB和NCLOB类型适用于存储超长的文本数据,BLOB字段适用于存储大量的二进制数据,如图像、视频、音频、文件等。
  • MySQL四种BLOB类型(除了在存储的最大信息量上不同之外,他们是等同的)

    • TinyBlob:最大255字节
    • Blob:最大65K
    • MediumBlob:最大16M
    • LongBlob:最大4G
  • 如果存储的文件过大,数据库的性能会下降。

  • 插入Blob数据

    package com.litian.jdbc;
    
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: InsertBlob.java
     * @time: 2020/3/31 14:40
     * @desc: |插入Blob类型的数据必须使用PreparedStatement,因为BLOB类型的数据是无法使用字符串拼写的
     *          调用setBlob方法插入BLOB
     */
    
    public class InsertBlob {
        public static void main(String[] args){
            Connection conn = null;
            PreparedStatement ps = null;
    
            try {
                conn = JDBCTools.getConnection();
                String sql = "insert into t_user(username, pwd, pic) values(?,?,?)";
                ps = conn.prepareStatement(sql);
                ps.setString(1, "picture");
                ps.setString(2, "123456");
                InputStream is = new FileInputStream("C:\\Users\\Administrator\\Desktop\\参考投稿进程.jpg");
                ps.setBlob(3, is);
    
                ps.executeUpdate();
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, ps, conn);
            }
        }
    }
    
  • 读取Blob数据

    package com.litian.jdbc;
    
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.sql.Blob;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: ReadBlob.java
     * @time: 2020/3/31 14:47
     * @desc: |读取Blob数据
     *      1. 使用getBlob方法读取到Blob对象
     *      2. 调用Blob的getBinaryStream()方法得到输入流。再使用IO操作即可。
     */
    
    public class ReadBlob {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
    
            try {
                conn = JDBCTools.getConnection();
                String sql = "select id, username 姓名, pwd, pic from t_user where id=21028";
                ps = conn.prepareStatement(sql);
                rs = ps.executeQuery();
    
                if (rs.next()) {
                    int id = rs.getInt(1);
                    String name = rs.getString(2);
                    String pwd = rs.getString(3);
                    System.out.println(id + "->" + name + "->" + pwd);
                    Blob pic = rs.getBlob(4);
                    InputStream in = pic.getBinaryStream();
                    OutputStream out = new FileOutputStream("info.jpg");
                    byte[] buffer = new byte[1024];
                    int len = 0;
                    while((len=in.read(buffer)) != -1){
                        out.write(buffer, 0, len);
                    }
                    out.close();
                    in.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(rs, ps, conn);
            }
        }
    }
    

11. 处理事务

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

  • 事务的ACID属性

    • 原子性,Atomicity:事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
    • 一致性,Consistency:事务必须使数据库从一个一致性状态变换到另一个一致性状态。
    • 隔离性,Isolation:一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    • 持久性,Durability:持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
  • 为了让多个SQL语句作为一个事务执行:

    • 调用Connection对象的setAutoCommit(false);以取消自动提交事务
    • 在所有SQL语句都执行后,调用commit();方法提交事务
    • 在出现异常时,调用rollback()方法回滚事务
    • 若此时Connection没有被关闭,则需要恢复其自动提交状态
  • 关于事务:

    • 如果多个操作,每个操作使用的是自己的单独的连接,则无法保证事务。
    • 具体步骤:
      • 事务操作开始前,开始事务:取消Connection默认的提交行为;
      • 如果事务的操作都成功,则提交事务;
      • 回滚事务:若出现异常,则在catch块中回滚事务。
  • 代码:

    • 新增DAO一个方法,这个方法把Connection提出来作为传参

          // 外部来处理Connection
          void update(Connection conn, String sql, Object... args) {
              PreparedStatement ps = null;
      
              try {
                  ps = conn.prepareStatement(sql);
      
                  for (int i = 0; i < args.length; i++) {
                      ps.setObject(i + 1, args[i]);
                  }
                  ps.executeUpdate();
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(null, ps, null);
              }
          }
      
    • 测试事务

      package com.litian.jdbc;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TransactionTest.java
       * @time: 2020/4/1 14:06
       * @desc: |
       */
      
      public class TransactionTest {
          public static void main(String[] args) {
              DAO dao = new DAO();
      
              Connection conn = null;
      
              try {
                  conn = JDBCTools.getConnection();
      
                  // 开始事务:取消默认提交
                  conn.setAutoCommit(false);
                  String sql = "update t_user2 set money = money - 500 where id = 21023";
                  dao.update(conn, sql);
      
                  // 插入错误
                  int i = 10 / 0;
                  System.out.println(i);
      
                  sql = "update t_user2 set money = money + 500 where id = 21024";
                  dao.update(conn, sql);
      
                  // 提交事务
                  conn.commit();
              } catch (Exception e) {
                  e.printStackTrace();
      
                  try {
                      conn.rollback();
                  } catch (SQLException ex) {
                      ex.printStackTrace();
                  }
              } finally {
                  JDBCTools.release(null, null, conn);
              }
          }
      }
      

12. 事务的隔离级别

  • 脏读:读取了更新还未提交的数据,但进行了回滚,所以读取的内容是临时的且无效的。
  • 不可重复读:某个事务多次读取一个字段,值却不同,这是因为中间别的事务更新的该字段。
  • 幻读:某个事务从一个表中读取了信息,然后另一个事务更新了该表,使得之前事务再读的时候,表的行数改变了。
  • 数据库提供了4种事务隔离级别:
    • READ UNCOMMITED:读未提交数据,3种问题都会出现
    • READ COMMITED:读已提交数据,避免脏读
    • REPEATABLE READ:可重复度,避免脏读和不可重复读
    • SERIALIZABLE:串行化,避免3种问题
  • Oracle支持2种事务隔离级别:READ COMMITED(默认)和SERIALIZABLE
  • Mysql支持4种事务隔离级别:默认REPEATABLE READ
  • 在JDBC程序中可以通过Connection的setTransactionIsolation来设置事务的隔离级别

13. 批量处理JDBC语句提高处理效率

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

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

    • addBatch(String):添加需要批量处理的SQL语句或是参数;
    • executeBatch():执行批量处理语句
  • 通常我们会遇到两种批量执行SQL语句的情况:

    • 多条SQL语句的批量处理
    • 一个SQL语句的批量传参
  • 代码(3种批量处理方式对比):

    package com.litian.jdbc;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.Statement;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: JDBCTest4.java
     * @time: 2020/4/4 14:18
     * @desc: |批量处理事务
     */
    
    public class JDBCTest4 {
    
        public static void main(String[] args){
            // testStatement();
            testPrepareStatement();
        }
    
        /**
         * 向数据库的数据表中插入10w条记录
         * 测试如何插入,用时最短
         * 1. 使用Statement:15038
         */
        public static void testStatement() {
            Connection conn = null;
            Statement st = null;
            String sql = null;
            try {
                conn = JDBCTools.getConnection();
                JDBCTools.beginTx(conn);
                st = conn.createStatement();
    
                long begin = System.currentTimeMillis();
                for (int i = 0; i < 100000; i++) {
                    sql = String.format("insert into t_user2(username, pwd) values(name_%d, 6666)", i);
                    st.executeUpdate(sql);
                }
                long end = System.currentTimeMillis();
                System.out.println(end - begin);
    
                JDBCTools.commit(conn);
            } catch (Exception e) {
                e.printStackTrace();
                JDBCTools.rollback(conn);
            } finally {
                JDBCTools.release(null, st, conn);
            }
        }
    
        /**
         * 向数据库的数据表中插入10w条记录
         * 测试如何插入,用时最短
         * 2. 使用PreparedStatement:13131
         * 3. 在2的基础上使用批量处理:24596?这就很尴尬了
         */
        public static void testPrepareStatement() {
            Connection conn = null;
            PreparedStatement st = null;
            String sql = null;
            try {
                conn = JDBCTools.getConnection();
                JDBCTools.beginTx(conn);
                sql = "insert into t_user2(username, pwd) values(?,?)";
                st = conn.prepareStatement(sql);
    
                long begin = System.currentTimeMillis();
                for (int i = 0; i < 100000; i++) {
                    st.setString(1, "name_" + i);
                    st.setString(2, "6666");
                    st.executeUpdate();
    
                    // “积攒”sql语句
                    st.addBatch();;
                    // 当积攒到一定程度,就统一地执行一次,并且清空先前积攒的sql
                    if((i + 1) % 300 == 0){
                        st.executeBatch();
                        st.clearBatch();
                    }
                }
    
                // 若总条数不是批量数值的整数倍,则还需要额外再执行一次
                if(100000 % 300 != 0){
                    st.executeBatch();
                    st.clearBatch();
                }
    
                long end = System.currentTimeMillis();
                System.out.println(end - begin);
    
                JDBCTools.commit(conn);
            } catch (Exception e) {
                e.printStackTrace();
                JDBCTools.rollback(conn);
            } finally {
                JDBCTools.release(null, st, conn);
            }
        }
    }
    

14. 数据库连接池

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

    • 在主程序(如servlet、bean)中建立数据库连接
    • 进行sql操作
    • 断开数据库连接
  • 这种模式开发存在各种各样的问题,最重要的是:数据库的连接资源并没有得到很好的重复利用

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术,其基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需要从“缓冲池”中取出一个,使用完毕之后再放回去。

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

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

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

    • 资源重用
    • 更快的系统反应速度
    • 新的资源分配手段
    • 统一的连接管理,避免数据库连接泄露
  • 两种开源的数据库连接池

    • JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由服务器(Weblogic,WebSphere,Tomcat)提供实现,也有一些开源组织提供实现
      • DBCP
      • C3P0
    • DataSource通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把DataSource称为连接池
  • DBCP测试,Tomcat内置

    1. 加入jar包(2个包)dbcp和pool
    2. 创建数据库连接池
  • DBCP工厂测试

    1. 加载dbcp的properties配置文件:配置文件中的键值对,键需要来自BasicDataSource的属性
    2. 调用BasicDataSourceFactory的createDataSource方法创建DataSource实例
    3. 从DataSource实例中获取数据库连接
  • dbcp.properties

    username=root
    password=123456
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/testjdbc?serverTimezone=GMT%2B8
    
    initialSize=10
    maxTotal=50
    minIdle=5
    maxWaitMillis=5000
    
  • DBCP测试

    package com.litian.jdbc;
    
    import org.apache.commons.dbcp2.BasicDataSource;
    import org.apache.commons.dbcp2.BasicDataSourceFactory;
    
    import javax.sql.DataSource;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.Properties;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestDBCP.java
     * @time: 2020/4/6 13:28
     * @desc: |使用DBCP数据库连接池
     */
    
    public class TestDBCP {
        
        public static void main(String[] args) throws Exception {
            testDBCPwithDataSourceFactory();
        }
    
        /**
         * 利用DBCP工厂建立连接池
         */
        public static void testDBCPwithDataSourceFactory() throws Exception {
            Properties p = new Properties();
            InputStream is = TestDBCP.class.getClassLoader().getResourceAsStream("dbcp.properties");
            p.load(is);
            DataSource ds = BasicDataSourceFactory.createDataSource(p);
            System.out.println(ds.getConnection());
    
            BasicDataSource bs = (BasicDataSource) ds;
            System.out.println(bs.getMaxWaitMillis());
        }
    
        public static void testDBCP(){
            // 1. 创建DBCP数据源实例
            BasicDataSource ds = null;
            ds = new BasicDataSource();
            // 2. 为数据源实例指定必须的属性
            ds.setUsername("root");
            ds.setPassword("123456");
            ds.setUrl("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=GMT%2B8");
            ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
            // 3. 指定数据源的一些可选属性
            // 3.1 指定数据库连接池初始化连接数的个数
            ds.setInitialSize(10);
            // 3.2 指定最大的连接数,现在的代码中已经没有setMaxActive这个方法名了
            // 同一时刻可以同时向数据库申请的连接数
            ds.setMaxTotal(50);
            // 3.3 指定小连接数
            // 在数据库中连接池中最少有多少个空闲连接数
            ds.setMinIdle(5);
            // 3.4 等待连接池分配连接,最长的等待时间.单位为毫秒,超出该时间抛异常。
            ds.setMaxWaitMillis(1000 * 5);
    
            try {
                // 4. 从数据源中获取数据库连接
                Connection conn = ds.getConnection();
                System.out.println(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
  • C3P0

    1. 加入jar包(2个包)C3P0和mchange-commons-java
    2. 创建数据库连接池
  • C3P0工厂测试,Hibernate官方推荐,参考连接

    1. 在src创建一个c3p0-config.xml文件
    2. 创建ComboPooledDataSource实例
    3. 从DataSource实例中获取数据库连接
  • c3p0-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <c3p0-config>
        <!-- 这是默认配置信息 -->
        <default-config>
            <!-- 连接四大参数配置 -->
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest</property>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="user">root</property>
            <property name="password">root</property>
            <!-- 池参数配置 -->
            <property name="acquireIncrement">3</property>
            <property name="initialPoolSize">10</property>
            <property name="minPoolSize">2</property>
            <property name="maxPoolSize">10</property>
        </default-config>
    
        <!-- 专门为oracle提供的配置信息 -->
        <named-config name="oracle-config">
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="user">root</property>
            <property name="password">123</property>
            <property name="acquireIncrement">3</property>
            <property name="initialPoolSize">10</property>
            <property name="minPoolSize">2</property>
            <property name="maxPoolSize">10</property>
        </named-config>
    
        <!-- 专门为Mysql提供的配置信息 -->
        <named-config name="mysql-config">
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/testjdbc?serverTimezone=GMT%2B8</property>
            <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
            <property name="user">root</property>
            <property name="password">123456</property>
    
    		<!-- 若数据库中连接数不足时,一次向数据库服务器申请多少个连接 -->
            <property name="acquireIncrement">5</property>
            <!-- 初始化数据库连接池时连接的数量 -->
            <property name="initialPoolSize">5</property>
            <!-- 数据库连接池中最小的数据库连接数 -->
            <property name="minPoolSize">5</property>
            <!-- 数据库连接池中最大的数据库连接数 -->
            <property name="maxPoolSize">10</property>
    
            <!-- C3P0数据库连接池可以维护的Statement的个数 -->
            <property name="maxStatements">20</property>
            <!-- 每个连接同时可以使用的Statement对象的个数 -->
            <property name="maxStatementsPerConnection">5</property>
    
        </named-config>
    </c3p0-config>
    
  • C3P0测试

    package com.litian.jdbc;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestC3P0.java
     * @time: 2020/4/6 14:40
     * @desc: |使用C3P0数据库连接池
     */
    
    public class TestC3P0 {
    
        public static void main(String[] args) throws Exception {
            // test2();
            test3();
        }
    
        public static void test3() throws Exception{
            Connection conn = JDBCTools.getDSConnection();
            System.out.println(conn);
        }
    
        public static void test2() throws Exception{
            DataSource ds = new ComboPooledDataSource("mysql-config");
            System.out.println(ds.getConnection());
            ComboPooledDataSource cpds = (ComboPooledDataSource) ds;
            System.out.println(((ComboPooledDataSource) ds).getMaxStatements());
        }
    
        public static void test1() throws Exception{
            ComboPooledDataSource cpds = new ComboPooledDataSource();
            cpds.setDriverClass("com.mysql.cj.jdbc.Driver");
            cpds.setJdbcUrl("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=GMT%2B8");
            cpds.setUser("root");
            cpds.setPassword("123456");
            System.out.println(cpds.getConnection());
        }
    }
    
  • 修改JDBCTools工具类,加入数据库连接池功能

    private static DataSource ds = null;
    
    // 数据库连接池应只被初始化一次。
    static {
    	ds = new ComboPooledDataSource("mysql-config");
    }
    
    public static Connection getDSConnection() throws Exception {
    	return ds.getConnection();
    }
    

15. DBUtils

  • DBUtils是Apache组织提供的一个开源的JDBC工具类库,能极大简化jdbc编码的工作量

  • API介绍

    • QueryRunner
    • ResultSetHandler
    • 工具类DbUtils
  • 用DBUtils进行增删改查操作:update

  • 查的操作要使用QueryRunner

    • 自定义返回值的操作:MyResultSetHandler
    • 返回一个类的操作(单条记录):BeanHandler
    • 返回一个类的操作(多条记录):BeanListHandler
    • 返回一个Map的操作(单条记录):MapHandler
    • 返回多个Map的操作(多条记录):MapListHandler
    • 返回一个值的操作(单行单列):ScalarHandler
  • 代码

    package com.litian.jdbc;
    
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.ResultSetHandler;
    import org.apache.commons.dbutils.handlers.*;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: DBUtilsTest.java
     * @time: 2020/4/7 12:09
     * @desc: |测试DBUtils工具类
     */
    
    public class DBUtilsTest {
        public static void main(String[] args) {
            // testUpdate();
            // testQuery();
            // testBeanHanlder();
            // testBeanListHanlder();
            // testMapHanlder();
            // testMapListHanlder();
            testScalarHanlder();
        }
    
        /**
         * ScalarHandler:把结果集转为一个数值(可以是任意基本数据类型和字符串)
         * 默认返回第1列第1行的值,所以多行多列也就返回第一个值
         */
        public static void testScalarHanlder() {
            // 1. 创建QueryRunner的实现类
            QueryRunner qr = new QueryRunner();
            Connection conn = null;
            try {
                conn = JDBCTools.getDSConnection();
                String sql = "select username from t_user where id > ? && id < ?";
                Object result = qr.query(conn, sql, new ScalarHandler(), 2, 5);
                System.out.println(result);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, null, conn);
            }
        }
    
        /**
         * MapListHandler:将结果集转为一个Map的list
         * Map对应查询的一条记录:键:sql查询的列名(不是列的别名),值:列的值。
         * 而MapListHandler:返回的是多条记录对应的Map的集合。
         */
        public static void testMapListHanlder() {
            // 1. 创建QueryRunner的实现类
            QueryRunner qr = new QueryRunner();
            Connection conn = null;
            try {
                conn = JDBCTools.getDSConnection();
                String sql = "select id, username, pwd from t_user where id > ? && id < ?";
                List<Map<String, Object>> u = qr.query(conn, sql, new MapListHandler(), 2, 5);
                System.out.println(u);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, null, conn);
            }
        }
    
        /**
         * MapHandler:返回sql对应的第一条记录对应的Map对象
         * 键:sql查询的列名(不是列的别名),值:列的值。
         */
        public static void testMapHanlder() {
            // 1. 创建QueryRunner的实现类
            QueryRunner qr = new QueryRunner();
            Connection conn = null;
            try {
                conn = JDBCTools.getDSConnection();
                String sql = "select id, username, pwd from t_user where id > ? && id < ?";
                Map<String, Object> u = qr.query(conn, sql, new MapHandler(), 2, 5);
                System.out.println(u);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, null, conn);
            }
        }
    
        /**
         * BeanListHandler:把结果集转为一个List,该List不为null,但可能为空集合(size()方法返回0)
         * 若sql语句的确能够查询到记录,List中存放创建BeanListHandler传入的Class对象对应的对象。
         */
        public static void testBeanListHanlder() {
            // 1. 创建QueryRunner的实现类
            QueryRunner qr = new QueryRunner();
            Connection conn = null;
            try {
                conn = JDBCTools.getDSConnection();
                String sql = "select id, username, pwd from t_user where id > ? && id < ?";
                List<User> u = (List<User>) qr.query(conn, sql, new BeanListHandler(User.class), 2, 5);
                System.out.println(u);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, null, conn);
            }
        }
    
        /**
         * BeanHandler:把结果集的第一条记录转为创建BeanHandler对象时传入的Class参数对应的对象。
         */
        public static void testBeanHanlder() {
            // 1. 创建QueryRunner的实现类
            QueryRunner qr = new QueryRunner();
            Connection conn = null;
            try {
                conn = JDBCTools.getDSConnection();
                String sql = "select id, username, pwd from t_user where id = ?";
                User u = (User) qr.query(conn, sql, new BeanHandler(User.class), 3);
                System.out.println(u);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, null, conn);
            }
        }
    
        /**
         * QueryRunner的query方法的返回值屈居于其ResultSetHandler参数的handle方法的返回值
         */
        public static void testQuery() {
    
            // 1. 创建QueryRunner的实现类
            QueryRunner qr = new QueryRunner();
    
            Connection conn = null;
            try {
                conn = JDBCTools.getDSConnection();
                String sql = "select id, username, pwd from t_user";
                Object obj = qr.query(conn, sql, new MyResultSetHandler());
                System.out.println(obj);
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, null, conn);
            }
    
        }
    
        /**
         * 测试QueryRunner类的update方法
         * 该方法可用于insert、update和delete
         */
        public static void testUpdate() {
            // 1. 创建QueryRunner的实现类
            QueryRunner qr = new QueryRunner();
            // 2. 使用update方法
            String sql = "delete from t_user where id in (?, ?)";
    
            Connection conn = null;
    
            try {
                conn = JDBCTools.getDSConnection();
                qr.update(conn, sql, 1, 2);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCTools.release(null, null, conn);
            }
        }
    
        // 静态内部类
        static class MyResultSetHandler implements ResultSetHandler {
    
            @Override
            public Object handle(ResultSet resultSet) throws SQLException {
                // System.out.println("handle。。。");
                // return "111";
                List<User> us = new ArrayList<>();
                while (resultSet.next()) {
                    Integer id = resultSet.getInt(1);
                    String username = resultSet.getString(2);
                    String pwd = resultSet.getString(3);
    
                    User u = new User(id, username, pwd, new Date(System.currentTimeMillis()), new Timestamp(System.currentTimeMillis()));
                    us.add(u);
                }
                return us;
            }
        }
    }
    
  • 使用DBUtils编写通用的DAO(讲道理这波我是没看懂的,DBUtils直接用不香吗)

    实现UML

  • 代码实现

    • DAO接口

      package com.litian.jdbc;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DBUtilsDAO.java
       * @time: 2020/4/8 10:56
       * @desc: |访问数据的DAO接口
       * 里面定义好访问数据表的各种方法
       * T 是DAO处理的实体类的类型
       */
      
      public interface DBUtilsDAO<T> {
      
          /**
           * 批量处理的方法
           *
           * @param conn
           * @param sql
           * @param args 填充占位符的Object[] 类型的可变参数
           */
          void batch(Connection conn, String sql, Object[]... args);
      
          /**
           * 返回具体的一个值,例如总人数,平均工资,某一个人的email等。
           *
           * @param conn
           * @param sql
           * @param args
           * @param <E>
           * @return
           */
          <E> E getForValue(Connection conn, String sql, Object... args);
      
          /**
           * 返回T的一个集合
           *
           * @param conn
           * @param sql
           * @param args
           * @return
           */
          List<T> getForList(Connection conn, String sql, Object... args);
      
          /**
           * 返回一个T的对象
           *
           * @param conn
           * @param sql
           * @param args
           * @return
           */
          T get(Connection conn, String sql, Object... args) throws SQLException;
      
      
          /**
           * insert update delete
           *
           * @param conn 数据库连接
           * @param sql  sql语句
           * @param args 填充占位符的可变参数
           */
          void update(Connection conn, String sql, Object... args);
      }
      
    • DAO接口的具体实现(以get方法为例)

      package com.litian.jdbc;
      
      import org.apache.commons.dbutils.QueryRunner;
      import org.apache.commons.dbutils.handlers.BeanHandler;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: JdbcDaoImpl.java
       * @time: 2020/4/8 11:07
       * @desc: |使用QueryRunner提供其具体的实现
       * <T>为子类需传入的泛型类型
       */
      
      public class JdbcDaoImpl<T> implements DBUtilsDAO {
      
          private QueryRunner qr = null;
          private Class<T> type;
      
          public JdbcDaoImpl(){
              qr = new QueryRunner();
              type = ReflectionUtils.getSuperClassGenricType(getClass());
          }
      
          @Override
          public void batch(Connection conn, String sql, Object[]... args) {
      
          }
      
          @Override
          public List getForList(Connection conn, String sql, Object... args) {
              return null;
          }
      
          @Override
          public Object get(Connection conn, String sql, Object... args) throws SQLException {
              return qr.query(conn, sql, new BeanHandler<>(type), args);
          }
      
          @Override
          public void update(Connection conn, String sql, Object... args) {
      
          }
      
          @Override
          public Object getForValue(Connection conn, String sql, Object... args) {
              return null;
          }
      }
      
    • 一个继承上面实现的子类,虽然啥也没写,但是方便以后扩展

      package com.litian.jdbc;
      
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UserDao.java
       * @time: 2020/4/8 11:08
       * @desc: |
       */
      
      public class UserDao extends JdbcDaoImpl<User> {
      }
      
    • 使用上面这个子类,看看能否完成DAO的功能

      package com.litian.jdbc;
      
      import java.sql.Connection;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UserDaoTest.java
       * @time: 2020/4/8 11:10
       * @desc: |
       */
      
      public class UserDaoTest {
      
          UserDao ud = new UserDao();
      
          public static void main(String[] args){
              new UserDaoTest().testGet();
          }
      
          public void testGet(){
              Connection conn = null;
              try {
                  conn = JDBCTools.getDSConnection();
                  String sql = "select id, username, pwd from t_user where id = ?";
                  User u = (User) ud.get(conn, sql, 3);
                  System.out.println(u);
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(null, null, conn);
              }
          }
      }
      

16. 调用函数&存储过程

  • 步骤

    1. 通过Connection对象的prepareCall()方法创建一个CallableStatement对象的实例。在使用Connection对象的preparedCall()方法时,需要传入一个String类型的字符串,该字符串用于指明如何调用存储过程。(注意:函数名不需要带<>,同理传参也不用[],不能理解的话看例子,不然会报错)

      {?= call <procedure-name> [(<arg1>, <arg2>, ...)]}
      call <procedure-name> [(<arg1>, <arg2>, ...)]}
      
    2. 通过CallableStatement对象的registerOutParameter()方法注册OUT参数

    3. 通过CallableStatement对象的setXxx()方法设定IN或IN OUT参数

      若想将参数默认值设为null,可以使用setNull()方法

    4. 通过CallableStatement对象的execute()方法执行存储过程

    5. 如果所调用的是带返回参数的存储过程,还需要通过CallableStatement对象的getXxx()方法获取其返回值

  • 通过数据字典查看存储过程或函数的定义:select text from user_source where lower(name) = 'add_sal_procedure;'

  • 实验

    • 我自己写了一个函数

      create function sum_salary(name varchar(20), i int) returns varchar(20)
      begin
      declare result int default 0;
      declare r varchar(20) default 'aaa';
      
      select count(*) into result from t_user where username=name and i = id;
      select if(result>0, '成功!', '失败!') into r;
      return r;
      end;
      
    • 这个函数在idea中显示的是

      create
          definer = root@localhost function sum_salary(name varchar(20), i int) returns varchar(20)
      begin
      declare result int default 0;
      declare r varchar(20) default 'aaa';
      
      select count(*) into result from t_user where username=name and i = id;
      select if(result>0, '成功!', '失败!') into r;
      return r;
      end;
      
    • 函数的功能是输入用户名和id,查看是否有这个人,测试如下

      package com.litian.jdbc;
      
      import java.sql.CallableStatement;
      import java.sql.Connection;
      import java.sql.Types;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestCalllableStatement.java
       * @time: 2020/4/8 12:14
       * @desc: |如何使用JDBC调用存储在数据库中的函数或存储过程
       */
      
      public class TestCalllableStatement {
          public static void main(String[] args){
              Connection conn = null;
              CallableStatement cs = null;
              try {
                  conn = JDBCTools.getDSConnection();
      
                  // 1. 通过Connection对象的prepareCall()方法创建一个CallableStatement对象的实例。
                  String sql = "{?= call sum_salary (?, ?)}";
                  cs = conn.prepareCall(sql);
      
                  // 2. 通过CallableStatement对象的registerOutParameter()方法注册OUT参数
                  cs.registerOutParameter(1, Types.VARCHAR);
                  // 3. 通过CallableStatement对象的setXxx()方法设定IN或IN OUT参数
                  cs.setString(2, "你大爷");
                  cs.setInt(3, 3);
                  // 4. 执行存储过程
                  cs.execute();
                  // 5. 获取返回值
                  String result = cs.getString(1);
                  System.out.println(result);
      
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  JDBCTools.release(null, cs, conn);
              }
          }
      }
      
    • 结果直接输出返回值!实验成功!


我的CSDN:https://blog.csdn.net/qq_21579045

我的博客园:https://www.cnblogs.com/lyjun/

我的Github:https://github.com/TinyHandsome

纸上得来终觉浅,绝知此事要躬行~

欢迎大家过来OB~

by 李英俊小朋友

posted @ 2020-04-08 14:02  李英俊小朋友  阅读(252)  评论(0编辑  收藏  举报