Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate eager loading (fetch all properties does not work)

Basically I want to eager-load properties. I have the following HQL query:

SELECT u.id AS id, u.name AS text, u AS obj FROM User AS u fetch all properties

I would expect this to execute one query only. Instead I got N+1 queries.

The code is the following:

Query q = mySession.createQuery(
    "SELECT u.id AS id, u.name AS text, u AS obj FROM User AS u fetch all properties")
    .setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);

for (Iterator i = q.iterate(); i.hasNext();) {
    Object line = i.next();
    System.out.println(line);
}

The output I get (with hibernate.show_sql set to true) is:

Hibernate: select user0_.id as col_0_0_, user0_.name as col_1_0_, user0_.id as col_2_0_ from user user0_
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_, user0_.location as location0_0_ from user user0_ where user0_.id=?
{id=1, obj=User@b6548 [id='1' name='John' ], text=John}
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_, user0_.location as location0_0_ from user user0_ where user0_.id=?
{id=2, obj=User@4865ce [id='2' name='Arnold' ], text=Arnold}

Ps: The situation is just the same without transformers.


Edit:

The file with the entity-mappings:

<hibernate-mapping>
    
    <class name="User" table="user">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
        <property name="location"/>
        <map name="customPrices" table="custprice">
            <key column="user"/>
            <map-key-many-to-many column="product" class="Product"/>
            <element column="price" type="double"/>
        </map>
    </class>
    
    <class name="Product" table="product">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
        <property name="listprice"/>
    </class>
    
</hibernate-mapping>

I tried adding lazy="false" to the class and to the individual properties. No difference.

My configuration file:

<hibernate-configuration>
<session-factory>
    <property name="connection.url">jdbc:mysql://192.168.0.203/hibtest</property>
    <property name="connection.username">hibtest</property>
    <property name="connection.password">hibb345</property>
    <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
    <property name="current_session_context_class">thread</property>
    <property name="hibernate.show_sql">true</property>
    
    <mapping resource="combined.hbm.xml" />
</session-factory> 
</hibernate-configuration>

Edit2:

Even the following code causes N+1 query. Although I only fetch the ID field, which according to documentation should not cause objects to load.

for (Iterator i = q.iterate(); i.hasNext();) {
    Object line = i.next();
    User u = (User)((Map)line).get("obj");
    System.out.println(u.getId());
}
like image 471
vbence Avatar asked Feb 24 '23 11:02

vbence


1 Answers

The problem was with .iterate(). According to Hibernate API docs:

Entities returned as results are initialized on demand. The first SQL query returns identifiers only.

This is a special function to be used when we expect the resulting objects to be already cached. They will be (lazy) loaded when accessed.

So for general use, to get an iterator on a result of a query you should use .list().iterate().

Thanks for Eran Harel for the help.

like image 133
vbence Avatar answered Mar 29 '23 20:03

vbence