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テーブルはこんな感じ。

mysql> desc 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;

だとダメということに。それはつまりで外部キーを参照している場合、その外部キーのIDでクエリを実行しようとしてもダメということになる。

これは結構困った。かなりの時間をかけて問題を探っていたが、結果としてはうまくいった。結論を書くと、これが正解。

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を引っ張ってくるから。

やれやれ。。。