Search This Blog

Friday, 23 April 2010

Coherence: Adding my Own POF Serializer for java.sql.Date

The standard serializers that come with POF unfortunately don't cover all standard Java classes. So in this case I have a domain model based on an oracle RDBMS table which was using a DATE column. in Oracle JDBC then we would call getDate() from the ResultSet Object to return a java.sql.Date(). Thanks to some help internally I was able to add my own POF Serializer as shown below.

1. Create a POF Serializer class as follows.

package pas.au.coherence.query.server;

import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofSerializer;
import com.tangosol.io.pof.PofWriter;

import java.io.IOException;

public class SQLDateSerializer implements PofSerializer
{
public void serialize(PofWriter pofWriter, Object o) throws IOException
{
java.sql.Date d = (java.sql.Date)o;
pofWriter.writeLong(1, d.getTime());
pofWriter.writeRemainder(null);
}

public Object deserialize(PofReader pofReader) throws IOException
{
java.sql.Date result = new java.sql.Date(pofReader.readLong(1));
pofReader.readRemainder();
return result;
}

}

2. Update my POF config file to add this serializer.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE pof-config PUBLIC "pof-config" "pof-config.dtd" >
<pof-config>
<user-type-list>
<include>coherence-pof-config.xml</include>
<user-type>
<type-id>1000</type-id>
<class-name>pas.au.coherence.query.server.AllDBObject</class-name>
</user-type>
<user-type>
<type-id>1001</type-id>
<class-name>java.sql.Date</class-name>
<serializer>
<class-name>pas.au.coherence.query.server.SQLDateSerializer</class-name>
<init-params/>
</serializer>
</user-type>
</user-type-list>
</pof-config>


3. My domain object would then use read/write object to trigger the use of this serializer

 
package pas.au.coherence.query.server;


import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;

import java.io.IOException;

import java.math.BigDecimal;

import java.sql.Date;

public class AllDBObject implements PortableObject
{
private String owner;
private String objectName;
private String subObjectName;
private BigDecimal objectId;
private BigDecimal dataObjectId;
private String objectType;
private Date created;
private Date lastDDLTime;
private String timestamp;
private String status;
private String temporary;
private String generated;
private String secondary;
private BigDecimal namespace;
private String editionName;

public AllDBObject()
{
}

public AllDBObject(String owner, String objectName, String subObjectName,
BigDecimal objectId, BigDecimal dataObjectId,
String objectType, Date created, Date lastDDLTime,
String timestamp, String status, String temporary,
String generated, String secondary,
BigDecimal namespace, String editionName)
{
super();
this.owner = owner;
this.objectName = objectName;
this.subObjectName = subObjectName;
this.objectId = objectId;
this.dataObjectId = dataObjectId;
this.objectType = objectType;
this.created = created;
this.lastDDLTime = lastDDLTime;
this.timestamp = timestamp;
this.status = status;
this.temporary = temporary;
this.generated = generated;
this.secondary = secondary;
this.namespace = namespace;
this.editionName = editionName;
}

public void setOwner(String owner)
{
this.owner = owner;
}

public String getOwner()
{
return owner;
}

public void setObjectName(String objectName)
{
this.objectName = objectName;
}

public String getObjectName()
{
return objectName;
}

public void setSubObjectName(String subObjectName)
{
this.subObjectName = subObjectName;
}

public String getSubObjectName()
{
return subObjectName;
}

public void setObjectId(BigDecimal objectId)
{
this.objectId = objectId;
}

public BigDecimal getObjectId()
{
return objectId;
}

public void setDataObjectId(BigDecimal dataObjectId)
{
this.dataObjectId = dataObjectId;
}

public BigDecimal getDataObjectId()
{
return dataObjectId;
}

public void setObjectType(String objectType)
{
this.objectType = objectType;
}

public String getObjectType()
{
return objectType;
}

public void setCreated(Date created)
{
this.created = created;
}

public Date getCreated()
{
return created;
}

public void setLastDDLTime(Date lastDDLTime)
{
this.lastDDLTime = lastDDLTime;
}

public Date getLastDDLTime()
{
return lastDDLTime;
}

public void setTimestamp(String timestamp)
{
this.timestamp = timestamp;
}

public String getTimestamp()
{
return timestamp;
}

public void setStatus(String status)
{
this.status = status;
}

public String getStatus()
{
return status;
}

public void setTemporary(String temporary)
{
this.temporary = temporary;
}

public String getTemporary()
{
return temporary;
}

public void setGenerated(String generated)
{
this.generated = generated;
}

public String getGenerated()
{
return generated;
}

public void setSecondary(String secondary)
{
this.secondary = secondary;
}

public String getSecondary()
{
return secondary;
}

public void setNamespace(BigDecimal namespace)
{
this.namespace = namespace;
}

public BigDecimal getNamespace()
{
return namespace;
}

public void setEditionName(String editionName)
{
this.editionName = editionName;
}

public String getEditionName()
{
return editionName;
}

public void readExternal(PofReader in) throws IOException
{
this.owner = in.readString(0);
this.objectName = in.readString(1);
this.subObjectName = in.readString(2);
this.objectId = in.readBigDecimal(3);
this.dataObjectId = in.readBigDecimal(4);
this.objectType = in.readString(5);
this.created = (Date) in.readObject(6);
this.lastDDLTime = (Date) in.readObject(7);
this.timestamp = in.readString(8);
this.status = in.readString(9);
this.temporary = in.readString(10);
this.generated = in.readString(11);
this.secondary = in.readString(12);
this.namespace = in.readBigDecimal(13);
this.editionName = in.readString(14);
}

public void writeExternal(PofWriter out) throws IOException
{
out.writeString(0, this.owner);
out.writeString(1, this.objectName);
out.writeString(2, this.subObjectName);
out.writeBigDecimal(3, this.objectId);
out.writeBigDecimal(4, this.dataObjectId);
out.writeString(5, this.objectType);
out.writeObject(6, this.created);
out.writeObject(7, this.lastDDLTime);
out.writeString(8, this.timestamp);
out.writeString(9, this.status);
out.writeString(10, this.temporary);
out.writeString(11, this.generated);
out.writeString(12, this.secondary);
out.writeBigDecimal(13, this.namespace);
out.writeString(14, this.editionName);
}

@Override
public String toString()
{
return "AllDbObject - owner: " + this.owner +
" ,objectName: " + this.objectName +
" ,subObjectName: " + this.subObjectName +
" ,objectId: " + this.objectId +
" ,dataObjectId: " + this.dataObjectId +
" ,objectType: " + this.objectType +
" ,created: " + this.created +
" ,lastDDLTime: " + this.lastDDLTime +
" ,timestamp: " + this.timestamp +
" ,status: " + this.status +
" ,temporrary: " + this.temporary +
" ,generated: " + this.generated +
" ,secondary: " + this.secondary +
" ,namespace: " + this.namespace +
" ,editionName: " + this.editionName;
}

@Override
public boolean equals(Object object)
{
if (this == object)
{
return true;
}
if (!(object instanceof AllDBObject))
{
return false;
}
final AllDBObject other = (AllDBObject) object;
if (!(owner == null ? other.owner == null : owner.equals(other.owner)))
{
return false;
}
if (!(objectName == null ? other.objectName == null : objectName.equals(other.objectName)))
{
return false;
}
if (!(subObjectName == null ? other.subObjectName == null : subObjectName.equals(other.subObjectName)))
{
return false;
}
if (!(objectId == null ? other.objectId == null : objectId.equals(other.objectId)))
{
return false;
}
if (!(dataObjectId == null ? other.dataObjectId == null : dataObjectId.equals(other.dataObjectId)))
{
return false;
}
if (!(objectType == null ? other.objectType == null : objectType.equals(other.objectType)))
{
return false;
}
if (!(created == null ? other.created == null : created.equals(other.created)))
{
return false;
}
if (!(lastDDLTime == null ? other.lastDDLTime == null : lastDDLTime.equals(other.lastDDLTime)))
{
return false;
}
if (!(timestamp == null ? other.timestamp == null : timestamp.equals(other.timestamp)))
{
return false;
}
if (!(status == null ? other.status == null : status.equals(other.status)))
{
return false;
}
if (!(temporary == null ? other.temporary == null : temporary.equals(other.temporary)))
{
return false;
}
if (!(generated == null ? other.generated == null : generated.equals(other.generated)))
{
return false;
}
if (!(secondary == null ? other.secondary == null : secondary.equals(other.secondary)))
{
return false;
}
if (!(namespace == null ? other.namespace == null : namespace.equals(other.namespace)))
{
return false;
}
if (!(editionName == null ? other.editionName == null : editionName.equals(other.editionName)))
{
return false;
}
return true;
}

@Override
public int hashCode()
{
final int PRIME = 37;
int result = 1;
result = PRIME * result + ((owner == null) ? 0 : owner.hashCode());
result = PRIME * result + ((objectName == null) ? 0 : objectName.hashCode());
result = PRIME * result + ((subObjectName == null) ? 0 : subObjectName.hashCode());
result = PRIME * result + ((objectId == null) ? 0 : objectId.hashCode());
result = PRIME * result + ((dataObjectId == null) ? 0 : dataObjectId.hashCode());
result = PRIME * result + ((objectType == null) ? 0 : objectType.hashCode());
result = PRIME * result + ((created == null) ? 0 : created.hashCode());
result = PRIME * result + ((lastDDLTime == null) ? 0 : lastDDLTime.hashCode());
result = PRIME * result + ((timestamp == null) ? 0 : timestamp.hashCode());
result = PRIME * result + ((status == null) ? 0 : status.hashCode());
result = PRIME * result + ((temporary == null) ? 0 : temporary.hashCode());
result = PRIME * result + ((generated == null) ? 0 : generated.hashCode());
result = PRIME * result + ((secondary == null) ? 0 : secondary.hashCode());
result = PRIME * result + ((namespace == null) ? 0 : namespace.hashCode());
result = PRIME * result + ((editionName == null) ? 0 : editionName.hashCode());
return result;
}
}

Wednesday, 21 April 2010

Oracle Coherence / POF (Portable Object Format) with JDeveloper 11g

Now I am constantly creating coherence projects in JDeveloper 11g as my IDE it has a useful source code wizard. The wizard is "Source -> Generate equals() and hashCode() methods". Given I am more often then not using POF (Portable Object Format) then this option saves me a lot of time. The "Source -> Generate Constructor from fields" also comes in very handy for Coherence projects as well.

From memory JDeveloper 10g didn't have these source code wizard's.

POF is described in the link below. Using POF has many advantages ranging from performance benefits to language independence.

http://coherence.oracle.com/display/COH35UG/The+Portable+Object+Format

Friday, 2 April 2010

Diagnosability Management with the 11g JDBC Driver

The JDBC diagnosability management feature introduces an MBean, oracle.jdbc.driver.OracleDiagnosabilityMBean. This MBean provides means to enable and disable JDBC logging. You problematically enable it as shown bythe method "enableOracleLogging()"from the code below.

1. First ensure you are using the debug version of the Orace JDBC driver. Given I was using JDK 1.6 I used ojdbc6_g.jar

2. Ensure you register the JDBC driver prior to enabling logging

DriverManager.registerDriver(new OracleDriver());

3. Full Code demo as follows:


package pas.au.jdbc.logging;

import java.lang.management.ManagementFactory;

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

import java.sql.Statement;

import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.management.Attribute;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import oracle.jdbc.OracleDriver;


public class Test
{
public Test()
{
}

public void run () throws SQLException
{
DriverManager.registerDriver(new OracleDriver());
enableOracleLogging();
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;

try
{
conn = getConnection();
System.out.println("Connection retrieved..");

stmt = conn.createStatement();
rset = stmt.executeQuery("select empno from emp");
while (rset.next())
{
System.out.println(rset.getInt(1));
}
}
catch (SQLException sqle)
{
sqle.printStackTrace();
}
finally
{
if (rset != null)
{
rset.close();
}

if (stmt != null)
{
stmt.close();
}

if (conn != null)
{
conn.close();
}
}


System.out.println("Connection closed");
}

public static Connection getConnection() throws SQLException
{
String username = "scott";
String password = "tiger";
String thinConn =
"jdbc:oracle:thin:@beast.au.oracle.com:1521/linux11g";

Connection conn =
DriverManager.getConnection(thinConn, username, password);
conn.setAutoCommit(false);
return conn;
}

private static void enableOracleLogging()
{
try
{
Handler fh = new FileHandler("./oracle_jdbc_log.log");
fh.setLevel(Level.ALL);
fh.setFormatter(new SimpleFormatter());
Logger.getLogger("").addHandler(fh);
Logger.getLogger("").setLevel(Level.ALL);

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
String loader = Thread.currentThread().getContextClassLoader().toString().
replaceAll("[,=:\"]+", "");
ObjectName pattern =
new ObjectName("com.oracle.jdbc:type=diagnosability,name=" +
loader );
ObjectName diag = ((ObjectName[])(mbs.queryNames(pattern, null).
toArray(new ObjectName[0])))[0];

mbs.setAttribute(diag, new Attribute("LoggingEnabled", true));
System.out.println("LoggingEnabled = " +
mbs.getAttribute(diag, "LoggingEnabled"));
}
catch(Exception e)
{
e.printStackTrace();
}
}

public static void main(String[] args) throws Exception
{
Test test = new Test();
test.run();
}
}
4. Sample Output as follows, most of it ommitted due to the size of the log file given we asked for everything to be logged.

Apr 2, 2010 10:09:30 AM DefaultMBeanServerInterceptor setAttribute
FINER: Object= com.oracle.jdbc:type=diagnosability,name=sun.misc.Launcher$AppClassLoader@47858e, attribute=LoggingEnabled
Apr 2, 2010 10:09:30 AM Repository retrieve
FINER: name=com.oracle.jdbc:type=diagnosability,name=sun.misc.Launcher$AppClassLoader@47858e
Apr 2, 2010 10:09:30 AM DefaultMBeanServerInterceptor getAttribute
FINER: Attribute= LoggingEnabled, obj= com.oracle.jdbc:type=diagnosability,name=sun.misc.Launcher$AppClassLoader@47858e
Apr 2, 2010 10:09:30 AM Repository retrieve
FINER: name=com.oracle.jdbc:type=diagnosability,name=sun.misc.Launcher$AppClassLoader@47858e
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.OracleDriver connect
TRACE_1: Public Enter: "jdbc:oracle:thin:@beast.au.oracle.com:1521/linux11g", {user=scott, password=tiger}
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.OracleDriver oracleDriverExtensionTypeFromURL
TRACE_16: Enter: "jdbc:oracle:thin:@beast.au.oracle.com:1521/linux11g"
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.OracleDriver oracleDriverExtensionTypeFromURL
TRACE_16: return: 0
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.OracleDriver oracleDriverExtensionTypeFromURL
TRACE_16: Exit
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection
TRACE_16: Enter: "jdbc:oracle:thin:@beast.au.oracle.com:1521/linux11g", {user=scott, password=tiger}, oracle.jdbc.driver.T4CDriverExtension@cb6009
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection readConnectionProperties
TRACE_16: Enter: "jdbc:oracle:thin:@beast.au.oracle.com:1521/linux11g", {user=scott, password=tiger}
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection getSystemProperty
TRACE_16: Enter: "oracle.jdbc.RetainV9LongBindBehavior", null
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection$1 run
TRACE_16: return: null
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection getSystemProperty
TRACE_16: return: null
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection getSystemProperty
TRACE_16: Exit
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection getSystemProperty
TRACE_16: Enter: "oracle.jdbc.database", null
Apr 2, 2010 10:09:30 AM oracle.jdbc.driver.PhysicalConnection$1 run

Monday, 29 March 2010

Discover/Monitor a Coherence Cluster from Oracle Enterprise Manager

I have wanted to do this for a while since it was added to OEM. This demo is using Coherence 3.5 with OEM 10.2.0.5. The steps are documented here.

http://download.oracle.com/docs/cd/B16240_01/doc/doc.102/e14631/emcgs.htm
Oracle® Enterprise Manager Getting Started Guide for Oracle Coherence
10g Release 5 (10.2.0.5)

Part Number E14631-02

Note: This demo is done within ANT , you can easily convert the ANT tasks to a J2SE client without to much issue.

1. Initially we have to start a Management Coherence Node using the Bulk Management MBeans shipped with OEM. Copy the files below to where your coherence node will run from.

$ORACLE_HOME/sysman/jlib/coherenceEMIntg.jar
$ORACLE_HOME/modules/bulkoperationsmbean_11.1.1.jar

2. In your ANT build.xml file define the following classpath which will include coherence.jar and the 2 jar files from OEM obtained at step #1


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

3. Start a Management Coherence Node with a target as follows.

<property name="jmxclass.name" value="oracle.sysman.integration.coherence.EMIntegrationServer"/>
...
<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.port" value="10001"/>
<sysproperty key="com.sun.management.jmxremote.authenticate" value="false"/>
<sysproperty key="com.sun.management.jmxremote.ssl" value="false"/>
<jvmarg line="${jvmargs}"/>
<classpath>
<path refid="j2ee.classpath"/>
</classpath>
</java>
</target>

4. Start one or more default cache servers using an ANT task as follows.

<property name="class.name" value="com.tangosol.net.DefaultCacheServer"/>
...
<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">
<jvmarg line="${jvmargs}"/>
<classpath>
<path refid="j2ee.classpath"/>
</classpath>
</java>
</target>

5. In OEM setup a target to the Management Coherence Node using connect details as follows

Note: It's assumed the Management Coherence Node is running on host "papicell-au.au.oracle.com" and the port we used is 10001. Also all other fields are left blank.

Machine Name: papicell-au.au.oracle.com
JMX Remote Port: 10001
Service URL: service:jmx:rmi:///jndi/rmi://papicell-au.au.oracle.com:10001/jmxrmi
Communication Protocol: rmi
Service Name: jmxrmi
Bulk Operations MBean: Coherence:type=BulkOperations

Note: I deliberately have set com.sun.management.jmxremote.authenticate=false to disable authentication here.

Thats it now you can monitor the cache's from OEM and get other JMX stats available through OEM. I believe the OEM agent will communicate directly with Management Coherence Node here.

Finally the build.properties for ANT is defined as follows. These are used in the ANT targets and references above.

# 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

Tuning a Oracle JDBC Variable In List Query

I recently showed how to create a variable in list for a JDBC query as follows. The problem with this demo is it won't be a very effecient query as the table grows as it will most likely do a full table scan.

Variable in list with Oracle JDBC and RDBMS
http://theblasfrompas.blogspot.com/2008/02/variable-in-list-with-oracle-jdbc-and.html

To show this lets trace the SQL to verify the path of a query against a table as follows. This table has a PRIMARY KEY defined on the ID column which will be what we drive our in list query from. The query we are running is defined as follows:

select loadall_type(id, message_type, message) as "Data"
from loadall_table
where id in
(SELECT * FROM TABLE
(CAST(loadall_pkg.in_number_list('4, 67, 88, 1001') as message_id_nt)));

Table Definition

create table loadall_table
(id number,
message_type varchar2(1),
message varchar2(100),
CONSTRAINT loadall_table_PK PRIMARY KEY (id))
/
So by turning autotrace on for the in list query we can see a full table scan needs to be performed to retrieve 4 rows from a table with 100,000 rows. Sure that won't take long but if the table grows to 10 million rows for example then a full table scan is what we want to avoid.

SCOTT@linux11g> set autotrace on
SCOTT@linux11g> select loadall_type(id, message_type, message) as "Data"
2 from loadall_table
3 where id in
4 (SELECT * FROM TABLE
5 (CAST(loadall_pkg.in_number_list('4, 67, 88, 1001') as message_id_nt)));

Data(MESSAGE_ID, MESSAGE_TYPE, MESSAGE)
------------------------------------------------------------------------------------------------------

LOADALL_TYPE(4, 'M', 'Message at 4')
LOADALL_TYPE(67, 'M', 'Message at 67')
LOADALL_TYPE(88, 'M', 'Message at 88')
LOADALL_TYPE(1001, 'M', 'Message at 1001')


Execution Plan
----------------------------------------------------------
Plan hash value: 3431305811

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 167 (2)| 00:00:03 |
|* 1 | HASH JOIN RIGHT SEMI | | 1 | 25 | 167 (2)| 00:00:03 |
| 2 | COLLECTION ITERATOR PICKLER FETCH| IN_NUMBER_LIST | | | | |
| 3 | TABLE ACCESS FULL | LOADALL_TABLE | 100K| 2246K| 137 (1)| 00:00:02 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("ID"=VALUE(KOKBF$))


Statistics
----------------------------------------------------------
6 recursive calls
0 db block gets
487 consistent gets
0 physical reads
0 redo size
4188 bytes sent via SQL*Net to client
1148 bytes received via SQL*Net from client
12 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed


So if we can assume only a small amount of rows will ever be queried using the in list then a query as follows would be a lot more efficient to return the same data.

SCOTT@linux11g> select /*+ cardinality(plist 2) */ loadall_type(id, message_type, message) as "Data"
2 from loadall_table,
3 (SELECT * FROM TABLE
4 (CAST(loadall_pkg.in_number_list('4, 67, 88, 1001') as message_id_nt))) plist
5 where id = plist.column_value;

Data(MESSAGE_ID, MESSAGE_TYPE, MESSAGE)
------------------------------------------------------------------------------------------------------

LOADALL_TYPE(4, 'M', 'Message at 4')
LOADALL_TYPE(67, 'M', 'Message at 67')
LOADALL_TYPE(88, 'M', 'Message at 88')
LOADALL_TYPE(1001, 'M', 'Message at 1001')


Execution Plan
----------------------------------------------------------
Plan hash value: 113937112

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 50 | 31 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 2 | 50 | 31 (0)| 00:00:01 |
| 3 | COLLECTION ITERATOR PICKLER FETCH| IN_NUMBER_LIST | | | | |
|* 4 | INDEX UNIQUE SCAN | SYS_C0050957 | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID | LOADALL_TABLE | 1 | 23 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("ID"=VALUE(KOKBF$))


Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
16 consistent gets
0 physical reads
0 redo size
1151 bytes sent via SQL*Net to client
428 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

Be careful using this technique if your in list query contains more then 5% of the table as the newer query will force the use of the index and hence avoid a full table scan. If you know that a very small part of the table is returned and the records are not known until runtime a query as follows would work well for large tables, where the original query would not be as efficient.

We did two things here to tune this query.

1. Used a hint for force the use of an index

/*+ cardinality(plist 2) */

2. Altered the query to no longer use IN list. New Query as follows now.

select /*+ cardinality(plist 2) */
loadall_type(id, message_type, message) as "Data"
from
loadall_table,
(SELECT * FROM TABLE
(CAST(loadall_pkg.in_number_list('4, 67, 88, 1001') as message_id_nt))) plist
where id = plist.column_value;

Monday, 22 March 2010

Universal Connection Pool (UCP) with OC4J 10.1.3.x

I setup a native OC4J 10.1.3.x Data Source using the Oracle UCP and allthough I didn't really do much more then that it did work. Personally I would stick with ICC in OC4J 10.1.3.x and the default pool implementation but if you have a pressing need to use UCP this would work.

Note: This demo is based on using stand alone OC4J so the container in this case is known as "home"

1. Ensure that the container or stand alone OC4J is using the 11.1.0.7 JDBC driver. I created a shared library as follows which automatically use the latest version. For some reason the 10.1.0.5 default driver won't work here.

Name: oracle.jdbc
Version: 11.1.0.7


2. I wanted to test my data source from asconsole so to do that I needed to edit the $ORACLE_HOME\j2ee\home\application-deployments\ascontrol\orion-application.xml file of the deployed ascontrol application, and comment out the line that removes the importing of the global.libraries.xml file, as shown below.


<imported-shared-libraries>
<!--
<remove-inherited name="global.libraries"/>
-->

<import-shared-library name="oracle.xml.security"/>
</imported-shared-libraries>

3. Create a native Data Source as follows in your data-sources file.

<native-data-source
name="jdbc/UcpNativeDS"
jndi-name="jdbc/UcpNativeDS"
description="UCP Native DataSource"
data-source-class="oracle.ucp.jdbc.PoolDataSourceImpl"
user="scott"
password="tiger"
url="jdbc:oracle:thin:@beast.au.oracle.com:1522:linux10g">
<property name="connectionFactoryClassName"
value="oracle.jdbc.pool.OracleDataSource"/>
<property name="connectionPoolName" value="TestPool"/>
<property name="initialPoolSize" value="1"/>
<property name="maxPoolSize" value="20"/>
</native-data-source>
4. Copy ucp.jar , version 11.1.0.7, into $ORACLE_HOME/j2ee/home/applib of the container.

5. Stop/Start OC4J

6. Test the Data Source from asconsole as follows, which should then work for J2EE applications as well.



Monday, 15 March 2010

OC4J 10.1.3.x - Accessing a Data Source remotely without oc4jadmin user

I previously showed how one would remotely access JNDI object's which include EJB's , Data Sources etc as shown in this post below.

http://theblasfrompas.blogspot.com/2008/07/enable-remote-clients-to-access-oas.html

Typically internally I use oc4jadmin who has access to everything and of course it has no issues. However in the real world 2 things are just about certain.

1. A Data Source would most likely be created at the container level for applications to share allthough it's not uncommon for them to be created at the application level.

2. The OAS administrator would almost certainly provide a separate user for JNDI access.

With such a setup the user created won't be able to access container specific JNDI objects such as data sources as most likely the OAS administrator won't make the user given part of the "oc4j-administrators" group. In this case the same principals as the blog entry apply however you won't find a container orion-application.xml.

So assuming you have a Data Source created in the HOME container you would need to alter the file below and then stop/start the instance to allow users to access container specific JNDI objects such as a Data Source. This demo assumes your user is part of the group called "users" as per the blog entry above.

$ORACLE_HOME/j2ee/home/config/application.xml


<namespace-access>
<read-access>
<namespace-resource root="">
<security-role-mapping>
<group name="oc4j-administrators" />
<group name="ascontrol_admin" />
<group name="users" />
</security-role-mapping>
</namespace-resource>
</read-access>
<write-access>
<namespace-resource root="">
<security-role-mapping>
<group name="oc4j-administrators" />
<group name="ascontrol_admin" />
<group name="users" />
</security-role-mapping>
</namespace-resource>
</write-access>
</namespace-access>

Thursday, 25 February 2010

Accessing a Data Source Remotely in FMW 11g (11.1.1.2.0) from JDeveloper 11g (11.1.1.2.0)

I needed to access a data source remotely on FMW 11g server from JDeveloper 11g. I found that to do this I needed to follow these steps.

Note: This was done using 11.1.1.2 which is the latest version of FMW 11g and JDeveloper 11g

1. Edit setDomainEnv script to ensure you set this property to true. By default it's false.

WLS_JDBC_REMOTE_ENABLED="-Dweblogic.jdbc.remoteEnabled=true"

2. Re-start your server to pick up the change done at step #1 above.

3. In your project add the following libraries to allow remote access and the required JDBC driver library.
  • Weblogic 10.3 Remote-Client
  • Oracle JDBC
4. From JDeveloper access your data source remotely with code as follows making sure you use your connection details and the correct JNDI name for the data source.


package pas.au.remote.wls11g;

import java.sql.Connection;

import java.sql.SQLException;

import java.util.Date;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.sql.DataSource;

public class RemoteDataSourceAccess
{
private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
public RemoteDataSourceAccess()
{
}

public void run ()
{
logger.log(Level.INFO, "Started RemoteDataSourceAccess at " + new Date());

InitialContext ctx = null;

try
{
ctx = getInitialContext();
DataSource ds = (DataSource) ctx.lookup("jdbc/scottDS");
Connection conn = (Connection) ds.getConnection();
logger.log(Level.INFO, "Got connection : " + conn);

logger.log(Level.INFO, "Auto Commit = " + conn.getAutoCommit());
}
catch (NamingException e)
{
logger.log(Level.SEVERE, "Error occurred", e);
System.exit(1);
}
catch (SQLException e)
{
logger.log(Level.SEVERE, "SQLException occurred", e);
System.exit(1);
}

logger.log(Level.INFO, "Ended RemoteDataSourceAccess at " + new Date());
}

public static InitialContext getInitialContext() throws NamingException
{
String JNDI_FACTORY="weblogic.jndi.WLInitialContextFactory";
String url = "t3://wayne-p2.au.oracle.com:7003";
String username = "weblogic";
String password = "welcome1";

Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
return new InitialContext(env);
}

public static void main(String[] args)
{

RemoteDataSourceAccess test = new RemoteDataSourceAccess();
test.run();
}
}

5. Output as follows.

25/02/2010 9:22:56 AM pas.au.remote.wls11g.RemoteDataSourceAccess run
INFO: Started RemoteDataSourceAccess at Thu Feb 25 09:22:56 EST 2010
25/02/2010 9:23:03 AM pas.au.remote.wls11g.RemoteDataSourceAccess run
INFO: Got connection : weblogic.jdbc.rmi.SerialConnection_weblogic_jdbc_rmi_internal_ConnectionImpl_weblogic_jdbc_wrapper_PoolConnection_oracle_jdbc_driver_T4CConnection_1032_WLStub@1
25/02/2010 9:23:03 AM pas.au.remote.wls11g.RemoteDataSourceAccess run
INFO: Auto Commit = true
25/02/2010 9:23:03 AM pas.au.remote.wls11g.RemoteDataSourceAccess run
INFO: Ended RemoteDataSourceAccess at Thu Feb 25 09:23:03 EST 2010

Wednesday, 24 February 2010

Getting detailed logging for Universal Connection Pool (UCP) / Fast Connection Failover (FCF) Testing

While testing UCP/FCF I was having some issues with the RAC cluster setup. One useful way to determine exactly what is happening or not happening from the FAN events sent from RAC cluster to the pool was to setup logging as described below.

1. Create a properties file as follows.

# This is the sample logging properties file that configures
# loggers of some classes (FCF-related ones) to produce detailed logging
# (all levels) and just SEVERE and WARNING level messages for the rest
# of loggers. Console output via UCPFormatter (ODL-like messages).

handlers = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = oracle.ucp.util.logging.UCPFormatter

.level = WARNING

oracle.ucp.common.FailoverEventHandlerThreadBase.level = ALL

oracle.ucp.jdbc.oracle.ONSDatabaseEventHandlerThread.level = ALL
oracle.ucp.jdbc.oracle.ONSDatabaseFailoverEvent.level = ALL
oracle.ucp.jdbc.oracle.ONSOracleFailoverEventSubscriber.level = ALL
oracle.ucp.jdbc.oracle.OracleDatabaseInstanceInfo.level = ALL
oracle.ucp.jdbc.oracle.OracleDatabaseInstanceInfoList.level = ALL
oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.level = ALL
oracle.ucp.jdbc.oracle.OracleFailoverEventNotification.level = ALL
oracle.ucp.jdbc.oracle.OracleFailoverEventSubscriber.level = ALL
oracle.ucp.jdbc.oracle.OracleFailoverHandler.level = ALL
oracle.ucp.jdbc.oracle.OracleFailoverablePooledConnection.level = ALL
oracle.ucp.jdbc.oracle.OracleConnectionConnectionPool.level = ALL
oracle.ucp.jdbc.oracle.OraclePooledConnectionConnectionPool.level = ALL
oracle.ucp.jdbc.oracle.OracleXAConnectionConnectionPool.level = ALL
oracle.ucp.jdbc.oracle.OracleJDBCConnectionPool.level = ALL
oracle.ucp.jdbc.oracle.OracleUniversalPooledConnection.level = ALL

2. Add the JVM command line option as follows - -Djava.util.logging.config.file=ucp_fcf_log.properties.

Then you should see some detailed output as follows. Very useful information in diagnosing UCP/FCF issues with your pool. Just showing small snippet as this is detailed output.

[java] 2010-02-24T10:44:16.734+1100 UCP FINE seq-103,thread-11 oracle.ucp.jdbc.oracle.ONSDatabaseEventHandlerThread.run event
triggered: Service name: orcl.apemrac.au.oracle.com, Instance name: orcl1, Unique name: orcl, Host name: apemrac1, Status: down,
Cardinality: 0, Reason: user, Event type: database/event/service
[java] 2010-02-24T10:44:16.734+1100 UCP FINEST seq-104,thread-11 oracle.ucp.jdbc.oracle.ONSDatabaseEventHandlerThread.run che
ck for events
[java] 2010-02-24T10:44:16.734+1100 UCP FINEST seq-105,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.setEventType
eventType: database/event/service
[java] 2010-02-24T10:44:16.750+1100 UCP FINEST seq-106,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.validateEvent
Type eventType: database/event/service
[java] 2010-02-24T10:44:16.750+1100 UCP FINEST seq-107,thread-11 oracle.ucp.jdbc.oracle.ONSDatabaseFailoverEvent. event
Type: database/event/service, eventBody: VERSION=1.0 service=HASERVICE.apemrac.au.oracle.com instance=orcl1 database=orcl host=ape
mrac1 status=down reason=failure
[java] 2010-02-24T10:44:16.765+1100 UCP FINEST seq-108,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.setServiceNam
e serviceName: HASERVICE.apemrac.au.oracle.com
[java] 2010-02-24T10:44:16.765+1100 UCP FINEST seq-109,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.setInstanceNa
me instanceName: orcl1
[java] 2010-02-24T10:44:16.765+1100 UCP FINEST seq-110,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.setDbUniqueNa
me dbUniqueName: orcl
[java] 2010-02-24T10:44:16.765+1100 UCP FINEST seq-111,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.setHostName h
ostName: apemrac1
[java] 2010-02-24T10:44:16.765+1100 UCP FINEST seq-112,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.setStatus sta
tus: down
[java] 2010-02-24T10:44:16.781+1100 UCP FINEST seq-113,thread-11 oracle.ucp.jdbc.oracle.OracleFailoverEventImpl.setReason rea
son: failure
[java] 2010-02-24T10:44:16.781+1100 UCP FINEST seq-114,thread-11 oracle.ucp.jdbc.oracle.OracleJDBCConnectionPool.handleFailov
erEvent failover event: Service name: HASERVICE.apemrac.au.oracle.com, Instance name: orcl1, Unique name: orcl, Host name: apemrac
1, Status: down, Cardinality: 0, Reason: failure, Event type: database/event/service
[java] 2010-02-24T10:44:16.796+1100 UCP FINEST seq-115,thread-11 oracle.ucp.jdbc.oracle.OracleJDBCConnectionPool.handleFailov
erEvent service name HASERVICE.apemrac.au.oracle.com in event does not match that in the pool: haservice.apemrac.au.oracle.com. No
FCF attempt.

Tuesday, 23 February 2010

Universal Connection Pool (UCP) / Fast Connection Failover (FCF)

I wanted to verify a UCP and how it works with FCF. I had previously used ICC/FCF which UCP/FCF now replaces. Development confirmed it is pretty much the same as explained below.

The basic usage model for FCF in UCP is the same as back in ICC. The user gets a Connection, performs JDBC operations on it, gets an exception from a RAC failure, reconnects and recovers if necessary. The main difference from ICC usage is the isValid() API that UCP provides, so that users do not need to check hard-coded exception error codes when deciding whether to reconnect.

So in short code as follows can now be added avoiding the need to check for error codes to determine if indeed a connection needs to reconnect.


public void run () throws SQLException
{
Connection conn = null;

try
{
counter++;
conn = pool.getConnection();
getInstanceDetails(conn, counter);
pool.displayDetails();
}
catch (SQLException sqle)
{
// The recommended way to check connection usability after a
// RAC-down event triggers UCP FCF actions.
if (conn == null || !((ValidConnection) conn).isValid())
{
logger.log
(Level.INFO,
"** FCFTest : Connection retry necessary : " + sqle.getMessage());

// Use UCP's FCF-specific statistics to verify the pool's
// FCF actions.
OracleJDBCConnectionPoolStatistics stats =
(OracleJDBCConnectionPoolStatistics) pool.getStats();

logger.log
(Level.INFO,
"** FCFTest : " + stats.getFCFProcessingInfo());
}
else
{
logger.log
(Level.SEVERE,
"** FCFTest : Exception occurred ->");
sqle.printStackTrace();
}
}
finally
{
try
{
pool.returnConnection(conn);
}
catch (SQLException se)
{
// not much we can do here
logger.log
(Level.INFO,
"** FCFTest : Exception detected when closing connection ");
se.printStackTrace();
}
}
}

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.