JAVA 의 동적 에이 전 트 를 사용 하여 데이터베이스 연결 풀 을 실현 합 니 다.

저 자 는 JAVA 의 동적 에이 전 트 를 사용 하여 데이터베이스 연결 풀 을 실현 하여 사용자 가 일반적인 jdbc 연결 의 사용 습관 으로 연결 풀 을 사용 할 수 있 도록 합 니 다.
데이터베이스 연결 탱크 는 응용 서 비 스 를 작성 하 는 데 자주 사용 되 는 모듈 입 니 다. 데이터 베 이 스 를 너무 자주 연결 하 는 것 은 서비스 성능 에 있어 병목 입 니 다. 버퍼 기술 을 사용 하면 이 병목 을 없 앨 수 있 습 니 다.우 리 는 인터넷 에서 데이터베이스 연결 탱크 에 관 한 소스 프로그램 을 많이 찾 을 수 있 지만 이러한 공 통 된 문 제 를 발견 했다. 이런 연결 탱크 의 실현 방법 은 사용자 와 의 결합 도 를 어느 정도 증가 시 켰 다.많은 연결 탱크 는 사용자 가 규정된 방법 을 통 해 데이터 베 이 스 를 연결 하 라 고 요구 한 다 는 점 을 이해 할 수 있다. 왜냐하면 현재 모든 응용 서버 가 데이터 베 이 스 를 연결 하 는 방식 은 이런 방식 으로 이 루어 진 것 이기 때문이다.그러나 또 다른 공 통 된 문 제 는 사용자 가 Connection. close () 방법 을 명시 적 으로 호출 하 는 것 을 허용 하지 않 고 정 해진 방법 으로 연결 을 닫 아야 한 다 는 것 이다.이런 방법 은 두 가지 단점 이 있다.
첫째, 사용자 의 사용 습관 을 바 꾸 고 사용자 의 사용 난이 도 를 증가 시 켰 다.
우선 정상 적 인 데이터베이스 조작 과정 을 살 펴 보 자.
int executeSQL(String sql) throws SQLException
{
	Connection conn = getConnection();	//             
	PreparedStatement ps = null;
	int res = 0;
	try{
		ps = conn.prepareStatement(sql);
		res = ps.executeUpdate();
}finally{
try{
ps.close();
}catch(Exception e){}
try{
	conn.close();//
}catch(Exception e){}
}
return res;
}

 
사용 자 는 데이터 베 이 스 를 연결 한 후에 연결 방법 close 를 직접 호출 하여 데이터 베이스 자원 을 방출 합 니 다. 만약 에 우리 가 앞에서 언급 한 연결 탱크 의 실현 방법 을 사용 하면 그 문 구 는 conn. close () 가 특정한 문구 로 대 체 됩 니 다.
둘째: 연결 탱크 가 그 중의 모든 연결 을 독점 적 으로 제어 할 수 없 게 한다.연결 탱크 는 사용자 가 직접 연결 하 는 close 방법 을 호출 할 수 없 기 때문에 사용자 가 사용 하 는 과정 에서 습관 적 인 문제 로 데이터베이스 연결 을 직접 닫 으 면 연결 탱크 는 모든 연결 상 태 를 정상적으로 유지 하지 못 하고 연결 탱크 와 응용 이 서로 다른 개발 자 에 의 해 이 루어 질 때 이런 문제 가 발생 하기 쉽다.
위 에서 언급 한 두 가지 문 제 를 종합 하여 우 리 는 이 두 가지 죽 을 문 제 를 어떻게 해결 할 것 인 가 를 토론 합 시다.
우선 우 리 는 먼저 사용자 가 이 데이터 베 이 스 를 어떻게 사용 하려 고 하 는 지 를 고려 해 보 자.사용 자 는 특정한 방법 을 통 해 데이터 베 이 스 를 연결 할 수 있 으 며, 이 연결 의 유형 은 표준 자바. sql. connection 이 어야 합 니 다.사용 자 는 이 데이터베이스 연결 을 가 져 온 후에 이 연결 을 닫 는 등 임의로 조작 할 수 있 습 니 다.
사용자 가 사용 하 는 설명 을 통 해 Connection. close 방법 을 어떻게 연결 할 수 있 는 지 하 는 것 이 우리 글 의 주제 가 되 었 습 니 다.
데이터 베 이 스 를 연결 하 는 close 방법 을 연결 하기 위해 서 는 갈고리 와 유사 한 메커니즘 이 있어 야 한다.예 를 들 어 윈도 프로 그래 밍 에서 우 리 는 훅 API 를 이용 하여 특정한 윈도 API 에 대한 연결 을 실현 할 수 있다.JAVA 에 도 이런 메커니즘 이 있다.JAVA 는 프 록 시 클래스 와 Invocation Handler 를 제공 합 니 다. 이 두 종 류 는 모두 자바. lang. reflect 패키지 에 있 습 니 다.우선 썬 이 제공 한 문서 가 이 두 가 지 를 어떻게 묘사 하 는 지 살 펴 보 자.
public interface InvocationHandler
InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 
Each proxy instance has an associated invocation handler. 
When a method is invoked on a proxy instance, 
the method invocation is encoded and dispatched to the invoke method of its invocation handler.

 
썬 의 API 문서 에는 프 록 시 에 대한 설명 이 많아 나열 되 지 않 습 니 다.인터페이스 InvocationHandler 에 대한 문 서 를 통 해 Proxy 인 스 턴 스 를 호출 하 는 방법 을 볼 수 있 습 니 다. Invocationhanlder 의 invoke 방법 을 실행 합 니 다.JAVA 의 문서 에서 우 리 는 이러한 동적 에이전트 가 인터페이스 만 연결 할 수 있 는 방법 을 동시에 알 게 되 었 고 일반적인 클래스 에 대해 서 는 유효 하지 않다 는 것 을 알 게 되 었 다. 자바. sql. connection 자체 도 하나의 인터페이스 임 을 감안 하여 close 방법 을 어떻게 연결 하 는 지 해결 하 는 길 을 찾 았 다.
먼저, 우 리 는 데이터베이스 연결 탱크 파라미터 의 클래스 를 정의 하고 데이터베이스 의 JDBC 드라이버 클래스, 연 결 된 URL 과 사용자 이름 암호 등 일부 정 보 를 정의 합 니 다. 이 클래스 는 연결 탱크 를 초기 화 하 는 데 사용 되 는 매개 변수 입 니 다. 구체 적 인 정 의 는 다음 과 같 습 니 다.
public class ConnectionParam implements Serializable
{
	private String driver;				//       
	private String url;					//     URL
	private String user;					//      
	private String password;				//     
	private int minConnection = 0;		//      
	private int maxConnection = 50;		//     
	private long timeoutValue = 600000;//         
	private long waitTime = 30000;		//                     

 
그 다음은 연결 탱크 의 공장 류 Connection Factory 입 니 다. 이 종 류 를 통 해 연결 탱크 의 대상 을 하나의 이름 과 대응 시 킵 니 다. 사용 자 는 이 이름 을 통 해 지정 한 연결 탱크 의 대상 을 얻 을 수 있 습 니 다. 구체 적 인 코드 는 다음 과 같 습 니 다.
/**
 *      ,                          
 * @author liusoft
 */
public class ConnectionFactory
{
	//                      
	static Hashtable connectionPools = null;
	static{
		connectionPools = new Hashtable(2,0.75F);
	} 
	/**
	 *                      
	 * @param dataSource	          
	 * @return DataSource	            
	 * @throws NameNotFoundException	          
	 */
	public static DataSource lookup(String dataSource) 
		throws NameNotFoundException
	{
		Object ds = null;
		ds = connectionPools.get(dataSource);
		if(ds == null || !(ds instanceof DataSource))
			throw new NameNotFoundException(dataSource);
		return (DataSource)ds;
	}
	/**
	 *                              
	 * @param name		        
	 * @param param	        ,     ConnectionParam
	 * @return DataSource	              
	 * @throws NameAlreadyBoundException	    name          
	 * @throws ClassNotFoundException		                 
	 * @throws IllegalAccessException		              
	 * @throws InstantiationException		          
	 * @throws SQLException				            
	 */
	public static DataSource bind(String name, ConnectionParam param)
		throws NameAlreadyBoundException,ClassNotFoundException,
				IllegalAccessException,InstantiationException,SQLException
	{
		DataSourceImpl source = null;
		try{
			lookup(name);
			throw new NameAlreadyBoundException(name);
		}catch(NameNotFoundException e){
			source = new DataSourceImpl(param);
			source.initConnection();
			connectionPools.put(name, source);
		}
		return source;
	}
	/**
	 *           
	 * @param name		        
	 * @param param	        ,     ConnectionParam
	 * @return DataSource	              
	 * @throws NameAlreadyBoundException	    name          
	 * @throws ClassNotFoundException		                 
	 * @throws IllegalAccessException		              
	 * @throws InstantiationException		          
	 * @throws SQLException				            
	 */
	public static DataSource rebind(String name, ConnectionParam param)
		throws NameAlreadyBoundException,ClassNotFoundException,
				IllegalAccessException,InstantiationException,SQLException
	{
		try{
			unbind(name);
		}catch(Exception e){}
		return bind(name, param);
	}
	/**
	 *             
	 * @param name
	 * @throws NameNotFoundException
	 */
	public static void unbind(String name) throws NameNotFoundException
	{
		DataSource dataSource = lookup(name);
		if(dataSource instanceof DataSourceImpl){
			DataSourceImpl dsi = (DataSourceImpl)dataSource;
			try{
				dsi.stop();
				dsi.close();
			}catch(Exception e){
			}finally{
				dsi = null;
			}
		}
		connectionPools.remove(name);
	}
	
}

 
Connection Factory 는 사용자 가 연결 탱크 를 구체 적 인 이름 에 연결 하고 연결 을 취소 하 는 동작 을 제공 합 니 다.사용 자 는 이 두 가지 유형 에 만 관심 을 가지 면 데이터베이스 연결 탱크 의 기능 을 사용 할 수 있다.연결 탱크 를 어떻게 사용 하 는 지 알려 드 리 겠 습 니 다.
	String name = "pool";
	String driver = " sun.jdbc.odbc.JdbcOdbcDriver ";
	String url = "jdbc:odbc:datasource";
	ConnectionParam param = new ConnectionParam(driver,url,null,null);
	param.setMinConnection(1);
	param.setMaxConnection(5);
	param.setTimeoutValue(20000);
	ConnectionFactory.bind(name, param);
	System.out.println("bind datasource ok.");
	//                ,                 
	//                 
	DataSource ds = ConnectionFactory.lookup(name);
	try{
		for(int i=0;i<10;i++){
			Connection conn = ds.getConnection();
			try{
				testSQL(conn, sql);
			}finally{
				try{
					conn.close();
				}catch(Exception e){}
			}
		}
	}catch(Exception e){
		e.printStackTrace();
	}finally{
		ConnectionFactory.unbind(name);
		System.out.println("unbind datasource ok.");
		System.exit(0);
	}

 
사용자 의 예제 코드 를 통 해 알 수 있 듯 이 우 리 는 일반적인 연결 탱크 에서 발생 하 는 두 가지 문 제 를 해결 했다.하지만 우리 가 가장 관심 을 가 지 는 것 은 close 방법 을 어떻게 해결 하 느 냐 하 는 것 이다.연결 작업 은 주로 Connection Factory 의 두 마디 코드 입 니 다.
source = new DataSourceImpl(param);
source.initConnection();

 
DataSourceImpl 은 인터페이스 javax. sql. dataSource 를 실현 하 는 클래스 로 연결 탱크 를 유지 하 는 대상 입 니 다.이 종 류 는 보 호 받 는 클래스 이기 때문에 사용자 에 게 노출 되 는 방법 은 인터페이스 DataSource 에서 정의 하 는 방법 만 있 고 다른 모든 방법 은 사용자 에 게 볼 수 없다.사용자 가 접근 할 수 있 는 방법 에 관심 을 가 져 보 겠 습 니 다. getConnection
/**
 * @see javax.sql.DataSource#getConnection(String,String)
 */
	public Connection getConnection(String user, String password) throws SQLException 
	{
		//              
		Connection conn = getFreeConnection(0);
		if(conn == null){
			//           ,         
			//                ,               
			if(getConnectionCount() >= connParam.getMaxConnection())
				conn = getFreeConnection(connParam.getWaitTime());
			else{//       ,            
				connParam.setUser(user);
				connParam.setPassword(password);
				Connection conn2 = DriverManager.getConnection(connParam.getUrl(), 
				user, password);
				//           
				_Connection _conn = new _Connection(conn2,true);
				synchronized(conns){
					conns.add(_conn);
				}
				conn = _conn.getConnection();
			}
		}
		return conn;
	}
	/**
	 *              
	 * @param nTimeout	       0            null
	 *       nTimeout           ,        
	 * @return Connection
	 * @throws SQLException
	 */
	protected synchronized Connection getFreeConnection(long nTimeout) 
		throws SQLException
	{
		Connection conn = null;
		Iterator iter = conns.iterator();
		while(iter.hasNext()){
			_Connection _conn = (_Connection)iter.next();
			if(!_conn.isInUse()){
				conn = _conn.getConnection();
				_conn.setInUse(true);				
				break;
			}
		}
		if(conn == null && nTimeout > 0){
			//  nTimeout            
			try{
				Thread.sleep(nTimeout);
			}catch(Exception e){}
			conn = getFreeConnection(0);
			if(conn == null)
				throw new SQLException("          ");
		}
		return conn;
	}

 
DataSourceImpl 클래스 에서 getConnection 방법 을 실현 하 는 것 은 정상 적 인 데이터 베이스 연결 탱크 의 논리 와 일치 합 니 다. 먼저 남 은 연결 여 부 를 판단 하고 없 으 면 연결 수가 최대 연결 수 를 초과 하 는 지 등 일부 논 리 를 판단 합 니 다.그러나 한 가지 다른 것 은 DriverManager 를 통 해 얻 은 데이터베이스 연결 이 제때에 돌아 오 는 것 이 아니 라 라 는 것 이다.Connection 의 클래스 를 중개 하고 호출Connection. getConnection 이 돌 아 왔 습 니 다.만약 우리 가 돌아 올 인터페이스 대상 을 중개, 즉 JAVA 의 Proxy 를 통 해 인수 하지 않 았 다 면, 우 리 는 Connection. close 방법 을 막 을 방법 이 없 었 을 것 이다.
드디어 핵심 이 왔 군. 우리 먼저 보 자Connection 이 어떻게 실현 되 었 는 지, 그리고 클 라 이언 트 가 Connection. close 방법 을 호출 할 때 어떤 절 차 를 밟 았 는 지, 왜 진정 으로 연결 을 닫 지 않 았 는 지 소개 합 니 다.
/**
 *         ,   close  
 * @author Liudong
 */
class _Connection implements InvocationHandler
{
	private final static String CLOSE_METHOD_NAME = "close";
	private Connection conn = null;
	//       
	private boolean inUse = false;
	//                
	private long lastAccessTime = System.currentTimeMillis();
	
	_Connection(Connection conn, boolean inUse){
		this.conn = conn;
		this.inUse = inUse;
	}
	/**
	 * Returns the conn.
	 * @return Connection
	 */
	public Connection getConnection() {
		//       conn    ,    close  
		Connection conn2 = (Connection)Proxy.newProxyInstance(
			conn.getClass().getClassLoader(),
			conn.getClass().getInterfaces(),this);
		return conn2;
	}
	/**
	 *                
	 * @throws SQLException
	 */
	void close() throws SQLException{
		//     conn         ,      close          
		conn.close();
	}
	/**
	 * Returns the inUse.
	 * @return boolean
	 */
	public boolean isInUse() {
		return inUse;
	}
	/**
	 * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object)
	 */
	public Object invoke(Object proxy, Method m, Object[] args) 
		throws Throwable 
	{
		Object obj = null;
		//       close   ,    close            
		if(CLOSE_METHOD_NAME.equals(m.getName()))
			setInUse(false);		
		else
			obj = m.invoke(conn, args);	
		//          ,           
		lastAccessTime = System.currentTimeMillis();
		return obj;
	}
		
	/**
	 * Returns the lastAccessTime.
	 * @return long
	 */
	public long getLastAccessTime() {
		return lastAccessTime;
	}
	/**
	 * Sets the inUse.
	 * @param inUse The inUse to set
	 */
	public void setInUse(boolean inUse) {
		this.inUse = inUse;
	}
}

 
사용자 가 연 결 된 close 방법 을 호출 하면 사용자 의 연결 대상 이 연결 을 거 친 대상 이기 때문에 JAVA 가상 기 회 는 먼저 호출Connection. invoke 방법 은 이 방법 에서 close 방법 인지 여 부 를 먼저 판단 하고 그렇지 않 으 면 코드 를 연결 되 지 않 은 연결 대상 conn 에 전달 합 니 다.그렇지 않 으 면 이 연결 상 태 를 간단하게 사용 할 수 있 도록 설정 할 수 있 습 니 다.이로써 당신 은 전체 인수 과정 을 알 게 될 것 입 니 다. 그러나 동시에 의문 도 있 습 니 다. 그러면 이미 만들어 진 연결 들 이 진정 으로 닫 을 수 없 는 것 이 아 닙 니까?답 은 돼.Connection Factory. unbind 방법 을 살 펴 보 겠 습 니 다. 이 방법 은 먼저 이름 에 해당 하 는 연결 풀 대상 을 찾 은 다음 이 연결 풀 의 모든 연결 을 닫 고 연결 풀 을 삭제 합 니 다.DataSourceImpl 클래스 에서 모든 연결 을 닫 는 close 방법 을 정 의 했 습 니 다. 자세 한 코드 는 다음 과 같 습 니 다.
	/**
	 *                
	 * @return int           
	 * @throws SQLException
	 */
	public int close() throws SQLException
	{
		int cc = 0;
		SQLException excp = null;
		Iterator iter = conns.iterator();
		while(iter.hasNext()){
			try{
				((_Connection)iter.next()).close();
				cc ++;
			}catch(Exception e){
				if(e instanceof SQLException)
					excp = (SQLException)e;
			}
		}
		if(excp != null)
			throw excp;
		return cc;
	}

 
이 방법 은 연결 탱크 의 모든 대상 을 연결 하 는 close 방법 을 일일이 호출 합 니 다. 이 close 방법 은 에 대응 합 니 다.Connection 에서 close 에 대한 실현 은Connection 정의 에서 데이터베이스 연결 을 닫 을 때 연결 되 지 않 은 대상 을 직접 호출 하 는 닫 는 방법 이기 때문에 이 close 방법 은 데이터베이스 자원 을 진정 으로 방출 합 니 다.
상기 문 자 는 인터페이스 방법의 연결 만 묘 사 했 을 뿐 구체 적 으로 실 용적 인 연결 탱크 모듈 은 남 은 연결 에 대한 모니터링 과 연결 을 제때에 풀 어야 합 니 다. 상세 한 코드 는 첨부 파일 을 참조 하 십시오.

좋은 웹페이지 즐겨찾기