天天看點

傳智播客李勇Jdbc視訊筆記(34-42)

34、編寫一個基本的連接配接池來實作連接配接的複用

大家都知道Arraylist的底層使用數組實作的,而LinkedList使用連結清單實作的,是以對于Arraylist讀取速度比較快而對于LinkedList修改和添加

比較快,是以我們這個連接配接池因為要頻繁的操作集合是以用LinkedList來實作。

public class MyDataSource {
		private static String url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true";
		private static String user = "root";
		private static String password = "admin";

		LinkedList<Connection> connectionsPool = new LinkedList<Connection>();

		//向我們的LinkedList集合中加入10個連結作為我們的連接配接池
		public MyDataSource() {
			for(int i=0; i<10; i++) {
				try {
					//将Connection放到連結清單的最後面
					connectionsPool.addLast(this.createConnection());
				} catch (SQLException e) {
					throw new ExceptionInInitializerError(e.getMessage());
				}
			}
		}
		//建立10個連結
		public Connection createConnection() throws SQLException{
			return DriverManager.getConnection(url, user, password);
		}

		//得到一個連結(先進先出算法)
		public Connection getConnection() {
			return connectionsPool.removeFirst();
		}

		//關閉一個連結,這個關閉不是真正意義上的關閉,而是又把他放回到連接配接池中,實作了Connection的複用
		public void free(Connection conn) {
			connectionsPool.addLast(conn);
		}
	}
           

35、對基本連接配接池進行一些工程細節上的優化

在上面實作的連接配接池中我們隻是預設建立了5個連接配接,但是如果這個時候有5個線程同時都來拿連接配接,那連接配接池裡就沒有連接配接

在有線程過來拿的時候就會報錯了,現在我們進行一些優化(重複的代碼就不寫了,隻寫改動的)

//規定預設建立的連接配接數
	private static int initCount = 5;
	//規定最大可以建立的連接配接數
	private static int maxCount = 10;
	//統計目前共建立了多少個連接配接
	private int currentCount = 0;

	LinkedList<Connection> connectionsPool = new LinkedList<Connection>();

	public MyDataSource() {
		for(int i=0; i<initCount; i++) {
			try {
				connectionsPool.addLast(this.createConnection());
				//每建立一個連結 currentCount ++
				this.currentCount ++;
			} catch (SQLException e) {
				throw new ExceptionInInitializerError(e.getMessage());
			}
		}
	}

	public Connection createConnection() throws SQLException{
		return DriverManager.getConnection(url, user, password);
	}

	public Connection getConnection() throws SQLException {
		//因為Connection不是線程安全的,是以我必須保證每個線程拿到的連結不是一個,是以要進行同步:當兩個線程同時來拿的時候
		//另外一個線程必須等待
		synchronized (connectionsPool) {
			if(connectionsPool.size() > 0)
				return connectionsPool.removeFirst();
			//如果目前建立的連結數沒有到最大值,那就繼續建立連結
			if(this.currentCount < maxCount) {
				this.currentCount ++;
				return this.createConnection();
			}
			//抛出異常
			throw new SQLException("目前已經沒有可用連接配接了");
		}
	}
           

36、通過代理模式來保持使用者關閉連接配接的習慣.

在上面的示例中我們在關閉連結的時候,調用的是free方法來把這個連接配接又放回到了池中,但是按照開發人員的使用習慣應該是調用colse()方法

來關閉一個連結,但是如果調用close方法關閉,那這個連接配接就真的關閉了,也就是說我們這個方法設計的不符合開發人員的使用習慣下面我用代理的

方法來解決這個問題:

定義一個類實作Connection接口,Connectio接口中有很多的方法,這些方法我們都無法自己完成,我們交給通過構造方法傳遞進來的真正的Connection

的對象來完成,我們隻是修改它的close方法,在使用者得到連結的時候我們傳回給使用者這個類的對象,那麼當使用者調用close方法關閉連結的時候,我們就可以在

這個close方法中将使用者要關閉的那個連結再次的放到連接配接池中,這樣連結就不會真正的關閉了。

public class MyConnection implements Connection {

	MyDataSource2 dataSource;
	Connection realConn;
	int maxUseCount = 5;
	int currentUseCount = 0;

	MyConnection(MyDataSource2 myDataSource2, Connection conn) {
		this.dataSource = myDataSource2;
		this.realConn = conn;
	}

	public void clearWarnings() throws SQLException {
		realConn.clearWarnings();
	}

	public void close() throws SQLException {
		this.currentUseCount++;
		//規定同一個連結隻能使用maxUseCount次超過這個次數,就把真正的連結關閉,這個時候在拿連接配接,拿到的就是
		新建立得一個新的連結對象了。
		if (this.currentUseCount < this.maxUseCount) {
			this.dataSource.connectionsPool.addLast(this);
		}
		else {
			this.realConn.close();
			this.dataSource.currentCount--;
		}
	}

	public void commit() throws SQLException {
		this.realConn.commit();
	}


	在類MyDataSource中我們别的都不改變,隻改變他的一個方法(MyDataSource的全部代碼檢視34)
	當使用者想拿到連結的時候傳回給他conn類的一個代理對象myConnection對象就可
	public Connection createConnection() throws SQLException{
		Connection conn = DriverManager.getConnection(url, user, password);
		MyConnection myConnection = new MyConnection(this, conn);
		return myConnection;
	}
           

37、Java的動态代理及使用該技術完善連接配接代理

在上面的示例中,我們為了産生一個代理對象實作了Connection接口的所有的方法,但是我們隻需要修改它的close方法,别的方法

我們都需要交給真正的Connection對象去處理,比較麻煩我們用動态代理來實作它

public class MyConnectionHandler implements InvocationHandler {

		private Connection conn;
		private MyDataSource2 dataSource;
		private int maxUseCount = 5;
		private int currentUseCount = 0;
		private Connection proxyConn;

		MyConnectionHandler(MyDataSource2 dataSource) {
			this.dataSource = dataSource;
		}

		Connection bind(Connection realConnection) {
			this.conn = realConnection;
			//生成Connection的代理對象
			proxyConn = (Connection)Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[]{Connection.class}, this);
			//System.out.println(proxyConn + "代理");
			return proxyConn;
		}

		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			//調用方法的時候如果是close方法就執行我們的邏輯,對于其他的所有的方法,全部交給真實Connection對象本身自己去處理
			if("close".equals(method.getName())) {
				this.currentUseCount ++;
				if(this.currentUseCount < this.maxUseCount) {
					this.dataSource.free(proxyConn);
				}
				else {
					this.conn.close();
					this.dataSource.currentCount --;
				}
			}
			return method.invoke(conn, args);
		}
	}
           

當寫完上面的代理類後,我們還是需要修改MyDataSource類的createConnection()方法來調用我們的代理類,将它需要的參數

傳遞給他并把生成的代理類傳回:

public Connection createConnection() throws SQLException{
		Connection conn = DriverManager.getConnection(url, user, password);
		MyConnectionHandler proxyConn = new MyConnectionHandler(this);
		return proxyConn.bind(conn);
	}
           

38、标準DataSource接口及資料源的總結介紹

了解資料源的優勢與特點:

DataSource用來取代DriverManager來擷取Connection;

通過DataSource獲得Connection速度很快;

通過DataSource獲得的Connection都是已經被包裹過的(不是驅動原來的連接配接),他的close方法已經被修改。

一般DataSource内部會用一個連接配接池來緩存Connection,這樣可以大幅度提高資料庫的通路速度;

連接配接池可以了解成一個能夠存放Connection的Collection;

我們的程式隻和DataSource打交道,不會直接通路連接配接池;

39、如何使用開源項目DBCP(實際項目中常用):主要分為三個步驟

(1)使用DBCP必須用的三個包:commons-dbcp-1.2.1.jar, commons-pool-1.2.jar, commons-collections-3.1.jar。

(2)添加dbcp的配置檔案

(3)Java API: BasicDataSourceFactory.createDataSource(properties);

在我們上面的示例中我們的MyDataSource可以實作Datasource接口,實作裡面的getConnection()方法,我們想用dbcp的時候,把

資料源的實作換成dbcp就行,因為這個元件也實作了DataSource接口,是以這就是面向接口程式設計的好處,可以換成不同的實作 ,不用

改變我們其他的代碼

40、将DAO中的修改方法提取到抽象父類中:

當你在寫程式的時候,如果你發現你的代碼總是有重複的地方那麼就有必要封裝一下了。把一段代碼中的變化的部分

抽取到父類裡,把要用的參數傳遞過去,然後再實作類裡面直接super調用父類的方法就可以了。把參數傳遞過去就行了

可以向外提過多個方法的重載 但是真正的代碼實作隻有一份。

41、使用模闆方法設計模式處理DAO中的查詢方法

在這個示例中需要用到多态的知識一般我們說滿足多态要有三個條件:

(1)要有繼承

(2)要有方法的重寫

(3)要有父類的引用指向子類對象

但是有一種情況是特殊的:如果子類調用父類的方法(包括構造方法)而在你調用的這個父類的方法裡你又調用了父類中的其他的方法,而你調用的這個

方法又被子類重寫了,那麼這就是多态

簡單的程式示例:

抽象類

public abstract class AbstractDao {
		abstract public String printName(String name) ;
		public String test2() {
			this.printName("aaa");
			return "你好";
		}
	}

	//調用抽象類的方法
	public class AbstractDaoImpl1 extends AbstractDao {

		public void getName() {
			super.test2();

		}

		@Override
		public String printName(String name) {
			return null;
		}
	}
           

我們用一個抽象類把重複的方法封裝到裡面,那麼多個類都實作了父類的方法,在調用的時候我們怎麼知道調用的是哪個

實作方法呢,這個時候Java的多态特性就發揮作用了:你在實作類中調用父類的方法,而你重寫了你要調用的這個方法,那麼

你調用的就是你自己實作的這個方法。

把相同的代碼抽取出來封裝成為一個方法,把容易變動的地方作為方法的參數傳遞進去 ,這樣我們的Dao層會省去很多的重複的代碼

而這個封裝的方法就可以稱之為“模闆方法”

42、使用政策模式對模闆方法設計模式進行改進

sql1 = "select name from user where id = ?";

sql2 = "select id, name, age from user where id = ?";

這兩個sql查詢的對象不一樣,一個是隻需要傳回一個name屬性的值就可以,而另外一個sql需要傳回一個User

對象,這樣的話我們上面的模闆方法就有不好用了,雖然可以查出來但是性能上有損失 我隻要查詢一個username

你可能會把整個User對象都給我查詢出來。我們可以針對每個不同的sql語句查詢的内容的不同把模闆方法也分解成

多個不一樣的能滿足相應sql查詢語句的方法,這就叫做政策模式,就是針對每一種情況都有不同的方法,來解決

//行映射器
	public interface RowMapper {
		public Object mapRow(ResultSet rs) throws SQLException;
	}
	//對于方法的封裝
	public class MyDaoTemplate {
		public Object find(String sql, Object[] args, RowMapper rowMapper) {
			Connection conn = null;
			PreparedStatement ps = null;
			ResultSet rs = null;
			try {
				conn = JdbcUtils.getConnection();
				ps = conn.prepareStatement(sql);
				for (int i = 0; i < args.length; i++)
					ps.setObject(i + 1, args[i]);
				rs = ps.executeQuery();
				Object obj = null;
				if (rs.next()) {
					obj = rowMapper.mapRow(rs);
				}
				return obj;
			} catch (SQLException e) {
				throw new DaoException(e.getMessage(), e);
			} finally {
				JdbcUtils.free(rs, ps, conn);
			}
		}
	}
           

//對于下面的不同的sql,在我們調用find方法的時候new RowMapper然後針對不同的sql有不同的實作就可以了