Showing posts with label Hibernate. Show all posts
Showing posts with label Hibernate. Show all posts

Wednesday, August 20, 2008

How to upgrade Hibernate in JBoss

JBoss comes shipped with Hibernate by default. Upgrading Hibernate is similar to upgrading any other 3rd party library in your application deployed on JBoss. As long as you understand how the classloading works in JBoss, the upgrading should be pretty straightforward.

For a brief (well not so brief) background about classloaders in JBoss, have a look at these wiki articles:

How classloading works in JBoss

How to configure classloaders in JBoss

Once you read through these wiki articles, you will understand that if your application needs to have its own version of a library (does not matter if it is Hibernate or some other 3rd party library), you will have to configure classloader scoping through the xml file.

So why am i writing this stuff all over again, when these two wiki articles have enough details about classloading scoping? Its mainly because of some tricky issues, which have been reported in the JBoss forums, with upgrading Hibernate (specifically to Hibernate version 3.2.6) on JBoss-4.2.x (specifically JBoss-4.2.2 GA). The rest of the article tries to explain these issues and way to fix them. Though this is written to be more oriented towards upgrading Hibernate, whatever has been explained here will apply to almost every 3rd party library upgrade on JBoss.

So let's start then!

Details about the default installation of JBoss-4.2.2 GA:

JBoss-4.2.2 GA ships with

 

Hibernate EntityManager 3.2.1.GA
Hibernate Annotations 3.2.1.GA
Hibernate 3.2.4.sp1


What we intend to do is, upgrade Hibernate to use 3.2.6 GA. Let's assume, we have an EAR which will be deployed to JBoss:


MyApp.ear
 |
 |--- META-INF
 |      |
 |      |
 |      |--- application.xml
 |      | 
 |      |--- jboss-app.xml
 |
 |
 |--- lib
 |    |
 |    |--- [some jar files required by my app]
 |
 |
 |--- MyApp.war


So first step would be package the upgraded Hibernate jar files in the application (MyApp.ear). Its crucial to understand that you have to be absolutely sure that you have packaged all the required hibernate jars and the correct versions of those jars in your application. This Hibernate compatibility matrix will help you in picking up the correct versions. However, you still have to know "which" hibernate jars you need to package in the application.

Based on what i have seen in the forums, the issues faced while upgrading Hibernate were more related to users missing out certain dependent hibernate jar files. Debugging such issues was not very easy since, the errors that got thrown were not simple ClassNotFoundException (which you usually associate with a missing jar). Various errors like ClassCastException, NoSuchMethodException were thrown mainly because Hibernate in this version (3.2.6 GA) refactored a lot of their code to move them to different "projects". For example, the org.hibernate.search package was earlier in the "Hibernate Annotations" project (hibernate-annotations.jar) but with this new release, it was moved to a separate "Hibernate Search" project (hibernate-search.jar). Same applies to org.hibernate.validator package which earlier was in the "Hibernate Annotations" project (hibernate-annotations.jar) but with this new release, it was moved to a separate "Hibernate Validator" project.

So how does it matter if those hibernate packages were moved to a different project (jar)? Here's a very brief explanation of what happens:

- JBoss, in its lib folder, has an older version of Hibernate (3.2.4) and other hibernate related jar files, including the hibernate-annotations.jar. In this version, the hibernate-annotations.jar contained the org.hibernate.search and org.hibernate.validator and various other packages.

- You decide to upgrade Hibernate in your application by packaging the hibernate jars in your application and enabling classloader configuration. You package *only* the latest version of core hibernate jar, the hibernate-annotations.jar and maybe even the hibernate-entitymanager.jar.
Note: You have NOT packaged the hibernate-validator.jar nor the hibernate-search.jar.

- You start JBoss and the server tries to deploy your application. While deploying, for configuring Hibernate, various Hibernate classes are used, which includes the classes belonging to core hibernate jar and also org.hibernate.validator and org.hibernate.search packages.

- Since you have configured classloader scoping for your application, JBoss loads the hibernate core classes, the hibernate entitymanager classes and the hibernate annotation classes from the upgraded jars packaged in your application.

- But when a class belonging to org.hibernate.validator or org.hibernate.search package is being requested for, JBoss sees that these classes are not present the jars packaged in your application. So it delegates the classloading to the parent classloader which looks for the classes in the jar files present in the JBoss lib folder (%JBOSS_HOME%/server/< serverName>/lib folder). Here it finds that these packages are present in the hibernate-annotations.jar (older version) and loads those classes from there. While doing so, it also loads the related classes from various other hibernate packages (which might already have been loaded by a different classloader - remember the classes loaded from the jars in your application). Ultimately, this results to the same classes being loaded twice by different classloaders. Later on when you access these classes in your application you might run into ClassCastExceptions.

This is just one example of what might go wrong. Infact, you might not get a clear picture based on this brief explanation. So if you are interested in understanding better (and have some time), then go through these forum discussions which have a lot more details (and which actually made me come up with this article):

ClassCastException for org.hibernate.search.event.FullTextIndexEventListener

Again the ClassCastException for org.hibernate.search.event.FullTextIndexEventListener

This time a NoSuchMethodException: org.hibernate.validator.ClassValidator.


So now that we have seen what kind of issues you might run into while upgrading, let's now come back to our original plan of upgrading hibernate :)

1) Enable classloader scoping through jboss-app.xml:


<jboss-app>

<loader-repository>
   org.myapp:loader=SomeClassloader
   <loader-repository-config>
      java2ParentDelegation=false
   </loader-repository-config>
 </loader-repository> 

  
</jboss-app>


Note: The string org.myapp:loader=SomeClassloader is any unique ObjectName

2) Include the following jar files in the application package:



Hibernate Core jar (3.2.6 GA)
Hibernate Annotations jar (3.2.x or 3.3.x)
Hibernate EntityManager jar (3.2.x or 3.3.x)
Hibernate Validator jar (3.0.x)
Hibernate Search jar (3.0.x)
and maybe even Lucene Core jar (lucene-core-2.2.0.jar)


Note: Please follow this page for downloading and figuring out the correct version of hibernate jars required (compatibility matrix).

So this is how your application packaging will look like finally:


MyApp.ear
 |
 |--- META-INF
 |     |
 |     |
 |     |--- application.xml
 |     | 
 |     |--- jboss-app.xml
 |
 |
 |--- lib
 |     |
 |     |--- [some jar files required by my app]
 |     |
 |     |--- hibernate3.jar (the hibernate core jar)
 |     | 
 |     |--- hibernate-annotations.jar 
 |     |
 |     |--- hibernate-entitymanager.jar 
 |     |  
 |     |--- hibernate-validator.jar 
 |     |
 |     |--- hibernate-search.jar 
 |     |
 |     |--- lucene-core-2.2.0.jar
 | 
 |
 |--- MyApp.war





That's it! The upgrade itself is simple enough. Note that, in this article, i have used an EAR as an example, but this applies to WAR files too. In WAR files, the jars will be placed in the WEB-INF/lib folder and the classloader configuration will be done through the jboss-web.xml file which will be in WEB-INF folder.

Thursday, August 24, 2006

inverse attribute in Hibernate - What does it mean?



This is the best explanation, that i have seen till date, about Hibernate's "inverse" attribute:
Meaning of "inverse" in Hibernate

Evict collection from Hibernate second level cache


Hibernate allows persistent objects to be cached in its second level cache(The first level cache in Hibernate is the Session object which is ON by default). Applications can switch on the second level cache. When a object is being retrieved by the application through Hibernate, Hibernate first checks in its Session cache and then the Second level cache to see if the object has be retrieved already. If it finds it either the Session cache or the Second level cache, it will NOT fire a query to the database.
While configuring second level cache, the object can be cached and also the collections contained in the object can be cached. Have a look at the following example:

<hibernate-mapping default-lazy="false" >
<class name="org.myapp.ho.Parent" table="Parent">
<b><cache usage="read-only" /> </b>
<id name="id" type="Integer" column="ID" />
<set name="myChildren">
<b> <cache usage="read-only"/> </b>
<one-to-many class="org.myapp.ho.Child"/>
</set>
</class>
</hibernate-mapping>



<hibernate-mapping default-lazy="false" >
<class name="org.myapp.ho.Child" table="Child">
<b><cache usage="read-only" /></b>
<id name="id" type="Integer" column="ID" />
</class>
</hibernate-mapping>


Note that we have used the cache setting at 3 places:
1) The org.myapp.ho.Parent object
2) The "myChildren" collection in the org.myapp.ho.Parent object
3) The org.myapp.ho.Child object
When you configure a collection to be second level cached in Hibernate, it internally maintains a SEPERATE cache for these collection than the one which it uses to cache the parent objects. So in the example above, the “myChildren” will be cached separately than the org.myapp.ho.Parent object.
There might be cases where applications would want to evict objects from the cache. If its the Session cache from which the application has to evict the object then the call to Session.evict will cascade even to collections and will evict the collection from the *Session cache*. However, if the object(and the collections contained in it) have to be evicted from the second level cache, then the application has to *explicitly* call the evictCollection method on the SessionFactory to remove the *collection* contained in the Parent object. The reason behind this is, as already mentioned, the collections are cached separately, than the parent objects, in the second level cache.
So, in our example above, if we have to evict the Parent with id 500 and its collection from the second level cache, then here’s what has to be done:

SessionFactory sf = MyUtil.getSessionFactory();
//this will evict the Parent Object from the second level cache
sf.evict(org.myapp.ho.Parent.class,new Integer(500));
//this will evict the collection from the second level cache for the Parent with id=500
sf.evictCollection(org.myapp.ho.Parent.class.getName() + ".myChildren", new Integer(500));


The first parameter to the evictCollection method is the ‘roleName’ of the collection. The roleName is formed as follows:

roleName = NameOfTheParentClass + "." + NameOfTheCollectionInsideTheParent

The second parameter the evictCollection method is the id of the parent object, to which this collection belongs.

Custom reverse engineering strategy in Hibernate


Hibernate has tools to create mapping files(hbm files) and domain model classes from database schemas(reverse engineering). can be used as part of ant task to do this. Hibernate creates the property names using its default reverse engineering strategy. Hibernate also, provides a way through which the user can specify his own custom reverse engineering strategy through which he can follow his own naming conventions etc…

There is a reversestrategy attribute which can be set to some custom class, which implements org.hibernate.cfg.reveng.ReverseEngineeringStrategy, in the . Here’s an example:

<jdbcconfiguration configurationfile="hibernate.cfg.xml"

packagename="${package.name}"

revengfile="hibernate.reveng.xml"

reversestrategy="org.myapp.hibernate.tool.SampleReverseEngineeringStrategy"/>


The org.myapp.hibernate.tool.SampleReverseEngineeringStrategy is our own custom class which implements org.hibernate.cfg.reveng.ReverseEngineeringStrategy. In this example, our SampleReverseEngineeringStrategy, overrides the columnToPropertyName(TableIdentifier table, String column) method to provide a custom implementation for generating property names out of a column name. Here’s the SampleReverseEngineeringStrategy code:

package org.myapp.hibernate.tool;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.cfg.reveng.DelegatingReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.ReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.TableIdentifier;
/**
*
* @author Jaikiran Pai
*
*/
public class SampleReverseEngineeringStrategy extends DelegatingReverseEngineeringStrategy {
/**
* Constructor
*
* @param delegate {@link org.hibernate.cfg.reveng.ReverseEngineeringStrategy}
*/
public SampleReverseEngineeringStrategy(ReverseEngineeringStrategy delegate) {
super(delegate);
}

/**
* Changes the default behaviour of naming the property.

* Does the following replacements(not neccessarily in the order) and returns the resulting
* {@link String} as property name:
* - Converts the first letter of the column to uppercase
* - Converts the letters following a ‘_’ character to uppercase in the column
* - Removes any underscores present from column
*
* @see org.hibernate.cfg.reveng.DelegatingReverseEngineeringStrategy
* @see org.hibernate.cfg.reveng.ReverseEngineeringStrategy
*
* @param table {@link TableIdentifier}
* @param column
* @return Returns the propert name after converting it appropriately
*/
public String columnToPropertyName(TableIdentifier table, String column) {

String replacedColumn = replaceFirstLetterToUpperCase(column);

replacedColumn = removeUnderScoresAndConvertNextLetterToUpperCase(replacedColumn);

if (anyReplacementsMadeToOriginalColumnName(column,replacedColumn)) {

return replacedColumn;

}

/*
* Let DelegatingReverseEngineeringStrategy handle this
*/
return super.columnToPropertyName(table, column);
}

/**
*
* Returns true if the originalString and replacedString are NOT equal
* (meaning there was some replacement done to the original column name). Else returns false.
*
* @param originalString The original column name
* @param replacedString The column name after doing necessary replacements
* @return Returns true if the originalString and replacedString are NOT equal
* (meaning there was some replacement done to the original column name). Else returns false.
*
* @throws {@link NullPointerException} if originalString is null.
*/
protected boolean anyReplacementsMadeToOriginalColumnName(String originalString, String replacedString) {
if (originalString.equals(replacedString)) {
return false;

}

return true;
}

/**
* Converts the first letter of the input to uppercase and
* returns the resultant {@link String}.
*
* Ex: If the input is startDate then the resulting {@link String}
* after replacement will be StartDate
*
* @param input The {@link String} whose contents have to be replaced
* @return Returns a {@link String} after doing the appropriate replacements
*/
protected String replaceFirstLetterToUpperCase(String input) {

/*
* The pattern to match a String starting with lower case
*/
final String startsWithLowerCasePattern = "^[a-z]";

Pattern patternForReplacingLowerCase = Pattern.compile(startsWithLowerCasePattern);
Matcher regexMatcher = patternForReplacingLowerCase.matcher(input);

/*
* This will hold the replaced contents
*/
StringBuffer replacedContents = new StringBuffer();

/*
* Check whether the first letter starts with lowercase.
* If yes, change it to uppercase, else pass on the control to
* DelegatingReverseEngineeringStrategy
*
*/
if (regexMatcher.find()) {

String firstCharacter = regexMatcher.group();
/*
* Convert it to uppercase
*/
regexMatcher.appendReplacement(replacedContents,firstCharacter.toUpperCase());
regexMatcher.appendTail(replacedContents);
regexMatcher.reset();

/*
* Return the replaced contents
*/
return replacedContents.toString();

}

//no replacements to do, just return the original input
return input;

}

/**
* Converts the letters following a ‘_’ character to uppercase and also removes
* the ‘_’ character from the input and returns the resulting {@link String}.
* Carries out a 2 pass strategy to do the replacements. During the first pass,
* replaces all the letters that immidiately follow a ‘_’ to uppercase.
* Ex: If the input is _start_Date__today_ then after the first pass of replacement, the
* resultant string will be _Start_Date__Today_
*
* This replaced {@link String} is then passed ahead for second pass (if no replacements were
* done during first pass, then the original {@link String} is passed). During the second pass
* the underscores are removed.
* Ex: If the input is _start_Date__today_ then after BOTH the passes the
* resultant string will be StartDateToday
*
* @param input The {@link String} whose contents have to be replaced
* @return Returns a {@link String} after doing the appropriate replacements
*/
protected String removeUnderScoresAndConvertNextLetterToUpperCase(String input) {

/*
* The pattern which matches a String that starts with a letter immidiately after
* a ‘_’ character
*/
final String stringFollowingUnderScore = "[.]*_[a-zA-Z]+";

Pattern patternForReplacingLowerCase = Pattern.compile(stringFollowingUnderScore);
Matcher regexMatcher = patternForReplacingLowerCase.matcher(input);
/*
* This will hold the replaced contents
*/
StringBuffer replacedContents = new StringBuffer();

boolean foundAnyMatch = false;

while (regexMatcher.find()) {
foundAnyMatch = true;
String matchedString = regexMatcher.group();
/*
* The character immidiately following the underscore
* Example:
* If matchedString is _tMn then originalCharAfterUnderScore will be the
* character t
*/

char originalCharAfterUnderScore = matchedString.charAt(1);
/*
* Convert the character to uppercase
*/

String replacedCharAfterUnderScore = String.valueOf(originalCharAfterUnderScore).toUpperCase();

/*
* Now place this replaced character back into the matchedString
*/

String replacement = matchedString.replace(originalCharAfterUnderScore,replacedCharAfterUnderScore.charAt(0));

/*
* Append this to the replacedColumn, which will be returned back to the user
*/
regexMatcher.appendReplacement(replacedContents,replacement);

} //end of while

regexMatcher.appendTail(replacedContents);
regexMatcher.reset();

/*
* Now the input string has been replaced to contain uppercase letters after the underscore.
* Ex: If input string was "_start_Date_today" then at this point after the above processing,
* the replaced string will be "_Start_Date_Today"
* The only thing that remains now is to remove the underscores from the input string.
* The following statements do this part.
*
*/

if (foundAnyMatch) {
return removeUnderScores(replacedContents.toString());

} else {
return removeUnderScores(input);

}

}

/**
* Removes any underscores present from input and returns the
* resulting {@link String}
* Ex: If the input is _start_Date__today_ then the resulting {@link String}
* after replacement will be startDatetoday
*
* @param input The {@link String} whose contents have to be replaced
* @return Returns a {@link String} after doing the appropriate replacements
*/
protected String removeUnderScores(String input) {

/*
* Pattern for matching underscores
*/
Pattern patternForUnderScore = Pattern.compile("[.]*_[.]*");

Matcher regexMatcher = patternForUnderScore.matcher(input);
/*
* This will hold the return value
*/
StringBuffer returnVal = new StringBuffer();
boolean foundAnyMatch = false;

while (regexMatcher.find()) {
foundAnyMatch = true;

String matchedString = regexMatcher.group();

/*
* Remove the underscore
*/
regexMatcher.appendReplacement(returnVal,"");

}

regexMatcher.appendTail(returnVal);
regexMatcher.reset();

/*
* If any match was found(and replaced) then return the replaced string.
* Else return the original input.
*/
if (foundAnyMatch) {
return returnVal.toString();

}
return input;

}

}


In the example above, the columnToPropertyName method is overridden to do the following:

- Creates property names that start with a Capital case(By default, Hibernate creates property names in camel-case)
- Converts the letter, that follows a ‘_’ (underscore character) to uppercase in the property name
- Removes any underscores in the property name

Ex: If the column name is start_Date_today, then the resulting property name after using the SampleReverseEngineeringStrategy would be StartDateToday.

Here’s a documentation from Hibernate about Controlling Reverse Engineering