JDBC 進階——元數據

本文包括:

1、元數據- DatabaseMetaData

2、元數據- ParameterMetaData

3、元數據- ResultSetMetaData

4、使用元數據簡化JDBC代碼

Paste_Image.png

1、元數據- DatabaseMetaData

  • 元數據:數據庫、表、列的定義信息,關于數據庫的整體綜合信息。

  • 獲得對象:Connection.getMetaData()

  • DataBaseMetaData類常用方法

    • getURL():返回一個String類對象,代表數據庫的URL。

    • getUserName():返回連接當前數據庫管理系統的用戶名。

    • getDriverName():返回驅動驅動程序的名稱。

    • getPrimaryKeys(String catalog, String schema, String table):返回指定表主鍵的結果集,一般catalog、schema都傳入null,得到一個結果集resultset,API文檔中有詳細描述:

      getPrimaryKeys(FROM API DOCUMENT)

      ResultSet getPrimaryKeys(String catalog,
      String schema,
      String table)
      throws SQLException

      獲取對給定表的主鍵列的描述。它們根據 COLUMN_NAME 進行排序。

      每個主鍵列描述都有以下列:

      • TABLE_CAT String =表類別(可為 null)
      • TABLE_SCHEM String =表模式(可為 null)
      • TABLE_NAME String =表名稱
      • COLUMN_NAME String =列名稱
      • KEY_SEQ short =主鍵中的序列號(值 1 表示主鍵中的第一列,值 2 表示主鍵中的第二列)。
      • PK_NAME String =主鍵的名稱(可為 null)

      參數:

      • catalog - 類別名稱;它必須與存儲在數據庫中的類別名稱匹配;該參數為 "" 表示獲取沒有類別的那些描述;為 null 則表示該類別名稱不應該用于縮小搜索范圍
      • schema - 模式名稱;它必須與存儲在數據庫中的模式名稱匹配;該參數為 "" 表示獲取沒有模式的那些描述;為 null 則表示該模式名稱不應該用于縮小搜索范圍
      • table - 表名稱;它必須與存儲在數據庫中的表名稱匹配

      返回:

      ResultSet - 每一行都是一個主鍵列描述

      拋出:

      SQLException - 如果發生數據庫訪問錯誤

    • demo:

        public void demo1() throws SQLException {
            // 通過Connection 獲得 DataBaseMetaData
            Connection conn = JDBCUtils.getConnection();
            DatabaseMetaData databaseMetaData = conn.getMetaData();
      
            // 獲得JDBC連接參數信息
            System.out.println(databaseMetaData.getURL());
            System.out.println(databaseMetaData.getDriverName());
            System.out.println(databaseMetaData.getUserName());
      
            // 獲得table主鍵信息
            ResultSet rs = databaseMetaData.getPrimaryKeys(null, null, "users");
            while (rs.next()) {
                System.out.println(rs.getString(3)); // 第三列,參照上問的描述,代表表名
                System.out.println(rs.getString("TABLE_NAME"));
                System.out.println(rs.getString(4));
            }
        }
      

2、元數據- ParameterMetaData

  • 獲得代表PreparedStatement元數據的ParameterMetaData對象,簡單來說: 獲得預編譯SQL語句中 “?” 信息。

      Select * from user where name=? And password=?
    
  • 獲得對象:PreparedStatement.getParameterMetaData()

  • ParameterMetaData類的常用方法:

    • getParameterCount()
      獲得指定參數的個數

    • getParameterType(int param)
      獲得指定參數的sql類型

      并不是所有數據庫都支持,使用MySQL時,這個方法只會返回varchar的int值(即14)

    • getParameterTypeName(int param) --- 參數類型名稱

  • getParameterType異常處理

      Parameter metadata not available for the given statement
    

    解決方法:url后面拼接參數

      ?generateSimpleParameterMetadata=true
    
  • demo:

      public void demo2() throws SQLException {
          Connection conn = JDBCUtils.getConnection();
          String sql = "select * from users where id = ? and password=?";
          PreparedStatement stmt = conn.prepareStatement(sql);
          // 通過ParameterMetaData 獲得 ? 相關信息
          ParameterMetaData parameterMetaData = stmt.getParameterMetaData();
          // 獲得個數
          int count = parameterMetaData.getParameterCount();
          System.out.println(count);
    
          for (int i = 1; i <= count; i++) {
              // 該方法并不是所有數據庫都支持 --- MySQL不支持(所有返回類型都是varchar)
              int type = parameterMetaData.getParameterType(i);
              System.out.println(type);
              System.out.println(parameterMetaData.getParameterTypeName(i));
          }
      }
    

3、元數據- ResultSetMetaData

  • 獲得對象:ResultSet.getMetaData()
    獲得代表ResultSet對象元數據的ResultSetMetaData對象。

  • ResultSetMetaData類常用方法:

    • getColumnCount()
      返回resultset對象的列數
    • getColumnName(int column)
      獲得指定列的名稱
    • getColumnTypeName(int column)
      獲得指定列的類型
  • demo:

      public void demo3() throws SQLException {
          Connection conn = JDBCUtils.getConnection();
          String sql = "select * from users";
          PreparedStatement stmt = conn.prepareStatement(sql);
    
          ResultSet rs = stmt.executeQuery();
          // 獲得結果集元數據
          ResultSetMetaData resultSetMetaData = rs.getMetaData();
    
          int count = resultSetMetaData.getColumnCount();
          // 打印table 第一行
          for (int i = 1; i <= count; i++) {
              System.out.print(resultSetMetaData.getColumnName(i) + "\t");
          }
          System.out.println();
    
          // 打印每列類型
          for (int i = 1; i <= count; i++) {
              System.out.print(resultSetMetaData.getColumnTypeName(i) + "\t");
          }
          System.out.println();
    
          // 打印table數據
          while (rs.next()) {
              for (int i = 1; i <= count; i++) {
                  System.out.print(rs.getObject(i) + "\t");
              }
              System.out.println();
          }
      }
    

4、使用元數據簡化JDBC代碼

  • 業務背景:系統中所有實體對象都涉及到基本的CRUD(create、read、update、delete)操作:

    • 所有實體的CUD操作代碼基本相同,僅僅發送給數據庫的SQL語句不同而已,因此可以把CUD操作的所有相同代碼抽取到工具類的一個update方法中,并定義參數接收變化的SQL語句。

    • 實體的R操作,除SQL語句不同之外,根據操作的實體不同,對ResultSet的映射也各不相同,因此可義一個query方法,除以參數形式接收變化的SQL語句外,可以使用策略模式由qurey方法的調用者決定如何把ResultSet中的數據映射到實體對象中。

  • 寫框架思路:

    • 將不變的內容留在框架內部

    • 將變的內容作為參數或者配置文件

  • 具體步驟:

    1. 通用CUD方法設計

      • framework代碼:

          /**
           * 通用insert update delete方法
           * 
           * @param sql
           *            預編譯需要SQL
           * @param args
           *            根據SQL中? 準備參數
           */
          public static void update(String sql, Object... args) {
              Connection conn = null;
              PreparedStatement stmt = null;
        
              try {
                  conn = JDBCUtils.getConnection();
        
                  stmt = conn.prepareStatement(sql);
                  // 設置參數 --- 根據?設置參數
                  ParameterMetaData parameterMetaData = stmt.getParameterMetaData();
                  int count = parameterMetaData.getParameterCount();
                  for (int i = 1; i <= count; i++) {
                      stmt.setObject(i, args[i - 1]);
                  }
        
                  stmt.executeUpdate();
        
              } catch (SQLException e) {
                  e.printStackTrace();
              } finally {
                  JDBCUtils.release(stmt, conn);
              }
          }
        
      • DAO代碼:

          public void updateUser(User user) {
              String sql = "update users set username=?,password=?,email=? where id =?";
              Object[] params = { user.getUsername(), user.getPassword(),
                      user.getEmail(), user.getId() };
              JDBCFramework.update(sql, params);
          }
        
          public void deleteAccount(Account account) {
              String sql = "delete from account where id = ?";
              JDBCFramework.update(sql, account.getId());
          }
        
    2. 通用R方法設計

      • 接口代碼:

          public interface MyResultSetHandler<T> {
              // 將rs中數據封裝對象
              public T handle(ResultSet rs);
          }
        
      • framework代碼:

          /**
           * 通用select方法
           */
          public static <T> T query(String sql, MyResultSetHandler<T> handler,
                  Object... args) {
              T obj = null;
        
              Connection conn = null;
              PreparedStatement stmt = null;
              ResultSet rs = null;
        
              try {
                  conn = JDBCUtils.getConnection();
                  stmt = conn.prepareStatement(sql);
        
                  // 設置參數
                  ParameterMetaData parameterMetaData = stmt.getParameterMetaData();
                  int count = parameterMetaData.getParameterCount();
                  for (int i = 1; i <= count; i++) {
                      stmt.setObject(i, args[i - 1]);
                  }
        
                  rs = stmt.executeQuery();
                  obj = handler.handle(rs);
        
              } catch (SQLException e) {
                  e.printStackTrace();
              } finally {
                  JDBCUtils.release(rs, stmt, conn);
              }
              return obj;
          }
        
      • DAO代碼;

          public Account findById(int id) {
              // 使用自定義框架
              String sql = "select * from account where id = ?";
              MyResultSetHandler handler = new MyResultSetHandler() { //匿名內部類
                  @Override
                  public Object handle(ResultSet rs) {
                      try {
                          if (rs.next()) {
                              Account account = new Account();
                              account.setId(rs.getInt("id"));
                              account.setName(rs.getString("name"));
                              account.setMoney(rs.getDouble("money"));
                              return account;
                          }
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                      return null;
                  }
              };
        
              return (Account) JDBCFramework.query(sql, handler, id);
          }
        
    3. 通用R方法設計——拓展:

      • 上面第2點的通用R方法設計,通過實現MyResultSetHandler接口,來實現rs的封裝,但從DAO代碼中可以看出并沒有省略很多代碼,不同的業務還是需要進行手動封裝

      • 所以,現在考慮設計一個通用接口,來實現自動封裝rs。

      • 框架實現(通用handler,處理將所有rs第一行數據 轉換指定 JavaBean對象):

          public class MyBeanHandler<T> implements MyResultSetHandler<T> {
          
              private Class<T> domainClass; 
          
              public MyBeanHandler(Class<T> domainClass) {
                  this.domainClass = domainClass; //通過字節碼文件
              }
          
              @Override
              public T handle(ResultSet rs) {
                  try {
                      ResultSetMetaData resultSetMetaData = rs.getMetaData();// 結果集元數據
                      int count = resultSetMetaData.getColumnCount();
          
                      BeanInfo beanInfo = Introspector.getBeanInfo(domainClass);
                      PropertyDescriptor[] descriptors = beanInfo
                              .getPropertyDescriptors();//屬性描述器
                      if (rs.next()) {
                          T t = domainClass.newInstance(); //因為不能通過泛型獲得T的實例,所以通過反射技術來獲得T的實例!!!
                          for (int i = 1; i <= count; i++) {
                              String columnName = resultSetMetaData.getColumnName(i);
                              // 獲得列名 --- 需要去查找匹配屬性
                              for (PropertyDescriptor propertyDescriptor : descriptors) {
                                  if (columnName.equals(propertyDescriptor.getName())) {
                                      // 列名 存在 同名屬性 ---- 列值 存到屬性里
                                      Method writeMethod = propertyDescriptor
                                              .getWriteMethod(); // 得到屬性的寫方法,如同setName、setMoney等方法
                                      writeMethod.invoke(t, rs.getObject(columnName));
                                  }
                              }
                          }
                          return t;
                      }
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  return null;
              }
          
          }
        
      • 接口代碼:

          public interface MyResultSetHandler<T> {
              // 將rs中數據封裝對象
              public T handle(ResultSet rs);
          }
        
      • DAO代碼:

          public User findById(int id) {
              String sql = "select * from users where id = ?";
              return JDBCFramework
                      .query(sql, new MyBeanHandler<User>(User.class), id);
          }
        

JDBC文集:

  1. Java 與數據庫的橋梁——JDBC:http://www.lxweimin.com/p/c0acbd18794c

  2. JDBC 進階——連接池:http://www.lxweimin.com/p/ad0ff2961597

  3. JDBC 進階——元數據:http://www.lxweimin.com/p/36d5d76342f1

  4. JDBC框架——DBUtils:http://www.lxweimin.com/p/10241754cdd7

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容