摘要:和很像,嚴格來說,應該是模仿了是設計。程序中使用該類的主要功能是獲取對象,該類包含如下方法該方法獲得對應數據庫的連接代表數據庫連接對象,每個代表一個物理連接會話。當執行查詢時,返回查詢到的結果集。程序可以通過操作該對象來取出查詢結果。
JDBC基礎
JDBC的全稱是Java Database Connectivity,即Java數據庫連接,它是一種可以執行SQL語句的Java API。程序可通過JDBC API連接到關系數據庫,并使用結構化查詢語言(SQL,數據庫標準的查詢語言)來完成對數據庫的查詢、更新
與其他數據庫編程環境相比,JDBC為數據庫開發提供了標準的API,使用JDBC開發的數據庫應用可以跨平臺運行,而且還可以跨數據庫(如果全部使用標準的SQL語句)。也就是說如果使用JDBC開發一個數據庫應用,則該應用既可以在Windows操作系統上運行,又可以在Unix等其他操作系統上運行,既可以使用MySQL數據庫,又可以使用Oracle等其他的數據庫,應用程序不需要做任何的修改
JDBC簡介Java語言的各種跨平臺特性,都采用相似的結構。因為他們都需要讓相同的程序在不同的平臺上運行,所以需要中間的轉換程序(為了實現Java程序的跨平臺,Java為不同的操作系統提供了不同的Java虛擬機)。同樣,為了JDBC程序可以跨平臺,也需要不同的數據庫廠商提供相應的驅動程序
Sun提供的JDBC可以完成以下三個基本操作:
建立與數據庫的鏈接
執行SQL語句
獲得SQL語句的執行結果
JDBC驅動程序數據庫驅動程序是JDBC程序和數據庫之間的轉換層,數據庫驅動程序負責將JDBC調用映射成特定的數據庫調用
ODB,Open Database Connectivity,即開放數據庫鏈接。ODBC和JDBC很像,嚴格來說,應該是JDBC模仿了ODBC是設計。ODBC也允許應用程序通過一種通用的API訪問不同的數據庫管理系統,從而使得基于ODBC的應用程序可以在不同的數據庫之間切換。同樣,ODBC也需要各數據庫廠商提供相應的驅動程序,而ODBC負責管理這些驅動程序
JDBC驅動通常有如下4種類型
JDBC + ODBC橋的方式
直接將JDBC API隱射成數據庫特定的客戶端API。這種驅動包含特定數據庫的本地代碼,用于訪問特定數據庫的客戶端
支持三層結構的JDBC訪問方式,主要用于Applet階段,通過Applet訪問數據庫
純java的,直接與數據庫實例交互,。這種驅動是智能型的,它知道數據庫使用的底層協議,是目前最流行的JDBC驅動
通常建議選擇第4種JDBC驅動,這種驅動避開了本地代碼,減少了應用開發的復雜性,也減少了產生沖突和出錯的可能。如果對性能有嚴格的要求,則可以考慮使用第2種JDBC驅動,但使用這種驅動,則勢必增加編碼和維護的困難
JDBC比ODBC多了如下幾個優勢
ODBC更復雜,ODBC中有幾個命令需要配置很多復雜的選項,而JDBC則采用簡單、直觀的方式來管理數據庫連接
JDBC比ODBC安全性更高,更易部署
JDBC的經典用法 JDBC 4.2常用接口和類簡介JAVA8關于JDBC4.2的新增功能:
DriverManager:用于管理JDBC驅動的服務類。程序中使用該類的主要功能是獲取Connection對象,該類包含如下方法
public static synchronized Connection getConnection(String url, String user, String password) throws SQLException:該方法獲得url對應數據庫的連接
ConnectionConnection:代表數據庫連接對象,每個Connection代表一個物理連接會話。要想訪問數據庫,必須先得到數據庫連接。該接口的常用方法如下:
Statement createStatement() throws SQLException:該方法返回一個Statement對象
PreparedStatement prepareStatement(String sql) throws SQLException:該方法返回預編譯的Statement對象,即將SQL語句提交到數據庫進行預編譯
CallableStatement prepareCall(String sql) throws SQLException:該方法返回CallableStatement對象,該對象用于調用存儲過程
上面三個方法都返回用于執行SQL語句的Statement對象,PreparedStatement、CallableStatement是Statement的子類,只有獲得了Statement之后才可以執行SQL語句
除此之外,Connection還有如下幾個用于控制事務的方法:
Savepoint setSavepoint() throws SQLException:創建一個保存點
Savepoint setSavepoint(String name):以指定名字來創建一個保存點
void setTransactionIsolation(int level):設置事務的隔離級別
void rollback():回滾事務
void rollback(Savepoint savepoint):將事務回滾到指定的保存點
void setAutoCommit(boolean autoCommit):關閉自動提交,打開事務
void commit() throws SQLException:提交事務
Java7位Connection新增了setSchema(String schema)、getSchema()兩個方法,這兩個方法用于控制該Connection訪問的數據庫Schema。還為Connection新增了setNetworkTimeout(Executor executor, int milliseconds)、getNetworkTimeout()兩個方法來控制數據庫連接的超時行為
StatementStatement:用于執行SQL語句的工具接口。該對象既可以執行DDL、DCL語句,也可以用于執行DML語句,還可以用于執行SQL查詢。當執行SQL查詢時,返回查詢到的結果集。它的常用方法如下:
ResultSet executeQuery(String sql) throws SQLException:該方法用于執行查詢語句,并返回查詢結果對應ResultSet對象。該方法只能用于執行查詢語句
int executeUpdate(String sql) throws SQLException:該方法用于執行DML語句,并返回受影響的行數;該方法也可用于執行DDL語句,執行DDL語句將返回0
boolean execute(String sql) throws SQLException:該方法可以執行任何SQL語句。如果執行后第一個結果為ResultSet對象,則返回true;如果執行后第一個結果為受影響的行數或沒有任何結果,則返回false
Java7為Statement新增了closeOnCompletion()方法,如果Statement執行了此方法,則當所有依賴于該Statement的ResultSet關閉時,該Statement會自動關閉。Java7還為Statement提供了一個isCloseOnCompletion()方法,該方法用于判斷該Statement是否打開了“closeOnCompletion”
PreparedStatementPreparedStatement:預編譯的Statement對象,PreparedStatement是Statement的子接口,它允許數據庫預編譯SQL語句(這些SQL語句通常帶有參數),以后每次只改變sql命令的參數,避免數據庫每次都需要編譯SQL語句,無需再傳入SQL語句,因此性能更好。使用PreparedStatement執行SQL語句時,無須再傳入SQL語句,只要為預編譯的SQL語句傳入參數值即可
PreparedStatement同樣有executeQuery()、executeUpdate()和execute()方法,只是這三個方法無須接收SQL字符串,因為PreparedStatement對象已預編譯了SQL命令,只要為這些方法傳入參數即可。所以它比Statement多了如下方法:
void setXxx(int parameterIndex, Xxx value):該方法根據傳入參數值的類型不同,需要使用不同的方法。傳入的值根據索引傳給SQL語句中指定位置的參數
ResultSetResultSet:結果集對象。該對象包含訪問查詢結果的方法,ResultSet可以通過列索引或列名獲得列數據。它包含了如下常用方法來移動記錄指針
void close():釋放ResultSet對象
boolean absolute(int row):將結果集的記錄指針移動到第row行,如果row是負數,則移動到倒數第row行,如果移動后的記錄指針指向一條有效記錄,則該方法返回true
void beforeFisrt():將ResultSet的記錄指針定位到首行之前,這是ResultSet結果集記錄指針的初始狀態——記錄指針的起始位置位于第一行之前。
boolean first():將ResultSet的記錄指針定位到首行。如果移動后的記錄指針指向一條有效記錄,則該方法返回true
boolean previous():將ResultSet的記錄指針定位到上一行,如果移動后的記錄指針指向一條有效記錄,則該方法返回true
boolean next():將結果集的記錄指針定位到下一行,如果移動后的記錄指針指向一條有效的記錄,則該方法返回true
boolean last():將結果集的記錄指針定位到最后一行,如果移動后的記錄指針指向一條有效的記錄,則該方法返回true
void afterLast():將ResultSet的記錄指針定位到最后一行之后
當把記錄指針移動到指定行之后,ResultSet可通過getXxx(int columnIndex)或getXxx(String columnLabel)方法來獲取當前行、指定列的值,前者根據列索引獲取值,后者根據列名獲取值
JDBC編程步驟 1 加載數據庫驅動通常使用Class類的forName()靜態方法來加載驅動
// 加載驅動,driverClass就是數據庫驅動類所對應的字符串 Class.forName(driverClass); // 加載MySQL的驅動 Class.forName("com.mysql.jdbc.Driver"); // 加載Oracle的驅動 Class.forName("oracle.jabc.driver.OracleDriver");2 通過DriverManager獲取數據庫的鏈接
// 獲取數據庫連接 DriverManager.getConnection(String url, Stirng user, String pass)
當使用DriverManager來獲取鏈接,通常需要傳入三個參數:數據庫URL、登錄數據庫的用戶名和密碼
數據庫URL通常遵循如下寫法:jdbc是固定的;subprotocol指定連接到特定數據庫的驅動;other和stuff也是不固定的
jdbc:subprotocol:other stuff3 通過Connection對象創建Statement(或者PreparedStatement)對象
createStatement():創建基本的Statement對象
prepareStatement(String sql):根據傳入的SQL語句創建預編譯的Statement對象
prepareCall(String sql):根據傳入的SQL語句創建CallableStatement對象
4 使用Statement執行SQL語句execute():可以執行任何SQL語句,但比較麻煩
executeUpdate():主要用于執行DML和DDL語句。執行DML返回受影響的SQL語句行數,執行DDL返回0
executeQuery():只能執行查詢語句,執行后返回代表查詢結果的ResultSet對象
5 操作結果集如果執行的SQL語句是查詢語句,則執行結果將返回一個ResultSet對象,該對象里保存了SQL語句查詢的結果。程序可以通過操作該ResultSet對象來取出查詢結果。ResultSet對象主要提供了如下兩類方法
next()、previous()、first()、last()、beforeFrist()、afterLast()、absolute()等移動指針的方法
getXxx()方法獲取記錄指針指向行,特定列的值。既可使用列名作為參數可讀性更好、使用索引作為參數性能更好
6 回收數據庫資源包括關閉ResultSet、Statement和Connection等資源
import java.sql.*; public class ConnMySql { public static void main(String[] args) throws Exception { // 1.加載驅動,使用反射的知識,現在記住這么寫。 Class.forName("com.mysql.jdbc.Driver"); try( // 2.使用DriverManager獲取數據庫連接, // 其中返回的Connection就代表了Java程序和數據庫的連接 // 不同數據庫的URL寫法需要查驅動文檔知道,用戶名、密碼由DBA分配 Connection conn = DriverManager.getConnection( "jdbc:mysql://127.0.0.1:3306/select_test" , "root" , "32147"); // 3.使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement(); // 4.執行SQL語句 /* Statement有三種執行sql語句的方法: 1 execute 可執行任何SQL語句。- 返回一個boolean值, 如果執行后第一個結果是ResultSet,則返回true,否則返回false 2 executeQuery 執行Select語句 - 返回查詢到的結果集 3 executeUpdate 用于執行DML語句。- 返回一個整數, 代表被SQL語句影響的記錄條數 */ ResultSet rs = stmt.executeQuery("select s.* , teacher_name" + " from student_table s , teacher_table t" + " where t.teacher_id = s.java_teacher")) { // ResultSet有系列的getXxx(列索引 | 列名),用于獲取記錄指針 // 指向行、特定列的值,不斷地使用next()將記錄指針下移一行, // 如果移動之后記錄指針依然指向有效行,則next()方法返回true。 while(rs.next()) { System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getString(3) + " " + rs.getString(4)); } } } }執行SQL語句的方式 使用Java8新增的executeLargeUpdate方法執行DDL和DML語句
以下程序示范了使用executeUpdate()方法(MySQL驅動暫不支持executeLargeUpdate()方法)創建數據表。該示例并沒有直接把數據庫連接信息寫在程序里,而是使用一個mysql.ini文件(properties文件)來保存數據庫連接信息,這是比較成熟的做法——當需要把應用程序從開發環境移植到生產環境時,無須修改源代碼,只需修改mysql.ini配置文件即可
import java.util.*; import java.io.*; import java.sql.*; public class ExecuteDDL { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void createTable(String sql)throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement()) { // 執行DDL,創建數據表 stmt.executeUpdate(sql); } } public static void main(String[] args) throws Exception { ExecuteDDL ed = new ExecuteDDL(); ed.initParam("mysql.ini"); ed.createTable("create table jdbc_test " + "( jdbc_id int auto_increment primary key, " + "jdbc_name varchar(255), " + "jdbc_desc text);"); System.out.println("-----建表成功-----"); } }
下面程序執行一條insert語句,這條insert語句會向剛剛建立的jdbc_test數據表中插入幾條記錄。因為使用了帶子查詢的insert語句,所以可以一次插入多條語句
import java.util.*; import java.io.*; import java.sql.*; public class ExecuteDML { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public int insertData(String sql)throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement()) { // 執行DML,返回受影響的記錄條數 return stmt.executeUpdate(sql); } } public static void main(String[] args)throws Exception { ExecuteDML ed = new ExecuteDML(); ed.initParam("mysql.ini"); int result = ed.insertData("insert into jdbc_test(jdbc_name,jdbc_desc)" + "select s.student_name , t.teacher_name " + "from student_table s , teacher_table t " + "where s.java_teacher = t.teacher_id;"); System.out.println("--系統中共有" + result + "條記錄受影響--"); } }使用execute方法執行SQL語句
Statement的execute()方法幾乎可以執行任何SQL語句,但它執行SQL語句時比較麻煩,通常沒有必要使用execute()方法來執行SQL語句,使用executeQuery()或executeUpdate()方法更簡單。但如果不清楚SQL語句的類型,則只能使用execute()方法來執行該SQL語句
getResult():獲取該Statement執行查詢語句所返回的ResultSet對象
getUpdateCount():獲取該Statement()執行DML語句所影響的記錄行數
import java.util.*; import java.io.*; import java.sql.*; public class ExecuteSQL { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void executeSql(String sql)throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個Statement對象 Statement stmt = conn.createStatement() ) { // 執行SQL,返回boolean值表示是否包含ResultSet boolean hasResultSet = stmt.execute(sql); // 如果執行后有ResultSet結果集 if (hasResultSet) { try( // 獲取結果集 ResultSet rs = stmt.getResultSet() ) { // ResultSetMetaData是用于分析結果集的元數據接口 ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); // 迭代輸出ResultSet對象 while (rs.next()) { // 依次輸出每列的值 for (int i = 0 ; i < columnCount ; i++ ) { System.out.print(rs.getString(i + 1) + " "); } System.out.print(" "); } } } else { System.out.println("該SQL語句影響的記錄有" + stmt.getUpdateCount() + "條"); } } } public static void main(String[] args) throws Exception { ExecuteSQL es = new ExecuteSQL(); es.initParam("mysql.ini"); System.out.println("------執行刪除表的DDL語句-----"); es.executeSql("drop table if exists my_test"); System.out.println("------執行建表的DDL語句-----"); es.executeSql("create table my_test" + "(test_id int auto_increment primary key, " + "test_name varchar(255))"); System.out.println("------執行插入數據的DML語句-----"); es.executeSql("insert into my_test(test_name) " + "select student_name from student_table"); System.out.println("------執行查詢數據的查詢語句-----"); es.executeSql("select * from my_test"); } }使用PreparedStatement執行SQL語句
創建PreparedStatement對象使用Connection的preparedStatement()方法,該方法需要傳入一個SQL字符串,該字符串可以包含符參數
// 創建一個PreparedStatement對象 pstmt = conn.preparedStatement("insert into student_table values(null,?,1)");
PreparedStatement也提供了execute()、executeUpdate()、executeQuery()三個方法來執行SQL語句,不過這三個方法無須參數,因為PreparedStatement提供了一系列的setXxx(int index, Xxx value)方法來傳入參數值
如果程序很清楚PreparedStatement預編譯SQL語句中各參數的類型,則使用相應的setXxx()方法來傳入參數即可;如果程序不清楚編譯SQL語句中各參數的類型,則可以使用setObject()方法來傳入參數,由PreparedStatement來負責類型轉換
下面程序示范使用Statement和PreparedStatement分別插入100條記錄的對比。使用Statement需要傳入100條SQL語句,但使用PreparedStatement則只需傳入1條預編譯的SQL語句,然后100次為該PreparedStatement的參數設值即可
import java.util.*; import java.io.*; import java.sql.*; public class PreparedStatementTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); // 加載驅動 Class.forName(driver); } public void insertUseStatement()throws Exception { long start = System.currentTimeMillis(); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement()) { // 需要使用100條SQL語句來插入100條記錄 for (int i = 0; i < 100 ; i++ ) { stmt.executeUpdate("insert into student_table values(" + " null ,"姓名" + i + "" , 1)"); } System.out.println("使用Statement費時:" + (System.currentTimeMillis() - start)); } } public void insertUsePrepare()throws Exception { long start = System.currentTimeMillis(); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個PreparedStatement對象 PreparedStatement pstmt = conn.prepareStatement( "insert into student_table values(null,?,1)")) { // 100次為PreparedStatement的參數設值,就可以插入100條記錄 for (int i = 0; i < 100 ; i++ ) { pstmt.setString(1 , "姓名" + i); pstmt.executeUpdate(); } System.out.println("使用PreparedStatement費時:" + (System.currentTimeMillis() - start)); } } public static void main(String[] args) throws Exception { PreparedStatementTest pt = new PreparedStatementTest(); pt.initParam("mysql.ini"); pt.insertUseStatement(); pt.insertUsePrepare(); } }
SQL注入是一個較常見的Cracker入侵方式,它利用SQL語句的漏洞來入侵。以下程序以一個簡單的登錄窗口為例來介紹這種SQL注入的結果。下面登錄窗口包含兩個文本框,一個用于輸入用戶名,一個用于輸入密碼,系統根據用戶輸入與jdbc_test表里的記錄進行匹配,如果找到相應記錄則提示登陸成功
public class LoginFrame { private final String PROP_FILE = "mysql.ini"; private String driver; // url是數據庫的服務地址 private String url; private String user; private String pass; // 登錄界面的GUI組件 private JFrame jf = new JFrame("登錄"); private JTextField userField = new JTextField(20); private JTextField passField = new JTextField(20); private JButton loginButton = new JButton("登錄"); public void init()throws Exception { Properties connProp = new Properties(); connProp.load(new FileInputStream(PROP_FILE)); driver = connProp.getProperty("driver"); url = connProp.getProperty("url"); user = connProp.getProperty("user"); pass = connProp.getProperty("pass"); // 加載驅動 Class.forName(driver); // 為登錄按鈕添加事件監聽器 loginButton.addActionListener(e -> { // 登錄成功則顯示“登錄成功” if (validate(userField.getText(), passField.getText())) { JOptionPane.showMessageDialog(jf, "登錄成功"); } // 否則顯示“登錄失敗” else { JOptionPane.showMessageDialog(jf, "登錄失敗"); } }); jf.add(userField , BorderLayout.NORTH); jf.add(passField); jf.add(loginButton , BorderLayout.SOUTH); jf.pack(); jf.setVisible(true); } private boolean validate(String userName, String userPass) { // 執行查詢的SQL語句 String sql = "select * from jdbc_test " + "where jdbc_name="" + userName + "" and jdbc_desc="" + userPass + """; System.out.println(sql); try( Connection conn = DriverManager.getConnection(url , user ,pass); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { // 如果查詢的ResultSet里有超過一條的記錄,則登錄成功 if (rs.next()) { return true; } } catch(Exception e) { e.printStackTrace(); } return false; } public static void main(String[] args) throws Exception { new LoginFrame().init(); } }
如果用戶正常輸入其用戶名、密碼,輸入正確時可以正常登陸,輸入錯誤將提示輸入失敗。但如果這個用戶是一個Cracker,可以輸入"or true or",也會顯示登陸成功。運行的后臺可以看到如下SQL語句
# 利用SQL注入后生成的SQL語句 select * from jdbc_test where jdbc_name = "" or true or "" and jdbc_desc = ""
如果換成使用PreparedStatement來執行驗證,而不是直接使用Statement
private boolean validate(String userName, String userPass) { try( Connection conn = DriverManager.getConnection(url , user ,pass); PreparedStatement pstmt = conn.prepareStatement( "select * from jdbc_test where jdbc_name=? and jdbc_desc=?")) { pstmt.setString(1, userName); pstmt.setString(2, userPass); try( ResultSet rs = pstmt.executeQuery()) { //如果查詢的ResultSet里有超過一條的記錄,則登錄成功 if (rs.next()) { return true; } } } catch(Exception e) { e.printStackTrace(); } return false; }
PreparedStatement預編譯SQL語句,性能更好
PreparedStatement無須“拼接”SQL字符串,編程更簡單
使用PreparedStatement可防止SQL注入,安全性更好
使用PreparedStatement執行帶占位符參數的SQL語句時,SQL語句中的占位符參數只能代替普通值,不要使用占位符參數代替表名、列名等數據庫對象,更不要用占位符參數來代替SQL語句中的insert、select等關鍵字
使用CallableStatement調用存儲過程MySQL數據庫中創建一個簡單的存儲過程的SQL語句
delimiter // create procedure add_pro(a int, b int ,out sum int) begin set sum = a + b; end; //
上面的SQL語句將MySQL的語句結束符改為雙斜線(//),這樣就可以在創建存儲過程中使用分號作為分隔符(默認使用分號作為語句結束符)程序創建了名為add_pro的存儲過程,該存儲過程包含三個參數:a、b是傳入參數,而sum使用out修飾,是傳出參數
調用存儲過程使用CallableStatement,通過Connection的prepareCall()方法來創建CallableStatement對象,創建該對象時需要傳入調用存儲過程的SQL語句。調用存儲過程的SQL語句總是這種格式:{call 過程名(?,?,?...)},其中的問號作為存儲過程參數的占位符
// 使用Connection來創建一個CallableStatement對象 cstmt = conn.prepareCall("{call add_pro(?,?,?)}");
存儲過程的參數既有傳入參數,也有傳出參數,所謂傳入參數就是Java程序必須為這些參數傳入值,可以通過CallableStatement的setXxx()方法為傳入參數設置值;所謂傳出參數就是Java程序可以通過該參數獲取存儲過程里的值,CallableStatement需要調用registerOutParameter()方法來注冊該參數
// 注冊CallableStatement的第三個參數是int類型 cstmt.registerOutParameter(3, Types.INTEGER);
之后調用CallableStatement的execute()方法來執行存儲過程,執行結束后通過CallableStatement對象的getXxx(int index)方法來獲取指定傳出參數的值
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.*; import java.io.*; import java.sql.*; public class CallableStatementTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void callProcedure()throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個CallableStatment對象 CallableStatement cstmt = conn.prepareCall( "{call add_pro(?,?,?)}")) { cstmt.setInt(1, 4); cstmt.setInt(2, 5); // 注冊CallableStatement的第三個參數是int類型 cstmt.registerOutParameter(3, Types.INTEGER); // 執行存儲過程 cstmt.execute(); // 獲取,并輸出存儲過程傳出參數的值。 System.out.println("執行結果是: " + cstmt.getInt(3)); } } public static void main(String[] args) throws Exception { CallableStatementTest ct = new CallableStatementTest(); ct.initParam("mysql.ini"); ct.callProcedure(); } }管理結果集
JDBC使用ResultSet來封裝執行查詢得到的查詢結果,然后通過移動ResultSet的記錄指針來取出結果集的內容。除此之外,JDBC還允許通過ResultSet來更新記錄,并提供了ResultSetMetaData來獲得ResultSet對象的相關信息
可滾動、可更新的結果集可滾動的結果集:可以使用absolute()、previous()、afterLast()等方法只有移動指針記錄的ResultSet
以默認形式打開的ResultSet是不可更新的,如果希望創建可更新的ResultSet,則必須在Connection在創建Statement或PreparedStatement時,傳入額外的參數:
resultSetType:控制ResultSet的類型,該參數可以取如下三個值
ResultSet.TYPE_FORWARD_ONLY:該常量控制記錄指針只能向前移動
ResultSet.TYPE_SCROLL_INSENSITIVE:該常量控制記錄指針自由移動(可滾動結果集),但底層的數據改變不影響結果集ResultSet的內容
ResultSet.TYPE_SCROLL_SENSITIVE:該常量控制記錄指針自由移動,但底層數據的影響會改變結果集ResultSet的內容
resultSetConcurrency:控制ResultSet的并發類型,該參數可以接收如下兩個值
ResultSet.CONCUR_READ_ONLY:該常量表示ResultSet是只讀并發模式(默認)
ResultSet.CONCUR_UPDATABLE:該常量表示ResultSet是更新并發模式
// 使用Connection創建一個PreparedStatement對象 // 傳入控制結果集可滾動、可更新的參數 pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
可更新的結果集還需要滿足如下兩個條件:
所有數據都應該來自一個表
選出的數據集必須包含主列鍵
通過該PreparedStatement創建的ResultSet就是可滾動的、可更新的,程序可調用的updateXxx(int columnIndex, Xxx value)方法來修改記錄指針所指記錄、特定列的值,最后調用ResultSet的updateRow()方法來提交修改
Java8為ResultSet添加了updateObject(String columnLabel, Object x, SQLType targetSqlType)和updateObject(int columnIndex, Object x, SQLType targetSqlType)兩個默認方法,這兩個方法可以直接用Object來修改記錄指針所指記錄、特定列的值,其中SQLType用于指定該數據列的類型
import java.util.*; import java.io.*; import java.sql.*; public class ResultSetTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void query(String sql)throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個PreparedStatement對象 // 傳入控制結果集可滾動,可更新的參數。 PreparedStatement pstmt = conn.prepareStatement(sql , ResultSet.TYPE_SCROLL_INSENSITIVE , ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery()) { rs.last(); int rowCount = rs.getRow(); for (int i = rowCount; i > 0 ; i-- ) { rs.absolute(i); System.out.println(rs.getString(1) + " " + rs.getString(2) + " " + rs.getString(3)); // 修改記錄指針所有記錄、第2列的值 rs.updateString(2 , "學生名" + i); // 提交修改 rs.updateRow(); } } } public static void main(String[] args) throws Exception { ResultSetTest rt = new ResultSetTest(); rt.initParam("mysql.ini"); rt.query("select * from student_table"); } }處理Blob類型數據
Blob——Binary long Object——二進制長對象,Blob列通常用于存儲大文件,典型的Blob內容是一張圖片或者一個聲音文件,由于他們的特殊性,必須使用特殊的方式來存儲。使用Blob列可以把照片聲音等文件的二進制數據保存在數據庫里,并可以從數據庫里恢復指定文件
如果需要將圖片插入數據庫,顯然不能通過普通的SQL語句來完成,因為有一個關鍵的問題,Blob常量無法表示,所以將Blob數據插入數據庫需要使用PreparedStatement。該對象有一個方法:setBinaryStream(int parameterIndex, InputStream x)該方法可以為指定參數傳入二進制流,從而可以實現將Blob數據保存到數據庫的功能
當需要從ResultSet里取出Blob數據時,可以調用ResultSet的getBlob(int columnIndex)方法,該方法將返回一個Blob對象Blob對象提供了getBinaryStream()方法獲取該獲取該Blob數據的輸入流,也可以使用Blob對象的getBytes()方法直接取出該Blob對象封裝的二進制數據
為了把圖片放入數據庫,本程序先使用如下SQL語句來建立一個數據表:
create table img_table { img_id int auto_increment primary key, img_name varchar(255), # 創建一個mediumblob類型的數據列,用于保存圖片數據 ima_data mediumblob };
img_data列使用mediumblob類型,而不是blob類型。因為MySQL數據庫里的blob類型最多只能存儲64kb的內容,所以使用mediumblob類型,該類型可以存儲16M內容
// ---------將指定圖片放入數據庫--------- public void upload(String fileName) { // 截取文件名 String imageName = fileName.substring(fileName.lastIndexOf("") + 1, fileName.lastIndexOf(".")); File f = new File(fileName); try( InputStream is = new FileInputStream(f) ) { // 設置圖片名參數 insert.setString(1, imageName); // 設置二進制流參數 insert.setBinaryStream(2, is, (int)f.length()); int affect = insert.executeUpdate(); if (affect == 1) { // 重新更新ListModel,將會讓JList顯示最新的圖片列表 fillListModel(); } } catch (Exception e) { e.printStackTrace(); } } // ---------根據圖片ID來顯示圖片---------- public void showImage(int id)throws SQLException { // 設置參數 query.setInt(1, id); try( // 執行查詢 ResultSet rs = query.executeQuery() ) { if (rs.next()) { // 取出Blob列 Blob imgBlob = rs.getBlob(1); // 取出Blob列里的數據 ImageIcon icon=new ImageIcon(imgBlob.getBytes(1L, (int)imgBlob.length())); imageLabel.setIcon(icon); } } }使用resultsetmetaData分析結果集
描述ResultSet信息的數據——ResultSetMetaData
MetaData即元數據,即描述其它數據的數據,因此ResultSetMetaData封裝了描述ResultSet對象的數據
ResultSet的getMetaData()方法返回該ResultSet對應的ResultSetMetaData對象,就可通過ResultSetMetaData提供的大量方法返回ResultSet的描述信息
int getColumnCount():返回該ResultSet的列數量
String getColumnName(int column):返回指定索引的列名
int getColumnType(int column):返回指定索引的列類型
Java7的RowSet1.1RowSet接口繼承了ResultSet接口,RowSet接口下包含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口。除了JdbcRowSet需要保持與數據庫的連接之外,其余4個子接口都是離線的RowSet,無須保持與數據庫的連接
RowSet默認是一個可滾動,可更新,可序列化的結果集,而且它作為JavaBeans,可以方便地在網絡間傳輸,用于兩端的數據同步。對于離線RowSet而言,程序在創建RowSet時已把數據從底層數據庫讀取到了內存,因此可以充分利用計算機的內存,從而降低數據庫服務器的負載,提供程序性能
RowSet規范的接口類圖
RowSet接口中定義的常用方法:
setUrl(String url):設置該RowSet要訪問的數據庫的URL
setUsername(String name):設置該RowSet要訪問的數據庫的用戶名
setPassword(String password):設置該RowSet要訪問的數據庫的密碼
setCommand(String sql):設置使用該sql語句的查詢結果來裝填該RowSet
execute():執行查詢
populate(ResultSet rs):讓該RowSet直接包裝給定的ResultSet對象
Java7新增了RowSetProvider類和RowSetFactory接口,其中RowSetProvider負載創建RowSetFactory,而RowSetFactory則提供了如下方法來創建RowSet實例:
CachedRowSet createCachedRowSet():創建一個默認的CachedRowSet
FilteredRowSet createFilteredRowSet():創建一個默認的FilteredRowSet
JoinRowSet createJoinRowSet():創建一個默認的JoinRowSet
WebRowSet createWebRowSet():創建一個默認的WebRowSet
JdbcRowSet createJdbcRowSet():創建一個默認的JdbcRowSet
提供使用RowSetFactory,就可以把應用程序與RowSet實現類分離開,避免直接使用JdbcRow SetImpl等非公開的API,也更有利于后期的升級、擴展
import java.util.*; import java.io.*; import java.sql.*; import javax.sql.rowset.*; public class RowSetFactoryTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void update(String sql)throws Exception { // 加載驅動 Class.forName(driver); // 使用RowSetProvider創建RowSetFactory RowSetFactory factory = RowSetProvider.newFactory(); try( // 使用RowSetFactory創建默認的JdbcRowSet實例 JdbcRowSet jdbcRs = factory.createJdbcRowSet() ) { // 設置必要的連接信息 jdbcRs.setUrl(url); jdbcRs.setUsername(user); jdbcRs.setPassword(pass); // 設置SQL查詢語句 jdbcRs.setCommand(sql); // 執行查詢 jdbcRs.execute(); jdbcRs.afterLast(); // 向前滾動結果集 while (jdbcRs.previous()) { System.out.println(jdbcRs.getString(1) + " " + jdbcRs.getString(2) + " " + jdbcRs.getString(3)); if (jdbcRs.getInt("student_id") == 3) { // 修改指定記錄行 jdbcRs.updateString("student_name", "源博雅"); jdbcRs.updateRow(); } } } } public static void main(String[] args)throws Exception { RowSetFactoryTest jt = new RowSetFactoryTest(); jt.initParam("mysql.ini"); jt.update("select * from student_table"); } }離線RowSet
離線RowSet會直接將底層數據讀入內存中,封裝成RowSet對象,而RowSet對象則完全可以當成Java Bean來使用。因此不僅安全,而且編程簡單。CachedRowSet是所有離線RowSet的父接口
如下程序①處調用了RowSet的populate(ResultSet rs)方法來包裝給的的ResultSet,接著關閉了ResultSet、Statement、Connection等數據庫資源。如果程序直接返回ResultSet,那么這個Result無法使用——因為底層的Connection已經關閉;但程序返回的是CachedRowSet,一個離線RowSet,因此程序依然可以讀取、修改RowSet中的記錄
為了將程序對離線RowSet所做的修改同步到底層數據庫,程序在調用RowSet的acceptChanges()方法時必須傳入Connection
public class CachedRowSetTest { private static String driver; private static String url; private static String user; private static String pass; public void initParam(String paramFile) throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public CachedRowSet query(String sql) throws Exception { // 加載驅動 Class.forName(driver); // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url, user, pass); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 使用RowSetProvider創建RowSetFactory RowSetFactory factory = RowSetProvider.newFactory(); // 創建默認的CachedRowSet實例 CachedRowSet cachedRs = factory.createCachedRowSet(); // 使用ResultSet裝填RowSet cachedRs.populate(rs); // ① // 關閉資源 rs.close(); stmt.close(); conn.close(); return cachedRs; } public static void main(String[] args)throws Exception { CachedRowSetTest ct = new CachedRowSetTest(); ct.initParam("mysql.ini"); CachedRowSet rs = ct.query("select * from student_table"); rs.afterLast(); // 向前滾動結果集 while (rs.previous()) { System.out.println(rs.getString(1) + " " + rs.getString(2) + " " + rs.getString(3)); if (rs.getInt("student_id") == 3) { // 修改指定記錄行 rs.updateString("student_name", "安倍晴明"); rs.updateRow(); } } // 重新獲取數據庫連接 Connection conn = DriverManager.getConnection(url, user, pass); conn.setAutoCommit(false); // 把對RowSet所做的修改同步到底層數據庫 rs.acceptChanges(conn); } }離線RowSet的查詢分頁
CachedRowSet的分頁功能:一次只裝載ResultSet里的某幾條記錄,這樣就可以避免CachedRowSet占用內存過大的問題
CachedRowSet提供了如下方法來控制分頁:
populate(ResultSet rs, int startRow):使用給定的Result裝填RowSet,從ResultSet的第startRow條記錄可是裝填
setPageSize(int pageSize):設置CachedRowSet每次返回記錄條數
previousPage():在底層ResultSet可用情況下,讓CachedRowSet讀取上一頁記錄
nextPage():在底層ResultSet可用情況下,讓CachedRowSet讀取下一頁記錄
public class CachedRowSetPage
{
private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public CachedRowSet query(String sql, int pageSize, int page) throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql) ) { // 使用RowSetProvider創建RowSetFactory RowSetFactory factory = RowSetProvider.newFactory(); // 創建默認的CachedRowSet實例 CachedRowSet cachedRs = factory.createCachedRowSet(); // 設置每頁顯示pageSize條記錄 cachedRs.setPageSize(pageSize); // 使用ResultSet裝填RowSet,設置從第幾條記錄開始 cachedRs.populate(rs, (page - 1) * pageSize + 1); return cachedRs; } } public static void main(String[] args)throws Exception { CachedRowSetPage cp = new CachedRowSetPage(); cp.initParam("mysql.ini"); CachedRowSet rs = cp.query("select * from student_table", 3, 2); // ① // 向后滾動結果集 while (rs.next()) { System.out.println(rs.getString(1) + " " + rs.getString(2) + " " + rs.getString(3)); } }
}
事務處理 事務的概念和MySQL事務支持事務是由一步或幾步數據庫操作序列組成的邏輯執行單元,這系列操作要么全部執行,要么全部放棄執行。
事務具有四個特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持續性(Durability)。這四個特性也簡稱ACID性
原子性:事務是應用中最小的執行單位,就如原子是自然界最小顆粒,具有不可再分的特征一樣。事務是應用中不可再分的最小邏輯執行體
一致性:事務執行的結果,必須使數據庫從一個一致性狀態,變到另一個一致性狀態。當數據庫中只包含事務成功提交的結果時,數據庫處于一致性狀態。一致性是通過原子性來保證的
隔離性:各個事務的執行互不干擾,任意一個事務的內部操作對其他并發的事務,都是隔離的。也就是說:并發執行的事務之間不能看到對方的中間狀態,并發執行的事務之間不能相互影響
持續性:持續性也稱為持久性,指事務一旦提交,對數據所做的任何改變,都要記錄到永久存儲器中,通常是保存進物理數據庫
數據庫的事務有下列語句組成:
一組DML(Data Manipulate Language,即數據操作語言),經過這組DML修改后數據將保持較好的一致性
一個DDL(Data Definition Language,即數據定義語言)語句
一個DCL(Data control Language,即數據控制語言)語句
DDL和DCL語句最多只能有一個,因為DDL和DCL語句都會導致事務立即提交
當事務所包含的全部數據庫操作都成功執行后,應該提交(commit)事務,使這些修改永久生效。事務提交有兩種方式:顯式提交和自動提交
顯式提交:使用commit
自動提交:執行DDL或DCL,或者程序正常退出
當事務所包含的任意一個數據庫操作執行失敗后,應該回滾(rollback)事務,使該事務中所做的修改全部失效。事務回滾的方式有兩種:顯式回滾和自動回滾
顯式回滾:使用rollback關鍵字
隱式回滾:系統錯誤或者強行退出
MySQL默認關閉事務(即打開自動提交事務),在默認情況下,在MySQL控制臺輸入一條DML語句,該語句會立刻保存到數據庫中。可以使用下面的語句來開啟事務(即關閉自動提交事務):
// 關閉自動提交,即開啟事務 set autocommit = 0; // 開啟自動提交,即關閉事務 set autocommit = 1;
調用 set autocommit = 0; 命令后,該命令行窗口里的所有DML語句都不會立即生效,上一個事務結束后第一條DML語句將開始一個新的事務,而后續執行的所有SQL語句都處于該事務中。除非使用commit提交事務、或正常退出、或運行DDL語句或DCL語句導致事務隱式提交。也可以使用rollback回滾來結束事務,使用rollback結束事務將會使此事務中的DML語句所做的修改全部失效
一個MySQL命令行窗口代表一個Session,在該窗口里設置set autocommit = 0; 相當于關閉了該連接Session的自動提交,對其他連接不會有任何影響
如果不想使得整個Session都打開事務,可以使用start transaction或begin這兩個命令,它們都表示臨時性地開始一次事務。處于start transaction或begin后的DML語句不會立即生效,除非使用commit顯式提交事務,或者使用DDL語句或DCL語句隱式提交事務
如下SQL將不會對數據庫有任何影響
# 臨時開始事務 begin; # 向player_table表插入3條數據 insert into player_table values(null, "Westbrook", 1); insert into player_table values(null, "Harden", 2); insert into player_table values(null, "Durant", 3); # 查詢player_table表的記錄 select * from player_table; # ① # 回滾事務 rollback; # 再次查詢 select * from player_table; # ②
通過使用savepoint設置事務的中間點可以讓事務回滾到指定中間點,而不是回滾全部事務。普通的提交、回滾都會結束當前事務,但回滾到指定中間點因為依然處于事務之中,所以不會結束當前事務
savepoint a; # 回滾到指定中間點 rollback to a;JDBC的事務支持
JDBC連接的事務支持由Connection提供,Connection默認打開自動提交,即關閉事務,在這種情況下,每條SQL語句一旦執行,便會立即提交到數據庫,永久生效,無法對其進行回滾操作
可以調用Connection的setAutoCommit()方法來關閉自動提交,開啟事務
//關閉自動提交,開啟事務 conn.setAutoCommit(false);
一旦事務開始之后,程序可以像平常一樣創建Statement對象,創建了Statement對象之后,可以執行任意多條DML語句,這些SQL語句雖然被執行了,但這些SQL語句所作的修改不會生效,因為事務還沒有結束。如果所有SQL語句執行成功,程序可以調用Connection的commit方法來提交事務
//提交事務 conn.commit();
如果任意一條SQL語句執行失敗,應該用Connection的rollback來回滾事務
//回滾事務 conn.rollback();
當Connection遇到一個未處理的SQLException異常時,系統將會非正常退出,事務也會自動回滾。但如果程序捕獲了該異常,則需要在異常處理塊中顯式地回滾事務
Connection設置中間點的方法:
Savepoint setSavepoint():在當前事務中創建一個未命名的中間點,并返回代表該中間點的Savepoint對象
Savepoint setSavepoint(String name):在當前事務中創建一個具有指定名稱的中間點,并返回代表該中間點的Savepoint對象
通常來說,設置中間點時沒有太大的必要指定名稱,因為Connection回滾到指定中間點時,并不是根據名字回滾的,而是根據中間點對象回滾的。Connection提供了rollback(Savepoint savepoint)方法來回滾到指定中間點
Java8增強的批量更新批量更新必須得到底層數據庫的支持,可通過調用DatabaseMetaData的supportsBatchUpdates()方法來查看底層數據庫是否支持批量更新
批量更新需要先創建一個Statement對象,然后利用該對象的addBatch()方法將多條SQL語句同時收集起來,最后調用Statement對象的executeBatch()(或executeLargeBatch())方法同時執行這些SQL語句
批量更新代碼:
Statement stmt = conn.createStatement(); //使用Statement同時收集多個SQL語句 stmt.addBatch(sql1); stmt.addBatch(sql2); stmt.addBatch(sql3); ... //同時執行所有的SQL語句 stmt.executeBatch();
為了讓批量操作可以正確地處理錯誤,必須把批量執行的操作視為單個事務,如果批量更新在執行過程中失敗,則讓事務回滾到批量操作開始之前的狀態。程序應該在開始批量操作之前先關閉自動提交,然后開始收集更新語句,當批量操作結束之后,提交事務,并恢復之前的自動提交模式
//保存當前的自動的提交模式 Boolean autoCommit = conn.getAutoCommit(); //關閉自動提交 conn.setAutoCommit(false); Statement stmt = conn.createStatement(); //使用Statement同時收集多條SQL語句 stmt.addBatch(sql1); stmt.addBatch(sql2); stmt.addBatch(sql3); ... //同時提交所有的SQL語句 stmt.executeBatch(); //提交修改 conn.commit(); //恢復原有的自動提交模式 conn.setAutoCommit(autoCommit);分析數據庫信息 使用DatabaseMetaData分析數據庫信息
JDBC提供了DatabaseMetaData來封裝數據庫連接對應數據庫的信息,通過Connection提供的getMetaData()方法就可以獲取數據庫對應的DatabaseMetaData對象
DatabaseMetaData接口通常由驅動程序供應商提供實現,其目的是讓用戶了解底層數據庫的相關信息。使用該接口的目的是發現如何處理底層數據庫,尤其是對于試圖與多個數據庫一起使用的應用程序
許多DatabaseMetaData方法以ResultSet對象的形式返回查詢信息,然后使用ResultSet的常規方法(如getString()和getInt())即可從這些ResultSet對象中獲取數據。如果查詢的信息不可用,則將返回一個空ResultSet對象
DatabaseMetaData的很多方法都需要傳入一個xxxPattern模式字符串,這里的xxxPattern不是正則表達式,而是SQL里的模式字符串,即用百分號(%)代表任意多個字符,使用下劃線(_)代表一個字符。在通常情況下,如果把該模式字符串的參數值設置為null,即表明該參數不作為過濾條件
import java.sql.*; import java.io.*; import java.util.*; public class DatabaseMetaDataTest{ private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception{ //使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void info() throws Exception{ //加載驅動 Class.forName(driver); try( //獲取數據庫連接 Connection conn = DriverManager.getConnection(url, user, pass); ){ //獲取DatabaseMetaData對象 DatabaseMetaData dbmd = conn.getMetaData(); //獲取MySQL支持的所有表類型 ResultSet rs = dbmd.getTableTypes(); System.out.println("---MySQL支持的表類型信息---"); printResultSet(rs); //獲取當前數據庫的全部數據表 rs = dbmd.getTables(null, null, "%", new String[]{"TABLE"}); System.out.println("---當前數據庫里的數據表信息---"); printResultSet(rs); //獲取student_table表的主鍵 rs = dbmd.getPrimaryKeys(null, null, "student_table"); System.out.println("---student_table表的主鍵信息---"); printResultSet(rs); //獲取當前數據庫的全部存儲過程 rs = dbmd.getProcedures(null, null, "%"); System.out.println("---當前數據庫里的存儲過程信息---"); printResultSet(rs); //獲取teacher_table表和student_table表之間的外鍵約束 rs = dbmd.getCrossReference(null, null, "teacher_table", null, null, "student_table"); System.out.println("---teacher_table表和student_table表之間的外鍵約束---"); printResultSet(rs); //獲取student_table表的全部數據列 rs = dbmd.getColumns(null, null, "student_table", "%"); System.out.println("---student_table表的全部數據列---"); printResultSet(rs); } } public void printResultSet(ResultSet rs) throws SQLException{ ResultSetMetaData rsmd = rs.getMetaData(); //打印ResultSet的所有列標題 for(int i = 0; i < rsmd.getColumnCount(); i++){ System.out.print(rsmd.getColumnName(i + 1) + " "); } System.out.print(" "); //打印ResultSet的全部數據 while(rs.next()){ for(int i = 0; i < rsmd.getColumnCount(); i ++){ System.out.print(rs.getString(i + 1) + " "); } System.out.print(" "); } rs.close(); } public static void main(String args[]) throws Exception{ DatabaseMetaDataTest dmdt = new DatabaseMetaDataTest(); dmdt.initParam("sql.ini"); dmdt.info(); } }使用系統表分析數據庫信息
如已確定應用程序所使用的數據庫系統,則可以通過數據庫的系統表來分析數據庫信息。系統表又稱為數據字典,數據字典的數據通常由數據庫系統負責維護,用戶通常只能查詢數據字典,而不能修改數據字典的內容
MySQL數據庫使用information_schema數據庫來保存系統表,在該數據庫里包含了大量系統表,常用系統表的簡單介紹如下:
tables:存放數據庫里所有數據表的信息
schemata:存放數據庫里所有數據庫(與MySQL的Schema對應)的信息
views:存放數據庫里所有視圖的信息
columns:存放數據庫里所有列的信息
triggers:存放數據庫里所有觸發器的信息
routines:存放數據庫里所有存儲過程和函數的信息
key_column_usage:存放數據庫里所有具有約束的鍵信息
table_constraints:存放數據庫里全部約束的表信息
statistics:存放數據庫里全部索引的信息
select * from schemata; select * from tables where table_schema = "select_test"; select * from columns where table_name = "student_table";選擇合適的分析方式
通常而言,如果使用DatabaseMetaData來分析數據庫信息,則具有更好的跨數據庫特性,應用程序可以做到數據庫無關;但可能無法準確獲取數據庫的更多細節
使用數據庫系統表來分析數據庫系統信息會更加準確,但使用系統表也有壞處——這種方式與底層數據庫耦合嚴重,采用這種方式將會導致程序只能運行在特定的數據庫之上
通常來說,如果需要獲得數據庫信息,包括該數據庫驅動提供了哪些功能,則應該利用DatabaseMetaData來了解該數據庫支持哪些功能。完全可能出現這樣一種情況:對于底層數據庫支持的功能,但數據庫驅動沒有提供該功能,程序還是不能使用該功能。使用DatabaseMetaData則不會出現這種問題
如果需要純粹地分析數據庫的靜態對象,例如分析數據庫系統里包含多少數據庫、數據
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66469.html
摘要:我們對語句做適當改變,就完成了注入,因為普通的不會對做任何處理,該例中單引號后的生效,拉出了所有數據。查詢資料后,發現還要開啟一個參數,讓端緩存,緩存是級別的。結論是個好東西。 背景 最近因為工作調整的關系,都在和數據庫打交道,增加了許多和JDBC親密接觸的機會,其實我們用的是Mybatis啦。知其然,知其所以然,是我們工程師童鞋們應該追求的事情,能夠幫助你更好的理解這個技術,面對問題...
摘要:不管是還是,表之間的連接查詢,被映射為實體類之間的關聯關系,這樣,如果兩個實體類之間沒有實現關聯關系,你就不能把兩個實體或者表起來查詢。 因為項目需要選擇數據持久化框架,看了一下主要幾個流行的和不流行的框架,對于復雜業務系統,最終的結論是,JOOQ是總體上最好的,可惜不是完全免費,最終選擇JDBC Template。 Hibernate和Mybatis是使用最多的兩個主流框架,而JOO...
摘要:學編程真的不是一件容易的事不管你多喜歡或是多會編程,在學習和解決問題上總會碰到障礙。熟練掌握核心內容,特別是和多線程初步具備面向對象設計和編程的能力掌握基本的優化策略。 學Java編程真的不是一件容易的事,不管你多喜歡或是多會Java編程,在學習和解決問題上總會碰到障礙。工作的時間越久就越能明白這個道理。不過這倒是一個讓人進步的機會,因為你要一直不斷的學習才能很好的解決你面前的難題...
摘要:同時也有一些兒高級的處理,比如批處理更新事務隔離和可滾動結果集等。連接對象表示通信上下文,即,與數據庫中的所有的通信是通過此唯一的連接對象。因為是針對類的關系而言,所以一個對象對應多個類的實例化。返回表示查詢返回表示其它操作。 JDBC是什么? JDBC是一個Java API,用中文可以通俗的解釋為,使用Java語言訪問訪問數據庫的一套接口集合。這是調用者(程序員)和實行者(數據庫廠商...
摘要:是訪問數據庫的標準規范提供了一種基準據此可以構建更高級的工具和接口,使數據庫開發人員能夠編寫數據庫應用程序。在將此值發送到數據庫時,驅動程序將它轉換成一個類型值。 1.JDBC概念和數據庫驅動程序 A: JDBC概述 JDBC(Java Data Base Connectivity,java數據庫連接)是一種用于執行SQL語句的Java API,可以為多種關系數據庫提供統一訪問,...
閱讀 1082·2021-11-19 09:40
閱讀 2222·2021-11-15 18:00
閱讀 1271·2021-10-18 13:34
閱讀 2253·2021-09-02 15:40
閱讀 1539·2019-08-30 14:01
閱讀 1118·2019-08-30 11:11
閱讀 2486·2019-08-29 15:26
閱讀 731·2019-08-29 14:15