Install this theme

Posts tagged: oracle

Load/Drop Jars to/from Oracle database using Ant

Oracle Client Package provides loadjava and dropjava tools to loading/dropping java classes / jars / resources to/from the Oracle database. 

Sometimes however it is necessary to run this functionality on the machine that doesn’t have Oracle Client package installed.

This post describes how to achieve this using Ant.

Note! This instruction is for Oracle 11g.

Prerequisites

From the machine having Oracle Client installed, copy ojdbc5.jar (typically located in $ORACLE_HOME/product/11.x/client_1/jdbc/lib) and aurora.zip (typically located in $ORACLE_HOME%/product/11.x/client_1/javavm/lib) to some folder accessible by your Ant script.

Below i’ll assume, that this 2 files are located in the same folder where the Ant script located.

Load Java Target

<target name="loadjava" description="target for deploying jars to the database">
	<java classname="oracle.aurora.server.tools.loadjava.LoadJavaMain" fork="true">
		<jvmarg value="-Xint" />
		<classpath>
			<pathelement location="aurora.zip" />
			<pathelement location="ojdbc5.jar" />
		</classpath>
		<arg line="-thin -user SCOTT/TIGER@DBHOST:1551:DBSID -resolve my-jar-to-upload.jar" />
	</java>
</target> 

This target will deploy my-jar-to-upload.jar file to the Oracle database identified by SCOTT/TIGER@DBHOST:1551:DBSID url.

Drop Java Target

<target name="dropjava" description="target for dropping jars from the database">
	<java classname="oracle.aurora.server.tools.loadjava.DropJavaMain" fork="true">
		<jvmarg value="-Xint" />
		<classpath>
			<pathelement location="aurora.zip" />
			<pathelement location="ojdbc5.jar" />
		</classpath>
		<arg line="-thin -user SCOTT/TIGER@DBHOST:1551:DBSID my-jar-to-upload.jar" />
	</java>
</target> 

This target will drop my-jar-to-upload.jar file from the Oracle database identified by SCOTT/TIGER@DBHOST:1551:DBSID url.

Oracle: Aggregating SQL statement results as single XML CLOB

Once again a short reminder how to aggregate SQL statement execution results as a single XML CLOB in Oracle.

Assume there is an Oracle table containing user data:

create table t_user(
	user_id		number(15) not null,	
	user_name	varchar2(100) not null,		
	role		varchar2(100) not null,			
	email		varchar2(100)	
)
/

Now aggregating all the user entries as single XML CLOB can be done using following statement:

select xmlelement("users",
                  xmlagg(xmlelement("user",
                                    xmlattributes(    t.user_id as "id",
                                                      t.user_name as "name",
                                                      t.role,
                                                      t.email
                                    )
            ))
        ).getclobval() xml
from t_user t

This will result in a CLOB that looks like this:

<users>
	<user id="1" name="foo" role="USER" email="[email protected]"></user>
	<user id="2" name="bar" role="ADMIN" email="[email protected]"></user>
	...
	<user id="999" name="xyz" role="USER" email="[email protected]"></user>
</users>  
Oracle: returning table contents as a custom table type

Often it is necessary to return table contents or some select-statement execution results as a custom table type (e.g. from within a stored procedure or function). 

A short reminder how this can be done.

Suppose following structures are defined in the database (some table, object type and a corresponding table type) :

create table t_user (
  id                 number(15) not null,
  username           varchar2(100) not null,
  email              varchar2(100),
  date_of_birth      date
)
/

create or replace type obj_user as object (
  id                 number,
  username           varchar2(100),
  email              varchar2(100),
  date_of_birth      date
)
/

create or replace type user_list as table of obj_user
/

Now using a combination of Oracle’s cast and multiset operators we can retrieve the contents of the t_user table and return it as user_list type:

select cast(
        multiset (select obj_user(id, username, email, date_of_birth)
        from (select * from t_user)
     ) as user_list) as users
from dual
Oracle Advanced Queuing with Spring and custom Oracle Types

There are already several blog articles on the web about this topic. In this post I want to demonstrate how you can process JMS messages that have been created based on the Oracle Custom Types.

This is also the solution to the common problem: JMS-137: Payload factory must be specified for destinations with ADT payloads.

In the run-up to the post please find below some links how to connect Spring via JMS to the Oracle Advanced Queing:

Assuming following custom oracle data type, queue table and the queue itself were defined in the database:

create or replace type aq_event_obj as object
(
  datetime date,
  id       varchar2(100),
  payload  varchar2(255)
);
sys.dbms_aqadm.create_queue_table(queue_table        => 'T_AQ_EVENT',
                                  queue_payload_type => 'AQ_EVENT_OBJ',
                                  sort_list          => 'PRIORITY, ENQUEUE_TIME',
                                  compatible         => '10.0.0',
                                  primary_instance   => 0,
                                  secondary_instance => 0);
sys.dbms_aqadm.create_queue(queue_name     => 'Q_AQ_EVENT',
                            queue_table    => 'T_AQ_EVENT',
                            queue_type     => sys.dbms_aqadm.normal_queue,
                            max_retries    => 5,
                            retry_delay    => 0,
                            retention_time => 0);

First step is a standard procedure creating JMS Connection Factory, Event Queue and JMS Template in Spring described in the posts above (see links). Just a brief overview for this here:

<bean id="jmsQueueConnectionFactory" class="oracle.jms.AQjmsFactory"
	factory-method="getQueueConnectionFactory">
	<constructor-arg index="0" ref="dataSource" />
</bean>
<bean id="aqEventQueue" class="net.javaforge.blog.oracle.aq.OracleAqQueueFactoryBean">
	<property name="connectionFactory" ref="jmsQueueConnectionFactory" />
	<property name="oracleQueueName" value="Q_AQ_EVENT" />
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
	<property name="connectionFactory" ref="jmsQueueConnectionFactory" />
	<property name="defaultDestination" ref="aqEventQueue" />
</bean>

And the corresponding java classes:

public abstract class AbstractOracleAqFactoryBean {

	private ConnectionFactory connectionFactory;
	private String oracleQueueName = null;
	private String oracleQueueUser = null;

	@Required
	public void setConnectionFactory(final ConnectionFactory connectionFactory) {
		this.connectionFactory = connectionFactory;
	}

	public String getOracleQueueName() {
		return oracleQueueName;
	}

	@Required
	public void setOracleQueueName(final String oracleQueueName) {
		this.oracleQueueName = oracleQueueName;
	}

	public String getOracleQueueUser() {
		return oracleQueueUser;
	}

	public void setOracleQueueUser(final String oracleQueueUser) {
		this.oracleQueueUser = oracleQueueUser;
	}

	public boolean isSingleton() {
		return false;
	}

	protected AQjmsSession getSession() throws JMSException {
		final AQjmsSession session = (AQjmsSession) connectionFactory
				.createConnection().createSession(true,
						Session.SESSION_TRANSACTED);
		return session;
	}

}

public class OracleAqQueueFactoryBean extends AbstractOracleAqFactoryBean
		implements FactoryBean {

	public Class getObjectType() {
		return javax.jms.Queue.class;
	}

	public Queue getObject() throws JMSException {
		final AQjmsSession session = getSession();
		return session.getQueue(getOracleQueueUser(), getOracleQueueName());
	}

}

Furthermore as soon as we want to process custom oracle data type as message payload, we have to implement the ORADataFactory interface and describe our aq_event_obj type in it.

This interface can be implemented by hand or you can just generate an appropriate implementation with Oracle’s JDeveloper IDE. To save time i have chosen the second option and generated following ugly but fully functional class:

image
public class AQCustomPayloadObject implements ORAData, ORADataFactory {
	
	public static final String _SQL_NAME = "AQ_EVENT_OBJ";
	public static final int _SQL_TYPECODE = OracleTypes.STRUCT;

	protected MutableStruct _struct;

	protected static int[] _sqlType = { 91, 12, 12 };
	protected static ORADataFactory[] _factory = new ORADataFactory[3];
	protected static final AQCustomPayloadObject _AqEventObjFactory = new AQCustomPayloadObject();

	public static ORADataFactory getORADataFactory() {
		return _AqEventObjFactory;
	}

	/* constructors */
	protected void _init_struct(boolean init) {
		if (init)
			_struct = new MutableStruct(new Object[3], _sqlType, _factory);
	}

	public AQCustomPayloadObject() {
		_init_struct(true);
	}

	public AQCustomPayloadObject(java.sql.Timestamp datetime, String id, String payload)
			throws SQLException {
		_init_struct(true);
		setDatetime(datetime);
		setId(id);
		setPayload(payload);
	}

	/* ORAData interface */
	public Datum toDatum(Connection c) throws SQLException {
		return _struct.toDatum(c, _SQL_NAME);
	}

	/* ORADataFactory interface */
	public ORAData create(Datum d, int sqlType) throws SQLException {
		return create(null, d, sqlType);
	}

	protected ORAData create(AQCustomPayloadObject o, Datum d, int sqlType)
			throws SQLException {
		if (d == null)
			return null;
		if (o == null)
			o = new AQCustomPayloadObject();
		o._struct = new MutableStruct((STRUCT) d, _sqlType, _factory);
		return o;
	}

	/* accessor methods */
	public java.sql.Timestamp getDatetime() throws SQLException {
		return (java.sql.Timestamp) _struct.getAttribute(0);
	}

	public void setDatetime(java.sql.Timestamp datetime) throws SQLException {
		_struct.setAttribute(0, datetime);
	}

	public String getId() throws SQLException {
		return (String) _struct.getAttribute(1);
	}

	public void setId(String id) throws SQLException {
		_struct.setAttribute(1, id);
	}

	public String getPayload() throws SQLException {
		return (String) _struct.getAttribute(2);
	}

	public void setPayload(String payload) throws SQLException {
		_struct.setAttribute(2, payload);
	}

}

Now it is time for the magic. We have to override Spring’s default DefaultMessageListenerContainer class by creating our special message consumer that can deal with the aq_event_obj type :

public class AQCustomPayloadMessageListenerContainer extends
		DefaultMessageListenerContainer {

	@Override
	protected MessageConsumer createConsumer(Session session,
			Destination destination) throws JMSException {

		return ((AQjmsSession) session).createConsumer(destination,
				getMessageSelector(),
				AQCustomPayloadObject.getORADataFactory(), null,
				isPubSubNoLocal());

	}
}

And the corresponding Spring’s XML configuration:

<bean id="jmsContainer"
	class="net.javaforge.blog.oracle.aq.AQCustomPayloadMessageListenerContainer">
	<property name="connectionFactory" ref="jmsQueueConnectionFactory" />
	<property name="destination" ref="aqEventQueue" />
	<property name="messageListener" ref="aq.receiver" />
	<property name="sessionTransacted" value="true" />
	<property name="transactionManager" ref="tx.manager" />
</bean>

The only task now is to implement the message receiver:

@Named("aq.receiver")
public class AQReceiver implements MessageListener {

	private static final Logger logger = LoggerFactory
			.getLogger(AQReceiver.class);

	@Override
	public void onMessage(Message message) {
		try {
			AQjmsAdtMessage msg = (AQjmsAdtMessage) message;
			AQCustomPayloadObject obj = (AQCustomPayloadObject) msg.getAdtPayload();

			logger.info("Id: {}", obj.getid());
			logger.info("Datetime: {}", obj.getDatetime());
			logger.info("Payload: {}", obj.getPayload());

		} catch (Exception e) {
			logger.error("Error receiving queue message!", e);
		}
	}
}
Random test data generation in Oracle

There is nothing new or extraordinary in this statement.

Just a reminder for me to not dig around the Google next time I need this.

select level id,
       mod(rownum, 100) city_id,
       trunc(dbms_random.value(20, 60), 0) age,
       trunc(dbms_random.value(1000, 50000), 2) income,
       decode(round(dbms_random.value(1, 2)), 1, 'M', 2, 'F') gender,
       to_date(round(dbms_random.value(1, 28)) || '-' ||
               round(dbms_random.value(1, 12)) || '-' ||
               round(dbms_random.value(1900, 2010)),
               'DD-MM-YYYY') dob,
       dbms_random.string('x', dbms_random.value(10, 30)) address
  from dual
connect by level <= 1000;

Will generate 1000 rows of random data like this:

image