本文包括:
1、元數據- DatabaseMetaData
2、元數據- ParameterMetaData
3、元數據- ResultSetMetaData
4、使用元數據簡化JDBC代碼
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)
獲得指定列的類型
- getColumnCount()
-
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中的數據映射到實體對象中。
-
寫框架思路:
將不變的內容留在框架內部
將變的內容作為參數或者配置文件
-
具體步驟:
-
通用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()); }
-
-
通用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); }
-
-
通用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文集:
Java 與數據庫的橋梁——JDBC:http://www.lxweimin.com/p/c0acbd18794c
JDBC 進階——連接池:http://www.lxweimin.com/p/ad0ff2961597
JDBC 進階——元數據:http://www.lxweimin.com/p/36d5d76342f1
JDBC框架——DBUtils:http://www.lxweimin.com/p/10241754cdd7