OSDN Git Service

track
[luz/luz.git] / src / com / lavans / luz2 / sql / ConnectionPool.java
1 /**\r
2  * $Id: ConnectionPool.java 222 2010-09-06 19:20:24Z dobashi $\r
3  *\r
4  * Copyright Lavans Networks Inc.\r
5  */\r
6 package com.lavans.luz2.sql;\r
7 \r
8 import java.sql.Connection;\r
9 import java.sql.Driver;\r
10 import java.sql.SQLException;\r
11 import java.sql.Statement;\r
12 import java.util.ArrayList;\r
13 import java.util.Collections;\r
14 import java.util.List;\r
15 import java.util.Properties;\r
16 \r
17 import org.apache.commons.logging.Log;\r
18 import org.apache.commons.logging.LogFactory;\r
19 \r
20 import com.lavans.luz2.sql.bind.BindConnection;\r
21 import com.lavans.luz2.sql.bind.BindConnectionImpl;\r
22 import com.lavans.luz2.sql.pool.PooledConnection;\r
23 import com.lavans.luz2.sql.stats.Statistics;\r
24 import com.lavans.luz2.sql.stats.StatsConnection;\r
25 \r
26 /**\r
27  * ConnectionPool。\r
28  * DBManagerからのみ利用される。\r
29  *\r
30  * @author      dobashi\r
31  * @version 1.0\r
32  * @version 1.1 dobashi20040707 接続の有効性はcreateStatement()だけでチェックできるはず\r
33  */\r
34 public class ConnectionPool{\r
35         /** ロガー。debug用 */\r
36         private static Log logger = LogFactory.getLog(ConnectionPool.class);\r
37 \r
38         /**\r
39          * DB接続用定義\r
40          */\r
41         private String driverName = null;\r
42         private String url     = null;\r
43         private String user   = null;\r
44         private String pass   = null;\r
45 \r
46         /**\r
47          * NativeDriver.\r
48          * ConnectionPoolは接続先ごとにインスタンス化されるので、\r
49          * DriverManagerに管理させる必要はなく、ConnectionPool毎に\r
50          * 実Driverインスタンスを持つ。\r
51          */\r
52         private Driver driver = null;\r
53 \r
54         /**\r
55          * SQL統計情報を収集するか。\r
56          * trueならcreateConnectionしたときにStatsConnectionでラップする。\r
57          */\r
58         private boolean statistics = false;\r
59 \r
60         /**\r
61          * SQLログを出力するか。\r
62          */\r
63         private boolean isLogging = true;\r
64 \r
65         /**\r
66          * コネクション最大数。\r
67          * デフォルトは10。lavansutil.xmlのinit_connectionsで変更可能。\r
68          */\r
69         private int max_connections  = 10;\r
70 \r
71         /**\r
72          * 初期コネクション数。\r
73          * デフォルトは2。lavansutil.xmlのinit_connectionsで変更可能。\r
74          */\r
75         private int init_connections = 2;\r
76 \r
77         /**\r
78          * 空きコネクションリスト。\r
79          */\r
80         private final List<PooledConnection> poolList = Collections.synchronizedList(new ArrayList<PooledConnection>());\r
81         /**\r
82          * 使用中コネクションリスト。\r
83          */\r
84         private final List<PooledConnection> useList  = Collections.synchronizedList(new ArrayList<PooledConnection>());\r
85 \r
86         /**\r
87          * トランザクション管理\r
88          */\r
89         private final ThreadLocal<PooledConnection> transactionList = new ThreadLocal<PooledConnection>();\r
90 \r
91         protected static final String MSG_ERR_TOOMANYCONNECTIONS = "接続数が最大値を超えています。";\r
92         protected static final String SQLSTATE_CONNECTION_EXCEPTION = "08000";\r
93         public static final int ERR_CONNECTION_OVERFLOW = 1;\r
94 \r
95         /**\r
96          * 強制チェックフラグ。\r
97          */\r
98         private boolean isForceCheck=false;\r
99         /**\r
100          * 強制チェック用SQL。\r
101          */\r
102         private String validSql=null;\r
103 \r
104 \r
105         /**\r
106          * コンストラクタ。\r
107          **/\r
108         public ConnectionPool(String aDriver, String aUrl,String aUser,String aPass){\r
109                 driverName    = aDriver;\r
110                 url       = aUrl;\r
111                 user      = aUser;\r
112                 pass      = aPass;\r
113         }\r
114 \r
115         /**\r
116          * 接続数最大値設定\r
117          **/\r
118         public void setMaxConnections(int value){\r
119                 max_connections = value;\r
120         }\r
121 \r
122         /**\r
123          * 接続数初期値設定\r
124          **/\r
125         public void setInitConnections(int value){\r
126                 init_connections = value;\r
127         }\r
128 \r
129         /**\r
130          * 初期化\r
131          **/\r
132         public void init()\r
133                 throws ClassNotFoundException,IllegalAccessException,InstantiationException, SQLException\r
134         {\r
135                 driver = (Driver)Class.forName(driverName).newInstance();\r
136                 for(int i=0; i<init_connections; i++){\r
137                         poolList.add(createConnection());\r
138                 }\r
139         }\r
140 \r
141         /**\r
142          * DBへのコネクション作成。\r
143          * 設定ファイルが間違っていてDriverManager#getConnection()の戻りが遅いときに、\r
144          * 何度もcreateConnectionするのを防ぐためsynchronizedとする。\r
145          * ただし、DoSアタックによるスタックオーバーフローは避けられないので\r
146          * 間違った設定ファイルのまま運用すべきでない。\r
147          */\r
148         protected synchronized PooledConnection createConnection()\r
149           throws SQLException\r
150         {\r
151                 // from java.sql.DriverManager\r
152                 Properties info = new Properties();\r
153                 if (user != null) {\r
154                     info.put("user", user);\r
155                 }\r
156                 if (pass != null) {\r
157                     info.put("password", pass);\r
158                 }\r
159 \r
160                 // 最大数チェック\r
161                 if((poolList.size() + useList.size()) >= max_connections){\r
162                         throw new SQLException(MSG_ERR_TOOMANYCONNECTIONS, SQLSTATE_CONNECTION_EXCEPTION, ERR_CONNECTION_OVERFLOW);\r
163                 }\r
164                 Connection conn = null;\r
165                 logger.info(url+":"+user+":"+pass);\r
166                 try {\r
167                         conn = driver.connect(url, info);\r
168                 } catch (SQLException e) {\r
169                         throw e;\r
170                 }\r
171 \r
172                 // Loggableを実装しないラッパークラスを作る場合はこれらの前にnewすること。\r
173 \r
174                 // 統計情報を収集するなら\r
175                 if(statistics){\r
176                         conn = new StatsConnection(conn);\r
177                 }\r
178 \r
179                 // BindConnection型を返すので、BindConnectionでラップするのは\r
180                 // 一番最後でないといけない。\r
181                 BindConnection bcon = new BindConnectionImpl(conn);\r
182 \r
183                 // さらにConnection#close()で物理的に閉じずにDBManagerに\r
184                 // 返却するためPooledConnectionでラップする。\r
185                 // ConnectionPoolではなくDBManagerを通すのは\r
186                 // 統計情報取得時に貸し出し管理を行うため。\r
187                 PooledConnection pcon = new PooledConnection(this,bcon);\r
188 \r
189                 return pcon;\r
190         }\r
191 \r
192         /**\r
193          * DBへのコネクションチェック。\r
194          * force_checkを再導入することでDB(postgres)を再起動したときにも\r
195          * 自動で再接続出来る事を確認。\r
196          */\r
197         protected boolean checkConnection(Connection conn)\r
198         {\r
199                 boolean result = false;\r
200                 Statement st = null;\r
201                 //ResultSet rs = null;\r
202                 try\r
203                 {\r
204                         st = conn.createStatement();\r
205                         if(isForceCheck){\r
206                                 st.executeQuery(validSql);\r
207                         }\r
208 \r
209                         result = true;                  // 例外がなければOK\r
210                 }catch (SQLException e) {\r
211                         // ここでキャッチしておかないとgetConnection()自身が\r
212                         // 例外を生成してしまう。\r
213                 }finally{\r
214                         try{\r
215                                 st.close();\r
216                                 //rs.close();\r
217                         }catch(Exception e){\r
218                         }\r
219                 }\r
220 \r
221                 return result;\r
222         }\r
223 \r
224         /**\r
225          * DBへのコネクション取得\r
226          */\r
227         public PooledConnection getConnection() throws SQLException{\r
228                 return getConnection(true);\r
229         }\r
230         /**\r
231          * DBへのコネクション取得\r
232          *\r
233          * @param countStats StatsでgetConnection()をカウントするかどうか。startTransaction()からはカウントしない。\r
234          * @return\r
235          * @throws SQLException\r
236          */\r
237         private PooledConnection getConnection(boolean countStats) throws SQLException {\r
238                 PooledConnection conn=null;\r
239                 // トランザクション中なら該当するコネクションを返す\r
240                 conn = transactionList.get();\r
241                 if(conn!=null){\r
242                         // トランザクション中のカウント\r
243                         if(statistics){\r
244                                 Statistics.getInstance().getConnectionTran();\r
245                         }\r
246                         // 接続チェックはしない。トランザクション中にエラーになるようであれば\r
247                         // 利用側がエラーハンドリングする必要がある。\r
248                         return conn;\r
249                 }\r
250 \r
251                 // 空きプールから捜す\r
252                 synchronized(poolList){\r
253                         if(poolList.size()>0){                  // プールがあれば\r
254                                 conn=poolList.remove(0);        //\r
255                                 if(!checkConnection(conn)){             // 接続失敗したら\r
256                                         try{\r
257                                                 conn.getRealConnection().close();\r
258                                         }catch(Exception e){\r
259                                         }\r
260                                         conn=null;\r
261                                 }\r
262                         }\r
263                 }\r
264 \r
265                 if(conn==null){\r
266                         conn=createConnection();\r
267                 }\r
268                 if(logger.isDebugEnabled()) logger.debug("□□ "+conn.toString());\r
269                 useList.add(conn);\r
270                 if(logger.isDebugEnabled()) logger.debug(useList.toString());\r
271 \r
272                 // 接続状態管理をする場合 ----------------\r
273                 if(statistics && countStats){\r
274                         Statistics.getInstance().getConnection();\r
275                 }\r
276 \r
277                 return conn;\r
278         }\r
279 \r
280         /**\r
281          * DBへのコネクション返却\r
282          */\r
283         public void releaseConnection(Connection conn) throws SQLException{\r
284                 releaseConnection(conn, true);\r
285         }\r
286         /**\r
287          * DBへのコネクション返却\r
288          *\r
289          * @param conn\r
290          * @param countStats StatsでgetConnection()をカウントするかどうか。commit/rollback()からはカウントしない。\r
291          * @throws SQLException\r
292          */\r
293         private void releaseConnection(Connection conn, boolean countStats) throws SQLException{\r
294                 // トランザクション中かどうかチェック\r
295                 PooledConnection connTran = transactionList.get();\r
296                 if(connTran==conn){\r
297                         // トランザクション中のカウント\r
298                         if(statistics){\r
299                                 Statistics.getInstance().releaseConnectionTran();\r
300                         }                       // トランザクション中の場合はなにもしない\r
301                         return;\r
302                 }\r
303 \r
304                 conn.setAutoCommit(true);                       // 返却時は必ずcommit()されている状態にする。\r
305                 // statement.close()を行ってないものがいたら閉じておく\r
306                 if(conn instanceof PooledConnection){\r
307                         ((PooledConnection)conn).clearStatementList();\r
308                 }\r
309                 if(logger.isDebugEnabled()) logger.debug("□□ "+conn.toString());\r
310                 if(logger.isDebugEnabled()) logger.debug(useList.toString());\r
311                 if(!useList.remove(conn)){              // 使用中リストに存在しなければ\r
312                         throw new SQLException("This is not my connection.");\r
313                 }\r
314 \r
315                 // 接続状態管理をする場合 ----------------\r
316                 if(statistics && countStats){\r
317                         Statistics.getInstance().releaseConnection();\r
318                 }\r
319 \r
320                 poolList.add((PooledConnection)conn);\r
321         }\r
322 \r
323         /**\r
324          * トランザクションスタート\r
325          * すでにトランザクションがスタートしていてもOK。\r
326          *\r
327          * @throws SQLException\r
328          */\r
329         public void startTransaction() throws SQLException{\r
330                 // すでにトランザクション中の場合\r
331                 if(isTransaction()){\r
332                         if(statistics){\r
333                                 Statistics.getInstance().startTransactionTran();\r
334                         }\r
335                         return;\r
336                 }\r
337 \r
338                 PooledConnection conn = getConnection(false);\r
339                 conn.setAutoCommit(false);\r
340                 transactionList.set(conn);\r
341                 // 接続状態管理をする場合 ----------------\r
342                 if(statistics){\r
343                         Statistics.getInstance().startTransaction();\r
344                 }\r
345 \r
346         }\r
347 \r
348         /**\r
349          * トランザクションコミット\r
350          * @throws SQLException\r
351          */\r
352         public void commit() throws SQLException{\r
353                 PooledConnection conn = transactionList.get();\r
354                 if(conn==null){\r
355                         // 接続状態管理をする場合 ----------------\r
356                         if(statistics){\r
357                                 Statistics.getInstance().commitNoTran();\r
358                         }\r
359                         // トランザクション中で無い場合\r
360                         // エラーにしない\r
361                         return;\r
362                 }\r
363                 conn.commit();\r
364                 transactionList.remove();\r
365                 releaseConnection(conn, false);\r
366                 // 接続状態管理をする場合 ----------------\r
367                 if(statistics){\r
368                         Statistics.getInstance().commit();\r
369                 }\r
370         }\r
371 \r
372         /**\r
373          * トランザクションロールバック\r
374          * @throws SQLException\r
375          */\r
376         public void rollback() throws SQLException{\r
377                 PooledConnection conn = transactionList.get();\r
378                 if(conn==null){\r
379                         // 接続状態管理をする場合 ----------------\r
380                         if(statistics){\r
381                                 Statistics.getInstance().rollbackNoTran();\r
382                         }\r
383                         // トランザクション中で無い場合\r
384                         // エラーにしない\r
385                         return;\r
386                 }\r
387                 conn.rollback();\r
388                 transactionList.remove();\r
389                 releaseConnection(conn, false);\r
390                 // 接続状態管理をする場合 ----------------\r
391                 if(statistics){\r
392                         Statistics.getInstance().rollback();\r
393                 }\r
394         }\r
395 \r
396         /**\r
397          * トランザクション実行中かどうかを返す。\r
398          * @return\r
399          */\r
400         public boolean isTransaction(){\r
401                 return transactionList.get()!=null;\r
402         }\r
403 \r
404         /**\r
405          * @param b\r
406          */\r
407         public void setStatistics(boolean b) {\r
408                 statistics = b;\r
409         }\r
410 \r
411         /**\r
412          * @return\r
413          */\r
414         public Log getLogger() {\r
415                 return logger;\r
416         }\r
417 \r
418         /**\r
419          * @param logger\r
420          */\r
421         public void setLogger(Log logger) {\r
422                 ConnectionPool.logger = logger;\r
423         }\r
424 \r
425         /**\r
426          * @return\r
427          */\r
428         protected int getMaxConnections() {\r
429                 return max_connections;\r
430         }\r
431 \r
432         /**\r
433          * @return\r
434          */\r
435         protected Driver getDriver() {\r
436                 return driver;\r
437         }\r
438 \r
439         /**\r
440          * @return\r
441          */\r
442         protected String getUser() {\r
443                 return user;\r
444         }\r
445 \r
446         /**\r
447          * @return\r
448          */\r
449         protected String getPass() {\r
450                 return pass;\r
451         }\r
452 \r
453         /**\r
454          * @return\r
455          */\r
456         protected List<PooledConnection> getPoolList() {\r
457                 return poolList;\r
458         }\r
459 \r
460         /**\r
461          * @return\r
462          */\r
463         protected boolean isStatistics() {\r
464                 return statistics;\r
465         }\r
466 \r
467         /**\r
468          * @return\r
469          */\r
470         protected List<PooledConnection> getUseList() {\r
471                 return useList;\r
472         }\r
473 \r
474         /**\r
475          * @param isForceCheck isForceCheck を設定。\r
476          */\r
477         public void setForceCheck(boolean isForceCheck) {\r
478                 this.isForceCheck = isForceCheck;\r
479         }\r
480         /**\r
481          * @param validSql validSql を設定。\r
482          */\r
483         public void setValidSql(String validSql) {\r
484                 this.validSql = validSql;\r
485         }\r
486         /**\r
487          * @return isLogging を戻します。\r
488          */\r
489         public boolean isLogging() {\r
490                 return isLogging;\r
491         }\r
492         /**\r
493          * @param isLogging isLogging を設定。\r
494          */\r
495         public void setLogging(boolean isLogging) {\r
496                 this.isLogging = isLogging;\r
497         }\r
498 \r
499         /**\r
500          * 現在使用中のコネクション数を返す。\r
501          * @return\r
502          */\r
503         public int getUseCount(){\r
504                 return useList.size();\r
505         }\r
506 \r
507         /**\r
508          * 現在待機中のコネクション数を返す。\r
509          * @return\r
510          */\r
511         public int getPoolCount(){\r
512                 return poolList.size();\r
513         }\r
514 }\r