Search This Blog

Monday, 8 February 2010

SCAN 11g R2 JDBC Load Balance Test

Now that I have a SCAN 11g R2 setup I was able to quickly verify the load balancing of connections using a SCAN URL as shown below. I find it very useful to have such a simple JDBC URL. No matter what nodes are added or removed I never have to alter my client JDBC URL again for this RAC cluster.

Output

Test Started at Mon Feb 08 13:59:21 EST 2010
Obtaining 5 connections

=============
Database Product Name is ... Oracle
Database Product Version is Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, Real Application Clusters, Automatic Storage Management, OLAP,
Data Mining and Real Application Testing options
=============
JDBC Driver Name is ........ Oracle JDBC driver
JDBC Driver Version is ..... 11.2.0.1.0
JDBC Driver Major Version is 11
JDBC Driver Minor Version is 2
=============
Connection #0 : instance[J11G22], host[auw2k4], service[J11G2]
Connection #1 : instance[J11G21], host[auw2k3], service[J11G2]
Connection #2 : instance[J11G22], host[auw2k4], service[J11G2]
Connection #3 : instance[J11G21], host[auw2k3], service[J11G2]
Connection #4 : instance[J11G22], host[auw2k4], service[J11G2]
Closing Connections
Test Ended at Mon Feb 08 13:59:22 EST 2010

Code
  
package au.support.jdbc.scan;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;

import java.sql.Statement;

import java.util.Date;

import oracle.jdbc.pool.OracleDataSource;

public class LoadBalanceTest
{
private OracleDataSource ods = null;
public final String userId = "scott";
public final String password = "tiger";

private static final String url =
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)" +
"(HOST=apctcsol1.au.oracle.com)(PORT=1521))" +
"(CONNECT_DATA=(SERVICE_NAME=J11G2)))";

public LoadBalanceTest() throws SQLException
{
ods = new OracleDataSource();
ods.setUser(userId);
ods.setPassword(password);
ods.setURL(url);
}

public Connection getConnection() throws SQLException
{
return ods.getConnection();
}

public void run () throws SQLException
{
Connection[] connArray = new Connection[5];

System.out.println("Obtaining 5 connections");
for (int i = 0; i < connArray.length; i++)
{
connArray[i] = getConnection();
}

for (int j = 0; j < connArray.length; j++)
{
if (j == 0)
{
DatabaseMetaData meta = connArray[j].getMetaData ();

// gets driver info:

System.out.println("\n=============\nDatabase Product Name is ... " +
meta.getDatabaseProductName());
System.out.println("Database Product Version is " +
meta.getDatabaseProductVersion());
System.out.println("=============\nJDBC Driver Name is ........ " +
meta.getDriverName());
System.out.println("JDBC Driver Version is ..... " +
meta.getDriverVersion());
System.out.println("JDBC Driver Major Version is " +
meta.getDriverMajorVersion());
System.out.println("JDBC Driver Minor Version is " +
meta.getDriverMinorVersion());
System.out.println("=============");
}

getInstanceDetails(connArray[j], j);
}

System.out.println("Closing Connections");
for (int y = 0; y < connArray.length; y++)
{
connArray[y].close();
}
}

public void getInstanceDetails (Connection conn, int i) throws SQLException
{
String sql =
"select sys_context('userenv', 'instance_name'), " +
"sys_context('userenv', 'server_host'), " +
"sys_context('userenv', 'service_name') " +
"from dual";

Statement stmt = conn.createStatement();
ResultSet rset = stmt.executeQuery(sql);
while (rset.next())
{
System.out.println
("Connection #" + i + " : instance[" + rset.getString(1) + "], host[" +
rset.getString(2) + "], service[" + rset.getString(3) + "]");
}

stmt.close();
rset.close();
}

public static void main(String[] args)
{
LoadBalanceTest loadBalanceTest;
try
{
System.out.println("Test Started at " + new Date());
loadBalanceTest = new LoadBalanceTest();
loadBalanceTest.run();
System.out.println("Test Ended at " + new Date());
}
catch (SQLException e)
{
e.printStackTrace();
System.exit(1);
}
}
}

Using SCAN - Single Client Access Name to Connect to 11g R2 RAC from JDeveloper 11g

The ability to have a single / simple connect string for a RAC cluster seemed like something worth trying which SCAN allows us to have as part of 11g R2. Trying to understand how SCAN works and it's setup was not what I had time for so I took an existing setup and verified I could connect from JDeveloper 11g without any issues.

The tnsnames.ora alias was defined as follows. As you can see there is nothing to suggest we are connecting to RAC here, but we are.

RAC11G2 =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = apctcsol1.au.oracle.com)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = J11G2)
)
)

Being able to connect from SQL*PLus was what I first tried and that worked fine.

d:\temp>sqlplus scott/tiger@RAC11G2

SQL*Plus: Release 11.1.0.6.0 - Production on Mon Feb 8 09:15:59 2010

Copyright (c) 1982, 2007, Oracle. All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, Real Application Clusters, Automatic Storage Management, OLAP,
Data Mining and Real Application Testing options

SCOTT@RAC11G2>
So from JDeveloper here it shows it can connect fine as well as expected.




















So from a JDBC client we would be connecting as follows ensuring we use a URL which indicates the use of service name as follows.

private static final String url =
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)" +
"(HOST=apctcsol1.au.oracle.com)(PORT=1521))" +
"(CONNECT_DATA=(SERVICE_NAME=J11G2)))";
We could also use a connect string as follows:

private static final String url = "jdbc:oracle:thin:@apctcsol1.au.oracle.com:1521/J11G2";
Here are some of the commands I ran to verify the SCAN setup and ports on one of the remote RAC instances. RAC Instances register to SCAN listeners as remote listeners.

Check we have SCAN listener configured

[oracle@auw2k3 ~]$ srvctl config scan_listener
SCAN Listener LISTENER_SCAN1 exists. Port: TCP:1521
SCAN Listener LISTENER_SCAN2 exists. Port: TCP:1521
SCAN Listener LISTENER_SCAN3 exists. Port: TCP:1521

Check status of SCAN listeners

[oracle@auw2k3 ~]$ srvctl status scan_listener
SCAN Listener LISTENER_SCAN1 is enabled
SCAN listener LISTENER_SCAN1 is running on node auw2k4
SCAN Listener LISTENER_SCAN2 is enabled
SCAN listener LISTENER_SCAN2 is running on node auw2k3
SCAN Listener LISTENER_SCAN3 is enabled
SCAN listener LISTENER_SCAN3 is running on node auw2k3

[oracle@auw2k3 ~]$ ps -aef | grep -i SCAN
oragrid 20168 1 0 Feb05 ? 00:00:12 /u01/app/11.2.0/grid/bin/tnslsnr LISTENER_SCAN2 -inherit
oragrid 20179 1 0 Feb05 ? 00:00:11 /u01/app/11.2.0/grid/bin/tnslsnr LISTENER_SCAN3 -inherit

Finally verify that the service I need to connect to existed on the SCAN listener

[oracle@auw2k3 ~]$ lsnrctl services LISTENER_SCAN2

LSNRCTL for Linux: Version 11.2.0.1.0 - Production on 08-FEB-2010 10:05:41

Copyright (c) 1991, 2009, Oracle. All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=LISTENER_SCAN2)))
Services Summary...
Service "J10G" has 2 instance(s).
Instance "J10G1", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k3-vip)(PORT=1521))
Instance "J10G2", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k4-vip)(PORT=1521))
Service "J10GXDB" has 2 instance(s).
Instance "J10G1", status READY, has 1 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:0 max:1022 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=auw2k3)(PORT=55880))
Instance "J10G2", status READY, has 1 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:0 max:1022 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=auw2k4)(PORT=31825))
Service "J10G_TAF" has 2 instance(s).
Instance "J10G1", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k3-vip)(PORT=1521))
Instance "J10G2", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k4-vip)(PORT=1521))
Service "J10G_XPT" has 2 instance(s).
Instance "J10G1", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k3-vip)(PORT=1521))
Instance "J10G2", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k4-vip)(PORT=1521))
Service "J11G2" has 2 instance(s).
Instance "J11G21", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k3-vip)(PORT=1521)))
Instance "J11G22", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k4-vip)(PORT=1521)))
Service "J11G2XDB" has 2 instance(s).
Instance "J11G21", status READY, has 1 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:0 max:1022 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=auw2k3)(PORT=63414))
Instance "J11G22", status READY, has 1 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:0 max:1022 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=auw2k4)(PORT=62891))
Service "sv1" has 2 instance(s).
Instance "J11G21", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k3-vip)(PORT=1521)))
Instance "J11G22", status READY, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0 state:ready
REMOTE SERVER
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=auw2k4-vip)(PORT=1521)))
The command completed successfully
[oracle@auw2k3 ~]$

Tuesday, 2 February 2010

Using Database Change Notification (DCN) with a Coherence Cache

I needed to ensure my coherence cache which was storing a table from a database was kept in sync. To do that I used Database Change Notification (DCN) in the 11g JDBC driver with a 11.2 RDBMS. Few things I needed to ensure were as follows.

1. Firstly I made sure my DCN listener on the client which determines what refresh to perform on the cache does this using the Executor interface to call a runnable task in it's own thread. That is done to make sure if the operation takes time it's done in it's own thread and won't hold up the listener itself.


package support.au.coherence.dcn.server.db;

import java.util.concurrent.Executor;

import oracle.jdbc.dcn.DatabaseChangeEvent;
import oracle.jdbc.dcn.DatabaseChangeListener;
import oracle.jdbc.dcn.RowChangeDescription;
import oracle.jdbc.dcn.RowChangeDescription.RowOperation;
import oracle.jdbc.dcn.TableChangeDescription;

public class DCNListener implements DatabaseChangeListener
{
DeptDCNRegister demo;
DCNListener(DeptDCNRegister dem)
{
demo = dem;
}

public void onDatabaseChangeNotification
(DatabaseChangeEvent databaseChangeEvent)
{
System.out.println("DCNListener: got an event (" + this + ")");
System.out.println(databaseChangeEvent.toString());
TableChangeDescription [] tableChanges =
databaseChangeEvent.getTableChangeDescription();

for (TableChangeDescription tableChange : tableChanges)
{
RowChangeDescription[] rcds = tableChange.getRowChangeDescription();
for (RowChangeDescription rcd : rcds)
{
System.out.println("Affected row -> " +
rcd.getRowid().stringValue());
RowOperation ro = rcd.getRowOperation();

Executor executor = new DBExecutor();
String rowid = rcd.getRowid().stringValue();

if (ro.equals(RowOperation.INSERT))
{

System.out.println("INSERT occurred");
executor.execute(new HandleDBRefresh(rowid, "insert"));
}
else if (ro.equals(RowOperation.UPDATE))
{
System.out.println("UPDATE occurred");
executor.execute(new HandleDBRefresh(rowid, "update"));
}
else if (ro.equals(RowOperation.DELETE))
{
System.out.println("DELETE occurred");
executor.execute(new HandleDBRefresh(rowid, "delete"));
}
else
{
System.out.println("Only handling INSERT/DELETE/UPDATE");
}
}
}

synchronized( demo )
{
demo.notify();
}

}
}

2. The "DBExecutor" is defined as follows.


package support.au.coherence.dcn.server.db;

import java.util.concurrent.Executor;

public class DBExecutor implements Executor
{
public void execute(Runnable command)
{
new Thread(command).run();
}
}

3. The runnable class "HandleDBRefresh" is defined as follows.


package support.au.coherence.dcn.server.db;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.CacheFactoryBuilder;
import com.tangosol.net.NamedCache;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import support.au.coherence.dcn.server.CacheHelper;
import support.au.coherence.dcn.server.Dept;

public class HandleDBRefresh implements Runnable
{
private DBConnectionManager connMgr = null;
private String rowId;
private String action;

public HandleDBRefresh()
{
}

public HandleDBRefresh(String rowId, String action)
{
super();
this.rowId = rowId;
this.action = action;
}

public void run()
{
PreparedStatement stmt = null;
ResultSet rset = null;
Connection conn = null;

try
{
connMgr = DBConnectionManager.getInstance();
if (!action.toLowerCase().equals("delete"))
{
conn = connMgr.getConnection();
stmt = conn.prepareStatement
("select rowid, deptno, dname from dept where rowid = ?");
stmt.setString(1, rowId);
rset = stmt.executeQuery();
rset.next();
}

CacheHelper cacheHelper = CacheHelper.getInstance();

// check if action
if (action.toLowerCase().equals("delete"))
{
cacheHelper.removeEntry(rowId);
System.out.println("Cache record delete");
}
else if (action.toLowerCase().equals("insert"))
{
// add to cache
if (rset != null)
{
Dept d = new Dept(rset.getInt(2), rset.getString(3));
cacheHelper.updateEntry(rset.getString(1), d);
System.out.println("Cache updated with new record");
}
}
else if (action.toLowerCase().equals("update"))
{
// refresh record in cache
if (rset != null)
{
Dept d = new Dept(rset.getInt(2), rset.getString(3));
cacheHelper.updateEntry(rset.getString(1), d);
System.out.println("Cache record updated");
}
}
}
catch (Exception e)
{
throw new RuntimeException
("Error updating cache: rowid [" + rowId + "] " + e);
}
finally
{
if (rset != null)
{
try
{
rset.close();
}
catch (SQLException se)
{
}
}

if (stmt != null)
{
try
{
stmt.close();
}
catch (SQLException se)
{
}
}

if (conn != null)
{
try
{
connMgr.returnConnection(conn);
}
catch (SQLException se)
{
}
}

}

}

public void setRowId(String rowId)
{
this.rowId = rowId;
}

public String getRowId()
{
return rowId;
}

public void setAction(String action)
{
this.action = action;
}

public String getAction()
{
return action;
}
}


4. "DeptDCNRegister" is defined as follows.

  
package support.au.coherence.dcn.server;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

import java.sql.Statement;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import oracle.jdbc.OracleConnection;
import oracle.jdbc.OracleStatement;
import oracle.jdbc.dcn.DatabaseChangeRegistration;

@SuppressWarnings("unchecked")
public class DeptDCNRegister
{
private DBConnectionManager connMgr = null;
//private final String depSQL = "select * from dept";
private DatabaseChangeRegistration dcr = null;
private static DeptDCNRegister instance = null;

static
{
try
{
instance = new DeptDCNRegister();
}
catch (Exception e)
{
throw new RuntimeException("Error creating instance of DeptDCNRegister", e);
}
}

private DeptDCNRegister () throws SQLException
{
connMgr = DBConnectionManager.getInstance();
OracleConnection conn = (OracleConnection) connMgr.getConnection();
if (dcr == null)
{
registerDCN(conn);
}
}

public static DeptDCNRegister getInstance()
{
return instance;
}

private void registerDCN (OracleConnection conn) throws SQLException
{
/*
* register a listener for change notofication to be displayed to standard out
* for testing purposes
*/
Properties props = new Properties();
props.put(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
props.put(OracleConnection.NTF_QOS_RELIABLE, "false");
props.setProperty(OracleConnection.DCN_BEST_EFFORT, "true");

dcr = conn.registerDatabaseChangeNotification(props);

// Add the dummy DCNListener which is DCNListener.java class
DCNListener list = new DCNListener(this);
dcr.addListener(list);

Statement stmt = conn.createStatement();
// Associate the statement with the registration.
((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);
ResultSet rs = stmt.executeQuery("select * from dept where 1 = 2");
while (rs.next())
{
// do nothing no , need to just need query to register the DEPT table
}

String[] tableNames = dcr.getTables();
for(int i=0; i < tableNames.length; i++)
{
System.out.println(tableNames[i]+" successfully registered.");
}

// close resources
stmt.close();
rs.close();

}

public void closeDCN (OracleConnection conn) throws SQLException
{
conn.unregisterDatabaseChangeNotification(dcr);
conn.close();
}
}


5. Finally the key for Dept cache records is the ROWID at the database table level to ensure it's always unique not to mention that's what drives the specific operation so makes it easier this way. The ROWID is what is provided to the listener for the table changes so makes sense to use that as the KEY within the coherence cache.

I did give some thought into making the DCN listener run within the DB but loading the required JARS turned me off that idea. Wasn't sure how I could connect to a coherence cluster itself as storage disabled from within the DB itself to be honest. Otherwise this would of been a good option as the registration would of been active as long as the DB was running.

Friday, 29 January 2010

Determining the DatabaseChangeEvent which occurred on a TABLE

Not for myself:

Registration Properties

Properties props = new Properties();
props.put(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
props.put(OracleConnection.NTF_QOS_RELIABLE, "false");
props.setProperty(OracleConnection.DCN_BEST_EFFORT, "true");

DatabaseChangeRegistration dcr =
conn.registerDatabaseChangeNotification(props);

Listener Class

package pas.jdbc;

import oracle.jdbc.dcn.DatabaseChangeEvent;
import oracle.jdbc.dcn.DatabaseChangeListener;
import oracle.jdbc.dcn.RowChangeDescription;
import oracle.jdbc.dcn.RowChangeDescription.RowOperation;
import oracle.jdbc.dcn.TableChangeDescription;


public class DCNListener implements DatabaseChangeListener
{
DCNDemo demo;
DCNListener(DCNDemo dem)
{
demo = dem;
}

public void onDatabaseChangeNotification(DatabaseChangeEvent databaseChangeEvent)
{
System.out.println("DCNListener: got an event (" + this + ")");
System.out.println(databaseChangeEvent.toString());
TableChangeDescription [] tableChanges =
databaseChangeEvent.getTableChangeDescription();

for (TableChangeDescription tableChange : tableChanges)
{
RowChangeDescription[] rcds = tableChange.getRowChangeDescription();
for (RowChangeDescription rcd : rcds)
{
System.out.println("Affected row -> " + rcd.getRowid().stringValue());
RowOperation ro = rcd.getRowOperation();
if (ro.equals(RowOperation.INSERT))
{
System.out.println("INSERT occurred");
}
else if (ro.equals(RowOperation.UPDATE))
{
System.out.println("UPDATE occurred");
}
else if (ro.equals(RowOperation.DELETE))
{
System.out.println("DELETE occurred");
}
else
{
System.out.println("Not a INSERT/DELETE/UPDATE change");
}
}
}

synchronized( demo )
{
demo.notify();
}

}

Thursday, 7 January 2010

Closing Obsolete Database Change Notification Registrations

When enabling Database Change Notification (DCN) a registration is created at the database level which requires it to closed when no longer required. Here is how to close an obsolete registration from JDBC in a case where you don't have a valid DatabaseChangeRegistration object (for example a JVM crash).

1. Find the
obsolete registration as shown below.

SCOTT@linux11g> select regid, CALLBACK, table_name from USER_CHANGE_NOTIFICATION_REGS;

REGID CALLBACK TABLE_NAME
---------- ---------------------------------------- --------------------
100 net8://(ADDRESS=(PROTOCOL=tcp)(HOST=10.1 SCOTT.DEPT
87.80.135)(PORT=47632))?PR=0

2. Use JDBC code as follows to delete the obsolete registration.

package pas.jdbc;

import java.sql.SQLException;

import oracle.jdbc.OracleConnection;
import oracle.jdbc.pool.OracleDataSource;

public class DeregisterDCN
{
private OracleDataSource ods = null;
public final String userId = "scott";
public final String password = "tiger";
public final String url = "jdbc:oracle:thin:@beast.au.oracle.com:1521:linux11g";
private int REGID = 100;

public DeregisterDCN() throws SQLException
{
ods = new OracleDataSource();
ods.setUser(userId);
ods.setPassword(password);
ods.setURL(url);
}

public void run () throws SQLException
{
try
{
OracleConnection conn = getConnection();
conn.unregisterDatabaseChangeNotification(REGID);
System.out.println("unregistered with id " + REGID);
}
catch (SQLException sqle)
{
System.out.println("Error trying to delete registration with id " + REGID);
sqle.printStackTrace();
}
}

public OracleConnection getConnection() throws SQLException
{
OracleConnection conn = (OracleConnection)ods.getConnection();

return conn;
}

public static void main(String[] args) throws SQLException
{
DeregisterDCN deregisterDCN = new DeregisterDCN();
deregisterDCN.run();
}
}
Few things to note though..

1. The database user has been granted the following as user SYS to enable DCN.

> grant change notification to scott;

> grant execute on DBMS_CQ_NOTIFICATION to scott;

2. The method to use is OracleConnection.unregisterDatabaseChangeNotification(int). That requires you to cast a JDBC Connection to an OracleConnection to use that method as shown below.

OracleConnection conn = (OracleConnection)ods.getConnection();

3. They may be more then one registration so make sure you determine the correct entry to remove. If not you may end up deleting a registration which is currently active.


Thursday, 24 December 2009

Fast Way to Create a Web Service from an EJB3 Entity

Normally I would create an EJB session bean to expose my Entity queries from my Entity beans, but I thought it would be easier to just create a Java Service Facade and expose that as a web service as shown below.

1. In my project create an "Entity from table" using the new object gallery item in the EJB node. In this example we use the classic DEPT table.
2. Then edit your Dept entity to create another named query as shown below, as well as a toString method to display the bean.
....

@Entity
@NamedQueries({
@NamedQuery(name = "Dept.findAll", query = "select o from Dept o"),
@NamedQuery(name = "Dept.findByDeptno", query = "select o from Dept o where o.deptno = :deptno")
})
public class Dept
implements Serializable

...

@Override
public String toString()
{
return "Dept [" + deptno + ", " + dname + ", " + loc + "]";
}

3. From the EJB new object gallery select "Java Service Facade", in the wizard I only exposed these methods as well as a main method to test it with.
  • public List getDeptFindAll() public
  • public List getDeptFindByDeptno(Long deptno)
4. Add code to the main as follows to ensure the Java Service Facade works.

public static void main(String [] args)
{
final JavaServiceFacade javaServiceFacade = new JavaServiceFacade();
// TODO: Call methods on javaServiceFacade here...
List<Dept> deps = javaServiceFacade.getDeptFindAll();
for (Dept d: deps)
{
System.out.println(d);
}
}

5. Run it to verify it works well. Inside the persistence.xml I edited the following property (eclipselink.logging.level=INFO) to reduce the amount of output we get at runtime.

<persistence-unit name="Model-Test" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>model.Dept</class>
<properties>
<property name="eclipselink.jdbc.driver"
value="oracle.jdbc.OracleDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:oracle:thin:@//beast.au.oracle.com:1523/linux11gr2"/>
<property name="eclipselink.jdbc.user" value="scott"/>
<property name="eclipselink.jdbc.password"
value="3E20F8982C53F4ABA825E30206EC8ADE"/>
<property name="eclipselink.logging.level" value="INFO"/>
<property name="eclipselink.target-server" value="WebLogic_10"/>
</properties>
</persistence-unit>

Output:

... [EL Warning]: 2009-12-24 11:48:40.203--Failed to get InitialContext for MBean registration: javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
Dept [10, ACCOUNTING, NEW YORK]

Dept [20, RESEARCH, DALLAS]
Dept [30, SALES, CHICAGO]

Dept [40, OPERATIONS, BOSTON]

6. Now finally create a web service from the "JavaServiceFacade.java". To do this I simply invoked the "Create Java Web Service" from the object gallery or even easier use it's context menu option "Create Web Service".

Now you can test it using the "Test Web Service Wizard" option from the web service itself as now it is a Web Service as per step #6.

Friday, 18 December 2009

Starting A Coherence Cache Server from ANT

Started setting up some coherence demos through ant, in doing so created a small template to work from which enables me to start a coherence cache server. With this you simply place your source code within the SRC directory and it will compile it, package it (once we add a package task), and run the cache server.

1. Firstly create a directory as follows , adding the files (build.properties, build.xml) and 2 directories (src, lib) as shown below.

Directory Name - start-cache-server

build.properties
build.xml
classes - DIRECTORY
lib - DIRECTORY
src - DIRECTORY

2. Ensure you have ant in your path.

D:\jdev\ant-demos\coherence\start-cache-server>ant -version
Apache Ant version 1.7.0 compiled on December 13 2006

3. Edit build.properties ensuring we set coherence home correctly

# oracle.coherence.home
#
oracle.coherence.home=D:/jdev/coherence/352/coherence

# jvmargs
#
# JVM args to pass into the command at runtime to set heap size etc

jvmargs=-server -showversion -Xms512m -Xmx512m

4. Edit build.xml to look as follows
<?xml version="1.0" encoding="windows-1252" ?>

<project default="run-default" name="demo" basedir=".">

<property file="build.properties"/>

<path id="j2ee.classpath">
<pathelement path="${oracle.coherence.home}/lib/coherence.jar"/>
</path>

<property name="src.dir" value="src"/>
<property name="classes.dir" value="classes"/>
<property name="lib.dir" value="lib"/>
<property name="class.name" value="com.tangosol.net.DefaultCacheServer"/>
<property name="jmxclass.name" value="com.tangosol.net.management.MBeanConnector"/>

<target name="init">
<tstamp/>
<mkdir dir="${classes.dir}"/>
</target>

<target name="clean" description="Clean lib, classes directories">
<delete dir="${classes.dir}"/>
</target>

<target name="compile" description="Compiles classes into ${classes.dir}" depends="init">
<javac srcdir="${src.dir}"
debug="true"
destdir="${classes.dir}"
includes="**/*.java" >
<classpath refid="j2ee.classpath"/>
</javac>
</target>

<target name="run-default" depends="compile" description="Run the cache server">
<echo message="Starting cache server with jvm args : ${jvmargs}"/>
<java classname="${class.name}" fork="true">
<!--
Add system properties as follows
<sysproperty key="tangosol.pof.enabled" value="true"/>
-->

<jvmarg line="${jvmargs}"/>
<classpath>
<path refid="j2ee.classpath"/>
</classpath>
</java>
</target>

<target name="run-jmx" depends="compile" description="Run the JMX enabled cache server">
<echo message="Starting JMX enabled cache server with jvm args : ${jvmargs}"/>
<java classname="${jmxclass.name}" fork="true">
<sysproperty key="tangosol.coherence.management" value="all"/>
<sysproperty key="tangosol.coherence.management.remote" value="true"/>
<sysproperty key="com.sun.management.jmxremote" value="true"/>
<jvmarg line="${jvmargs}"/>
<arg line="-rmi" />
<classpath>
<path refid="j2ee.classpath"/>
</classpath>
</java>
</target>

</project>
5. Run cache server using the ant task shown below.

D:\jdev\ant-demos\coherence\start-cache-server>ant run-default
Buildfile: build.xml

init:
[mkdir] Created dir: D:\jdev\ant-demos\coherence\start-cache-server\classes

compile:

run-default:
[echo] Starting cache server with jvm args : -server -showversion -Xms512m -Xmx512m
[java] java version "1.6.0_07"
[java] Java(TM) SE Runtime Environment (build 1.6.0_07-b06)
[java] Java HotSpot(TM) Server VM (build 10.0-b23, mixed mode)
[java] 2009-12-18 10:15:15.062/0.406 Oracle Coherence 3.5.2/463 (thread=main, member=n/a): Loaded operational configur
ation from resource "jar:file:/D:/jdev/coherence/352/coherence/lib/coherence.jar!/tangosol-coherence.xml"
[java] 2009-12-18 10:15:15.078/0.422 Oracle Coherence 3.5.2/463 (thread=main, member=n/a): Loaded operational override
s from resource "jar:file:/D:/jdev/coherence/352/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
[java] 2009-12-18 10:15:15.078/0.422 Oracle Coherence 3.5.2/463 (thread=main, member=n/a): Optional configuration overri
de "/tangosol-coherence-override.xml" is not specified
[java] 2009-12-18 10:15:15.093/0.437 Oracle Coherence 3.5.2/463 (thread=main, member=n/a): Optional configuration overri
de "/custom-mbeans.xml" is not specified
[java]
[java] Oracle Coherence Version 3.5.2/463
[java] Grid Edition: Development mode
[java] Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
[java]
[java] 2009-12-18 10:15:15.531/0.875 Oracle Coherence GE 3.5.2/463 (thread=main, member=n/a): Loaded cache configurati
on from "jar:file:/D:/jdev/coherence/352/coherence/lib/coherence.jar!/coherence-cache-config.xml"
[java] 2009-12-18 10:15:16.265/1.609 Oracle Coherence GE 3.5.2/463 (thread=Cluster, member=n/a): Service Cluster joined
the cluster with senior service member n/a
[java] 2009-12-18 10:15:19.515/4.859 Oracle Coherence GE 3.5.2/463 (thread=Cluster, member=n/a): Created a new cluster
"cluster:0xD3FB" with Member(Id=1, Timestamp=2009-12-18 10:15:16.031, Address=10.187.80.135:8088, MachineId=57735, Location=site:
au.oracle.com,machine:papicell-au,process:3708, Role=CoherenceServer, Edition=Grid Edition, Mode=Development, CpuCount=2, SocketCo
unt=1) UID=0x0ABB5087000001259EEC7BBFE1871F98
[java] 2009-12-18 10:15:19.546/4.890 Oracle Coherence GE 3.5.2/463 (thread=Invocation:Management, member=1): Service Man
agement joined the cluster with senior service member 1
[java] 2009-12-18 10:15:19.875/5.219 Oracle Coherence GE 3.5.2/463 (thread=DistributedCache, member=1): Service Distribu
tedCache joined the cluster with senior service member 1
[java] 2009-12-18 10:15:19.953/5.297 Oracle Coherence GE 3.5.2/463 (thread=ReplicatedCache, member=1): Service Replicate
dCache joined the cluster with senior service member 1
[java] 2009-12-18 10:15:19.953/5.297 Oracle Coherence GE 3.5.2/463 (thread=OptimisticCache, member=1): Service Optimisti
cCache joined the cluster with senior service member 1
[java] 2009-12-18 10:15:19.968/5.312 Oracle Coherence GE 3.5.2/463 (thread=Invocation:InvocationService, member=1): Serv
ice InvocationService joined the cluster with senior service member 1
[java] 2009-12-18 10:15:19.968/5.312 Oracle Coherence GE 3.5.2/463 (thread=main, member=1): Started DefaultCacheServer
...
[java]
[java] SafeCluster: Name=cluster:0xD3FB
[java]
[java] Group{Address=224.3.5.2, Port=35463, TTL=4}
[java]
[java] MasterMemberSet
[java] (
[java] ThisMember=Member(Id=1, Timestamp=2009-12-18 10:15:16.031, Address=10.187.80.135:8088, MachineId=57735, Location=sit
e:au.oracle.com,machine:papicell-au,process:3708, Role=CoherenceServer)
[java] OldestMember=Member(Id=1, Timestamp=2009-12-18 10:15:16.031, Address=10.187.80.135:8088, MachineId=57735, Location=s
ite:au.oracle.com,machine:papicell-au,process:3708, Role=CoherenceServer)
[java] ActualMemberSet=MemberSet(Size=1, BitSetCount=2
[java] Member(Id=1, Timestamp=2009-12-18 10:15:16.031, Address=10.187.80.135:8088, MachineId=57735, Location=site:au.orac
le.com,machine:papicell-au,process:3708, Role=CoherenceServer)
[java] )
[java] RecycleMillis=120000
[java] RecycleSet=MemberSet(Size=0, BitSetCount=0
[java] )
[java] )
[java]
[java] Services
[java] (
[java] TcpRing{TcpSocketAccepter{State=STATE_OPEN, ServerSocket=10.187.80.135:8088}, Connections=[]}
[java] ClusterService{Name=Cluster, State=(SERVICE_STARTED, STATE_JOINED), Id=0, Version=3.5, OldestMemberId=1}
[java] InvocationService{Name=Management, State=(SERVICE_STARTED), Id=1, Version=3.1, OldestMemberId=1}
[java] DistributedCache{Name=DistributedCache, State=(SERVICE_STARTED), LocalStorage=enabled, PartitionCount=257, BackupCou
nt=1, AssignedPartitions=257, BackupPartitions=0}
[java] ReplicatedCache{Name=ReplicatedCache, State=(SERVICE_STARTED), Id=3, Version=3.0, OldestMemberId=1}
[java] Optimistic{Name=OptimisticCache, State=(SERVICE_STARTED), Id=4, Version=3.0, OldestMemberId=1}
[java] InvocationService{Name=InvocationService, State=(SERVICE_STARTED), Id=5, Version=3.1, OldestMemberId=1}
[java] )
[java]

Few things to note here.

- We don't have anything in the SRC directory we simply add code we wish to use in our cache server here, like cahe store implementations etc.

- When you do have SRC to compile you will need to add a package task as shown below for example.

<target name="package"
depends="compile"
description="Package application into ${ant.project.name}.jar">
<jar basedir="${classes.dir}" destfile="${lib.dir}/${ant.project.name}.jar">
<include name="**/*.class"/>
</jar>
</target>

From the build.xml there is also a target to start a JMX enabled node using "ant run-jmx"

Wednesday, 9 December 2009

PLSQL commit every X records and Coherence putAll every X records

Note for self:

Was using this in Write-Behind Caching Demo with Oracle Coherence using PLSQL Bulk Binds.

When using PLSQL to bulk insert code as follows to control the commit time.
-- control number of records to insert before calling COMMIT
NUM_OF_RECORDS_TO_INSERT constant number := 10000;

IF (mod(l_count, NUM_OF_RECORDS_TO_INSERT) = 0) THEN
FORALL i IN mid.FIRST..mid.LAST
insert into messages (message_id, message_type, message)
values (mid(i), mt(i), m(i));
END IF;

When loading many cache records ensure we only call putAll to control the amount of records to insert into the cache in one hit.
final private static int BATCH_SIZE = 1000;

Map buffer = new HashMap();

long start = System.currentTimeMillis();

for (int i = 1; i <= records; i++)
{
Message message = new Message
(new BigDecimal(i),
"M",
String.format("Message %s from test client", i));
buffer.put(String.valueOf(i), message);

if ((i % BATCH_SIZE) == 0)
{
messageCache.putAll(buffer);
buffer.clear();
}
}

if (!buffer.isEmpty())
{
messageCache.putAll(buffer);
buffer.clear();
}

Friday, 4 December 2009

Having SQL*Plus run within Jdeveloper's Log window

Invoking SQL*Plus is what I do a lot from JDeveloper, so there are 2 things I setup everytime I do a fresh install of JDeveloper.

1. Use external tools to avoid having to enter password as per this blog entry a while ago.

http://theblasfrompas.blogspot.com/2007/10/avoiding-having-to-enter-password-when.html

2. Secondly I now only use sqlplus.exe rather then sqlplusw.exe to ensure that it never leaves JDeveloper itself and so the output is redirected to JDeveloper's log window. Of course you have to then add "exit" to your scripts so that the process itself doesn't hang around.

Also it's worth making sure you start from the directory of the SQL file your invoking if you wish to include other files as part of the SQL script itself which don't reference a path themselves.

Eg:

Program Executable: D:\oracle\product\10.2.0\db_1\BIN\sqlplus.exe
Arguments: scott/tiger@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=beast.au.oracle.com)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=linux11g))) @${file.path}
Run Directory: ${file.dir}

Demo output as follows showing how I invoke SQL*Plus and what we pass into it to avoid opening up a new window for SQL*Plus.

Thursday, 19 November 2009

Accessing the Oracle Universal Connection Pool MBean using JConsole

Now I am just tranistioning to the Oracle Universal Connection Pool I found that it has a useful MBean known as "UniversalConnectionPoolMBean" which you can view/manipulate with "jconsole" using JDK 1.6. Here is a screen shot of jconsole attached to the JVM which is has a Universal Connection Pool running within it. Once attached to the JVM access it as follows.

1. Click on the MBean tab
2. Click the + symbol for "oracle.ucp.admin.UniversalConnectionPoolMBean"
3. Click on the "Attributes" or "Methods" node and you can view/update the pool using that MBean

The javadoc for the MBean this can be found here.

http://download.oracle.com/docs/cd/B28359_01/java.111/e11990/oracle/ucp/admin/UniversalConnectionPoolManagerMBean.html

In the example below I was using Oracle's UCP within a coherence cache server for write behind to an Oracle database.

Thursday, 5 November 2009

Oracle Coherence - read-write-backing-map-scheme

I spent the last 2 weeks in Boston working with the oracle coherence team and how that product works and I am impressed with it. One thing which I was keen on testing out / learning in depth was the read-write-backing-map-scheme. I specifically wanted to learn write-behind which when enabled the cache will delay writes to the back end cache store, which in my example would be an Oracle 11g database. To me there is really only 2 options for this to be as quick as possible and of course use connection pooling which UCP (Oracle Universal Connection Pool) is perfect for.

1. JDBC Batching
2. PLSQL bulk binds

Whats important though is that we only commit after X amount of records, and only go to the database when we are ready to insert those X amount of records in a single round trip. To me PLSQL bulk binds make sense here, as all it takes is to convert those cache entries into an SQL object which the database can interpret from PLSQL. Once that's done your PLSQL is as simple as this really.

FORALL i IN mid.FIRST..mid.LAST
insert into messages (message_id, message_type, message)
values (mid(i), mt(i), m(i));

COMMIT;

I will post a full example on this shortly which also shows how to read the data back into the cache.

More about this can be found here on what I want to setup. My initial testing showed 1,000,000 records inserted without too much issues BUT there are a couple of things to be aware from coherence and oracle itself.

http://coherence.oracle.com/display/COH35UG/Read-Through%2C+Write-Through%2C+Write-Behind+and+Refresh-Ahead+Caching

Thursday, 8 October 2009

Quick unix command to determine JDBC driver version

Thought this was alot easier then extracting the actual JDBC JAR file and viewing META-INF/MANIFEST.MF. Of course you can still use the JDBC API via DatabaseMetaData, but normally you don't need to go to that effort.

UNIX

> unzip -p ojdbc14.jar META-INF/MANIFEST.MF | grep -C 1 version

WINDOWS

> unzip -p ojdbc14.jar META-INF/MANIFEST.MF

Note: If you have grep.exe for windows you could use this.

> unzip -p ojdbc14.jar META-INF/MANIFEST.MF | grep -C version

Output (Unix demo)
------------------------

[oracle@beast lib]$ unzip -p ojdbc14.jar META-INF/MANIFEST.MF | grep -C 1 version
Manifest-Version: 1.0
Implementation-Version: "Oracle JDBC Driver version - 10.1.0.5.0"
Specification-Title: "Oracle JDBC driver classes for use with JDK1.4"
Specification-Version: "Oracle JDBC Driver version - 10.1.0.5.0"
Implementation-Title: "ojdbc14.jar"

If your using 11g JDBC driver then you can do it as follows.

http://theblasfrompas.blogspot.com/2007/09/fast-way-to-determine-exact-11g-jdbc.html