package pooling; import java.sql.*; import java.util.HashMap; /** * This class demonstrates the use of ThreadLocal to pin a particular connection to a RAC node. * */ public class RACConnectionFactory { /** * Instance connect descriptors to the database */ private static final String url1 = "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=MYHOST1)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=DEV)(INSTANCE_NAME=DEV1)))"; private static final String url2 = "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=MYHOST2)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=DEV)(INSTANCE_NAME=DEV2)))"; /** * Store the list of connections, ideally should store references to node pools */ private HashMap poolMap = new HashMap(); /** * Simple algo to keep track of which node is least used */ private String lastNodePoolUsed = null; /** * Singleton pattern */ private static RACConnectionFactory singletonFactory = null; private static boolean isDebug = true; /* * Our Golden Horse, Thread local. */ private static ThreadLocal nodePoolTracker = new ThreadLocal(); /** * Easy method for logging */ private static void log(String log) { if (isDebug) { System.out.println(log); System.out.flush(); } } /** * Gets the instance name from the V$instance view, the connected user should have the required prevelige * @param conn * @return * @throws Exception */ private String getInstanceName(Connection conn) throws Exception { PreparedStatement stmt = null; ResultSet rs = null; try { stmt = conn.prepareStatement("select instance_name from v$instance"); rs = stmt.executeQuery(); if (rs.next()) { return rs.getString(1); } else { throw new Exception("No Rows found, should never happen"); } } finally { stmt.close(); rs.close(); } } /** * Gets a connection from the database * @param url * @return * @throws Exception */ private Connection getRawConnection(String url) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); return DriverManager.getConnection(url, "sachin", "sachin"); } /** * Just returns the last not used pool * @return */ private String getLeastLoadedPool() { if (lastNodePoolUsed == null) { lastNodePoolUsed = "1"; return "1"; } if (lastNodePoolUsed.equals("1")) { lastNodePoolUsed = "2"; return "2"; } else { lastNodePoolUsed = "1"; return "1"; } } private Connection getConnection(String poolId) { return (Connection)poolMap.get(poolId); } private RACConnectionFactory() throws Exception { Connection conn = getRawConnection(url1); // Validate that this connection is indeed gone to DEV1 if (!(getInstanceName(conn).equalsIgnoreCase("DEV1"))) { throw new Exception("This was supposed to hit DEV1, actual: " + getInstanceName(conn)); } // Not creating any pools, assume pools are of size 1 ;) poolMap.put("1", conn); conn = getRawConnection(url2); // Validate that this connection is indeed gone to DEV2 if (!(getInstanceName(conn).equalsIgnoreCase("DEV2"))) { throw new Exception("This was supposed to hit DEV2, actual: " + getInstanceName(conn)); } poolMap.put("2", conn); } /** * This method does all the work of getting the connection, sticking the poolid in to the thread .... * @return * @throws Exception */ public synchronized static Connection getConnection() throws Exception { if (singletonFactory == null) singletonFactory = new RACConnectionFactory(); String poolId = (String) nodePoolTracker.get(); if (poolId == null) { // one and store it in the thread poolId = singletonFactory.getLeastLoadedPool(); nodePoolTracker.set(poolId); log("No Pool Associated:" + Thread.currentThread().getId() + ", adding: " + poolId); return singletonFactory.getConnection(poolId); } else { log("Pool Associated:" + Thread.currentThread().getId() + ", " + poolId); return singletonFactory.getConnection(poolId); } } public static void main (String args[] ) throws Exception { log("main thread is: " + Thread.currentThread().getId()); TaskProcessor taskProcessor[] = new TaskProcessor[5]; Thread threads[] = new Thread[taskProcessor.length]; for (int i=0; i