JDBC核心技術java
持久化(persistence):把數據保存到可掉電式存儲設備中以供以後使用。大多數狀況下,特別是企業級應用,數據持久化意味着將內存中的數據保存到硬盤上加以」固化」,而持久化的實現過程大多經過各類關係數據庫來完成。mysql
持久化的主要應用是將內存中的數據存儲在關係型數據庫中,固然也能夠存儲在磁盤文件、XML數據文件中。git
JDO (Java Data Object )技術程序員
第三方O/R工具,如Hibernate, Mybatis 等github
JDBC是sun公司提供一套用於數據庫操做的接口,java程序員只須要面向這套接口編程便可。web
不一樣的數據庫廠商,須要針對這套接口,提供不一樣實現。不一樣的實現的集合,即爲不一樣數據庫的驅動。 ————面向接口編程sql
補充:ODBC(Open Database Connectivity,開放式數據庫鏈接),是微軟在Windows平臺下推出的。使用者在程序中只須要調用ODBC API,由 ODBC 驅動程序將調用轉換成爲對特定的數據庫的調用請求。數據庫
java.sql.Driver 接口是全部 JDBC 驅動程序須要實現的接口。這個接口是提供給數據庫廠商使用的,不一樣數據庫廠商提供不一樣的實現。apache
在驅動jar上右鍵-->Build Path-->Add to Build Path編程
注意:若是是Dynamic Web Project(動態的web項目)話,則是把驅動jar放到WebContent(有的開發工具叫WebRoot)目錄中的WEB-INF目錄中的lib目錄下便可
加載驅動:加載 JDBC 驅動需調用 Class 類的靜態方法 forName(),向其傳遞要加載的 JDBC 驅動的類名
使用DriverManager.registerDriver(com.mysql.jdbc.Driver)來註冊驅動
一般不用顯式調用 DriverManager 類的 registerDriver() 方法來註冊驅動程序類的實例,由於 Driver 接口的驅動程序類都包含了靜態代碼塊,在這個靜態代碼塊中,會調用 DriverManager.registerDriver() 方法來註冊自身的一個實例。下圖是MySQL的Driver實現類的源碼:
JDBC URL 用於標識一個被註冊的驅動程序,驅動程序管理器經過這個 URL 選擇正確的驅動程序,從而創建到數據庫的鏈接。
舉例:
幾種經常使用數據庫的 JDBC URL
MySQL的鏈接URL編寫方式:
Oracle 9i的鏈接URL編寫方式:
SQLServer的鏈接URL編寫方式:
jdbc:sqlserver://主機名稱:sqlserver服務端口號:DatabaseName=數據庫名稱
jdbc:sqlserver://localhost:1433:DatabaseName=atguigu
@Test public void testConnection1() { try { //1.提供java.sql.Driver接口實現類的對象 Driver driver = null; driver = new com.mysql.jdbc.Driver(); //2.提供url,指明具體操做的數據 String url = "jdbc:mysql://localhost:3306/test"; //3.提供Properties的對象,指明用戶名和密碼 Properties info = new Properties(); info.setProperty("user", "root"); info.setProperty("password", "abc123"); //4.調用driver的connect(),獲取鏈接 Connection conn = driver.connect(url, info); System.out.println(conn); } catch (SQLException e) { e.printStackTrace(); } }
說明:上述代碼中顯式出現了第三方數據庫的API
@Test public void testConnection2() { try { //1.實例化Driver String className = "com.mysql.jdbc.Driver"; Class clazz = Class.forName(className); Driver driver = (Driver) clazz.newInstance(); //2.提供url,指明具體操做的數據 String url = "jdbc:mysql://localhost:3306/test"; //3.提供Properties的對象,指明用戶名和密碼 Properties info = new Properties(); info.setProperty("user", "root"); info.setProperty("password", "abc123"); //4.調用driver的connect(),獲取鏈接 Connection conn = driver.connect(url, info); System.out.println(conn); } catch (Exception e) { e.printStackTrace(); } }
說明:相較於方式一,這裏使用反射實例化Driver,不在代碼中體現第三方數據庫的API。體現了面向接口編程思想。
@Test public void testConnection3() { try { //1.數據庫鏈接的4個基本要素: String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "abc123"; String driverName = "com.mysql.jdbc.Driver"; //2.實例化Driver Class clazz = Class.forName(driverName); Driver driver = (Driver) clazz.newInstance(); //3.註冊驅動 DriverManager.registerDriver(driver); //4.獲取鏈接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); } catch (Exception e) { e.printStackTrace(); } }
說明:使用DriverManager實現數據庫的鏈接。體會獲取鏈接必要的4個基本要素。
@Test public void testConnection4() { try { //1.數據庫鏈接的4個基本要素: String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "abc123"; String driverName = "com.mysql.jdbc.Driver"; //2.加載驅動 (①實例化Driver ②註冊驅動) Class.forName(driverName); //Driver driver = (Driver) clazz.newInstance(); //3.註冊驅動 //DriverManager.registerDriver(driver); /* 能夠註釋掉上述代碼的緣由,是由於在mysql的Driver類中聲明有: static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } */ //3.獲取鏈接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); } catch (Exception e) { e.printStackTrace(); } }
說明:沒必要顯式的註冊驅動了。由於在DriverManager的源碼中已經存在靜態代碼塊,實現了驅動的註冊。
@Test public void testConnection5() throws Exception { //1.加載配置文件 InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); //2.讀取配置信息 String user = pros.getProperty("user"); String password = pros.getProperty("password"); String url = pros.getProperty("url"); String driverClass = pros.getProperty("driverClass"); //3.加載驅動 Class.forName(driverClass); //4.獲取鏈接 Connection conn = DriverManager.getConnection(url,user,password); System.out.println(conn); }
其中,配置文件聲明在工程的src目錄下:【jdbc.properties】
user=root password=abc123 url=jdbc:mysql://localhost:3306/test driverClass=com.mysql.jdbc.Driver
說明:使用配置文件的方式保存配置信息,在代碼中加載配置文件
使用配置文件的好處:
①實現了代碼和數據的分離,若是須要修改配置信息,直接在配置文件中修改,不須要深刻代碼
②若是修改了配置信息,省去從新編譯的過程。
數據庫鏈接被用於向數據庫服務器發送命令和 SQL 語句,並接受數據庫服務器返回的結果。其實一個數據庫鏈接就是一個Socket鏈接。
經過調用 Connection 對象的 createStatement() 方法建立該對象。該對象用於執行靜態的 SQL 語句,而且返回執行結果。
Statement 接口中定義了下列方法用於執行 SQL 語句:
int excuteUpdate(String sql):執行更新操做INSERT、UPDATE、DELETE ResultSet executeQuery(String sql):執行查詢操做SELECT
可是使用Statement操做數據表存在弊端:
SQL 注入是利用某些系統沒有對用戶輸入的數據進行充分的檢查,而在用戶輸入數據中注入非法的 SQL 語句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,從而利用系統的 SQL 引擎完成惡意行爲的作法。
對於 Java 而言,要防範 SQL 注入,只要用 PreparedStatement(從Statement擴展而來) 取代 Statement 就能夠了。
代碼演示:
public class StatementTest { // 使用Statement的弊端:須要拼寫sql語句,而且存在SQL注入的問題 @Test public void testLogin() { Scanner scan = new Scanner(System.in); System.out.print("用戶名:"); String userName = scan.nextLine(); System.out.print("密 碼:"); String password = scan.nextLine(); // SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1'; String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password + "'"; User user = get(sql, User.class); if (user != null) { System.out.println("登錄成功!"); } else { System.out.println("用戶名或密碼錯誤!"); } } // 使用Statement實現對數據表的查詢操做 public <T> T get(String sql, Class<T> clazz) { T t = null; Connection conn = null; Statement st = null; ResultSet rs = null; try { // 1.加載配置文件 InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); // 2.讀取配置信息 String user = pros.getProperty("user"); String password = pros.getProperty("password"); String url = pros.getProperty("url"); String driverClass = pros.getProperty("driverClass"); // 3.加載驅動 Class.forName(driverClass); // 4.獲取鏈接 conn = DriverManager.getConnection(url, user, password); st = conn.createStatement(); rs = st.executeQuery(sql); // 獲取結果集的元數據 ResultSetMetaData rsmd = rs.getMetaData(); // 獲取結果集的列數 int columnCount = rsmd.getColumnCount(); if (rs.next()) { t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { // //1. 獲取列的名稱 // String columnName = rsmd.getColumnName(i+1); // 1. 獲取列的別名 String columnName = rsmd.getColumnLabel(i + 1); // 2. 根據列名獲取對應數據表中的數據 Object columnVal = rs.getObject(columnName); // 3. 將數據表中獲得的數據,封裝進對象 Field field = clazz.getDeclaredField(columnName); field.setAccessible(true); field.set(t, columnVal); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { // 關閉資源 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } return null; } }
綜上:
能夠經過調用 Connection 對象的 preparedStatement(String sql) 方法獲取 PreparedStatement 對象
PreparedStatement 接口是 Statement 的子接口,它表示一條預編譯過的 SQL 語句
PreparedStatement 對象所表明的 SQL 語句中的參數用問號(?)來表示,調用 PreparedStatement 對象的 setXxx() 方法來設置這些參數. setXxx() 方法有兩個參數,第一個參數是要設置的 SQL 語句中的參數的索引(從 1 開始),第二個是設置的 SQL 語句中的參數的值
代碼的可讀性和可維護性。
PreparedStatement 能夠防止 SQL 注入
Java類型 | SQL類型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
byte array | BINARY , VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
//通用的增、刪、改操做(體現一:增、刪、改 ; 體現二:針對於不一樣的表) public void update(String sql,Object ... args){ Connection conn = null; PreparedStatement ps = null; try { //1.獲取數據庫的鏈接 conn = JDBCUtils.getConnection(); //2.獲取PreparedStatement的實例 (或:預編譯sql語句) ps = conn.prepareStatement(sql); //3.填充佔位符 for(int i = 0;i < args.length;i++){ ps.setObject(i + 1, args[i]); } //4.執行sql語句 ps.execute(); } catch (Exception e) { e.printStackTrace(); }finally{ //5.關閉資源 JDBCUtils.closeResource(conn, ps); } }
// 通用的針對於不一樣表的查詢:返回一個對象 (version 1.0) public <T> T getInstance(Class<T> clazz, String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 1.獲取數據庫鏈接 conn = JDBCUtils.getConnection(); // 2.預編譯sql語句,獲得PreparedStatement對象 ps = conn.prepareStatement(sql); // 3.填充佔位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 4.執行executeQuery(),獲得結果集:ResultSet rs = ps.executeQuery(); // 5.獲得結果集的元數據:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); // 6.1經過ResultSetMetaData獲得columnCount,columnLabel;經過ResultSet獲得列值 int columnCount = rsmd.getColumnCount(); if (rs.next()) { T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) {// 遍歷每個列 // 獲取列值 Object columnVal = rs.getObject(i + 1); // 獲取列的別名:列的別名,使用類的屬性名充當 String columnLabel = rsmd.getColumnLabel(i + 1); // 6.2使用反射,給對象的相應屬性賦值 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columnVal); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { // 7.關閉資源 JDBCUtils.closeResource(conn, ps, rs); } return null; }
說明:使用PreparedStatement實現的查詢操做能夠替換Statement實現的查詢操做,解決Statement拼串和SQL注入問題。
查詢須要調用PreparedStatement 的 executeQuery() 方法,查詢結果是一個ResultSet 對象
ResultSet 返回的實際上就是一張數據表。有一個指針指向數據表的第一條記錄的前面。
當指針指向一行時, 能夠經過調用 getXxx(int index) 或 getXxx(int columnName) 獲取每一列的值。
boolean next()
…
可用於獲取關於 ResultSet 對象中列的類型和屬性信息的對象
getColumnCount():返回當前 ResultSet 對象中的列數。
isNullable(int column):指示指定列中的值是否能夠爲 null。
isAutoIncrement(int column):指示是否自動爲指定列進行編號,這樣這些列仍然是隻讀的。
問題1:獲得結果集後, 如何知道該結果集中有哪些列 ? 列名是什麼?
須要使用一個描述 ResultSet 的對象, 即 ResultSetMetaData
問題2:關於ResultSetMetaData
面向接口編程的思想
sql是須要結合列名和表的屬性名來寫。注意起別名。
練習題1:從控制檯向數據庫的表customers中插入一條數據,表結構以下:
練習題2:創立數據庫表 examstudent,表結構以下:
向數據表中添加以下數據:
代碼實現1:插入一個新的student 信息
請輸入考生的詳細信息
Type:
IDCard:
ExamCard:
StudentName:
Location:
Grade:
信息錄入成功!
代碼實現2:在 eclipse中創建 java 程序:輸入身份證號或准考證號能夠查詢到學生的基本信息。結果以下:
代碼實現3:完成學生信息的刪除功能
插入BLOB類型的數據必須使用PreparedStatement,由於BLOB類型的數據沒法使用字符串拼接寫的。
MySQL的四種BLOB類型(除了在存儲的最大信息量上不一樣外,他們是等同的)
//獲取鏈接 Connection conn = JDBCUtils.getConnection(); String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)"; PreparedStatement ps = conn.prepareStatement(sql); // 填充佔位符 ps.setString(1, "徐海強"); ps.setString(2, "xhq@126.com"); ps.setDate(3, new Date(new java.util.Date().getTime())); // 操做Blob類型的變量 FileInputStream fis = new FileInputStream("xhq.png"); ps.setBlob(4, fis); //執行 ps.execute(); fis.close(); JDBCUtils.closeResource(conn, ps);
Connection conn = JDBCUtils.getConnection(); String sql = "update customers set photo = ? where id = ?"; PreparedStatement ps = conn.prepareStatement(sql); // 填充佔位符 // 操做Blob類型的變量 FileInputStream fis = new FileInputStream("coffee.png"); ps.setBlob(1, fis); ps.setInt(2, 25); ps.execute(); fis.close(); JDBCUtils.closeResource(conn, ps);
String sql = "SELECT id, name, email, birth, photo FROM customer WHERE id = ?"; conn = getConnection(); ps = conn.prepareStatement(sql); ps.setInt(1, 8); rs = ps.executeQuery(); if(rs.next()){ Integer id = rs.getInt(1); String name = rs.getString(2); String email = rs.getString(3); Date birth = rs.getDate(4); Customer cust = new Customer(id, name, email, birth); System.out.println(cust); //讀取Blob類型的字段 Blob photo = rs.getBlob(5); InputStream is = photo.getBinaryStream(); OutputStream os = new FileOutputStream("c.jpg"); byte [] buffer = new byte[1024]; int len = 0; while((len = is.read(buffer)) != -1){ os.write(buffer, 0, len); } JDBCUtils.closeResource(conn, ps, rs); if(is != null){ is.close(); } if(os != null){ os.close(); } }
當須要成批插入或者更新記錄時,能夠採用Java的批量更新機制,這一機制容許多條語句一次性提交給數據庫批量處理。一般狀況下比單獨提交處理更有效率
JDBC的批量處理語句包括下面三個方法:
一般咱們會遇到兩種批量執行SQL語句的狀況:
舉例:向數據表中插入20000條數據
CREATE TABLE goods( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20) );
Connection conn = JDBCUtils.getConnection(); Statement st = conn.createStatement(); for(int i = 1;i <= 20000;i++){ String sql = "insert into goods(name) values('name_' + "+ i +")"; st.executeUpdate(sql); }
long start = System.currentTimeMillis(); Connection conn = JDBCUtils.getConnection(); String sql = "insert into goods(name)values(?)"; PreparedStatement ps = conn.prepareStatement(sql); for(int i = 1;i <= 20000;i++){ ps.setString(1, "name_" + i); ps.executeUpdate(); } long end = System.currentTimeMillis(); System.out.println("花費的時間爲:" + (end - start));//82340 JDBCUtils.closeResource(conn, ps);
/* * 修改1: 使用 addBatch() / executeBatch() / clearBatch() * 修改2:mysql服務器默認是關閉批處理的,咱們須要經過一個參數,讓mysql開啓批處理的支持。 * ?rewriteBatchedStatements=true 寫在配置文件的url後面 * 修改3:使用更新的mysql 驅動:mysql-connector-java-5.1.37-bin.jar * */ @Test public void testInsert1() throws Exception{ long start = System.currentTimeMillis(); Connection conn = JDBCUtils.getConnection(); String sql = "insert into goods(name)values(?)"; PreparedStatement ps = conn.prepareStatement(sql); for(int i = 1;i <= 1000000;i++){ ps.setString(1, "name_" + i); //1.「攢」sql ps.addBatch(); if(i % 500 == 0){ //2.執行 ps.executeBatch(); //3.清空 ps.clearBatch(); } } long end = System.currentTimeMillis(); System.out.println("花費的時間爲:" + (end - start));//20000條:625 //1000000條:14733 JDBCUtils.closeResource(conn, ps); }
/* * 層次四:在層次三的基礎上操做 * 使用Connection 的 setAutoCommit(false) / commit() */ @Test public void testInsert2() throws Exception{ long start = System.currentTimeMillis(); Connection conn = JDBCUtils.getConnection(); //1.設置爲不自動提交數據 conn.setAutoCommit(false); String sql = "insert into goods(name)values(?)"; PreparedStatement ps = conn.prepareStatement(sql); for(int i = 1;i <= 1000000;i++){ ps.setString(1, "name_" + i); //1.「攢」sql ps.addBatch(); if(i % 500 == 0){ //2.執行 ps.executeBatch(); //3.清空 ps.clearBatch(); } } //2.提交數據 conn.commit(); long end = System.currentTimeMillis(); System.out.println("花費的時間爲:" + (end - start));//1000000條:4978 JDBCUtils.closeResource(conn, ps); }
事務:一組邏輯操做單元,使數據從一種狀態變換到另外一種狀態。
事務處理(事務操做):保證全部事務都做爲一個工做單元來執行,即便出現了故障,都不能改變這種執行方式。當在一個事務中執行多個操做時,要麼全部的事務都被提交(commit),那麼這些修改就永久地保存下來;要麼數據庫管理系統將放棄所做的全部修改,整個事務回滾(rollback)到最初狀態。
爲確保數據庫中數據的一致性,數據的操縱應當是離散的成組的邏輯單元:當它所有完成時,數據的一致性能夠保持,而當這個單元中的一部分操做失敗,整個事務應所有視爲錯誤,全部從起始點之後的操做應所有回退到開始狀態。
JDBC程序中爲了讓多個 SQL 語句做爲一個事務執行:
若此時 Connection 沒有被關閉,還可能被重複使用,則須要恢復其自動提交狀態 setAutoCommit(true)。尤爲是在使用數據庫鏈接池技術時,執行close()方法前,建議恢復自動提交狀態。
【案例:用戶AA向用戶BB轉帳100】
public void testJDBCTransaction() { Connection conn = null; try { // 1.獲取數據庫鏈接 conn = JDBCUtils.getConnection(); // 2.開啓事務 conn.setAutoCommit(false); // 3.進行數據庫操做 String sql1 = "update user_table set balance = balance - 100 where user = ?"; update(conn, sql1, "AA"); // 模擬網絡異常 //System.out.println(10 / 0); String sql2 = "update user_table set balance = balance + 100 where user = ?"; update(conn, sql2, "BB"); // 4.若沒有異常,則提交事務 conn.commit(); } catch (Exception e) { e.printStackTrace(); // 5.如有異常,則回滾事務 try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { //6.恢復每次DML操做的自動提交功能 conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } //7.關閉鏈接 JDBCUtils.closeResource(conn, null, null); } }
其中,對數據庫操做的方法爲:
//使用事務之後的通用的增刪改操做(version 2.0) public void update(Connection conn ,String sql, Object... args) { PreparedStatement ps = null; try { // 1.獲取PreparedStatement的實例 (或:預編譯sql語句) ps = conn.prepareStatement(sql); // 2.填充佔位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 3.執行sql語句 ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { // 4.關閉資源 JDBCUtils.closeResource(null, ps); } }
原子性(Atomicity)
原子性是指事務是一個不可分割的工做單位,事務中的操做要麼都發生,要麼都不發生。
一致性(Consistency)
事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態。
隔離性(Isolation)
事務的隔離性是指一個事務的執行不能被其餘事務干擾,即一個事務內部的操做及使用的數據對併發的其餘事務是隔離的,併發執行的各個事務之間不能互相干擾。
持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來的其餘操做和數據庫故障不該該對其有任何影響。
數據庫事務的隔離性: 數據庫系統必須具備隔離併發運行各個事務的能力, 使它們不會相互影響, 避免各類併發問題。
一個事務與其餘事務隔離的程度稱爲隔離級別。數據庫規定了多種事務隔離級別, 不一樣隔離級別對應不一樣的干擾程度, 隔離級別越高, 數據一致性就越好, 但併發性越弱。
數據庫提供的4種事務隔離級別:
Oracle 支持的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE。 Oracle 默認的事務隔離級別爲: READ COMMITED 。
Mysql 支持 4 種事務隔離級別。Mysql 默認的事務隔離級別爲: REPEATABLE READ。
每啓動一個 mysql 程序, 就會得到一個單獨的數據庫鏈接. 每一個數據庫鏈接都有一個全局變量 @@tx_isolation, 表示當前的事務隔離級別。
查看當前的隔離級別:
SELECT @@tx_isolation;
設置當前 mySQL 鏈接的隔離級別:
set transaction isolation level read committed;
設置數據庫系統的全局的隔離級別:
set global transaction isolation level read committed;
補充操做:
建立mysql數據庫用戶:
create user tom identified by 'abc123';
授予權限
#授予經過網絡方式登陸的tom用戶,對全部庫全部表的所有權限,密碼設爲abc123. grant all privileges on *.* to tom@'%' identified by 'abc123'; #給tom用戶使用本地命令行方式,授予atguigudb這個庫下的全部表的插刪改查的權限。 grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123';
package com.atguigu.bookstore.dao; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; /** * 定義一個用來被繼承的對數據庫進行基本操做的Dao * * @author HanYanBing * * @param <T> */ public abstract class BaseDao<T> { private QueryRunner queryRunner = new QueryRunner(); // 定義一個變量來接收泛型的類型 private Class<T> type; // 獲取T的Class對象,獲取泛型的類型,泛型是在被子類繼承時才肯定 public BaseDao() { // 獲取子類的類型 Class clazz = this.getClass(); // 獲取父類的類型 // getGenericSuperclass()用來獲取當前類的父類的類型 // ParameterizedType表示的是帶泛型的類型 ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass(); // 獲取具體的泛型類型 getActualTypeArguments獲取具體的泛型的類型 // 這個方法會返回一個Type的數組 Type[] types = parameterizedType.getActualTypeArguments(); // 獲取具體的泛型的類型· this.type = (Class<T>) types[0]; } /** * 通用的增刪改操做 * * @param sql * @param params * @return */ public int update(Connection conn,String sql, Object... params) { int count = 0; try { count = queryRunner.update(conn, sql, params); } catch (SQLException e) { e.printStackTrace(); } return count; } /** * 獲取一個對象 * * @param sql * @param params * @return */ public T getBean(Connection conn,String sql, Object... params) { T t = null; try { t = queryRunner.query(conn, sql, new BeanHandler<T>(type), params); } catch (SQLException e) { e.printStackTrace(); } return t; } /** * 獲取全部對象 * * @param sql * @param params * @return */ public List<T> getBeanList(Connection conn,String sql, Object... params) { List<T> list = null; try { list = queryRunner.query(conn, sql, new BeanListHandler<T>(type), params); } catch (SQLException e) { e.printStackTrace(); } return list; } /** * 獲取一個但一值得方法,專門用來執行像 select count(*)...這樣的sql語句 * * @param sql * @param params * @return */ public Object getValue(Connection conn,String sql, Object... params) { Object count = null; try { // 調用queryRunner的query方法獲取一個單一的值 count = queryRunner.query(conn, sql, new ScalarHandler<>(), params); } catch (SQLException e) { e.printStackTrace(); } return count; } }
package com.atguigu.bookstore.dao; import java.sql.Connection; import java.util.List; import com.atguigu.bookstore.beans.Book; import com.atguigu.bookstore.beans.Page; public interface BookDao { /** * 從數據庫中查詢出全部的記錄 * * @return */ List<Book> getBooks(Connection conn); /** * 向數據庫中插入一條記錄 * * @param book */ void saveBook(Connection conn,Book book); /** * 從數據庫中根據圖書的id刪除一條記錄 * * @param bookId */ void deleteBookById(Connection conn,String bookId); /** * 根據圖書的id從數據庫中查詢出一條記錄 * * @param bookId * @return */ Book getBookById(Connection conn,String bookId); /** * 根據圖書的id從數據庫中更新一條記錄 * * @param book */ void updateBook(Connection conn,Book book); /** * 獲取帶分頁的圖書信息 * * @param page:是隻包含了用戶輸入的pageNo屬性的page對象 * @return 返回的Page對象是包含了全部屬性的Page對象 */ Page<Book> getPageBooks(Connection conn,Page<Book> page); /** * 獲取帶分頁和價格範圍的圖書信息 * * @param page:是隻包含了用戶輸入的pageNo屬性的page對象 * @return 返回的Page對象是包含了全部屬性的Page對象 */ Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice); }
package com.atguigu.bookstore.dao; import java.sql.Connection; import com.atguigu.bookstore.beans.User; public interface UserDao { /** * 根據User對象中的用戶名和密碼從數據庫中獲取一條記錄 * * @param user * @return User 數據庫中有記錄 null 數據庫中無此記錄 */ User getUser(Connection conn,User user); /** * 根據User對象中的用戶名從數據庫中獲取一條記錄 * * @param user * @return true 數據庫中有記錄 false 數據庫中無此記錄 */ boolean checkUsername(Connection conn,User user); /** * 向數據庫中插入User對象 * * @param user */ void saveUser(Connection conn,User user); }
package com.atguigu.bookstore.dao.impl; import java.sql.Connection; import java.util.List; import com.atguigu.bookstore.beans.Book; import com.atguigu.bookstore.beans.Page; import com.atguigu.bookstore.dao.BaseDao; import com.atguigu.bookstore.dao.BookDao; public class BookDaoImpl extends BaseDao<Book> implements BookDao { @Override public List<Book> getBooks(Connection conn) { // 調用BaseDao中獲得一個List的方法 List<Book> beanList = null; // 寫sql語句 String sql = "select id,title,author,price,sales,stock,img_path imgPath from books"; beanList = getBeanList(conn,sql); return beanList; } @Override public void saveBook(Connection conn,Book book) { // 寫sql語句 String sql = "insert into books(title,author,price,sales,stock,img_path) values(?,?,?,?,?,?)"; // 調用BaseDao中通用的增刪改的方法 update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(),book.getImgPath()); } @Override public void deleteBookById(Connection conn,String bookId) { // 寫sql語句 String sql = "DELETE FROM books WHERE id = ?"; // 調用BaseDao中通用增刪改的方法 update(conn,sql, bookId); } @Override public Book getBookById(Connection conn,String bookId) { // 調用BaseDao中獲取一個對象的方法 Book book = null; // 寫sql語句 String sql = "select id,title,author,price,sales,stock,img_path imgPath from books where id = ?"; book = getBean(conn,sql, bookId); return book; } @Override public void updateBook(Connection conn,Book book) { // 寫sql語句 String sql = "update books set title = ? , author = ? , price = ? , sales = ? , stock = ? where id = ?"; // 調用BaseDao中通用的增刪改的方法 update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getId()); } @Override public Page<Book> getPageBooks(Connection conn,Page<Book> page) { // 獲取數據庫中圖書的總記錄數 String sql = "select count(*) from books"; // 調用BaseDao中獲取一個單一值的方法 long totalRecord = (long) getValue(conn,sql); // 將總記錄數設置都page對象中 page.setTotalRecord((int) totalRecord); // 獲取當前頁中的記錄存放的List String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books limit ?,?"; // 調用BaseDao中獲取一個集合的方法 List<Book> beanList = getBeanList(conn,sql2, (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE); // 將這個List設置到page對象中 page.setList(beanList); return page; } @Override public Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice) { // 獲取數據庫中圖書的總記錄數 String sql = "select count(*) from books where price between ? and ?"; // 調用BaseDao中獲取一個單一值的方法 long totalRecord = (long) getValue(conn,sql,minPrice,maxPrice); // 將總記錄數設置都page對象中 page.setTotalRecord((int) totalRecord); // 獲取當前頁中的記錄存放的List String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books where price between ? and ? limit ?,?"; // 調用BaseDao中獲取一個集合的方法 List<Book> beanList = getBeanList(conn,sql2, minPrice , maxPrice , (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE); // 將這個List設置到page對象中 page.setList(beanList); return page; } }
package com.atguigu.bookstore.dao.impl; import java.sql.Connection; import com.atguigu.bookstore.beans.User; import com.atguigu.bookstore.dao.BaseDao; import com.atguigu.bookstore.dao.UserDao; public class UserDaoImpl extends BaseDao<User> implements UserDao { @Override public User getUser(Connection conn,User user) { // 調用BaseDao中獲取一個對象的方法 User bean = null; // 寫sql語句 String sql = "select id,username,password,email from users where username = ? and password = ?"; bean = getBean(conn,sql, user.getUsername(), user.getPassword()); return bean; } @Override public boolean checkUsername(Connection conn,User user) { // 調用BaseDao中獲取一個對象的方法 User bean = null; // 寫sql語句 String sql = "select id,username,password,email from users where username = ?"; bean = getBean(conn,sql, user.getUsername()); return bean != null; } @Override public void saveUser(Connection conn,User user) { //寫sql語句 String sql = "insert into users(username,password,email) values(?,?,?)"; //調用BaseDao中通用的增刪改的方法 update(conn,sql, user.getUsername(),user.getPassword(),user.getEmail()); } }
package com.atguigu.bookstore.beans; /** * 圖書類 * @author songhongkang * */ public class Book { private Integer id; private String title; // 書名 private String author; // 做者 private double price; // 價格 private Integer sales; // 銷量 private Integer stock; // 庫存 private String imgPath = "static/img/default.jpg"; // 封面圖片的路徑 //構造器,get(),set(),toString()方法略 }
package com.atguigu.bookstore.beans; import java.util.List; /** * 頁碼類 * @author songhongkang * */ public class Page<T> { private List<T> list; // 每頁查到的記錄存放的集合 public static final int PAGE_SIZE = 4; // 每頁顯示的記錄數 private int pageNo; // 當前頁 // private int totalPageNo; // 總頁數,經過計算獲得 private int totalRecord; // 總記錄數,經過查詢數據庫獲得
package com.atguigu.bookstore.beans; /** * 用戶類 * @author songhongkang * */ public class User { private Integer id; private String username; private String password; private String email;
數據庫鏈接池的基本思想:就是爲數據庫鏈接創建一個「緩衝池」。預先在緩衝池中放入必定數量的鏈接,當須要創建數據庫鏈接時,只需從「緩衝池」中取出一個,使用完畢以後再放回去。
數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中,這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的。不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量。鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中。
數據庫鏈接池技術的優勢
1. 資源重用
因爲數據庫鏈接得以重用,避免了頻繁建立,釋放鏈接引發的大量性能開銷。在減小系統消耗的基礎上,另外一方面也增長了系統運行環境的平穩性。
2. 更快的系統反應速度
數據庫鏈接池在初始化過程當中,每每已經建立了若干數據庫鏈接置於鏈接池中備用。此時鏈接的初始化工做均已完成。對於業務請求處理而言,直接利用現有可用鏈接,避免了數據庫鏈接初始化和釋放過程的時間開銷,從而減小了系統的響應時間
3. 新的資源分配手段
對於多應用共享同一數據庫的系統而言,可在應用層經過數據庫鏈接池的配置,實現某一應用最大可用數據庫鏈接數的限制,避免某一應用獨佔全部的數據庫資源
4. 統一的鏈接管理,避免數據庫鏈接泄漏
在較爲完善的數據庫鏈接池實現中,可根據預先的佔用超時設定,強制回收被佔用鏈接,從而避免了常規數據庫鏈接操做中可能出現的資源泄露
//使用C3P0數據庫鏈接池的方式,獲取數據庫的鏈接:不推薦 public static Connection getConnection1() throws Exception{ ComboPooledDataSource cpds = new ComboPooledDataSource(); cpds.setDriverClass("com.mysql.jdbc.Driver"); cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test"); cpds.setUser("root"); cpds.setPassword("abc123"); // cpds.setMaxPoolSize(100); Connection conn = cpds.getConnection(); return conn; }
//使用C3P0數據庫鏈接池的配置文件方式,獲取數據庫的鏈接:推薦 private static DataSource cpds = new ComboPooledDataSource("helloc3p0"); public static Connection getConnection2() throws SQLException{ Connection conn = cpds.getConnection(); return conn; }
其中,src下的配置文件爲:【c3p0-config.xml】
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <named-config name="helloc3p0"> <!-- 獲取鏈接的4個基本信息 --> <property name="user">root</property> <property name="password">abc123</property> <property name="jdbcUrl">jdbc:mysql:///test</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <!-- 涉及到數據庫鏈接池的管理的相關屬性的設置 --> <!-- 若數據庫中鏈接數不足時, 一次向數據庫服務器申請多少個鏈接 --> <property name="acquireIncrement">5</property> <!-- 初始化數據庫鏈接池時鏈接的數量 --> <property name="initialPoolSize">5</property> <!-- 數據庫鏈接池中的最小的數據庫鏈接數 --> <property name="minPoolSize">5</property> <!-- 數據庫鏈接池中的最大的數據庫鏈接數 --> <property name="maxPoolSize">10</property> <!-- C3P0 數據庫鏈接池能夠維護的 Statement 的個數 --> <property name="maxStatements">20</property> <!-- 每一個鏈接同時可使用的 Statement 對象的個數 --> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
屬性 | 默認值 | 說明 |
---|---|---|
initialSize | 0 | 鏈接池啓動時建立的初始化鏈接數量 |
maxActive | 8 | 鏈接池中可同時鏈接的最大的鏈接數 |
maxIdle | 8 | 鏈接池中最大的空閒的鏈接數,超過的空閒鏈接將被釋放,若是設置爲負數表示不限制 |
minIdle | 0 | 鏈接池中最小的空閒的鏈接數,低於這個數量會被建立新的鏈接。該參數越接近maxIdle,性能越好,由於鏈接的建立和銷燬,都是須要消耗資源的;可是不能太大。 |
maxWait | 無限制 | 最大等待時間,當沒有可用鏈接時,鏈接池等待鏈接釋放的最大時間,超過該時間限制會拋出異常,若是設置-1表示無限等待 |
poolPreparedStatements | false | 開啓池的Statement是否prepared |
maxOpenPreparedStatements | 無限制 | 開啓池的prepared 後的同時最大鏈接數 |
minEvictableIdleTimeMillis | 鏈接池中鏈接,在時間段內一直空閒, 被逐出鏈接池的時間 | |
removeAbandonedTimeout | 300 | 超過期間限制,回收沒有用(廢棄)的鏈接 |
removeAbandoned | false | 超過removeAbandonedTimeout時間後,是否進 行沒用鏈接(廢棄)的回收 |
public static Connection getConnection3() throws Exception { BasicDataSource source = new BasicDataSource(); source.setDriverClassName("com.mysql.jdbc.Driver"); source.setUrl("jdbc:mysql:///test"); source.setUsername("root"); source.setPassword("abc123"); // source.setInitialSize(10); Connection conn = source.getConnection(); return conn; }
//使用dbcp數據庫鏈接池的配置文件方式,獲取數據庫的鏈接:推薦 private static DataSource source = null; static{ try { Properties pros = new Properties(); InputStream is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties"); pros.load(is); //根據提供的BasicDataSourceFactory建立對應的DataSource對象 source = BasicDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection4() throws Exception { Connection conn = source.getConnection(); return conn; }
其中,src下的配置文件爲:【dbcp.properties】
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false username=root password=abc123 initialSize=10 #...
Druid是阿里巴巴開源平臺上一個數據庫鏈接池實現,它結合了C3P0、DBCP、Proxool等DB池的優勢,同時加入了日誌監控,能夠很好的監控DB池鏈接和SQL的執行狀況,能夠說是針對監控而生的DB鏈接池,能夠說是目前最好的鏈接池之一。
package com.atguigu.druid; import java.sql.Connection; import java.util.Properties; import javax.sql.DataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; public class TestDruid { public static void main(String[] args) throws Exception { Properties pro = new Properties(); pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties")); DataSource ds = DruidDataSourceFactory.createDataSource(pro); Connection conn = ds.getConnection(); System.out.println(conn); } }
其中,src下的配置文件爲:【druid.properties】
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true username=root password=123456 driverClassName=com.mysql.jdbc.Driver initialSize=10 maxActive=20 maxWait=1000 filters=wall
配置 | 缺省 | 說明 |
---|---|---|
name | 配置這個屬性的意義在於,若是存在多個數據源,監控的時候能夠經過名字來區分開來。 若是沒有配置,將會生成一個名字,格式是:」DataSource-」 + System.identityHashCode(this) | |
url | 鏈接數據庫的url,不一樣數據庫不同。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 鏈接數據庫的用戶名 | |
password | 鏈接數據庫的密碼。若是你不但願密碼直接寫在配置文件中,可使用ConfigFilter。詳細看這裏:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根據url自動識別 這一項可配可不配,若是不配置druid會根據url自動識別dbType,而後選擇相應的driverClassName(建議配置下) | |
initialSize | 0 | 初始化時創建物理鏈接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時 |
maxActive | 8 | 最大鏈接池數量 |
maxIdle | 8 | 已經再也不使用,配置了也沒效果 |
minIdle | 最小鏈接池數量 | |
maxWait | 獲取鏈接時最大等待時間,單位毫秒。配置了maxWait以後,缺省啓用公平鎖,併發效率會有所降低,若是須要能夠經過配置useUnfairLock屬性爲true使用非公平鎖。 | |
poolPreparedStatements | false | 是否緩存preparedStatement,也就是PSCache。PSCache對支持遊標的數據庫性能提高巨大,好比說oracle。在mysql下建議關閉。 |
maxOpenPreparedStatements | -1 | 要啓用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改成true。在Druid中,不會存在Oracle下PSCache佔用內存過多的問題,能夠把這個數值配置大一些,好比說100 |
validationQuery | 用來檢測鏈接是否有效的sql,要求是一個查詢語句。若是validationQuery爲null,testOnBorrow、testOnReturn、testWhileIdle都不會其做用。 | |
testOnBorrow | true | 申請鏈接時執行validationQuery檢測鏈接是否有效,作了這個配置會下降性能。 |
testOnReturn | false | 歸還鏈接時執行validationQuery檢測鏈接是否有效,作了這個配置會下降性能 |
testWhileIdle | false | 建議配置爲true,不影響性能,而且保證安全性。申請鏈接的時候檢測,若是空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測鏈接是否有效。 |
timeBetweenEvictionRunsMillis | 有兩個含義: 1)Destroy線程會檢測鏈接的間隔時間2)testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 | |
numTestsPerEvictionRun | 再也不使用,一個DruidDataSource只支持一個EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理鏈接初始化的時候執行的sql | |
exceptionSorter | 根據dbType自動識別 當數據庫拋出一些不可恢復的異常時,拋棄鏈接 | |
filters | 屬性類型是字符串,經過別名的方式配置擴展插件,經常使用的插件有: 監控統計用的filter:stat日誌用的filter:log4j防護sql注入的filter:wall | |
proxyFilters | 類型是List,若是同時配置了filters和proxyFilters,是組合關係,並不是替換關係 |
commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,而且使用dbutils能極大簡化jdbc編碼的工做量,同時也不會影響程序的性能。
API包說明:
該類簡單化了SQL查詢,它與ResultSetHandler組合在一塊兒使用能夠完成大部分的數據庫操做,可以大大減小編碼量。
測試
// 測試添加 @Test public void testInsert() throws Exception { QueryRunner runner = new QueryRunner(); Connection conn = JDBCUtils.getConnection3(); String sql = "insert into customers(name,email,birth)values(?,?,?)"; int count = runner.update(conn, sql, "何成飛", "he@qq.com", "1992-09-08"); System.out.println("添加了" + count + "條記錄"); JDBCUtils.closeResource(conn, null); }
// 測試刪除 @Test public void testDelete() throws Exception { QueryRunner runner = new QueryRunner(); Connection conn = JDBCUtils.getConnection3(); String sql = "delete from customers where id < ?"; int count = runner.update(conn, sql,3); System.out.println("刪除了" + count + "條記錄"); JDBCUtils.closeResource(conn, null); }
該接口用於處理 java.sql.ResultSet,將數據按要求轉換爲另外一種形式。
ResultSetHandler 接口提供了一個單獨的方法:Object handle (java.sql.ResultSet .rs)。
接口的主要實現類:
測試
/* * 測試查詢:查詢一條記錄 * * 使用ResultSetHandler的實現類:BeanHandler */ @Test public void testQueryInstance() throws Exception{ QueryRunner runner = new QueryRunner(); Connection conn = JDBCUtils.getConnection3(); String sql = "select id,name,email,birth from customers where id = ?"; // BeanHandler<Customer> handler = new BeanHandler<>(Customer.class); Customer customer = runner.query(conn, sql, handler, 23); System.out.println(customer); JDBCUtils.closeResource(conn, null); }
/* * 測試查詢:查詢多條記錄構成的集合 * * 使用ResultSetHandler的實現類:BeanListHandler */ @Test public void testQueryList() throws Exception{ QueryRunner runner = new QueryRunner(); Connection conn = JDBCUtils.getConnection3(); String sql = "select id,name,email,birth from customers where id < ?"; // BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class); List<Customer> list = runner.query(conn, sql, handler, 23); list.forEach(System.out::println); JDBCUtils.closeResource(conn, null); }
/* * 自定義ResultSetHandler的實現類 */ @Test public void testQueryInstance1() throws Exception{ QueryRunner runner = new QueryRunner(); Connection conn = JDBCUtils.getConnection3(); String sql = "select id,name,email,birth from customers where id = ?"; ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() { @Override public Customer handle(ResultSet rs) throws SQLException { System.out.println("handle"); // return new Customer(1,"Tom","tom@126.com",new Date(123323432L)); if(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); Date birth = rs.getDate("birth"); return new Customer(id, name, email, birth); } return null; } }; Customer customer = runner.query(conn, sql, handler, 23); System.out.println(customer); JDBCUtils.closeResource(conn, null); }
/* * 如何查詢相似於最大的,最小的,平均的,總和,個數相關的數據, * 使用ScalarHandler * */ @Test public void testQueryValue() throws Exception{ QueryRunner runner = new QueryRunner(); Connection conn = JDBCUtils.getConnection3(); //測試一: // String sql = "select count(*) from customers where id < ?"; // ScalarHandler handler = new ScalarHandler(); // long count = (long) runner.query(conn, sql, handler, 20); // System.out.println(count); //測試二: String sql = "select max(birth) from customers"; ScalarHandler handler = new ScalarHandler(); Date birth = (Date) runner.query(conn, sql, handler); System.out.println(birth); JDBCUtils.closeResource(conn, null); }
總結 @Test public void testUpdateWithTx() { Connection conn = null; try { //1.獲取鏈接的操做( //① 手寫的鏈接:JDBCUtils.getConnection(); //② 使用數據庫鏈接池:C3P0;DBCP;Druid //2.對數據表進行一系列CRUD操做 //① 使用PreparedStatement實現通用的增刪改、查詢操做(version 1.0 \ version 2.0) //version2.0的增刪改public void update(Connection conn,String sql,Object ... args){} //version2.0的查詢 public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ... args){} //② 使用dbutils提供的jar包中提供的QueryRunner類 //提交數據 conn.commit(); } catch (Exception e) { e.printStackTrace(); try { //回滾數據 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } }finally{ //3.關閉鏈接等操做 //① JDBCUtils.closeResource(); //② 使用dbutils提供的jar包中提供的DbUtils類提供了關閉的相關操做 } }