Skip to content

By Programmer For Programmer

Here I lay down the useful tips, tricks and utilities for programmers like myself.

Archive

Category: Example Code
Background

So at my current job at jamasoftware.com, our Contour application supports the three major databases MySQL, SQL Server, and Oracle (sorry PostgreSQL fans… maybe soon).  Recently I needed to run UPDATE statements with JOINs, and had to learn the different syntax for each of these databases.  I thought I would share all of them side-by-side, so that you can easily see the differences:

Our Simplified Schema:
Table: ORGANIZATION
id int primary key

Table: PROJECT
id int primary key
organizationId int foreign key ORGANIZATION(id)

Table: DOCUMENT
id int primary key
projectId int foreign key ORGANIZATION(id)
organizationId int null
The Task

Update all DOCUMENT records with the organizationId of their corresponding project

The Solution
MySQL:
UPDATE DOCUMENT d
    INNER JOIN PROJECT p ON d.projectId = p.id 
SET d.organizationId = p.organizationId 
WHERE d.organizationId IS NULL;

SQL Server:
UPDATE d
SET d.organizationId = p.organizationId 
FROM DOCUMENT d
    INNER JOIN PROJECT p ON d.projectId = p.id 
WHERE d.organizationId IS NULL

Oracle:
UPDATE (SELECT d.organizationId AS docOrgId, p.organizationId AS projectOrgId 
        FROM DOCUMENT d
            INNER JOIN PROJECT p ON d.projectId = p.id 
        WHERE d.organizationId IS NULL) v 
set v.docOrgId = v.projectOrgId

Here’s the situation:

  • Auto-flush is OFF (either FlushMode.COMMIT or FlushMode.MANUAL)
  • You save() or saveOrUpdate() an object with Hibernate
  • Your object is not using IDENTITY for its id
  • Later (possible unrelated) code tries to get the saved object back by id

If you found this post, you probably know that no object is returned with the given id.  This is because the session has not been flushed.

So just flush the session before retrieval, right?  Well yes, but not so fast.  Most people, including myself, don’t want to have a big “session.flush()” in the middle of service layer code.

My solution was to register a “save” and “save-update” event listener with Hibernate, and process the insert immediately after.

Register lisenters (using Spring):

<bean id="sessionFactory">
    <property name="eventListeners">
        <map>
            <entry key="save">
                <bean class="com.jamasoftware.contour.util.hibernate.ExecuteImmediateSaveEventListener"/>
            </entry>
            <entry key="save-update">
                <bean class="com.jamasoftware.contour.util.hibernate.ExecuteImmediateSaveOrUpdateEventListener"/>
            </entry>
        </map>
    </property>
</bean>

Listeners:

public class ExecuteImmediateSaveEventListener extends DefaultSaveEventListener {

	private static final long serialVersionUID = 1L;
	
	protected Serializable performSaveOrReplicate(
			Object entity,
			EntityKey key,
			EntityPersister persister,
			boolean useIdentityColumn,
			Object anything,
			EventSource source,
			boolean requiresImmediateIdAccess) {
		Serializable id = super.performSaveOrReplicate(
				entity, 
				key, 
				persister, 
				useIdentityColumn, 
				anything, 
				source, 
				requiresImmediateIdAccess);
		if (!useIdentityColumn) {
			immediatelyInsert(source);
		}
		return id;
	}

	private void immediatelyInsert(EventSource source) {
		source.getActionQueue().executeInserts();
	}
	
}
public class ExecuteImmediateSaveOrUpdateEventListener extends DefaultSaveOrUpdateEventListener {

	private static final long serialVersionUID = 1L;
	
	protected Serializable performSaveOrReplicate(
			Object entity,
			EntityKey key,
			EntityPersister persister,
			boolean useIdentityColumn,
			Object anything,
			EventSource source,
			boolean requiresImmediateIdAccess) {
		Serializable id = super.performSaveOrReplicate(
				entity, 
				key, 
				persister, 
				useIdentityColumn, 
				anything, 
				source, 
				requiresImmediateIdAccess);
		if (!useIdentityColumn) {
			immediatelyInsert(source);
		}
		return id;
	}

	private void immediatelyInsert(EventSource source) {
		source.getActionQueue().executeInserts();
	}
	
}

This code was tested with Hibernate 3.3.1.

This problem was especially annoying to me because I work with an application that can be deployed on many databases. MySQL and SQL Server, for example, allow IDENTITY columns, which Hibernate will immediately execute INSERT statements for since it needs to in order to get their id. But Oracle doesn’t work this way, and the execution of the INSERT statements goes onto the ActionQueue, since it uses a SEQUENCE. What happens then, is that you end up with code that works for one database that doesn’t work for another.

Using the above listeners, my code now acts the same whether using IDENTITY columns or not.

Every time I feel like I know all the Java syntax there is, I learn something new :)

Let’s say you have a class with a static constructor that returns a map, and that map is typed and does fancy things, but you still want to use generics from wherever you are using the Map.

public class MyClass {

    public static final <K,V> Map<K,V> createMyMap() {
        ...
        return map;
    }
}

If you are like me, you hate hate hate casting to generics, but you are forced to do that or have a big ugly @SuppressWarnings("unchecked") on the method that you access this method.

@SuppressWarnings("unchecked")
function Map<String, Object> getMyMap() {
    return MyClass.createMyMap();
}

Well not anymore!. Today I just learned (by looking through some Java source code) that you can assign generics on your static calls by using the following syntax.

function Map<String, Object> getMyMap() {
    return MyClass.<String, Object>createMyMap();
}

Just put the generics right after the period and before the method name. Easy.

I learned this while trying to figure out why java.util.Collections has static fields EMPTY_LIST, EMPTY_SET, and EMPTY_MAP, and also static methods emptyList(), emptySet, and emptyMap. It says right in the code, “Unlike this method, the field does not provide type safety”.

So recently I had to build a web service, and had a really hard time finding documentation or instructions on how to make web services work in Tomcat using the web service annotations from JSR 181 (@WebService, @WebMethod, @WebParam, etc).  So I created this post as a way to help someone else in the same situation.  I’m sure there are many ways to do this, but this is one way.

My goal was to create java files annotated with web service annotations and deploy the application to Tomcat, and just have everything magically work.  I didn’t want to use the wsgen tool.  I wanted it to work like it does in JBoss, Weblogic, and Glassfish, where you just deploy the application and the web service endpoints are created automatically.

Environment

Here is the environment that I was working with:

  • Java 1.6.0_16
  • Tomcat 6.0.18
  • Eclipse 3.4
  • Building a WAR and dropping into webapps folder in Tomcat

Example Class

MyWebService.java:

package com.example.webservice;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

/**
 * An example web service implementation
 * @author Sean Adkinson
 */
@WebService(targetNamespace = "http://com.example.webservice", name = "mywebservice")
public class MyWebService
{
    
    /**
     * Says hello to the given name
     * @param name A name
     * @return Hello, name
     */
    @WebMethod(operationName = "hello")
    @WebResult(name = "return")
    public String hello(@WebParam(name = "name") String name)
    {
        return "Hello, " + name;
    }
    
}

Instructions

Follow these instructions to create a web service from our example class above:

  1. Download Metro 1.5 binary (or latest point release) at https://metro.dev.java.net/1.5/
  2. Run java -jar metro-1_5.jar to unpack the contents of the jar
  3. Take all the lib/webservices-*.jar files from the expanded Metro installation, and copy them into your web application’s WEB-INF/lib folder. Note that I chose to put these in my application’s library, but you can also put them in the server’s library at tomcat.home/lib.
  4. Create a sun-jaxws.xml file right next to web.xml under WEB-INF. This is where you specify where to find the classes that have your web service annotations. Here is a sample file:

    sun-jaxws.xml:

    <endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>
        <endpoint
            name='mywebservice'
            implementation='com.example.webservice.MyWebService'
            url-pattern='/mywebservice' />
    </endpoints>
    

    Here, name can be anything, implementation is the classpath reference to the class with the web service annotations that should have a web service created from it, and url-pattern is the path after your web application’s URL that will hit this web service (so if your application is at http://localhost:8080/MyApplication, this web service will be available at http://localhost:8080/MyApplication/mywebservice).

  5. Edit your web.xml to map the URL pattern above to class com.sun.xml.ws.transport.http.servlet.WSServlet, and add a listener for class com.sun.xml.ws.transport.http.servlet.WSServletContextListener. Here is an example:
    <listener>
        <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>myws</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>myws</servlet-name>
        <url-pattern>/mywebservice</url-pattern>
    </servlet-mapping>
    
  6. Now deploy your webapp, start Tomcat, and go to http://localhost:8080/MyApplication/mywebservice, and you should see details about the endpoint that was created, with a link to the generated WSDL file.
  7. From here you should be able point whatever client code generating utilities you have at the WSDL and talk with the web service from a client. I use built-in Eclipse Axis2 code generation for this, but check out wsimport in the Metro /bin folder for another option.

That’s it! Hope this helps someone who had as hard a time as I did with getting this working in Tomcat.