hibernate: many-to-oneの外部キーでデータを取ろうとしたら、IllegalArgumentException occurred calling getter of と出る。
1年ぶりにHibernateをつかったらつまらないところでハマった。バージョンはHibernate 3.2.5。
やりたいことは単純に、ユーザーであるadministratorのIDに紐づいているデータをjava.util.List
select * from history where administrator_id = 1;
で出せるようなものだ。ちなみにそのhistoryテーブルはこんな感じ。
Field | Type | Null | Key | Default | Extra |
id | int(11) | NO | PRI | NULL | auto_increment |
administrator_id | int(11) | NO | MUL | NULL | |
created | datetime | NO | NULL |
そのhistoryを構成する、history.hbm.xmlは以下のとおり。
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="data.History" table="history" lazy="false"> <meta attribute="class-description"> Models an History in the database. </meta> <meta attribute="class-code"> private static final long serialVersionUID = 1; </meta> <id name="id" type="int" column="id"> <meta attribute="use-in-tostring">true</meta> <meta attribute="scope-set">protected</meta> <generator class="native" /> </id> <many-to-one name="administrator" class="data.Administrator" cascade="all" outer-join="auto" update="true" insert="true" access="property" column="administrator_id" not-null="true" /> <property name="created" type="timestamp" column="created" not-null="true"/> </class> </hibernate-mapping>
で、メソッドは以下の通り。
public static List<History> getHistoriesForUser (Session session, int administratorId) { List<History> histories = (List<History>)session.createCriteria(History.class) .add( Restrictions.eq("administrator_id", administratorId)) .list(); return histories; }
が、これがうまくいかない。このメソッドを呼ぶと、こうなる。
org.hibernate.QueryException: could not resolve property: administrator_id of: data.History at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:44) at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractPropertyMapping.java:38) at org.hibernate.persister.entity.AbstractEntityPersister.getSubclassPropertyTableNumber(AbstractEntityPersister.java:1375) at org.hibernate.persister.entity.BasicEntityPropertyMapping.toColumns(BasicEntityPropertyMapping.java:31) at org.hibernate.persister.entity.AbstractEntityPersister.toColumns(AbstractEntityPersister.java:1350) at org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumns(CriteriaQueryTranslator.java:434) at org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumnsUsingProjection(CriteriaQueryTranslator.java:394) at org.hibernate.criterion.SimpleExpression.toSqlString(SimpleExpression.java:45) at org.hibernate.loader.criteria.CriteriaQueryTranslator.getWhereCondition(CriteriaQueryTranslator.java:334) at org.hibernate.loader.criteria.CriteriaJoinWalker.(CriteriaJoinWalker.java:82) at org.hibernate.loader.criteria.CriteriaLoader.(CriteriaLoader.java:67) at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1550) at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:283) at data.Queries.getHistoriesForUser(Unknown Source) at adminsite.MainMenuServlet$1.act(Unknown Source) at adminsite.MainMenuServlet$1.act(Unknown Source) at adminsite.util.DbActionProcessor.dbAction(Unknown Source) at adminsite.MainMenuServlet.getHistoriesForUser(Unknown Source) at adminsite.MainMenuServlet.doGet(Unknown Source) at javax.servlet.http.HttpServlet.service(HttpServlet.java:690) at javax.servlet.http.HttpServlet.service(HttpServlet.java:803) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:263) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:584) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Thread.java:619)
クエリから発想すると、'where administrator_id = 1'となるのでつい
Restrictions.eq("administrator_id", administratorId)
と書いてしまっていた。しかし実際にはhbmファイルのname属性を使うので、administratorと入れないとまずいだろう。
そこで先述のメソッドをこんな風に書き換えてみた。
public static List<History> getHistoriesForUser (Session session, int administratorId) { List<History> histories = (List<History>)session.createCriteria(History.class) .add( Restrictions.eq("administrator", administratorId)) .list(); return histories; }
すると今度は、
org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of data.Administrator.id at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:171) at org.hibernate.tuple.entity.AbstractEntityTuplizer.getIdentifier(AbstractEntityTuplizer.java:183) at org.hibernate.persister.entity.AbstractEntityPersister.getIdentifier(AbstractEntityPersister.java:3591) at org.hibernate.persister.entity.AbstractEntityPersister.isTransient(AbstractEntityPersister.java:3307) at org.hibernate.engine.ForeignKeys.isTransient(ForeignKeys.java:181) at org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:218) at org.hibernate.type.EntityType.getIdentifier(EntityType.java:397) at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:87) at org.hibernate.loader.Loader.bindPositionalParameters(Loader.java:1707) at org.hibernate.loader.Loader.bindParameterValues(Loader.java:1678) at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1563) at org.hibernate.loader.Loader.doQuery(Loader.java:673) at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236) at org.hibernate.loader.Loader.doList(Loader.java:2220) at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2104) at org.hibernate.loader.Loader.list(Loader.java:2099) at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:94) at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1569) at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:283) at data.Queries.getHistoriesForUser(Unknown Source) at adminsite.MainMenuServlet$1.act(Unknown Source) at adminsite.MainMenuServlet$1.act(Unknown Source) at adminsite.util.DbActionProcessor.dbAction(Unknown Source) at adminsite.MainMenuServlet.getHistoriesForUser(Unknown Source) at adminsite.MainMenuServlet.doGet(Unknown Source) at javax.servlet.http.HttpServlet.service(HttpServlet.java:690) at javax.servlet.http.HttpServlet.service(HttpServlet.java:803) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:263) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:584) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Thread.java:619) Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:145) ... 38 more
意味不明・・・。
ためしに、こんな風にして別のフィールドからWhere文をかけてみた。
Restrictions.eq("id", administratorId)
つまり、
select * from history where id=1;
となるわけだ。
するとエラーが出なかった!
そうするとSQLで
select * from history where id=1;
だと問題なく、
select * from history where administrator_id=1;
だとダメということに。それはつまり
これは結構困った。かなりの時間をかけて問題を探っていたが、結果としてはうまくいった。結論を書くと、これが正解。
public static List<History> getHistoriesForUser (Session session, int administratorId) { List<History> histories = (List<History>)session.createCriteria(History.class) .add( Restrictions.eq("administrator.id", administratorId)) .list(); return histories; }
administrator.id となるのだ。つまりhibernateでは外部キーを参照している場合、同じテーブルにその_IDみたいなフィールドがあっても、そのフィールドは参照先テーブルに属する、と考えるということなのだと思う。
HISTORY.administrator_id * - 1 ADMINISTRATOR.id
のような関係の場合、historyテーブルのadministrator_idからクエリをかけるわけではないのだ。
まとめるとこうなった。
× Restrictions.eq("administrator_id", administratorId)
なぜならこれは、テーブルとして考えると正しいが、データ(オブジェクト)としてはadministrator_idなるものは存在しないから。
× Restrictions.eq("administrator", administratorId)
なぜなら、Administratorの本体オブジェクトでWhere文を作ろうとしているから。
◎ Restrictions.eq("administrator.id", administratorId)
正解。なぜならAdministrator.getId()からIDを引っ張ってくるから。
やれやれ。。。