1.异常内容
我遇到的具体现象是,将工程发布到tomcat可以正常运行,但是通过junit跑测试,就会如下错误:
Caused by: java.lang.NoSuchMethodError: javax.persistence.Table.indexes()[Ljavax/persistence/Index; at org.hibernate.cfg.annotations.EntityBinder.processComplementaryTableDefinitions(EntityBinder.java:936) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:824) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3788) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3742) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1410) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850) ~[hibernate-entitymanager-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:843) ~[hibernate-entitymanager-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:397) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:842) ~[hibernate-entitymanager-4.3.5.Final.jar:4.3.5.Final] at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60) ~[spring-orm-4.1.1.RELEASE.jar:4.1.1.RELEASE] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-4.1.1.RELEASE.jar:4.1.1.RELEASE] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318) ~[spring-orm-4.1.1.RELEASE.jar:4.1.1.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1627) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1564) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE] ... 25 more
如果按照网上的一些解决办法,将@Table(name="t_user")改写为@Entity(name="t_user")这种形式,问题依然无法解决的,会出现如下的Exception:
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: User is not mapped at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:189) at org.hibernate.hql.internal.ast.tree.FromElementFactory.addFromElement(FromElementFactory.java:109) at org.hibernate.hql.internal.ast.tree.FromClause.addFromElement(FromClause.java:95) at org.hibernate.hql.internal.ast.HqlSqlWalker.createFromElement(HqlSqlWalker.java:331) at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3633) at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3522) at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:706) at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:562) at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:299) at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:247) at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:278) at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:206) ... 42 more
2.原因定位
顺着异常提示信息,打开hibernate-core-4.3.5.Final.jar:4.3.5.Final这个jar包(我用的反编译工具),一路追到这个类org.hibernate.cfg.AnnotationBinder,在bindClass方法中,可以发现在最后面连续调用了三次EntityBinder类的processComplementaryTableDefinitions方法,也就是异常信息中最后抛出Exception的方法。
public static void bindClass(XClass clazzToProcess, MapinheritanceStatePerClass, Mappings mappings) throws MappingException{ //此处省略N行代码 mappings.addClass(persistentClass); mappings.addSecondPass(new SecondaryTableSecondPass(entityBinder, propertyHolder, clazzToProcess)); entityBinder.processComplementaryTableDefinitions((org.hibernate.annotations.Table)clazzToProcess.getAnnotation(org.hibernate.annotations.Table.class)); entityBinder.processComplementaryTableDefinitions((Tables)clazzToProcess.getAnnotation(Tables.class)); entityBinder.processComplementaryTableDefinitions(tabAnn);}
再打开EntityBinder类,可以发现有两个同名的processComplementaryTableDefinitions方法,两个方法中都调用了table.indexes(),两个方法的参数虽然都是table,但是类型是不同的。一个是JPA api中的Table类,另一个是hibernate的Table类。
public void processComplementaryTableDefinitions(javax.persistence.Table table) { if (table == null) return; TableBinder.addIndexes(this.persistentClass.getTable(), table.indexes(), this.mappings);}
public void processComplementaryTableDefinitions(org.hibernate.annotations.Table table) { if (table == null) return; String appliedTable = table.appliesTo(); Iterator tables = this.persistentClass.getTableClosureIterator(); org.hibernate.mapping.Table hibTable = null; while (tables.hasNext()) { org.hibernate.mapping.Table pcTable = (org.hibernate.mapping.Table)tables.next(); if (pcTable.getQuotedName().equals(appliedTable)) { hibTable = pcTable; break; } hibTable = null; } if (hibTable == null) { for (Join join : this.secondaryTables.values()) { if (join.getTable().getQuotedName().equals(appliedTable)) { hibTable = join.getTable(); break; } } } if (hibTable == null) { throw new AnnotationException("@org.hibernate.annotations.Table references an unknown table: " + appliedTable); } if (!BinderHelper.isEmptyAnnotationValue(table.comment())) hibTable.setComment(table.comment()); TableBinder.addIndexes(hibTable, table.indexes(), this.mappings); }回头再看看异常信息,指向的是 javax.persistence.Table这个类的indexes方法。首先想到的是打开 hibernate-jpa-2.1-api-1.0.0.Final.jar这个jar包,找到 javax.persistence.Table,一探究竟。结果两个类都是有indexes这个方法的,不免让人挠头,莫非有鬼。
后来仔细一想,javax.persistence.Table是JPA标准api类,不一定就只有那个jar包里有啊,赶紧打开Java Build Path看了一下,发现了EclipseLink这玩意
这厮的jar包里果然有个没有indexes方法的Table类,再仔细看看jar包的名字"javax.persistence_2.0.4",原来这是JPA2.0的,而Table的indexes在JPA2.1中才有的,而junit在运行的时候,恰巧是先走了这个jar包中的类。
3.总结分析
Hibernate在进行注解处理的过程中,既处理了JPA标准的注解,也处理了hibernate特有的注解,hibernate4.3处理注解是基于JPA2.1版本的,所以,我推想,其他遇到这个exception的朋友,很有可能也是使用的jar包中有旧版本的Table类。不妨用JarSearch搜索下,就可以发现问题。