Automatically Mapping Annotated Hibernate Classes With Spring
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.
- Login or register to post comments
- 15167 reads
- Printer-friendly version
(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
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:
(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
Andy Leung replied on Fri, 2009/08/21 - 2:36pm
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
Zoltan Magyar replied on Sun, 2009/08/23 - 5:29am
in response to: alexander.a
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
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
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: zoldorf