JDBC的全稱是Java Database Connectivity,即Java數據庫鏈接,它是一種能夠執行SQL語句的Java API。程序可經過JDBC API鏈接到關係數據庫,並使用結構化查詢語言(SQL,數據庫標準的查詢語言)來完成對數據庫的查詢、更新java
與其餘數據庫編程環境相比,JDBC爲數據庫開發提供了標準的API,使用JDBC開發的數據庫應用能夠跨平臺運行,並且還能夠跨數據庫(若是所有使用標準的SQL語句)。也就是說若是使用JDBC開發一個數據庫應用,則該應用既能夠在Windows操做系統上運行,又能夠在Unix等其餘操做系統上運行,既可使用MySQL數據庫,又可使用Oracle等其餘的數據庫,應用程序不須要作任何的修改mysql
Java語言的各類跨平臺特性,都採用類似的結構。由於他們都須要讓相同的程序在不一樣的平臺上運行,因此須要中間的轉換程序(爲了實現Java程序的跨平臺,Java爲不一樣的操做系統提供了不一樣的Java虛擬機)。一樣,爲了JDBC程序能夠跨平臺,也須要不一樣的數據庫廠商提供相應的驅動程序正則表達式
Sun提供的JDBC能夠完成如下三個基本操做:sql
創建與數據庫的連接數據庫
執行SQL語句編程
得到SQL語句的執行結果設計模式
數據庫驅動程序是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安全性更高,更易部署
JAVA8關於JDBC4.2的新增功能:
DriverManager:用於管理JDBC驅動的服務類。程序中使用該類的主要功能是獲取Connection對象,該類包含以下方法
public static synchronized Connection getConnection(String url, String user, String password) throws SQLException:該方法得到url對應數據庫的鏈接
Connection:表明數據庫鏈接對象,每一個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()兩個方法來控制數據庫鏈接的超時行爲
Statement:用於執行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」
PreparedStatement:預編譯的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語句中指定位置的參數
ResultSet:結果集對象。該對象包含訪問查詢結果的方法,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)方法來獲取當前行、指定列的值,前者根據列索引獲取值,後者根據列名獲取值
一般使用Class類的forName()靜態方法來加載驅動
// 加載驅動,driverClass就是數據庫驅動類所對應的字符串 Class.forName(driverClass); // 加載MySQL的驅動 Class.forName("com.mysql.jdbc.Driver"); // 加載Oracle的驅動 Class.forName("oracle.jabc.driver.OracleDriver");
// 獲取數據庫鏈接 DriverManager.getConnection(String url, Stirng user, String pass)
當使用DriverManager來獲取連接,一般須要傳入三個參數:數據庫URL、登陸數據庫的用戶名和密碼
數據庫URL一般遵循以下寫法:jdbc是固定的;subprotocol指定鏈接到特定數據庫的驅動;other和stuff也是不固定的
jdbc:subprotocol:other stuff
createStatement():建立基本的Statement對象
prepareStatement(String sql):根據傳入的SQL語句建立預編譯的Statement對象
prepareCall(String sql):根據傳入的SQL語句建立CallableStatement對象
execute():能夠執行任何SQL語句,但比較麻煩
executeUpdate():主要用於執行DML和DDL語句。執行DML返回受影響的SQL語句行數,執行DDL返回0
executeQuery():只能執行查詢語句,執行後返回表明查詢結果的ResultSet對象
若是執行的SQL語句是查詢語句,則執行結果將返回一個ResultSet對象,該對象裏保存了SQL語句查詢的結果。程序能夠經過操做該ResultSet對象來取出查詢結果。ResultSet對象主要提供了以下兩類方法
next()、previous()、first()、last()、beforeFrist()、afterLast()、absolute()等移動指針的方法
getXxx()方法獲取記錄指針指向行,特定列的值。既可以使用列名做爲參數可讀性更好、使用索引做爲參數性能更好
包括關閉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) + "\t" + rs.getString(2) + "\t" + rs.getString(3) + "\t" + rs.getString(4)); } } } }
如下程序示範了使用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 + "條記錄受影響--"); } }
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) + "\t"); } System.out.print("\n"); } } } 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對象使用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等關鍵字
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) + "\t" + rs.getString(2) + "\t" + 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——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); } } }
描述ResultSet信息的數據——ResultSetMetaData
MetaData即元數據,即描述其它數據的數據,所以ResultSetMetaData封裝了描述ResultSet對象的數據
ResultSet的getMetaData()方法返回該ResultSet對應的ResultSetMetaData對象,就可經過ResultSetMetaData提供的大量方法返回ResultSet的描述信息
int getColumnCount():返回該ResultSet的列數量
String getColumnName(int column):返回指定索引的列名
int getColumnType(int column):返回指定索引的列類型
RowSet接口繼承了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) + "\t" + jdbcRs.getString(2) + "\t" + 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對象則徹底能夠當成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) + "\t" + rs.getString(2) + "\t" + 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); } }
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) + "\t" + rs.getString(2) + "\t" + rs.getString(3)); } }
}
事務是由一步或幾步數據庫操做序列組成的邏輯執行單元,這系列操做要麼所有執行,要麼所有放棄執行。
事務具備四個特性:原子性(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鏈接的事務支持由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)方法來回滾到指定中間點
批量更新必須獲得底層數據庫的支持,可經過調用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);
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) + "\t"); } System.out.print("\n"); //打印ResultSet的所有數據 while(rs.next()){ for(int i = 0; i < rsmd.getColumnCount(); i ++){ System.out.print(rs.getString(i + 1) + "\t"); } System.out.print("\n"); } 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則不會出現這種問題
若是須要純粹地分析數據庫的靜態對象,例如分析數據庫系統裏包含多少數據庫、數據表、視圖、索引等信息,則利用系統會更加合適
數據庫鏈接的創建以及關閉是極耗費系統資源的操做,在多層結構的應用環境中,這種資源的耗費對系統性能影響尤其明顯。經過前面介紹的方式(經過DriverManager獲取鏈接)獲取的數據庫鏈接,一個數據庫鏈接對象均對應一個物理數據庫鏈接,每次操做都打開一個物理鏈接,使用完後當即關閉鏈接。頻繁地打開、關閉鏈接將形成系統性能低下
數據庫鏈接池的解決方案是: 當應用程序啓動時,系統主動創建足夠的數據庫鏈接,並將這些鏈接組成一個鏈接池。每次應用程序請求數據庫鏈接時,無須從新打開鏈接,而是從鏈接池中取出已有的鏈接使用,使用完後再也不關閉數據庫鏈接,而是直接將鏈接歸還給鏈接池。經過使用鏈接池,將大大提供程序的運行效率
對於共享資源的惡狀況,有一個通用的設計模式:資源池(Resource Pool),用於解決資源的頻繁請求、釋放所形成的性能降低。爲了解決數據庫鏈接的頻繁請求,JDBC2.0規範引入了數據庫鏈接池技術
數據庫鏈接池是Connection對象的工廠。數據庫鏈接池的經常使用參數以下:
數據庫的初始鏈接數
鏈接池的最大鏈接數
鏈接池的最小鏈接數
鏈接池每次增長的容量
JDBC的數據庫鏈接池使用javax.sql.DataSource來表示,DataSource只是一個接口,該接口一般由商用服務器(如WebLogic、WeSphere)等提供實現,也有一些開源組織提供實現(如DBCP和C3P0)
DataSource一般被稱爲數據源,它包含鏈接池和鏈接池管理兩個部分,但習慣上咱們也常常把DataSource稱爲鏈接池
DBCP是Apache軟件基金組織下的開源鏈接池實現,該鏈接池依賴該組織下的另外一個開源系統:common-pool。若是須要使用該鏈接池實現,則應在系統中增長以下兩個jar文件:
commons-dbcp.jar:鏈接池的實現
commons-pool.jar:鏈接池實現的依賴庫
Tomcat的鏈接池正是採用該鏈接池實現的。數據庫鏈接池既能夠與應用服務器整合使用,也能夠由應用程序獨立使用。下面的代碼片斷示範了使用DBCP來得到數據庫鏈接的方式:
// 建立數據源對象 BasicDataSource ds = new BasicDataSource(); // 設置鏈接池所需的驅動 ds.setDriverClassName("com.mysql.jdbc.Driver"); // 設置鏈接數據庫的URL ds.setUrl("jdbc:mysql://localhost:3306/javaee"); // 設置鏈接數據庫的用戶名 ds.setUsername("root"); / /設置鏈接數據庫的密碼 ds.setPassword("pass"); // 設置鏈接池的初始鏈接數 ds.setInitialSize(5); // 設置鏈接池最多可有多少個活動鏈接數 ds.setMaxActive(20); // 設置鏈接池中最少有2個空閒的鏈接 ds.setMinIdle(2);
數據源和數據庫鏈接不一樣,數據源無須建立多個,它是產生數據庫鏈接的工廠,所以整個應用只須要一個數據源便可。也就是說,對於一個應用,上面代碼只要執行一次便可。建議把上面程序中的ds設置成static成員變量,而且在應用開始時當即初始化數據源對象,程序中全部須要獲取數據庫鏈接的地方直接訪問該ds對象,並獲取數據庫鏈接便可
// 經過數據源獲取數據庫鏈接 Connection conn = ds.getConnection(); // 當數據庫訪問結束後,釋放數據庫鏈接 conn.close(); // 上面代碼並無關閉據庫的物理鏈接 // 僅僅把數據庫鏈接釋放,歸還給鏈接池 // 讓其餘客戶端可使用該鏈接
C3P0數據源性能更勝一籌,Hibernate推薦使用該鏈接池。C3P0鏈接池不只能夠自動清理再也不使用的Connection,還能夠自動清理Statement和ResultSet
若是須要使用C3P0鏈接池,則應在系統中增長以下JAR文件
c3p0-0.9.1.2.jar: C3P0鏈接池的實現
// 建立鏈接池實例 ComboPooledDataSource ds = new ComboPooledDataSource(); // 設置鏈接池鏈接數據庫所需的驅動 ds.setDriverClass("com.mysql.jdbc.Driver"); // 設置鏈接數據庫的url ds.setJdbcUrl("jdbc:mysql://localhost:3306/javaee"); // 設置鏈接數據庫的用戶名、密碼 ds.setUser("root"); ds.setPassword("root"); // 設置鏈接池的最大鏈接數 ds.setMaxPoolSize(40); // 設置鏈接池的最小鏈接數 ds.setMinPoolSize(2); // 設置鏈接池的初始鏈接數 ds.setInitialPoolSize(10); // 設置鏈接池的緩存Statment的最大數 ds.setMaxStatements(180); //得到數據庫鏈接 Connection conn = ds.getConnection();