MySQL第七天----Connection連線池、包裝模式(裝飾模式)與動態代理模式
阿新 • • 發佈:2019-01-25
在上次寫的連線模板中,Connection是單例,只適用於單執行緒也就是一個使用者操作,一旦多執行緒同時執行,就會掛掉,這裡將探討原因和解決辦法。
單例工廠類的漏洞:
這裡寫了一個類,專門對此做了測試:
TxDemo.java
<span style="font-size:14px;">package cn.hncu.tx; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import cn.hncu.pubs.ConnFactory; //使用的是單例連線工廠ConnFactory public class TxDemo { /* * 我這裡開了四個執行緒,加上mian執行緒,共五個執行緒,每次拿Connection都是拿的同一個物件, * 則這些執行緒之間會串資料,如: * 我讓main執行緒執行插入,然後在提交之前睡眠,讓第三個執行緒執行插入時出錯, * 然後回滾,就會把main執行緒剛才所執行的全部回滾,main執行緒之前的動作全部無效, * 若其他執行緒比三執行緒提前執行完,則把main執行緒的動作也一起提交了,若main執行緒下面出錯,則無法回滾 * 這樣不安全 */ public static void main(String[] args) { Connection con = ConnFactory.getConn(); try { Statement st = con.createStatement(); con.setAutoCommit(false);// 開啟一個事務 st.executeUpdate("insert into person2 values('P07','趙子龍','1')"); MyThread th1 = new MyThread(1); MyThread th2 = new MyThread(2); MyThread th3 = new MyThread(3); MyThread th4 = new MyThread(4); th1.start(); th2.start(); th3.start(); th4.start(); Thread.sleep(1000); con.commit(); System.out.println("main執行緒提交一個事務"); } catch (Exception e) { try { con.rollback(); System.out.println("main執行緒回滾一個事務"); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { // con.setAutoCommit(true); // con.close(); } catch (Exception e) { e.printStackTrace(); } } } } class MyThread extends Thread { private int num; public MyThread(int num) { this.num = num; } @Override public void run() { Connection con = ConnFactory.getConn(); try { Statement st = con.createStatement(); con.setAutoCommit(false);// 開啟一個事務 String sql = "insert into person2 values('P07" + num + "','趙子龍" + num + "','1')"; if(num==3){ sql = "insert into person2 values('P07" + num + ",'趙子龍" + num + "','1')"; }else{ Thread.sleep(100); } //System.out.println("sql-----" + sql); st.executeUpdate(sql); con.commit(); System.out.println(num + "執行緒提交一個事務"); } catch (Exception e) { try { con.rollback(); System.out.println(num + "執行緒回滾一個事務"); } catch (SQLException e1) { e1.printStackTrace(); } finally { try { // con.setAutoCommit(true); // con.close(); } catch (Exception e1) { e1.printStackTrace(); } } } } } </span>
串資料:雖然main執行緒提交了事務,但是被3執行緒給回滾了,所以main執行緒做的插入動作也被回滾了,再提交也沒用
解決:要開一個連線池,池中放連線,對池做維護
ConnUtils.java工具類
<span style="font-size:14px;">package cn.hncu.pool; import java.sql.Connection; import java.sql.DriverManager; import java.util.ArrayList; import java.util.List; import java.util.Properties; //new出多個連線,放進池中,連線不再是單例,用池當單例 public class ConnUtils { private static List<Connection> pool=new ArrayList<Connection>(); private final static int NUM=3; private ConnUtils(){ } static{ Properties p=new Properties(); try { p.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));//可從本類ConnUtils.Class.getClassLoader().getResourceAsStream("jdbc.properties")拿 String dirver=p.getProperty("driver"); String url=p.getProperty("url"); String user=p.getProperty("username"); String password=p.getProperty("password"); Class.forName(dirver); for(int i=0;i<NUM;i++){ Connection con=DriverManager.getConnection(url, user, password); pool.add(con); } } catch (Exception e) { e.printStackTrace(); } } public static synchronized Connection getConn(){//加把鎖,防止有多條執行緒在這裡等待 try { if(pool.size()<=0){ System.out.println("池中沒有連線了....."); Thread.sleep(1000); return getConn(); } } catch (InterruptedException e) { } return pool.remove(0); } public static void back(Connection con){ System.out.println("向池中還回一個連線-----"+con); pool.add(con); } } </span>
TxDemo2.java測試類
<span style="font-size:14px;">package cn.hncu.tx; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import cn.hncu.pool.ConnUtils; import cn.hncu.pubs.ConnFactory; //使用的是多例連線工具ConnUtils public class TxDemo2 { public static void main(String[] args) { Connection con = ConnUtils.getConn(); try { Statement st = con.createStatement(); con.setAutoCommit(false);// 開啟一個事務 st.executeUpdate("insert into person2 values('P07','趙子龍','1')"); MyThread2 th1 = new MyThread2(1); MyThread2 th2 = new MyThread2(2); MyThread2 th3 = new MyThread2(3); MyThread2 th4 = new MyThread2(4); th1.start(); th2.start(); th3.start(); th4.start(); Thread.sleep(1000); con.commit(); System.out.println("main執行緒提交一個事務"); } catch (Exception e) { try { con.rollback(); System.out.println("main執行緒回滾一個事務"); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { con.setAutoCommit(true); // con.close(); ConnUtils.back(con);//這裡是回收Connection連線,應做成con.close(),池在背後回收資源,呼叫back方法 } catch (Exception e) { e.printStackTrace(); } } } } class MyThread2 extends Thread { private int num; public MyThread2(int num) { this.num = num; } @Override public void run() { Connection con = ConnUtils.getConn(); try { Statement st = con.createStatement(); con.setAutoCommit(false);// 開啟一個事務 String sql = "insert into person2 values('P07" + num + "','趙子龍" + num + "','1')"; if(num==2){ sql = "insert into person2 values('P07" + num + ",'趙子龍" + num + "','1')"; }else{ Thread.sleep(100); } //System.out.println("sql-----" + sql); st.executeUpdate(sql); con.commit(); System.out.println(num + "執行緒提交一個事務"); } catch (Exception e) { try { con.rollback(); System.out.println(num + "執行緒回滾一個事務"); } catch (SQLException e1) { e1.printStackTrace(); } finally { try { con.setAutoCommit(true); // con.close(); ConnUtils.back(con); } catch (Exception e1) { e1.printStackTrace(); } } } } } </span>
每個執行緒都獨立開了,各不影響:
裝修模式:
對Connection功能進行增強
ConnUtils2.java
由於本程式碼實現了Connection介面,程式碼較多,但大部分都是 不重要的,這裡我只對改動的程式碼貼出來:
在靜態塊for迴圈中
for(int i=0;i<NUM;i++){
Connection conn=DriverManager.getConnection(url, user, password);
//更改con.close()方法
MyConnection con=new MyConnection(conn);
pool.add(con);
}
這裡用到了MyConnection內部類
<span style="font-size:14px;">//採用包裝模式(裝修模式)對Connection con功能進行增強
//因為是靜態塊中new MyConnection 同時還要訪問靜態變數pool,所以要宣告成靜態,不然new 的時候會出現錯誤
static class MyConnection implements Connection {
private Connection con;
public MyConnection(Connection con){
this.con=con;
}
</span>
下面都是實現Connection抽象方法,我們只對close方法進行更改,其餘還是用原來的
<span style="font-size:14px;">//功能增強就是這個地方,把close方法,改成放回池中
@Override
public void close() throws SQLException {
System.out.println("往池中還回一個連線------"+this);
pool.add(this);//注意這裡要放this
}</span>
TxDemo3.java中也只是把原先的ConnUtils.back(con)方法,改成con.close()方法,其餘與TxDemo2.java相同,這裡不再重敘。
結果也是正確的:
代理模式:
代理模式的作用是:為其他物件提供一種代理以控制對這個物件的訪問。
代理模式的初期引入:
房東與租客:
Renter.java
package cn.hncu.proxy.rent;
public class Renter implements IRenter {
@Override
public void rent(){
System.out.println("房東:租出房子,收取房租");
}
@Override
public String toString() {
return "好好保護我的房子....";
}
}
IRenter.java
package cn.hncu.proxy.rent;
public interface IRenter {
public abstract void rent();
}
Client.java
package cn.hncu.proxy.rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
final Renter r=new Renter();//被代理物件-----原型物件
Object newObj=Proxy.newProxyInstance(
//ClassLoader loader, Class<?>[] interfaces, InvocationHandler h
Client.class.getClassLoader(),
new Class[]{IRenter.class},
new InvocationHandler() {
@Override//引數:proxy是代理後的物件(和外面的newObj一樣的),method是當前被呼叫的method物件,args是當前method物件的引數
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("rent")){
System.out.println("給中介交點費用...");
return method.invoke(r, args);
}
return method.invoke(r, args);//這裡是放行,不做任何攔截,只是從這裡過了一下
}
});
/*
* 代理後的物件newObj和原型物件r 是不同型別的物件。二者屬於兄弟關係,都是IRenter介面的實現類(子類)
* 因此newObj是無法強轉成Renter型別
*/
//代理後的newObj通常都是強轉換成介面型別來使用
IRenter ir=(IRenter)newObj;
ir.rent();
String str=ir.toString();
System.out.println(str);
}
}
執行結果圖:
連線池用代理模式:
ConnnUtils3.java
package cn.hncu.pool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
//new出多個連線,放進池中,連線不再是單例,用池當單例
public class ConnUtils3 {
private static List<Connection> pool=new ArrayList<Connection>();
private final static int NUM=3;
private ConnUtils3(){
}
static{
Properties p=new Properties();
try {
p.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));//可從本類ConnUtils.Class.getClassLoader().getResourceAsStream("jdbc.properties")拿
String dirver=p.getProperty("driver");
String url=p.getProperty("url");
String user=p.getProperty("username");
String password=p.getProperty("password");
Class.forName(dirver);
for(int i=0;i<NUM;i++){
final Connection conn=DriverManager.getConnection(url, user, password);
//用代理模式生成一個增強Connection物件,把它的close方法攔截並改掉
Object newObj=Proxy.newProxyInstance(
//載入器
ConnUtils3.class.getClassLoader(),
//介面型別
new Class[]{Connection.class},
//攔截
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("close")){
pool.add((Connection)proxy);
return null;
}
return method.invoke(conn, args);
}
});
pool.add((Connection)newObj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static synchronized Connection getConn(){//加把鎖,防止有多條執行緒在這裡等待
try {
if(pool.size()<=0){
System.out.println("池中沒有連線了.....");
Thread.sleep(1000);
return getConn();
}
} catch (InterruptedException e) {
}
return pool.remove(0);
}
}
測試類與測試結果和上次的一樣,這裡不再貼出。
通用模板:
ProxyUtils.java
package cn.hncu.proxy.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtils implements InvocationHandler {
private Object obj;
private ProxyUtils(Object obj){
this.obj=obj;
}
public static Object getProxy(Object obj){
Object newObj=Proxy.newProxyInstance(
ProxyUtils.class.getClassLoader(),
obj.getClass().getInterfaces(),
new ProxyUtils(obj));//構造傳參
return newObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("前面攔攔:......");
Object resObj=method.invoke(obj, args);
System.out.println("後面攔攔:......");
return resObj;
}
}
測試模板:
人和狗
dog.java
package cn.hncu.proxy.util;
public class Dog implements IDog {
private String name;
public Dog(){
this.name="小黑";
}
/* (non-Javadoc)
* @see cn.hncu.proxy.util.IDog#bark()
*/
@Override
public void bark(){
System.out.println(name+": 汪``汪``汪``");
}
}
IDog.java
package cn.hncu.proxy.util;
public interface IDog {
public abstract void bark();
}
Person.java
package cn.hncu.proxy.util;
public class Person implements IPerson {
private String name;
public Person(String name){
this.name=name;
}
/* (non-Javadoc)
* @see cn.hncu.proxy.util.IPerson#sayHi()
*/
@Override
public void sayHi(){
System.out.println("Hello,I am "+name);
}
}
IPerson.java
package cn.hncu.proxy.util;
public interface IPerson {
public abstract void sayHi();
}
Client.java
package cn.hncu.proxy.util;
public class Client {
public static void main(String[] args) {
Dog dog=new Dog();
IDog dog2=(IDog) ProxyUtils.getProxy(dog);
Person p=new Person("二牛");
IPerson p2=(IPerson) ProxyUtils.getProxy(p);
p2.sayHi();
System.out.println("------------------------");
dog2.bark();
}
}
執行結果: