JDBC第三次学习
? ?這是我的JDBC第三次學(xué)習(xí)了,在學(xué)習(xí)的過(guò)程中,老是會(huì)忘掉一些知識(shí),不記下筆記實(shí)在不行啊!
? ?使用JDBC調(diào)用存儲(chǔ)過(guò)程
? ?(1)關(guān)于如何使用Navicat(11.1.13) for MySQL如何創(chuàng)建存儲(chǔ)過(guò)程。我在另一篇文章寫過(guò),在此不贅述了。
? ?使用Navicat(11.1.13) for MySQL如何創(chuàng)建存儲(chǔ)過(guò)程,存儲(chǔ)過(guò)程的主要代碼如下:
BEGININSERT INTO user (name, birthday, money) values (pname, birthday, money);SELECT LAST_INSERT_ID() into pid; END? ?注意:
? ?(2)使用JDBC調(diào)用存儲(chǔ)過(guò)程(即返回當(dāng)前這條記錄插入后形成的id),代碼如下:
static void ps() throws SQLException {Connection conn = null;CallableStatement cs = null;ResultSet rs = null;try {conn = JdbcUtils.getConnection();/** call:是固定寫法,addUser()是我們?cè)跀?shù)據(jù)庫(kù)中定義的存儲(chǔ)過(guò)程的名字* ()后面指定參數(shù) ,如果沒有任何參數(shù)addUser后的括號(hào)也要寫上。* 所以,類似于函數(shù) */String sql = "{call addUser(?,?,?,?)}";cs = conn.prepareCall(sql);cs.registerOutParameter(4, Types.INTEGER);//注冊(cè)輸出參數(shù)/** 設(shè)置輸入?yún)?shù)*/cs.setString(1, "pa name");cs.setDate(2, new java.sql.Date(System.currentTimeMillis()));cs.setFloat(3, 100f);cs.executeUpdate();int id = cs.getInt(4);//不注冊(cè)輸出參數(shù)是不能這樣拿出來(lái)的System.out.println("id="+id);} finally {JdbcUtils.free(rs, cs, conn);}}? ?返回當(dāng)前這條記錄插入后形成的id,還有如下你方法:
static int create() throws SQLException {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {//2.建立連接conn = JdbcUtils.getConnection();//3.創(chuàng)建語(yǔ)句String sql = "insert into user (name,birthday,money) values ('name1 gk','1987-01-01',400)";/** 自動(dòng)產(chǎn)生主鍵,用參數(shù)Statement.RETURN_GENERATED_KEYS拿出產(chǎn)生的主鍵* mysql參數(shù)Statement.RETURN_GENERATED_KEYS加不加都可以拿出來(lái)* 但是這和不同的數(shù)據(jù)庫(kù)產(chǎn)品以及相應(yīng)的驅(qū)動(dòng)有關(guān),所以最好寫上!!!*/ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);ps.executeUpdate();/** getGeneratedKeys()這個(gè)方法為什么不是返回int類型而是要返回一個(gè)ResultSet類型呢?* 因?yàn)?#xff0c;一、主鍵不一定是int類型,* 二、聯(lián)合(復(fù)合)主鍵 ,有可能是一張表中的幾個(gè)字段合起來(lái)構(gòu)成一個(gè)id,這樣就不能返回一個(gè)int類型了,* 如果是聯(lián)合主鍵返回的是多列的內(nèi)容,我們可以遍歷ResultSet得到聯(lián)合主鍵列的值。 * 所以返回ResultSet*/rs = ps.getGeneratedKeys();int id = 0;if(rs.next()) {id = rs.getInt(1);}return id;} finally {JdbcUtils.free(rs, ps, conn);}}?
? ?使用JDBC的批處理功能?
? ?批處理,可以大幅度提升大量增、刪、改的速度。
public class BatchTest {//main方法調(diào)用測(cè)試批量插入與普通的insert所消耗的時(shí)間比 public static void main(String[] args) throws SQLException {long start = System.currentTimeMillis();for(int i = 0; i < 100; i++) {create(i);}long end = System.currentTimeMillis();System.out.println("create:"+(end-start));start = System.currentTimeMillis();createBatch();end = System.currentTimeMillis();System.out.println("createBatch:"+(end-start));}//普通方法插入數(shù)據(jù)static void create(int i) throws SQLException {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {//2.建立連接conn = JdbcUtils.getConnection();//3.創(chuàng)建語(yǔ)句String sql = "insert into user (name,birthday,money) values (?,?,?)";ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);ps.setString(1, "batch name"+i);ps.setDate(2, new java.sql.Date(System.currentTimeMillis()));ps.setFloat(3, 100f+i);ps.executeUpdate();} finally {JdbcUtils.free(rs, ps, conn);}}//批量插入數(shù)據(jù)static void createBatch() throws SQLException {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {//2.建立連接conn = JdbcUtils.getConnection();//3.創(chuàng)建語(yǔ)句String sql = "insert into user (name,birthday,money) values (?,?,?)";ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);/** 每循環(huán)一次都會(huì)形成一條新的sql語(yǔ)句被打包,直到循環(huán)完成然后進(jìn)行批量的處理* 那么可不可以無(wú)限量的增加呢?其實(shí)會(huì)產(chǎn)生內(nèi)存溢出的情況,到底循環(huán)多少次進(jìn)行打包才合適,這個(gè)值要經(jīng)過(guò)測(cè)試*/for(int i = 0; i < 100; i++) {ps.setString(1, "batch name"+i);ps.setDate(2, new java.sql.Date(System.currentTimeMillis()));ps.setFloat(3, 100f+i);/** 并不是使用批處理就會(huì)提高效率* 把sql語(yǔ)句打成一個(gè)包* 包不能太大(并不是越大越好),會(huì)內(nèi)存溢出*/ps.addBatch();}int[] is = ps.executeBatch();} finally {JdbcUtils.free(rs, ps, conn);}}}? ?
? ?可滾動(dòng)結(jié)果集與分頁(yè)技術(shù)?
static void scroll() throws SQLException {Connection conn = null;Statement st = null;ResultSet rs = null;try {conn = JdbcUtils.getConnection();/** 在創(chuàng)建一個(gè)Statement的時(shí)候指定可滾動(dòng)的結(jié)果集的類型* TYPE_SCROLL_SENSITIVE:滾動(dòng)的過(guò)程中,對(duì)數(shù)據(jù)庫(kù)是敏感的* (按我的理解就是查詢數(shù)據(jù)的時(shí)候,如果又新增、刪除、更新,那么能感覺得到)* CONCUR_READ_ONLY:字面意思是同意只讀*/st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);/** 在mysql中通過(guò)limit關(guān)鍵字實(shí)現(xiàn)分頁(yè),* 每種數(shù)據(jù)庫(kù)產(chǎn)品的關(guān)鍵字不同例如:Oracle使用rownum,sqlServer用top* 現(xiàn)在有幾十種關(guān)系型數(shù)據(jù)庫(kù),如果數(shù)據(jù)庫(kù)不支持這種關(guān)鍵字進(jìn)行分頁(yè)的時(shí)候可以用滾動(dòng)的結(jié)果集來(lái)實(shí)現(xiàn)分頁(yè),* 但是性能比較低 *///sql = "select id,name,birthday,money from user limit 150, 10";//mysql支持分頁(yè)rs = st.executeQuery("select id,name,birthday,money from user");//5.處理結(jié)果while(rs.next()) {System.out.println(rs.getObject("id")+"\t"+rs.getObject("name")+"\t"+rs.getObject("birthday")+"\t"+rs.getObject("money"));}System.out.println("-------------------------------------");/** 絕對(duì)定位,可以直接定位到rs所有返回結(jié)果中指定的一條記錄上 * 例定位到第150行*/rs.absolute(150);int i = 0;/** 可以通過(guò)i來(lái)控制循環(huán)次數(shù),實(shí)現(xiàn)分頁(yè)效果* 但是要數(shù)據(jù)庫(kù)產(chǎn)品或者驅(qū)動(dòng)支持此功能! */while(rs.next() && i < 10) {i++;System.out.println(rs.getObject("id")+"\t"+rs.getObject("name")+"\t"+rs.getObject("birthday")+"\t"+rs.getObject("money"));}} finally {JdbcUtils.free(rs, st, conn);}}? ?
? ?可更新和對(duì)更新敏感的結(jié)果集?
static void read() throws SQLException, InterruptedException {Connection conn = null;Statement st = null;ResultSet rs = null;try {//2.建立連接conn = JdbcUtils.getConnection();//3.創(chuàng)建語(yǔ)句/** 設(shè)置滾動(dòng)結(jié)果集的類型為:ResultSet.TYPE_SCROLL_SENSITIVE,就是能感知到數(shù)據(jù)庫(kù)的變化 * CONCUR_UPDATABLE:字面意思是同意更新*/st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);//4.執(zhí)行語(yǔ)句rs = st.executeQuery("select id,name,birthday,money from user where id < 5");//5.處理結(jié)果/** 驗(yàn)證TYPE_SCROLL_SENSITIVE* 下面讓rs每循環(huán)一次rs睡眠10秒鐘,然后再這個(gè)過(guò)程中我們用mysql客戶端修改數(shù)據(jù)庫(kù)中的數(shù)據(jù) * 我們看看它讀出來(lái)的是修改前的數(shù)據(jù),還是修改后的。我們?cè)谏厦嬖O(shè)置的可滾動(dòng)的結(jié)果集的類型 * 是ResultSet.TYPE_SCROLL_SENSITIVE,也就是能感知數(shù)據(jù)庫(kù)的變化,那如果在rs沒有讀出數(shù)據(jù)庫(kù)里的* 那條數(shù)據(jù)之前我們?cè)趍ysql的客戶端將原先的數(shù)據(jù)修改掉,這里讀出來(lái)的數(shù)據(jù)應(yīng)該是修改后的數(shù)據(jù),但是在* 測(cè)試的時(shí)候讀出的數(shù)據(jù)卻依然是修改之前的,這應(yīng)該和數(shù)據(jù)庫(kù)的驅(qū)動(dòng)有關(guān)系 * 但是如果是能感知數(shù)據(jù)庫(kù)的變化,那么數(shù)據(jù)庫(kù)的性能也是降低了,你執(zhí)行executeQuery()方法后,它已經(jīng)將數(shù)據(jù)查詢完成* 打包后給你發(fā)送過(guò)來(lái)了,如果察覺到數(shù)據(jù)庫(kù)的變化那么它要在輸出之前再查詢一遍數(shù)據(jù)庫(kù),這種需求用的比較少,作為了解即可 */while(rs.next()) {int id = rs.getInt("id");System.out.println("show " + id + "...");Thread.sleep(10000);System.out.println(id+"\t"+rs.getObject("name")+"\t"+rs.getObject("birthday")+"\t"+rs.getObject("money"));/** 查詢的時(shí)候可以更新(可更新的結(jié)果集)* 可更新的結(jié)果集,我們并不建議這樣做因?yàn)樯厦娴膕ql語(yǔ)句是查詢操作 * 但是下面還隱藏著更新操作,對(duì)于程序的可讀性不好,這種需求也比較少 * 了解即可*/String name = rs.getString("name");if("lisi".equals(name)) {rs.updateFloat("money", 300f);rs.updateRow();//修改完成后要修改一行 }}} finally {JdbcUtils.free(rs, st, conn);}}??
? ?數(shù)據(jù)庫(kù)的元數(shù)據(jù)信息?
? ?通過(guò)DatabaseMetaData可以獲得數(shù)據(jù)庫(kù)相關(guān)的信息如:數(shù)據(jù)庫(kù)版本、數(shù)據(jù)庫(kù)名、數(shù)據(jù)庫(kù)廠商信息、是否支持事務(wù)、是否支持某種事務(wù)隔離級(jí)別,是否支持滾動(dòng)結(jié)果集等。對(duì)于我們編寫程序來(lái)說(shuō)不常用,但是在框架的編寫中經(jīng)常會(huì)用到,例如hibernate,它要屏蔽不同數(shù)據(jù)庫(kù)之間的區(qū)別,那么它就要知道當(dāng)前是什么數(shù)據(jù)庫(kù),然后做出相應(yīng)的判斷處理:在使用hibernate的時(shí)候有一項(xiàng)是配置數(shù)據(jù)庫(kù)的方言,其實(shí)就是指定你使用的是什么數(shù)據(jù)庫(kù)產(chǎn)品。如果你不進(jìn)行指定,hibernate會(huì)自動(dòng)的嘗試著去檢測(cè)當(dāng)前數(shù)據(jù)庫(kù)產(chǎn)品的類型,其實(shí)就是根據(jù)DatabaseMetaData來(lái)檢測(cè)的。
? ?示例代碼:
public class DBMO {public static void main(String[] args) throws SQLException {Connection conn = JdbcUtils.getConnection();//得到數(shù)據(jù)庫(kù)的元信息 DatabaseMetaData dbmd = conn.getMetaData();//取出當(dāng)前使用數(shù)據(jù)庫(kù)的名稱 System.out.println("db name:" + dbmd.getDatabaseProductName());//看看當(dāng)前數(shù)據(jù)庫(kù)支不支持事務(wù) System.out.println("tx:" + dbmd.supportsTransactions());conn.close();}}?
? ?參數(shù)的元數(shù)據(jù)信息?
public class ParameterMetaTest {public static void main(String[] args) throws SQLException {Object[] params = new Object[] {"lisi", new java.sql.Date(System.currentTimeMillis()), 100f};read("select * from user where name = ? and birthday < ? and money > ?", params);}static void read(String sql, Object[] params) throws SQLException {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JdbcUtils.getConnection();ps = conn.prepareStatement(sql);//得到參數(shù)信息的元數(shù)據(jù) /*第一種方式給sql語(yǔ)句中的占位符賦值,但是要約定:sql語(yǔ)句中占位符的所表示的類型和個(gè)數(shù)和參數(shù)數(shù)組中是一致的 ParameterMetaData pmd = ps.getParameterMetaData();int count = pmd.getParameterCount();for(int i = 1; i <= count; i++) {System.out.print(pmd.getParameterClassName(i) + "\t");System.out.print(pmd.getParameterType(i) + "\t");System.out.println(pmd.getParameterTypeName(i));//Ctrl+T打開其基礎(chǔ)體系ps.setObject(i, params[i-1]);}*//** 第二種方式給sql語(yǔ)句中的占位符賦值*/for(int i = 1; i <= params.length; i++) {ps.setObject(i, params[i-1]);}rs = ps.executeQuery();while(rs.next()) {System.out.println(rs.getInt("id")+"\t"+rs.getString("name")+"\t"+rs.getDate("birthday")+"\t"+rs.getFloat("money"));}} finally {JdbcUtils.free(rs, ps, conn);}} }? ?小知識(shí):快捷鍵Ctrl+T——打開其整個(gè)繼承體系。
? ?有可能產(chǎn)生的異常:
java.sql.SQLException: Parameter metadata not available for the given statement? ?解決的方法不難,就是在連接數(shù)據(jù)庫(kù)時(shí)的URL后面加上可以返回的元數(shù)據(jù)類型
? ?例如出異常時(shí),我的URL是這樣寫的:
url = "jdbc:mysql://localhost:3306/jdbc";正確寫法應(yīng)該是:
url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true";出現(xiàn)異常的原因:因?yàn)閙ysql驅(qū)動(dòng)默認(rèn)generateSimpleParameterMetadata=false只有設(shè)置為true,metadata類型會(huì)將每一個(gè)參數(shù)反射為Varchar類型。(時(shí)間才過(guò)去幾天,就有點(diǎn)不是很清楚了)
? ?
? ?將結(jié)果集元數(shù)據(jù)封裝為Map??
? ?現(xiàn)在我們有一種需求將ResultSet結(jié)果集中的數(shù)據(jù)封裝成Map,map的key是數(shù)據(jù)庫(kù)中字段的值,value就是在字段中的值。
? ?通過(guò)ResultSetMetaData可以獲得結(jié)果有幾列、各列名、各列別名、各列類型等。
? ?可以將ResultSet放入Map(key:列名 value:列值)。
? ?用反射ResultSetMetaData將查詢結(jié)果讀入對(duì)象中(簡(jiǎn)單的O/RMapping)。
? ? 示例代碼如下:
public class ResultSetMetaDataTest {public static void main(String[] args) throws SQLException {List<Map<String, Object>> data = read("select id, name as n from user where id < 5");System.out.println(data);}static List<Map<String, Object>> read(String sql) throws SQLException {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JdbcUtils.getConnection();ps = conn.prepareStatement(sql);rs = ps.executeQuery();//得到ResultSet的元數(shù)據(jù) ResultSetMetaData rsmd = rs.getMetaData();//得到ResultSet元數(shù)據(jù)的列數(shù) int count = rsmd.getColumnCount();String[] colNames = new String[count];for(int i = 1; i <= count; i++) {System.out.print(rsmd.getColumnClassName(i)+"\t");//得到參數(shù)的類名,例java.lang.String System.out.print(rsmd.getColumnName(i)+"\t");//取列的實(shí)際名字System.out.println(rsmd.getColumnLabel(i));//取列的別名colNames[i-1] = rsmd.getColumnLabel(i);}List<Map<String, Object>> datas = new ArrayList<Map<String, Object>>();while(rs.next()) {Map<String, Object> data = new HashMap<String, Object>();for(int i = 0; i < colNames.length; i++) {data.put(colNames[i], rs.getObject(colNames[i]));}datas.add(data);}return datas;} finally {JdbcUtils.free(rs, ps, conn);}} }? ?
? ?編寫一個(gè)基本的連接池來(lái)實(shí)現(xiàn)連接的復(fù)用?
大家都知道Arraylist的底層使用數(shù)組實(shí)現(xiàn)的,而LinkedList使用鏈表實(shí)現(xiàn)的,所以對(duì)于Arraylist讀取速度比較快而對(duì)于LinkedList修改和添加比較快,所以我們這個(gè)連接池因?yàn)橐l繁的操作集合所以用LinkedList來(lái)實(shí)現(xiàn)。
public class MyDataSource {private static String url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true";private static String user = "root";private static String password = "yezi";/** 對(duì)集合中的元素進(jìn)行頻繁的取出,* 用LinkedList*/LinkedList<Connection> connectionsPool = new LinkedList<Connection>();//向我們的LinkedList集合中加入10個(gè)鏈接作為我們的連接池 public MyDataSource() {try {for(int i = 0; i < 10; i++) {//將Connection放到鏈表的最后面 this.connectionsPool.addLast(this.createConnection());}} catch(SQLException e) {throw new ExceptionInInitializerError(e);}}//得到一個(gè)鏈接(先進(jìn)先出算法)public Connection getConnection() throws SQLException {return this.connectionsPool.removeFirst();}//創(chuàng)建鏈接private Connection createConnection() throws SQLException {return DriverManager.getConnection(url, user, password);}/** 關(guān)閉一個(gè)鏈接,這個(gè)關(guān)閉不是真正意義上的關(guān)閉,* 而是又把它放回到連接池中,實(shí)現(xiàn)了Connection的復(fù)用 */public void free(Connection conn) {this.connectionsPool.addLast(conn);} }? ?對(duì)基本連接池進(jìn)行一些工程細(xì)節(jié)上的優(yōu)化
? ?在上面實(shí)現(xiàn)的連接池中我們只是默認(rèn)創(chuàng)建了10個(gè)連接,但是如果這個(gè)時(shí)候有10個(gè)線程同時(shí)都來(lái)拿連接,那連接池里就沒有連接了,在有線程過(guò)來(lái)拿的時(shí)候就會(huì)報(bào)錯(cuò)了,現(xiàn)在我們進(jìn)行一些優(yōu)化。
public class MyDataSource {private static String url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true";private static String user = "root";private static String password = "yezi";//規(guī)定默認(rèn)創(chuàng)建的連接數(shù) private static int initCount = 5;//規(guī)定最大可以創(chuàng)建的連接數(shù) private static int maxCount = 10;//統(tǒng)計(jì)當(dāng)前共創(chuàng)建了多少個(gè)連接 private int currentCount = 0;/** 對(duì)集合中的元素進(jìn)行頻繁的取出,* 用LinkedList*/LinkedList<Connection> connectionsPool = new LinkedList<Connection>();public MyDataSource() {try {for(int i = 0; i < initCount; i++) {this.connectionsPool.addLast(this.createConnection());//每創(chuàng)建一個(gè)鏈接,currentCount++ this.currentCount++;}} catch(SQLException e) {throw new ExceptionInInitializerError(e);}}public Connection getConnection() throws SQLException {/** 因?yàn)镃onnection不是線程安全的,* 所以我必須保證每個(gè)線程拿到的鏈接不是同一個(gè),所以要進(jìn)行同步:當(dāng)兩個(gè)線程同時(shí)來(lái)拿的時(shí)候 * 另外一個(gè)線程必須等待 */synchronized (connectionsPool) {//①連接池中還有連接,取出if(this.connectionsPool.size() > 0)return this.connectionsPool.removeFirst();//②連接池中已沒有連接,并且當(dāng)前創(chuàng)建的鏈接數(shù)沒有到最大值,那就繼續(xù)創(chuàng)建鏈接 if(this.currentCount < maxCount) {this.currentCount++;return this.createConnection();}//③大于連接池中的最大數(shù),拋出異常throw new SQLException("已沒有連接");}}public void free(Connection conn) {this.connectionsPool.addLast(conn);}private Connection createConnection() throws SQLException {return DriverManager.getConnection(url, user, password);} }? ?
? ?通過(guò)代理模式來(lái)保持用戶關(guān)閉連接的習(xí)慣
? ?在上面的示例中我們?cè)陉P(guān)閉鏈接的時(shí)候,調(diào)用的是free方法來(lái)把這個(gè)連接又放回到了池中,但是按照開發(fā)人員的使用習(xí)慣應(yīng)該是調(diào)用colse()方法來(lái)關(guān)閉一個(gè)鏈接,但是如果調(diào)用close方法關(guān)閉,那這個(gè)連接就真的關(guān)閉了,也就是說(shuō)我們這個(gè)方法設(shè)計(jì)的不符合開發(fā)人員的使用習(xí)慣。下面我用代理模式(關(guān)于代理模式,我在另一篇文章中寫過(guò))的方法來(lái)解決這個(gè)問題:?
? ?定義一個(gè)類實(shí)現(xiàn)Connection接口,Connectio接口中有很多的方法,這些方法我們都無(wú)法自己完成,我們交給通過(guò)構(gòu)造方法傳遞進(jìn)來(lái)的真正的Connection的對(duì)象來(lái)完成,我們只是修改它的close方法,在用戶得到鏈接的時(shí)候我們返回給用戶這個(gè)類的對(duì)象,那么當(dāng)用戶調(diào)用close方法關(guān)閉鏈接的時(shí)候,我們就可以在這個(gè)close方法中將用戶要關(guān)閉的那個(gè)鏈接再次的放到連接池中,這樣鏈接就不會(huì)真正的關(guān)閉了。
public class MyConnetion implements Connection {private Connection realConnection;private MyDataSource2 dataSource;/** 限制其(連接)最大使用次數(shù)*/private int maxUseCount = 5;/** 記錄(連接)當(dāng)前使用次數(shù)*/private int currentUseCount = 0;/** 由于訪問修飾符是default,* 所以只能在包c(diǎn)n.itcast.jdbc.datasource中使用MyConnetion*/MyConnetion(Connection connection, MyDataSource2 dataSource) {this.realConnection = connection;this.dataSource = dataSource;}//清除警告 @Overridepublic void clearWarnings() throws SQLException {this.realConnection.clearWarnings();}@Overridepublic Statement createStatement() throws SQLException {return this.realConnection.createStatement();}@Overridepublic void commit() throws SQLException {this.realConnection.commit();}@Overridepublic void close() throws SQLException {this.currentUseCount++;/** 規(guī)定同一個(gè)鏈接只能使用maxUseCount次,* 超過(guò)這個(gè)次數(shù)就把真正的鏈接關(guān)閉,連接池中就要少一個(gè)鏈接* 這個(gè)時(shí)候再拿鏈接,拿到的就是新創(chuàng)建得一個(gè)新的鏈接對(duì)象了。 */if(this.currentUseCount < this.maxUseCount)this.dataSource.connectionsPool.addLast(this);else {this.realConnection.close();this.dataSource.currentCount--;}}//Connectio接口中實(shí)在是有太多的方法,在此就不寫了,我們主要關(guān)注close()方法
}
? ?為了更清楚地表達(dá)思想,我們可以慢慢來(lái)優(yōu)化代碼(不好的代碼)
public class MyDataSource2 {private static String url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true";private static String user = "root";private static String password = "yezi";private static int initCount = 1;private static int maxCount = 1;int currentCount = 0;LinkedList<MyConnetion> connectionsPool = new LinkedList<MyConnetion>();public MyDataSource2() {try {for(int i = 0; i < initCount; i++) {this.connectionsPool.addLast(this.createConnection());this.currentCount++;}} catch(SQLException e) {throw new ExceptionInInitializerError(e);}}public Connection getConnection() throws SQLException {synchronized (connectionsPool) {if(this.connectionsPool.size() > 0)return this.connectionsPool.removeFirst();if(this.currentCount < maxCount) {this.currentCount++;return this.createConnection();}throw new SQLException("已沒有連接");}}public void free(Connection conn) {if(conn instanceof MyConnetion) {this.connectionsPool.addLast((MyConnetion)conn);}}private MyConnetion createConnection() throws SQLException {Connection realConn = DriverManager.getConnection(url, user, password);MyConnetion myConnetion = new MyConnetion(realConn, this);return myConnetion;}}
? ?因?yàn)獒槍?duì)接口編程是面向?qū)ο蟮牡谝辉瓌t,所以我們優(yōu)化代碼為:
public class MyDataSource2 implements DataSource {//實(shí)現(xiàn)了DataSource接口之后,就是一個(gè)標(biāo)準(zhǔn)的數(shù)據(jù)源了,我們的程序只和數(shù)據(jù)源打交道,后面會(huì)講DBCP實(shí)現(xiàn)private static String url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true";private static String user = "root";private static String password = "yezi";private static int initCount = 1;private static int maxCount = 1;int currentCount = 0;/** 針對(duì)接口編程——面向?qū)ο蟮牡谝辉瓌t*/LinkedList<Connection> connectionsPool = new LinkedList<Connection>();public MyDataSource2() {try {for(int i = 0; i < initCount; i++) {this.connectionsPool.addLast(this.createConnection());this.currentCount++;}} catch(SQLException e) {throw new ExceptionInInitializerError(e);}}public Connection getConnection() throws SQLException {synchronized (connectionsPool) {if(this.connectionsPool.size() > 0)return this.connectionsPool.removeFirst();if(this.currentCount < maxCount) {this.currentCount++;return this.createConnection();}throw new SQLException("已沒有連接");}}public void free(Connection conn) {this.connectionsPool.addLast(conn);}private Connection createConnection() throws SQLException {/** 這是一個(gè)真實(shí)的connection*/Connection realConn = DriverManager.getConnection(url, user, password);MyConnetion myConnetion = new MyConnetion(realConn, this);return myConnetion;//返回一個(gè)代理對(duì)象}//實(shí)現(xiàn)DataSource接口中一系列方法,較多,不寫
}
? ?
? ?Java的動(dòng)態(tài)代理及使用該技術(shù)完善連接代理?
? ?在上面的示例中,我們?yōu)榱水a(chǎn)生一個(gè)代理對(duì)象實(shí)現(xiàn)了Connection接口的所有的方法,但是我們只需要修改它的close方法,別的方法我們都需要交給真正的Connection對(duì)象去處理,比較麻煩,我們用動(dòng)態(tài)代理(?)來(lái)實(shí)現(xiàn)它。
class MyConnectionHandler implements InvocationHandler {private Connection realConnection;private Connection warpedConnection;private MyDataSource2 dataSource;/** 限制其最大使用次數(shù)*/private int maxUseCount = 5;/** 記錄當(dāng)前使用次數(shù)*/private int currentUserCount = 0;MyConnectionHandler(MyDataSource2 dataSource) {this.dataSource = dataSource;}Connection bind(Connection realConn) {this.realConnection = realConn;/** Proxy類就像程序員一樣可寫代碼* Proxy寫一個(gè)類,此類實(shí)現(xiàn)了Connection接口,* 對(duì)connection接口的方法調(diào)用轉(zhuǎn)化給調(diào)用處理器MyConnectionHandler* * 動(dòng)態(tài)產(chǎn)生warped(包裹的)Connection。*/this.warpedConnection = (Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {Connection.class}, this);return warpedConnection;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if("close".equals(method.getName())) {this.currentUserCount++;if(this.currentUserCount < this.maxUseCount)this.dataSource.connectionsPool.addLast(this.warpedConnection);else {this.realConnection.close();this.dataSource.currentCount--;}}/** 調(diào)用方法的時(shí)候如果是close方法就執(zhí)行我們的邏輯(上面的代碼),* 對(duì)于其他的所有的方法,全部交給真實(shí)Connection對(duì)象本身自己去處理 * 調(diào)用realConnection實(shí)例的method方法*/ return method.invoke(this.realConnection, args);} }? ?當(dāng)寫完上面的代理類后,我們還是需要修改MyDataSource2類的createConnection()方法來(lái)調(diào)用我們的代理類,將它需要的參數(shù)傳遞給它并把生成的代理類返回:(注意:MyDataSource2類中只須修改createConnection()),部分代碼如下:
private Connection createConnection() throws SQLException {/** 這是一個(gè)真實(shí)的connection*/Connection realConn = DriverManager.getConnection(url, user, password);MyConnectionHandler proxy = new MyConnectionHandler(this);return proxy.bind(realConn);}?
? ?標(biāo)準(zhǔn)DataSource接口及數(shù)據(jù)源的總結(jié)介紹?
? ?理解數(shù)據(jù)源的優(yōu)勢(shì)與特點(diǎn):?
? ?
轉(zhuǎn)載于:https://www.cnblogs.com/yerenyuan/p/5328115.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
- 上一篇: org.apache.catalina.
- 下一篇: JAVA调用动态链接库