3
votes

My Hibernate HQL query seems to be returning stale data.

I have a simple java class called Account, instances of which map onto a single database table with two varchar columns, username and surname.

If I run a HQL query such as:

List<?> accountList = session.createQuery("from Account where surname is null").list();

I get back a List of Account objects, as expected (some of the rows in the table indeed have null surname fields).

I then set the surname on the returned objects to some non-null value:

Iterator<?> accountIter = accountList.iterator();
while (accountIter.hasNext()) {
  Account account = (Account) accountIter.next();
  log("Adding surname of Jones to : " + account.getUsername());
  account.setSurname("Jones");
}

At this point, if I ran the HQL query again, I would expect to get back an empty List (as all surnames should be non-null), but instead I get back the same objects as when I ran the query the first time. This is not what I expected.

Quoting from the Hibernate docs:

http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/

"there are absolutely no guarantees about when the Session executes the JDBC calls, only the order in which they are executed. However, Hibernate does guarantee that the Query.list(..) will never return stale or incorrect data."

This seems contrary to the behaviour of my code. Looking at the program output in Listing 4 below, the SQL Update statement happens after all the select statements, so the last select returns incorrect data. Can anyone shed light on what is going on, or what I am doing wrong?

If I surround the setting of the surnames with a transaction, and perform a session.saveOrUpdate(account) it all works, but I thought that this was not required.

I would like my code to only deal with the domain classes if possible, and be free of persistence code as much as possible.

I am using Hibernate 4.1.8.Final, with Java 1.6

My full code listing is below:

Listing 1: Main.java:

package uk.ac.york.cserv.hibernatetest;

import java.util.Iterator;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class Main {

    private static SessionFactory sf;
    Session session;

    public static void main(String[] args) {
        Main main = new Main();
        main.doExample();
    }

    public Main() {
        sf = new Configuration()
            .configure("hibernate-ora.cfg.xml")
            .buildSessionFactory();
        session = sf.openSession();
    }

    public void closeSession() {
        session.flush();
        session.close();
    }

    public List<?> getAccountList() {
        return session.createQuery("from Account where surname is null").list();
    }

    public void printAccountList(List<?> accountList) {
        Iterator<?> accountIter = accountList.iterator();
        while (accountIter.hasNext()) {
            System.out.println(accountIter.next());
        }
    }

    public void log(String msg) {
        System.out.println(msg);
    }

    public void doExample() {

        log("Print all accounts with null surnames...");
        printAccountList(getAccountList());

        log("Adding surnames to accounts that have null surnames...");
        //session.beginTransaction();
        Iterator<?> accountIter = getAccountList().iterator();
        while (accountIter.hasNext()) {
            Account account = (Account) accountIter.next();
            log("Adding surname of Jones to : " + account.getUsername());
            account.setSurname("Jones");
            //session.saveOrUpdate(account);
        }
        //session.getTransaction().commit();

        log("Again print all accounts that have null surnames (should be none)...");
        printAccountList(getAccountList());

        closeSession();
    }
}

Listing 2: Account.java:

package uk.ac.york.cserv.hibernatetest;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="ACCOUNTS")
public class Account {

    @Id
    @Column(name = "USERNAME", unique = true, nullable = false)
    private String username;

    @Column(name = "SURNAME")
    private String surname;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Account [username=" + username + ", surname=" + surname + "]";
    }
}

Listing 3: Hibernate-ora.cfg.xml:

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="connection.url">jdbc:oracle:thin:@testhost:1521:test</property>
        <property name="connection.username">testschema</property>
        <property name="connection.password">testpassword</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.Oracle10gDialect</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Names of the annotated classes -->
        <mapping class="uk.ac.york.cserv.hibernatetest.Account"/>

    </session-factory>

</hibernate-configuration>

Listing 4: Output of the program:

Print all accounts with null surnames...
Hibernate: select account0_.USERNAME as USERNAME0_, account0_.SURNAME as SURNAME0_ from ACCOUNTS account0_ where account0_.SURNAME is null
Account [username=user2, surname=null]
Adding surnames to accounts that have null surnames...
Hibernate: select account0_.USERNAME as USERNAME0_, account0_.SURNAME as SURNAME0_ from ACCOUNTS account0_ where account0_.SURNAME is null
Adding surname of Jones to : user2
Again print all accounts that have null surnames (should be none)...
Hibernate: select account0_.USERNAME as USERNAME0_, account0_.SURNAME as SURNAME0_ from ACCOUNTS account0_ where account0_.SURNAME is null
Account [username=user2, surname=Jones]
Hibernate: update ACCOUNTS set SURNAME=? where USERNAME=?
1

1 Answers

2
votes

There is nothing strange about the Hibernate behavior you're describing

"At this point, if I ran the HQL query again, I would expect to get back an empty List (as all surnames should be non-null), but instead I get back the same objects as when I ran the query the first time. This is not what I expected."

At that point, when you run the HQL query again, you haven't done anything concerning the database so far. This is the reason why you're obtaining what you call 'stale' data but it's in fact the most current version of what is still unmodified in the table

If you issue the saveOrUpdate command and close the transaction the changes you have done in your Java class are persisted to database so that the new HQL query executions show the updated data

I think you're misunderstanding the way Hibernate works in this use case. Precisely because "Hibernate does guarantee that the Query.list(..) will never return stale or incorrect data." you see an updated version of the data coming from the database, from the database point of view your changes in your Java class are the 'stale' ones and are replaced by new "fresh" real data coming from the original still unmodified source