基于注解与自动扫描的某些实体没被映射的原因之一
在尝试修改基于SpringSide 3.1.3的mini-web过程中,又遇到了一个奇怪的问题。一个hql如下:
StringBuilder hql = new StringBuilder();
hql.append("SELECT new ");
hql.append(CommentBean.class.getName());
hql.append(" (o,u.name,u.image) ");
hql.append(" FROM ");
hql.append(Comment.class.getSimpleName()).append(" o, ");
hql.append(User.class.getSimpleName()).append(" u ");
hql.append(" WHERE o.userId=u.id AND o.infoId=? ");
hql.append(" ORDER BY o.commentTime DESC");
Comment这个实体类的全限定名是:org.shoopman.entity.Comment
但在运行时却报错:
Comment is not mapped [SELECT new org.shoopman.service.dto.CommentBean (o,u.name,u.image) FROM Comment o, org.shoopman.entity.user.User u WHERE o.userId=u.id AND o.infoId=? ORDER BY o.commentTime DESC]
org.hibernate.hql.ast.QuerySyntaxException: Comment is not mapped [SELECT new org.shoopman.service.dto.CommentBean (o,u.name,u.image) FROM Comment o, org.shoopman.entity.user.User u WHERE o.userId=u.id AND o.infoId=? ORDER BY o.commentTime DESC]
at org.hibernate.hql.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:181)
at org.hibernate.hql.ast.tree.FromElementFactory.addFromElement(FromElementFactory.java:110)
at org.hibernate.hql.ast.tree.FromClause.addFromElement(FromClause.java:93)
at org.hibernate.hql.ast.HqlSqlWalker.createFromElement(HqlSqlWalker.java:277)
而其他实体的查询却没有问题。先看一下spring的applicationContext.xml关于Hibernate的配置:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="namingStrategy">
<bean class="org.hibernate.cfg.ImprovedNamingStrategy" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
</prop>
<prop key="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>
</props>
</property>
<property name="packagesToScan" value="org.shoopman.entity.*" />
</bean>
问题就在这个自动描述packagesToScan的配置上。如上的配置,Spring只会扫描到org.shoopman.entity这个包下的子包里的实体类,而直接放在这个包下的实体却没有描述。
无奈,只好在新建一个org.shoopman.entity.common包,把Comment移到此包下。到此,问题解决。
题外话:看上面的hql生成过程,在使用User这个实体类时,我用的是User.class.getSimpleName(),为什么Hibernate在生成hql却是全限定名。当把Comment移到common包下,控制台里打印出来的hql又是SimpleName。这是Hibernate的一个容错处理吗?
Spring @Autowired 的使用问题
最近小小的在研究一下SpringSide3的代码,并对照着它的mini-web示例项目开始了测试。开始还是比较顺利的, mini-web在tomcat6 + mysql5里跑起来了,测试用例也没有问题,于是开始动手改造。
但是改了一下,再运行UserManager的测试用例,却报了以下错误:
[main] ERROR org.springframework.test.context.TestContextManager – Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@1904e0d] to prepare test instance [testCreate(cn.cu.manager.UserManagerTest)]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘cn.cu.manager.UserManagerTest’: Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private cn.cu.manager.user.UserManager cn.cu.manager.UserManagerTest.userManager; nested exception is java.lang.IllegalArgumentException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:243)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:959)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:329)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:255)
at org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests.runBare(AbstractJUnit38SpringContextTests.java:183)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:120)
at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private cn.cu.manager.user.UserManager cn.cu.manager.UserManagerTest.userManager; nested exception is java.lang.IllegalArgumentException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:435)
at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:105)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:240)
… 16 more
Caused by: java.lang.IllegalArgumentException
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
at java.lang.reflect.Field.set(Field.java:656)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:431)
… 18 more
只得到网上找答案,未果,不过还是找到了点有价值的东西:
不使用自动装配。必须通过ref元素指定依赖,这是默认设置。由于显式指定协作者可以使配置更灵活、更清晰,因此对于较大的部署配置,推荐采用该设置。而且在某种程度上,它也是系统架构的一种文档形式。
byName 根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。例如,在bean定义中将autowire设置为by name,而该bean包含master属性(同时提供setMaster(..)方法),Spring就会查找名为master的bean定义,并用它来装配给master属性。
byType 如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,那么将会抛出异常,并指出不能使用byType方式进行自动装配。若没有找到相匹配的bean,则什么事都不发生,属性也不会被设置。如果你不希望这样,那么可以通过设置dependency-check="objects"让Spring抛出异常。
constructor 与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
autodetect 通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式。
———————————————————————————-
理解自动装配的优缺点是很重要的。其中优点包括:
自动装配能显著减少配置的数量。不过,采用bean模板也可以达到同样的目的。
自动装配可以使配置与java代码同步更新。例如,如果你需要给一个java类增加一个依赖,那么该依赖将被自动实现而不需要修改配置。因此强烈推荐在开发过程中采用自动装配,而在系统趋于稳定的时候改为显式装配的方式。
自动装配的一些缺点:
尽管自动装配比显式装配更神奇,但是,正如上面所提到的,Spring会尽量避免在装配不明确的时候进行猜测,因为装配不明确可能出现难以预料的结果,而且Spring所管理的对象之间的关联关系也不再能清晰的进行文档化。
对于那些根据Spring配置文件生成文档的工具来说,自动装配将会使这些工具没法生成依赖信息。
如果采用by type方式自动装配,那么容器中类型与自动装配bean的属性或者构造函数参数类型一致的bean只能有一个,如果配置可能存在多个这样的bean,那么就要考虑采用显式装配了。
尽管使用autowire没有对错之分,但是能在一个项目中保持一定程度的一致性是最好的做法。例如,通常情况下如果没有使用自动装配,那么仅自动装配一个或两个bean定义可能会引起开发者的混淆。
想了想自己对mini-web的改造经过,觉得可能问题出在一个接口上面:我把EntityManager提出了个接口IEntityManager