SQL Zone is brought to you in partnership with:

I'm a software architect specializing in web-based solutions with Ajax frameworks such as Ext, Dojo, Prototype, Scriptaculous and JQuery on the front end and Spring and Hibernate as middleware. I also have experience with .NET solutions, Web Services and many more aspects of software development. Chris has posted 2 posts at DZone. You can read more from them at their website. View Full User Profile

Automatically Mapping Annotated Hibernate Classes With Spring

08.21.2009
| 29745 views |
  • submit to reddit

Doesn't it annoy you when you have to add each class individually to the Spring config when you are dealing with annotated classes? I bet it annoys you even more when you have to refactor a package and Eclipse doesn't refactor the package inside the xml file. I have your solution and it won't cost you anything.

First you need to write your XML just like mine.

 

<!-- add dynamic factory here -->
<bean id="sessionFactory"
class="com.package.spring.AutomatedAnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />

<property name="automaticAnnotatedPackages">
<list>
<value>com.package.domain</value>
<value>com.package.domain.view</value>

</list>
</property>
<property name="annotatedPackages">
<list>
<value>com.package.domain</value>
<value>com.package.domain.view</value>
</list>
</property>

<!-- annotated source files -->
<!-- <property name="annotatedClasses">
<list>
<value>example.Account</value>
<value>example.AccountDetail</value>
<value>example.Employee</value>
</list>
</property> -->

<property name="hibernateProperties">
<props>
<prop key="hibernate.generate_statistics">true</prop>
<prop key="hibernate.dialect">${db.dialect}</prop>
<prop key="hibernate.cache.provider_class">${cache.provider}</prop>
<!-- <prop key="hibernate.cache.provider_class">org.hibernate.cache.TreeCacheProvider</prop> -->
<prop key="hibernate.show_sql">false</prop>
<!-- <prop key="hibernate.hbm2ddl.auto">create</prop> -->
<prop key="hibernate.validator.apply_to_ddl">true</prop>
<!-- <prop key="hibernate.validator.autoregister_listeners">true</prop>
<prop key="hibernate.validator.apply_to_ddl">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop> -->
<!-- <prop key="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</prop> -->
</props>
</property>
</bean>


You'll notice really quick that you need a new class as well.

 

public class AutomatedAnnotationSessionFactoryBean extends
LocalSessionFactoryBean
{

private Class[] annotatedClasses;

private String[] annotatedPackages;

private String[] automaticAnnotatedPackages;

private static Log log = LogFactory
.getLog(AutomatedAnnotationSessionFactoryBean.class);

public String[] getAutomaticAnnotatedPackages()
{
return automaticAnnotatedPackages;
}

public void setAutomaticAnnotatedPackages(String[] annotatedPackages)
{
automaticAnnotatedPackages = annotatedPackages;

}

public AutomatedAnnotationSessionFactoryBean()
{
setConfigurationClass(AnnotationConfiguration.class);
}

public void setConfigurationClass(Class configurationClass)
{
if (configurationClass == null
|| !AnnotationConfiguration.class
.isAssignableFrom(configurationClass))
{
throw new IllegalArgumentException(
"AnnotationSessionFactoryBean only supports AnnotationConfiguration or subclasses");
}
super.setConfigurationClass(configurationClass);
}

/**
* Specify annotated classes, for which mappings will be read from
* class-level JDK 1.5+ annotation metadata.
*
* @see org.hibernate.cfg.AnnotationConfiguration#addAnnotatedClass(Class)
*/
public void setAnnotatedClasses(Class[] annotatedClasses)
{
this.annotatedClasses = annotatedClasses;
}

/**
* Specify the names of annotated packages, for which package-level JDK 1.5+
* annotation metadata will be read.
*
* @see org.hibernate.cfg.AnnotationConfiguration#addPackage(String)
*/
public void setAnnotatedPackages(String[] annotatedPackages)
{
this.annotatedPackages = annotatedPackages;
}

/**
* Reads metadata from annotated classes and packages into the
* AnnotationConfiguration instance.
* <p>
* Calls <code>postProcessAnnotationConfiguration</code> afterwards, to give
* subclasses the chance to perform custom post-processing.
*
* @see #postProcessAnnotationConfiguration
*/
protected final void postProcessConfiguration(Configuration config)
throws HibernateException
{
AnnotationConfiguration annConfig = (AnnotationConfiguration) config;

if (this.annotatedClasses != null)
{
for (int i = 0; i < this.annotatedClasses.length; i++)
{
annConfig.addAnnotatedClass(this.annotatedClasses[i]);
}
}
if (this.annotatedPackages != null)
{
for (int i = 0; i < this.annotatedPackages.length; i++)
{
annConfig.addPackage(this.annotatedPackages[i]);
}
}
try
{
if (this.automaticAnnotatedPackages != null)
{
for (int i = 0; i < this.automaticAnnotatedPackages.length; i++)
{

List<String> classList = ResourceLocator.getClassesInPackage(
automaticAnnotatedPackages[i], new ArrayList<String>()
{
}, false);

for (String clazz : classList)
{
log.info("Found a Class: " + clazz);
Class thisClass = Class.forName(clazz);
if (thisClass.isAnnotationPresent(Hibernate.class))
{
log.debug("Adding Mapped Package - CLASS: " + clazz);
annConfig.addAnnotatedClass(thisClass);
addMetaData(thisClass);
}

}

}
}

}
catch (Exception e)
{

throw new HibernateException(e);
}

// Perform custom post-processing in subclasses.
postProcessAnnotationConfiguration(annConfig);
}

private static void addMetaData(Class clazz) throws Exception
{

EntityMetaData metaData = new EntityMetaDataImpl();

Method[] methods = clazz.getMethods();

for (int i = 0; i < methods.length; i++)
{

Method method = methods[i];
log.debug("Adding method: " + method.getName());
metaData.addMethod(method.getName(), method.getReturnType());
if (method.isAnnotationPresent(FriendlyName.class))
{
metaData.setFriendlyName(method.getName());
}

}

if (clazz.isAnnotationPresent(Table.class))
{

Table table = (Table) clazz.getAnnotation(Table.class);
metaData.setTableName(table.name());

}

if (clazz.isAnnotationPresent(Entity.class))
{

Entity entity = (Entity) clazz.getAnnotation(Entity.class);

if (entity.name() != null && !StringUtil.isEmpty(entity.name()))
metaData.setEntityName(entity.name());
else
metaData.setEntityName(clazz.getSimpleName());

}

if (clazz.isAnnotationPresent(Auditable.class))
metaData.setAuditable(true);

if (clazz.isAnnotationPresent(Cache.class))
metaData.setCache(true);

if (clazz.isAnnotationPresent(DefaultSort.class))
{

String[] fields = ((DefaultSort) clazz
.getAnnotation(DefaultSort.class)).fields();
SortDirection[] directions = ((DefaultSort) clazz
.getAnnotation(DefaultSort.class)).directions();

Set<ExtOrder> sorts = new LinkedHashSet<ExtOrder>();

for (int i = 0; i < fields.length; i++)
{

sorts.add(new ExtOrder(fields[i], directions[i], true));

}

metaData.setDefaultSort(sorts);

}

if (clazz.isAnnotationPresent(Proxy.class))
{

Proxy proxy = (Proxy) clazz.getAnnotation(Proxy.class);
metaData.setLazy(proxy.lazy());

}

log.debug("Adding meta data for Class: " + clazz);
SpringLoader.addEntityMetaData(clazz, metaData);
}

/**
* To be implemented by subclasses that want to to perform custom
* post-processing of the AnnotationConfiguration object after this
* FactoryBean performed its default initialization.
*
* @param config
* the current AnnotationConfiguration object
* @throws HibernateException
* in case of Hibernate initialization errors
*/
protected void postProcessAnnotationConfiguration(
AnnotationConfiguration config) throws HibernateException
{
}

public SessionFactory getCurrentSessionFactory()
{
return getSessionFactory();
}

public Map<String, EntityMetaData> getEntityMetaData()
{
return SpringLoader.getEntityMetaData();
}

}

The property that you want to pay attention to in the configuration file is automaticAnnotatedPackages. It does all the magic. My custom class will traverse the packages listed and look for the @Hibernate annotation. This is an annotation that you will have to create. You can name it anything, but you just have to look for it. You find it, map your classes in the class and you are off and running. No more having to list each class individually.

From http://www.jeviathon.com

Published at DZone with permission of its author, Chris Hardin.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Jacek Furmankiewicz replied on Fri, 2009/08/21 - 9:42am

Could you not just use the standard JPA @Entity annotation?

Rich Whitsell replied on Fri, 2009/08/21 - 11:38am

Spring's AnnotationSessionFactoryBean already supports this, though the feature isn't widely known.  You can define a property named "packagesToScan" with a list of package names containing annotated entity classes.

 The configuration XML would look something like this:

    <!-- Configure the session factory to scan the model packages for annotated classes. -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/>
        <property name="packagesToScan">
            <list>
                <value>com.company.persistence.model.package1</value>
                <value>com.company.persistence.model.package2</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <ref bean="hibernateProperties"/>
        </property>
    </bean>

(With the "dataSource" and "hibernateProperties" bean defined elsewhere.)

Stevi Deter replied on Fri, 2009/08/21 - 11:45am

Looks like you just rolled your own AnnotationSessionFactoryBean, which has a annotatedPackages property.

As for Eclipse, when you use refactor to rename your package,use "Update fully qualified names in non-Java text file" and restrict the filename patterns as needed (e.g., *context.xml) and you'll find Eclipse will update your spring config files as well. 

 

Jacek Furmankiewicz replied on Fri, 2009/08/21 - 2:04pm

Quick question: is there any way to plug into Hibernate's mapping process so that I don't have to define @Table and @Column on every property. In most cases both the DB and the Entity model follow clear naming conventions and the transformation from one name to another could be done in straight Java code. Would save me countless amount of annotations on each class...

Andy Leung replied on Fri, 2009/08/21 - 2:36pm

 

I have your solution and it won't cost you anything.

 

 It costs me to write an XML file that uses up my expensive hours...:P

 Useful article.

Alexander Ashitkin replied on Sat, 2009/08/22 - 12:55pm

Guys, have you ever work with idea? Just choose JPA facet>new Entity and forget about configs. If you need move/rename class idea will handle spring confgi transparency. Does the author is a guy who invent wheels?

Zoltan Magyar replied on Sun, 2009/08/23 - 5:29am in response to: Alexander Ashitkin

Maybe you accidentally haven't realized, but the author uses Hibernate, and not JPA. Probably he has a good reason to do that, since it gives way more flexibility, than JPA, at least until 2.0 will be announced for sure. Yes, maybe reinvented the wheel, but not beacuse of jpa, but because of AnnotationSessionFactoryBean.

Serdyn du Toit replied on Mon, 2009/08/24 - 10:31am

The packagesToScan attribute is a new feature of Spring 2.5.6 (if I'm not mistaken).  Also to include subpackages with it you can do something like the following

 <property name="packagesToScan" value="/your/core/package/" />

The "dot" notation for the package doesn't automatically include entities contained in subpackages.

Stevi Deter replied on Mon, 2009/08/24 - 12:31pm in response to: Jacek Furmankiewicz

If you follow the default naming strategy, then all properties of the @Entity will automatically considered persistent. javax.peristence annotations favor convention over configruation, so you only have to annotate the exceptions.

 This article on at onjava.com discusses this a bit.

 

Andy Jefferson replied on Tue, 2009/08/25 - 2:10am in response to: Jacek Furmankiewicz

DataNucleus (JDO/JPA) provides a plugin point so you can define your own naming conventions, hence avoiding the need to define such @Table/@Column namings. Hence you could just run that as the JPA impl pointing to your identifier naming plugin.

 --Andy (DataNucleus)

Alexander Ashitkin replied on Tue, 2009/09/01 - 4:06pm in response to: Zoltan Magyar

i just don't understand the problem. if you need support for refactoring - you have to use good ide, like intellij. it will refactor spring xml on java move\rename\deletion  as well. if you prefer to use packages instead of particular classes - use packagesToScan as was mentioned above. Also, I don't see any reason to use custom annotation @Hibernate. it's useless code and wasted time. don't repeat it.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.