The author uses the dynamic proxy in Java to implement the database connection pool, so that users can use the connection pool with common JDBC connection habits.
The database connection pool is frequently used in writing application services. Too frequent connection to the database is a bottleneck for service performance. The buffer pool technology can be used to eliminate this bottleneck. We can find a lot of source programs about the database connection pool on the Internet, but we all find such a common problem: the implementation methods of these connection pools increase the Coupling Degree with users to varying degrees. Many connection pools require users to obtain database connections through the methods specified by them. We can understand that, after all, all application servers currently use this method to connect to databases. However, the other common problem is that they do not allow users to explicitly call the connection. Close () method at the same time, and they need to use a prescribed method to close the connection. This approach has two disadvantages:
First, it changes users' usage habits and increases users' difficulty.
First, let's look at a normal database operation process:
Int executesql (string SQL) throws sqlexception {connection conn = getconnection (); // obtain the database connection preparedstatement PS = NULL in some way; int res = 0; try {PS = Conn. preparestatement (SQL); Res = ps.exe cuteupdate ();} finally {try {ps. close () ;}catch (exception e) {}try {Conn. close (); //} catch (exception e) {}} return res ;} |
After using the database connection, the user usually directly calls the connection method close to release the database resources. If we use the implementation method of the connection pool we mentioned earlier, the statement Conn. close () will be replaced by some specific statements.
Second, the connection pool cannot exclusively control all connections in the pool. Because the connection pool does not allow users to directly call the close method of the connection, once the user closes the database connection due to habits during use, the connection pool will not be able to maintain the status of all connections normally, this problem is more likely to occur when the connection pool and application are implemented by different developers.
Based on the two problems mentioned above, let's discuss how to solve these two terrible problems.
First, let's consider how the user wants to use the database connection pool. You can use a specific method to obtain the database connection, and the connection type should be standard Java. SQL. Connection. After obtaining the database connection, you can perform any operations on the connection, including closing the connection.
Through the description of the user, how to take over the connection. Close method becomes the topic of our article.
To take over the database connection close method, we should have a mechanism similar to hook. For example, in Windows programming, we can use the hook api to take over a Windows API. There is also a mechanism in Java. Java provides a proxy class and an invocationhandler, both of which are in the Java. Lang. Reflect package. Let's take a look at how sun's documents describe these two classes.
public interface InvocationHandlerInvocationHandler 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. |
There are many proxy descriptions in Sun's API documentation, which are not listed here. Through the description of the interface invocationhandler, we can see that when you call a proxy instance method, the invocationhanlder invoke method will be triggered. From the Java documentation, we also learned that this dynamic proxy mechanism can only take over interface methods, but it is not valid for general classes, considering Java. SQL. connection itself is also an interface, thus finding a way to take over the close method.
First, we first define a database connection pool parameter class, defines the database's JDBC Driver Class Name, connection URL, user name and password, and other information, this class is used to initialize the connection pool parameters, the specific definition is as follows:
Public class connectionparam implements serializable {private string driver; // database driver private string URL; // urlprivate string user for data connection; // database username private string password; // Database Password private int minconnection = 0; // number of initial connections private int maxconnection = 50; // maximum number of connections private long timeoutvalue = 600000; // maximum idle time of the connection private long waittime = 30000; // if no available connection has the maximum waiting time |
The second is the onfactory class of the connection pool, which maps a connection pool object to a name. The user can obtain the specified connection pool object through this name. The specific code is as follows:
/*** Connection pool factory, this type is often used to save multiple data source names and hash corresponding to the database connection pool * @ author liusoft */public class connectionfactory {// This hash table is used to save the relationship table static hashtable of the data source name and connection pool object connectionpools = NULL; static {connectionpools = new hashtable (2, 0.75f );} /*** obtain the connection pool object corresponding to the specified name from the connection pool factory * @ Param datasource connection pool object name * @ return datasource return the connection pool object corresponding to the name * @ throws namenotfoundexception cannot be found specified connection pool */public static datasource Lookup (string datasource) throws name Notfoundexception {object DS = NULL; DS = connectionpools. Get (datasource); If (DS = NULL |! (DS instanceof datasource) throw new namenotfoundexception (datasource); Return (datasource) ds ;} /*** bind the specified name with the database connection configuration and initialize the database connection pool * @ Param name corresponds to the connection pool name * @ Param connection pool configuration parameter, for details, see connectionparam * @ return datasource. If the connection pool object * @ throws namealreadyboundexception is returned after the connection pool is successfully bound, the exception will be thrown if a certain name has been bound * @ throws classnotfoundexception cannot find the driver in connection class * @ throws illegalaccessexception Driver Class error in connection pool configuration * @ throws instantiationexception unable to instantiate Driver Class * @ throws sqlexception unable to normally connect to the specified database */public static datasource BIND (string name, connectionparam PARAM) throws exceptions, classnotfoundexception, exceptions, instantiationexception, sqlexception {performanceimpl source = NULL; try {Lookup (name); throw new partition (name);} catch (namenotfoundexception E) {source = new performanceimpl (PARAM); source. initconnection (); connectionpools. put (name, source);} return source;}/*** re-bind the database connection pool * @ Param name corresponds to the name of the connection pool * @ Param connection pool configuration parameter, for details, see connectionparam * @ return datasource. If the connection pool object * @ throws namealreadyboundexception is returned after the connection pool is successfully bound, the exception will be thrown if a certain name has been bound * @ throws classnotfoundexception cannot find the driver in connection class * @ throws illegalaccessexception Driver Class error in connection pool configuration * @ throws instantiationexception unable to instantiate Driver Class * @ throws sqlexception unable to normally connect to the specified database */public static datasource rebind (string name, connectionparam PARAM) throws failed, classnotfoundexception, illegalaccessexception, instantiationexception, sqlexception {try {unbind (name);} catch (exception e) {} return BIND (name, Param );} /*** delete a database connection pool object * @ Param name * @ throws namenotfoundexception */public static void unbind (string name) throws namenotfoundexception {datasource = Lookup (name ); if (datasource instanceof performanceimpl) {performanceimpl DSI = (performanceimpl) datasource; try {DSI. stop (); DSI. close () ;}catch (exception e) {}finally {DSI = NULL ;}} connectionpools. remove (name );}} |
Connectionfactory allows you to bind a connection pool to a specific name and unbind the connection pool. Users only need to care about these two classes to use the database connection pool function. The following code shows how to use the connection pool:
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. "); // The above code is used to register a connection pool object. This operation can only be performed once during program initialization. // the following code starts with 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 );} |
From the user's sample code, we can see that we have solved two problems arising from the regular connection pool. However, we are most concerned about how to take over the close method. The following code is mainly used to take over connectionfactory:
source = new DataSourceImpl(param);source.initConnection(); |
Datasourceimpl is a class that implements the interface javax. SQL. datasource, which maintains an object in the connection pool. Because this class is a protected class, only the methods defined in the datasource interface are exposed to the user. All other methods are invisible to the user. First, let's take a look at the method getconnection that users can access.
/*** @ See javax. SQL. datasource # getconnection (string, string) */public connection getconnection (string user, string password) throws sqlexception {// first, find the idle object connection conn = getfreeconnection (0) from the connection pool; If (conn = NULL) {// determine whether the maximum number of connections is exceeded, if the maximum number of connections is exceeded, wait for a certain period of time to check whether there are idle connections. Otherwise, an exception is thrown, indicating that there is no available connection if (getconnectioncount ()> = connparam. getmaxconnection () Conn = getfreeconnection (connparam. getwaittime (); else {// no more connections exceeded. obtain a new one. Database Connection connparam. setuser (User); connparam. setpassword (password); connection conn2 = drivermanager. getconnection (connparam. geturl (), user, password); // connection object _ connection _ conn = new _ connection (conn2, true) to be returned by the proxy; synchronized (Conns) {Conns. add (_ conn);} conn = _ Conn. getconnection () ;}} return conn ;} /*** get an idle connection from the connection pool * @ Param ntimeout if the value of this parameter is 0 and there is no connection, only a NULL is returned * Otherwise, wait for ntimeout milliseconds to see if there is any idle connection. connection, if no exception is thrown * @ return Co Nnection * @ 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) {// wait for ntimeout milliseconds to see if there are idle connections try {thread. sleep (ntimeout);} catch (exception e) {} conn = getfreeconnection (0); If (conn = NULL) throw new sqlexception ("no available database connection");} return conn ;} |
The getconnection method implemented in the performanceimpl class is consistent with the logic of the normal database connection pool. First, determine whether there is idle connection, if not, determine whether the number of connections has exceeded the maximum number of connections. However, the difference is that the database connection obtained through drivermanager is not returned in time, but is mediated by a class called _ connection, and then returned by _ connection. getconnection is called. If we didn't take over the interface object to be returned through an intermediary, that is, the proxy in Java, We can't intercept the connection. Close method.
Finally, let's take a look at how _ connection is implemented, and then introduce how the client calls connection. what kind of process does the close method follow? Why is the connection not actually closed.
/*** The data connection self-encapsulation shields the close Method * @ author liudong */class _ connection implements invocationhandler {private final static string close_method_name = "close "; private connection conn = NULL; // The busy state of the database private Boolean inuse = false; // The last time the user accessed the connection method private long lastaccesstime = system. currenttimemillis (); _ connection (connection Conn, Boolean inuse) {This. conn = conn; this. inuse = inuse;}/*** returns the conn. * @ return connection */public connection getconnection () {// return the conn class of the database connection to intercept the close method connection conn2 = (connection) proxy. newproxyinstance (Conn. getclass (). getclassloader (), Conn. getclass (). getinterfaces (), this); Return conn2;}/*** this method actually closes the database connection * @ throws sqlexception */void close () throws sqlexception {// because the class attribute Conn is not a connection to be taken over, once the close method is called, the connection Conn is directly closed. 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; // determines whether the close method is called, if you call the close method, the connection is set to useless if (close_method_name.equals (M. getname () setinuse (false); elseobj = m. invoke (Conn, argS); // sets the last access time to promptly clear the timeout connection 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 ;}} |
Once the user calls the close method of the connection, because the user's connection object is the object after the takeover, the Java Virtual Machine will first call _ connection. the invoke method first checks whether it is the close method. If not, the code is transferred to the real connection object conn that is not taken over. Otherwise, you can simply set the connection status to available. At this point, you may understand the entire process of taking over, but there is also a question: is it true that these established connections cannot be actually closed? The answer is yes. Let's take a look at the connectionfactory. Unbind method. This method first finds the connection pool object corresponding to the name, closes all connections in the connection pool, and deletes the connection pool. The datasourceimpl class defines a close method to close all connections. The detailed code is as follows:
/*** Close all database connections in the connection pool * @ return int returns the number of closed connections * @ 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 ;} |
This method calls the close method of each object in the connection pool one by one. This close method corresponds to the close implementation in _ connection, when closing a database connection in the _ connection definition, the method of closing an object that has not been taken over is called directly. Therefore, this method truly releases database resources.
The above text only describes how to take over the interface method. A practical connection pool module also needs to monitor idle connections and release connections in time. For detailed code, see the attachment.