Saturday, August 29, 2009

richfaces pickList - validation error

A common error when using pickList is a "Value is not valid" error.

One reason for this is that the selectItem list does not contain one of the selected values. This happens often when you try to created a pre-selected list.

For example, suppose your selectItems are: A, B, C, D, E
And A & B are already selected.

This means that when the pickList is shows, you want A & B to appear in the right side, and you want C, D, E to appear on the left side.

What you need to do is put all five - A, B, C, D & E in the selectItems list (left side list).
Then in the list which will hold the selected items (right side list - the one that you will bind to the value attribute of the picklist), you need to put the select Item value of A & B.

richfaces will automatically show A & B on the right and C, D, E on the left.

However, if you put A & B in the right side list, and pnly C, D, E in the left side list - you will get a value is not valid error!


Secondly, remember that if your value is a non-String object, then you need to override the "equals" method in your Object class.

richfaces pickList - converters

Here are a few things to remember when using richfaces pickList:

1. If the value of your selectItem is a non-String object type, then you need to write a converter. You can have a property on your backing bean to hold an instance of the converter, and you can point the pickList converter to use it.

Like below:

1. -------------- Converter ----------------------------

package org.fastkangaroo.quasimodo.converters;

import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

import org.fastkangaroo.quasimodo.entities.UserProfile;

public class UserProfileConverter implements Converter {
private Map choiceMap;

public Object getAsObject(FacesContext arg0, UIComponent arg1, String label) {
return choiceMap.get(label);
}

public String getAsString(FacesContext arg0, UIComponent arg1, Object obj) {
UserProfile userProfile = (UserProfile) obj;
String label = userProfile.getFirstName() + " " + userProfile.getLastName() + " " + userProfile.getMiddleName();
return label;
}

/**
* @param choiceMap the choiceMap to set
*/
public void setChoiceMap(Map choiceMap) {
this.choiceMap = choiceMap;
}

}


2. ------------------ Beacking Bean --------------------------

public class ProjectBean {

...
private UserProfileConverter userProfileConverter;

...

+ getter/setter
}




3. ------------------- XHTML ------------------

<rich:pickList id="memberPickList"
value="#{projectBean.selectedMembers}"
converter="#{projectBean.userProfileConverter}"
validator="#{projectBean.projectMembersValidator}" >
<f:selectItems value="#{projectBean.memberSelection}" />
</rich:pickList>

richfaces DataTable - getting selected row

Unfortunately, a simple thing like getting which row in the rich:DataTable was selected is not so simple!

For excellent examples, see this page:
http://balusc.blogspot.com/2006/06/using-datatables.html

And, in addition, remember this:

1. Binding the datatable in your xhtml to a backing bean datatable (using the 'binding' attribute) will work only if the backing bean is in request scope. If it is in session scope, you will get a duplicate Id error when you refresh or revisit the page.

2. If you put a commandButton (or commandLink) on each row - it will work only if the action handler backing bean is in session scope. If it is in request scope, the action handler will not be called.

So, these two things are in contradiction to each other. The solution, if you are in this situation, is to have two backing beans - one for binding the datatable and another for handling the commandButton!

Now, if the reason you are binding the datatable in the first place is to use dataTable.getRowData() to identify the row - so that you can get to the row object, then you are in trouble. Because the action handler will go to a different backing bean!

The solution is to use f:setPropertyActionListener to pass the selected row object to the commandButton, as below:
<a4j:commandLink value="#{project.name}" action="#{projectBean.view}">
<f:setPropertyActionListener target="#{projectBean.project}" value="#{project}" />
</a4j:commandLink>

The backing bean, in this case projectBean should have a property called 'project' with getters and setters.

Friday, July 31, 2009

Running Apache Tomcat from inside Eclipse

You can run Apache Tomcat from inside Eclipse by:
* Window--> Show View --> Server

In the Servers panel that opens up:
* R-click --> New --> Server
* Select the Apache Tomcat Server from List
* Add your WebApp Project

You can now Right Click on the Server and Start/Stop it from inside Eclipse.


Here is a Gotcha:
- The application is run directly from your src folder (i.e. the war file is not actually copied to the /webapps directory of the tomcat install!)
- This is great because it speeds up development - you do not have to keep building the project to test it out on the server

- But REMEMBER to make a copy of all the jar files in the src/WEB-INF/lib directory otherwise TomCat will not find them!

How to maven-ize richfaces

For some reason, richfaces jars are no longer available on the maven repository.
To do it yourself:
- Download richfaces-ui-3.3.1.GA-bin.zip from the JBoss richfaces website
- Unzip it to say C:\Program Files\Richfaces


- Open a command prompt and cd to the /lib directory
You will see 3 jars in the /lib directory

- Give the following commands to load the jars to maven
> mvn install:install-file -DgroupId=org.richfaces -DartifactId=richfaces-impl -Dversion=3.3.1.GA -Dpackaging=jar -Dfile=./richfaces-impl-3.3.1.GA.jar

> mvn install:install-file -DgroupId=org.richfaces -DartifactId=richfaces-ui -Dversion=3.3.1.GA -Dpackaging=jar -Dfile=./richfaces-ui-3.3.1.GA.jar

> mvn install:install-file -DgroupId=org.richfaces -DartifactId=richfaces-api -Dversion=3.3.1.GA -Dpackaging=jar -Dfile=./richfaces-api-3.3.1.GA.jar


You can now reference these libraries in your pom.xml:

<dependency>
<groupId>org.richfaces</groupId>
<artifactId>richfaces-ui</artifactId>
<version>3.3.1.GA</version>
</dependency>

<dependency>
<groupId>org.richfaces</groupId>
<artifactId>richfaces-api</artifactId>
<version>3.3.1.GA</version>
</dependency>

<dependency>
<groupId>org.richfaces</groupId>
<artifactId>richfaces-impl</artifactId>
<version>3.3.1.GA</version>
</dependency>

Thursday, July 23, 2009

Example: Generating a SHA hash in Java

import java.security.MessageDigest;
...
public String createHashSHA (String someString){

try {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(someString.getBytes());
byte[] someStringDigest = md.digest();
String someStringHashSHA = new String(someStringDigest);
return someStringHashSHA;
}
catch (Exception e){
...
}
}

Jetty cant find applicationContext.xml

If your web aplication uses Spring, then you probably have an applicationContext.xml. By default, this file is in the /WEB-INF directory. However, for some reason, Jetty looks for it in /WEB-INF/classes. So Jetty gives this "applicationContext.xml file does not exist" error. Putting the file in /WEB-INF/classes seems to resolve this issue.

Note: I had to explode my war to do this. You can use zipitfree (http://www.zipitfast.com/) to explode (uncompress) the war.

How to install web application in Jetty

To install web application in Jetty, simply copy the your_web_app.war into /webapps directory of Jetty. Jetty will automatically pick it up on restart.

How to install and start Jetty Server

Here is what works for me:
- Download Jetty (I downloaded version 7)
- Unzip it in a directory (I used C:\Program Files\jetty_7.0.x)
- Install a JRE (I installed mine in C:\Program Files\JAVA)
- Open a cygwin command prompt (download cygwin from http://www.cygwin.com/)
- Type:
> cd /cygwin/c/Program Files/jetty_7.0.x
> export JETTY_HOME=/cygwin/c/Program Files/jetty_7.0.x
> export JAVA_HOME=/cygwin/c/Program Files/Java/jre
> java -jar start.jar

This should start Jetty.
To stop it, I just use ctrl-C (jetty will stop gracefully and not just abort)

MySQL 5.1 - server does not start on Vista

There seems to be some problem staring the MySQL 5.1 server on Vista. It won't start no matter what you do. I went back to version 5.0 and it installed and runs fine.

how to become administrator in Vista

By default, Vista administrator account is "hidden". This post shows how to activate it:
http://www.howtogeek.com/howto/windows-vista/enable-the-hidden-administrator-account-on-windows-vista/

In brief:
- open a "administrator" command prompt (start->programs->accesories->commandprompt : R-cick and choose - run as admin)
- Type: net user administrator /active:yes
- Logout and you will now see admin acount when you log back in (it has no password)

To rehide admin:
- open admin command prompt
- type: net user administrator /active:no

why "localhost" does not resolve

There is a problem accessing localhost on Vista. This post shows the solution to this problem:
http://www.moorlandit.net/index.php/2009/05/accessing-localhost-on-vistawindows-7-96

I am copy-pasting it here:

For some reason in Vista and Windows 7 localhost doesn’t work out of the box. To get it working you have to add this line to the c:\windows\system32\drivers\etc\hosts file:

127.0.0.1 locahost

Now the hosts file is a Windows system file so the only way Windows will allow you to edit the file is if you do it as Administrator. To do this click Start and type CMD in the search box, when Windows shows cmd.exe right-click it and click run as administrator then when the DOS box opens type:

edit c:\windows\system32\drivers\etc\hosts

Then add the 127.0.0.1 localhost line and save and close the file. Then localhost should work as it did in XP.

Monday, July 6, 2009

Tapestry5 - How to override elements of standard stylesheet

Create a file styles.css (or any other name of your choice) in your main/webapp directory (where you have all your .tml files)

Enter the styles in this file, for example - to override the color of chenillekit sliding panel, you can make an entry in this file:

DIV.ck_slidingPanelSubject {
background: #CCFF99;
}


Now, in yourpage.java - enter the following property to pull in the sylesheet:

import org.apache.tapestry5.Asset;

@Inject
@Path("context:styles.css")
@Property
private Asset styles;


And in yourpage.tml, pull the stylesheet in your header tag, as follows:

<head>
<link rel="stylesheet" href="${styles}" type="text/css"/>
</head>

Wednesday, June 24, 2009

Tapestry5 - How to dynamically populate a drop down list

Suppose you have 2 drop down lists - A & B
You want to populate the contents of B based on the selection in A
(For example: A = car manufacturer & B = car dealership)


In yourpage.tml


<t:form t:id="createAssetsForm">
<input t:id="selectManufacturer"/>
<input t:id="selectDealer" model="dealerSelection" />
</t:form>

<script>
function onManufacturerSelectFunction(response)
{
var selectDealer = document.getElementById("selectDealer");

// Unload the existing dealers
for (i = selectDealer.length; i != 0; i--) {
selectDealer.remove(i-1);
}

// Load the new dealers
for (i=0; i != response.dealers.length; i++){
var option = document.createElement('option');
option.text = response.dealers[i];
try {
selectDealer.add(option,null); // standards compliant
}
catch(ex){
selectDealer.add(option); //IE only
}
}
}
</script>


In yourpage.java
@Property
@Persist
private String manufacturer;

@Component(parameters = {"value=manufacturer", "model=literal:Honda, Toyota, Nissan, Subaru", "event=change",
"onCompleteCallback=literal:onManufacturerSelectFunction"})
@Mixins({"ck/OnEvent"})
private Select selectManufacturer;

@Property
@Persist
private String dealer;

@Component(parameters = {"value=dealer"})
private Select selectDealer;

@Property
@Persist
private SelectModel dealerSelection;


@OnEvent(component = "selectManufacturer", value = "change")
public JSONObject onTypeChangeEvent(String selectedManufacturer)
{
List<String> dealers = new ArrayList<String>();
... code to update dealers based on selectedManufacturer ...

// update the dealer selection
dealerSelection = TapestryInternalUtils.toSelectModel(dealers);

// IF you stop here - then the dealers will be updated only on page refresh!
// To update the dealers by AJAX (without page loading) do the following

// Pass the information to javascript as a JSON array
JSONArray JSONDealers = new JSONArray();

... code to enter the dealer names in the JSONArray ...

JSONObject json = new JSONObject();
json.put("dealers", JSONDealers);

return json;
}

Tuesday, June 23, 2009

Tapestry5 - How to handle multiple submit buttons on a form

In yourpage.tml:

<t:form t:id="multiSubmitForm">
<input t:type="submit" t:id="OKButton" value="OK"/>
<input t:type="submit" t:id="CancelButton" value="Cancel"/>
<input t:type="submit" t:id="HelpButton" value="Help"/>
</t:form>


In yourpage.java:

private Class nextPage;

Object onSubmitFromMultiSubmitForm(){
return nextPage;
}

@OnEvent(value="selected", component="OKButton")
void onOKButton(){
.... code ...
nextPage = OKPage.class;
}

@OnEvent(value="selected", component="CancelButton")
void onCancelButton(){
.... code ...
nextPage = CancelPage.class;
}

@OnEvent(value="selected", component="HelpButton")
void onHelpButton(){
.... code ...
nextPage = HelpPage.class;
}

Tapestry - How to create a drop-down selection list

In yourpage.tml:

<t:form>
<input t:id="selectName" model="nameSelection" />
</t:form>


In yourpage.java:


@Property
@Persist
private String selectedName;

@Component(parameters = {"value=selectedName"})
private Select selectName;

@Property
@Persist
private SelectModel nameSelection;

.... in your code .....

List<String> names = new ArrayList<String>();
// populate
for (Iterator<String> iter = .....; iter.hasNext();){
names.add(iter.next());
}

nameSelection = TapestryInternalUtils.toSelectModel(names);

Monday, June 22, 2009

JSONArray example

Here is how to create a JSONArray on the Java side:
public JSONObject onNameChangeEvent(String name)
{
Share share = shareDAO.getShareByName(name);

JSONArray JSONAttributes = new JSONArray();
JSONAttributes.put(name);
JSONAttributes.put("Market Price");
JSONAttributes.put(share.getPrice());
JSONAttributes.put("Market Year");
JSONAttributes.put(share.getPriceYear());
JSONAttributes.put("Average Growth(%)");
JSONAttributes.put(share.getGrowthPercent());

JSONObject json = new JSONObject();
json.put("attributes", JSONAttributes);
return json;
}


Here is how to use it in the javascript side:

function onNameSelectFunction(response)
{
$('details').update(
"<strong>" + response.attributes[0] + "</strong>"
+ "<br/>"
+ response.attributes[1] + " : " + response.attributes[2]
+ "<br/>"
+ response.attributes[3] + " : " + response.attributes[4]
+ "<br/>"
+ response.attributes[5] + " : " + response.attributes[6]
+ "<br/>"
+ "<br/>"
);
}

Tapestry + Hibernate + Jetty + JUnit: How to use a different database for testing?

Assuming your directory structure is based on the standard tapestry-maven project, as created using this method:
http://tapestryjava.blogspot.com/2009/01/using-maven-to-create-new-tapestry-51.html


here is how to tell junit to use a separate database (so it does not interfere with your development database)

1. Make a copy of the hibernate.cfg.xml to your src/test/resources
2. Point it to a different (test) database:

<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/nesteggtest</property>

*** IF you just stop here, your development will use the hibernate.cfg.xml from your test, simply because of the default classpath! ***

** To make your development use the correct hibernate.cfg.xml, do this:

3. Assuming you are using Eclipse and run-jetty-run: go to Run->OPen Run Dialog

4. Select your run-jetty-run configuration

5. Go to classpath and add your target/WEB-INF directory (this will contain the hibernate.cfg.xml for the development)

6. Move this directory (target/WEB-INF) to the top (so that it is the first one to be looked in during development)

Tapestry + Spring + Hibernate setting up project for Eclipse

FOLLOW THE FIVE INSTRUCTIONS BELOW

1. Follow the instructions here to create a base Tapestry project:
http://tapestryjava.blogspot.com/2009/01/using-maven-to-create-new-tapestry-51.html

2. Here is a pom.xml for:
Tapestry + Spring + Hibernate + JUnit + TestNG + Chenillekit + MySQL + Jetty

Modify your own pom.xml accordingly

Then create an eclipse project by typing the command:
cmd> mvn eclipse:eclipse

You can then open the project in eclipse.

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ikangaroo</groupId>
<artifactId>NestEgg</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<name>NestEgg Tapestry 5 Application</name>
<dependencies>
<dependency>
<groupId>org.apache.tapestry</groupId>
<artifactId>tapestry-core</artifactId>
<version>${tapestry-release-version}</version>
</dependency>
<!-- A dependency on either JUnit or TestNG is required, or the surefire plugin (which runs the tests)
will fail, preventing Maven from packaging the WAR. Tapestry includes a large number
of testing facilities designed for use with TestNG (http://testng.org/), so it's recommended. -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.8</version>
<classifier>jdk15</classifier>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>2.4</version>
<scope>test</scope>
</dependency>

<!-- tapestry-test will conflict with RunJettyRun inside Eclipse. tapestry-test brings in Selenium, which
is based on Jetty 5.1; RunJettyRun uses Jetty 6.
<dependency>
<groupId>org.apache.tapestry</groupId>
<artifactId>tapestry-test</artifactId>
<version>${tapestry-release-version}</version>
<scope>test</scope>
</dependency>

-->

<!-- Tapestry Spring integration -->
<dependency>
<groupId>org.apache.tapestry</groupId>
<artifactId>tapestry-spring</artifactId>
<version>5.0.18</version>
</dependency>

<!-- Provided by the servlet container, but sometimes referenced in the application
code. -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>

<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>2.5</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5</version>
</dependency>

<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.4.ga</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.3.0.ga</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.3.1.ga</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.1</version>
</dependency>

<dependency>
<groupId>org.chenillekit</groupId>
<artifactId>chenillekit-tapestry</artifactId>
<version>1.0.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>(5.1,)</version>
</dependency>

</dependencies>
<build>
<finalName>NestEgg</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<optimize>true</optimize>
</configuration>
</plugin>

<!-- Run the application using "mvn jetty:run" -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.9</version>
<configuration>
<!-- Log to the console. -->
<requestLog implementation="org.mortbay.jetty.NCSARequestLog">
<!-- This doesn't do anything for Jetty, but is a workaround for a Maven bug
that prevents the requestLog from being set. -->
<append>true</append>
</requestLog>
</configuration>
</plugin>
</plugins>
</build>

<reporting>

<!-- Adds a report detailing the components, mixins and base classes defined by this module. -->
<plugins>
<plugin>
<groupId>org.apache.tapestry</groupId>
<artifactId>tapestry-component-report</artifactId>
<version>${tapestry-release-version}</version>
<configuration>
<rootPackage>org.ikangaroo.nestegg</rootPackage>
</configuration>
</plugin>
</plugins>
</reporting>

<repositories>

<!-- This repository is only needed if the Tapestry released artifacts haven't made it to the central Maven repository yet. -->
<repository>
<id>tapestry</id>
<url>http://tapestry.formos.com/maven-repository/</url>
</repository>

<!-- This repository is only needed when the tapestry-release-version is a snapshot release. -->
<repository>
<id>tapestry-snapshots</id>
<url>http://tapestry.formos.com/maven-snapshot-repository/</url>
</repository>

<repository>
<id>codehaus.snapshots</id>
<url>http://snapshots.repository.codehaus.org</url>
</repository>
<repository>
<id>OpenQA_Release</id>
<name>OpenQA Release Repository</name>
<url>http://archiva.openqa.org/repository/releases/</url>
</repository>

<repository>
<id>chenillekit</id>
<url>http://www.chenillekit.org/mvnrepo/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<pluginRepositories>

<!-- As above, this can be commented out when access to the snapshot version
of a Tapestry Maven plugin is not required. -->
<pluginRepository>
<id>tapestry-snapshots</id>
<url>http://tapestry.formos.com/maven-snapshot-repository/</url>
</pluginRepository>


</pluginRepositories>

<properties>
<tapestry-release-version>5.2.0-SNAPSHOT</tapestry-release-version>
</properties>
</project>


3. Here is a web.xml - modify yours accordingly (it is located in src/main/webapp/web-inf)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>NestEgg Tapestry 5 Application</display-name>
<context-param>
<!-- The only significant configuration for Tapestry 5, this informs Tapestry
of where to look for pages, components and mixins. -->
<param-name>tapestry.app-package</param-name>
<param-value>org.ikangaroo.nestegg</param-value>
</context-param>
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry5.spring.TapestrySpringFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>app</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
</web-app>


4. Here is a hibernate.cfg.xml - modify your accordingly (it is located in /src/main/resources - if it is not there, create one)

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">



<hibernate-configuration>

<session-factory>

<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.username">user</property>
<property name="hibernate.connection.password">passwd</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/nestegg</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.hbm2ddl.auto">create</property>
<property name="hibernate.hbm2ddl.auto">false</property>
<property name="show_sql">true</property> </session-factory>

</hibernate-configuration>


5.Here is an applicationContext.xml- modify yours accordingly (it is located in /src/main/webapp/web-inf - if it is not there, create one)

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

<!-- the parent application context definition for the springapp application -->



</beans>

Hibernate cascade save-update NonUniqueObject Exception

If you have, say 3 hibernate entitites related as follows:
A --> mapped to --> collection of B : one-to-many
B --> mapped to --> C: many-to-one

If you do a session.update(A) - hibernate will attempt to update all the instances of B in the collection. If two instances of B point to the same instance of C --- you will get a NonUniqueObject Exception!

This is because Hibernate does not allow two instances of the same object in the same session.

The workaround to resolve this issue is to do a session.merge(A) instead of session.update(A)

This is another reason I feel it is better to avoid using save-update.

Hibernate cascade save-update issues

Using "save-update" has some potential performance issues. For example, consider:

<hibernate-mapping>
<class name="org.ikangaroo.nestegg.entities.AssetTracker" table="ASSET_TRACKER">
<id name="id" column="ASSET_TRACKER_ID">
<generator class="increment" />
</id>

<map name="trackedAssets" table="TRACKED_ASSETS" lazy="false" cascade="save-update">
<key column="ASSET_TRACKER_ID" />
<map-key type="integer" column="ASSETS_YEAR"/>
<one-to-many class="org.ikangaroo.nestegg.entities.Assets"/>
</map>

</class>

</hibernate-mapping>

Here TrackedAssets contains a collection (Map) of Assets.

Suppose, TrackedAssets contains 10 Assets. Now I add a 11th one. And I do session.update(trackedAssets). This will cause Hibernate to generate 11 database queries to update all the 11 Assets. This is not very efficient, since the 10 have not changed at all.

As you can see, if there were 100 entries and you add 1 new one - that will cause 101 database queries!!!

I personally think it is better to not use save-update through cascade. Instead, do the save-update yourself as required in your code. That way you can do incremental updates, instead of making hibernate do full updates which are expensive.

Note that "delete" option of cascade is quite alright to use.