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=?
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
Related
I am using Hibernate 2.1 on a Wildfly (JBoss) 10 to fetch an entity from the Database. Here is the model of the entity:
#Entity
#Table(name = "account")
#NamedQuery(name = "Account.findAll", query = "SELECT a FROM Account a")
public class Account implements Serializable {
private int id;
private double balance;
private double bonus;
#Id
public int getId() {
return this.id;
}
// Rest of the fields/setters/getters ommited
}
I have implemented a REST API call that updates the balance of a given account. The account is first retrieved from the database, validated, then the balance is updated through a separate bean-managed transaction in MySQL. After this completes I am asked to do some other operation on the account, on which I need the (new) updated balance. Find code attached below:
#Stateless
#Path("/financial")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class FinancialApi {
#Inject private AccountBean accountBean;
#Inject private AccountBalanceBean accountBalanceBean;
#POST
#Path("/balance/add")
public Response addBalance(TransactionProperties properties) {
Account account;
try {
account = accountBean.get(properties.getAccountId());
validateAccount(account);
} catch (ValidationException ex) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
// Update balance
accountBalanceBean.updateBalance(account.getId(), properties.getAmount());
// Refresh from DB to retrieve updated balance
accountBean.refresh(account);
// Do stuff with refreshed account data here
return Response.ok().build();
}
}
The "DAO" EJB handling the account
#Stateless
#LocalBean
public class AccountEJBean {
#PersistenceContext(unitName = "my-unit-name")
private EntityManager em;
public Account get(int id) {
try {
return (Account) em.createQuery("SELECT a FROM Account a WHERE a.id = :id")
.setParameter("id", id)
.setMaxResults(1)
.getSingleResult();
} catch (NoResultException ex) {
return null;
}
}
public void refresh(Account account) {
em.refresh(account);
}
}
and a (severely reduced) sample of the EJB that uses bean-managed transactions to update account balance:
#Stateless
#LocalBean
#TransactionManagement(TransactionManagementType.BEAN)
public class AccountBalanceEJBean {
#PersistenceContext(unitName = "my-unit-name")
private EntityManager em;
#Resource
private UserTransaction transaction;
public boolean updateBalance(int id, float price) {
try {
transaction.begin();
getUpdateQuery(id, price).executeUpdate();
transaction.commit();
} catch (Exception ex) {
transaction.rolloback();
}
}
}
and here is my persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence">
<persistence-unit name="my-unit-name" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/jboss/datasources/SQLDatasource</jta-data-source>
<class>mypackage.Account</class>
<properties>
<property name="hibernate.generate_statistics" value="false"/>
<property name="hibernate.connection.useUnicode" value="true"/>
<property name="hibernate.connection.characterEncoding" value="UTF-8"/>
</properties>
</persistence-unit>
</persistence>
This works perfectly fine until the part where I am trying to refresh account data from the database. At this point the account is refreshed with no issue on every case I have tried but the data of the account do not get updated. I am still retrieving the "stale" data that the account had when retrieved initially. After looking around for some time I figured it may be related to Hibernate caching mechanism. I have tried the following:
Detaching the entity on the get() method from the EntityManager and replacing refresh() with get()
Calling em.clear() and then retrieving the entity with a get() call again.
Attempting to clear the cache using em.getEntityManagerFactory().get().evictAll()
Setting hints "javax.persistence.cache.retrieveMode" and "javax.persistence.cache.storeMode" to BYPASS on the query of the get() method.
None of those seems to be working. I have tried updating the entity in my code and setting random values and every time the entity is refreshed in the initial retrieval state (not the one that is in the database, even if I alter different fields than balance not mentioned here).
From my understanding second-level cache is disabled by default on Hibernate and as far as I can tell that's not the case here. With this in my mind I move to the first-level cache but I can't seem to understand how to retrieve a Session (or if a session even exists here) from the entity manager in order to clear that cache which seems to be causing the problem. As a side note I am not sure of what's the point of having a first-level cache that doesn't get overriden by refresh() method and how this works in a distributed environment.
If that helps the Instance of the EntityManager that is injected is an instance of class org.jboss.as.jpa.container.TransactionScopedEntityManager.
Any ideas?
UPDATE: I have replicated the get() code in a second bean, let's call it AccountBean2 that's also injected into the API class. It seems to be working now. I am pretty confident it's a caching issue at this point but I am still not sure how to correct the initial issue so the question remains.
i'm using JPA repository to save simple data objects to the database. To avoid duplicates i created a unique constraint on multiple fields. If now a duplicate according to the unique fields/constraint should be saved i want to catch the exception, log the object and the application should proceed and saves the next object. But here i always get this exception: "org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)".
In general i understand what hibernate is doing, but how i can revert the session or start a new session to proceed with saving of the next data objects. Please have a look to the code below:
PeopleDBO.java
#Entity
#Data
#Table(
name = "PEOPLE",
uniqueConstraints = {#UniqueConstraint(columnNames = {"firstname", "lastname"}})
public class PeopleDBO {
public PeopleDBO(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
}
The Test:
public void should_save_people_and_ignore_constraint_violation(){
final List<PeopleDBO> peopleList = Arrays.asList(
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Paul","Smith")
);
peopleList.forEach(p -> {
try {
peopleRepository.save(p);
} catch (DataIntegrityViolationException e) {
log.error("Could not save due to constraint violation: {}",p);
}
}
Assertions.assertThat(peopleRepository.count()).isEqualTo(2);
}
The problem is, that with saving of the second people the unique constraint gets violated. The error log happens, and with the next call of peopleRepository.save() the mentioned exception above is thrown:
"org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)"
How i can avoid this behaviour? How i can clean the session or start a new session?
Thanks a lot in advance
d.
--------- Edit / new idea ------
I just tried some things and have seen that i could implement a PeopleRepositoryImpl, like this:
#Service
public class PeopleRepositoryImpl {
final private PeopleRepository peopleRepository;
public PeopleRepositoryImpl(PeopleRepository peopleRepository) {
this.peopleRepository = peopleRepository;
}
#Transactional
public PeopleDBO save(PeopleDBO people){
return peopleRepository.save(people);
}
}
This is working pretty fine in my tests. ... what do you think?
One single transaction
The reason is that all inserts occur in one transaction. As this transaction is atomic, it either succeeds entirely or fails, there is nothing in-between.
The most clean solution is to check if a People exists before trying to insert it:
public interface PeopleRespository {
boolean existsByLastnameAndFirstname(String lastname, String firstname);
}
and then:
if (!peopleRepository.existsByLastnameAndFirstname(p.getLastname, p.getFirstname)) {
peopleRepository.save(p);
}
One transaction per people
An alternative is indeed to start a new transaction for each person. But I am not sure it will be more efficient, because there is an extra cost to create transaction.
I am totally new to JPA and try to get at it with the tools provided from my University. So what I am trying to do is to set up a database carsdb with one table car and read the table from my main with a JPQL Query.
What I did so far:
I created a user carsdbuser with password carsdbpw, created a postgres database carsdb, which is owned by carsdbuser, added a table car and inserted a few columns.
I created a new Intellij project with JPA.
I then added the postgresql jdbc drivers (I think... the file was given to us by the university) postgresql-42.2.1.jar
as well as the oracle provider eclipslink.jar.
I then modified the persistence.xml to look like this:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="carsdb" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>Car</class>
<properties>
<property name="eclipselink.jdbc.url" value="jdbc:postgresql://localhost:5432/carsdb"/>
<property name="eclipselink.jdbc.driver" value="org.postgresql.Driver"/>
<property name="eclipselink.jdbc.user" value="carsdbuser"/>
<property name="eclipselink.jdbc.password" value="carsdbpw"/>
<property name="eclipselink.target-database" value="PostgreSQL"/>
<property name="eclipselink.logging.level" value="ALL"/>
</properties>
</persistence-unit>
</persistence>
I created the Class Car.java:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "car")
public class Car {
#Id
#GeneratedValue(generator = "incrementor")
private int Id;
public int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
private String Name;
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
#Override
public String toString() {
return "Car " + Id + ": Name: " + Name;
}
}
as well as the Main:
import javax.persistence.*;
import java.util.List;
public class Main {
public static void main(String[] args){
var factory = Persistence.createEntityManagerFactory("carsdb");
EntityManager em = factory.createEntityManager();
Query query = em.createQuery("select a from Car a");
List<Car> list = query.getResultList();
for (Car c : list) {
System.out.println(c);
}
}
}
Given the tutorials I am following this looks good to me, however when I run the program I get the following Error Message:
Exception in thread "main" java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
Exception Description: Problem compiling [select a from Car a].
[14, 17] The abstract schema type 'Car' is unknown.
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1743)
at Main.main(Main.java:9)
Caused by: Exception [EclipseLink-0] (Eclipse Persistence Services - 2.7.1.v20171221-bd47e8f): org.eclipse.persistence.exceptions.JPQLException
Exception Description: Problem compiling [select a from Car a].
[14, 17] The abstract schema type 'Car' is unknown.
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.buildException(HermesParser.java:155)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.validate(HermesParser.java:347)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.populateQueryImp(HermesParser.java:278)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.buildQuery(HermesParser.java:163)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:140)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:116)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:102)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:86)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1741)
... 1 more
I tried to find some solutions with the help of some classmates and google, but did not find any solution that helped.
I added the Database in Intellij to make sure I got the right url. The connection test works properly and I also find my car table in the Intellij database view.
The following Question discusses a similar Issue:
Error on compiling query: The abstract schema type 'entity' is unknown
I do however have Car in the select statement which is the case sensitive name of the entity, so I cant see how it is related to my problem.
I have added this in my application context file
<!-- Added to encrypt user identification fields using jasypt -->
<bean id="stringEncryptor" class="org.jasypt.encryption.pbe.StandardPBEStringEncryptor" lazy-init="false">
<property name="algorithm" value="PBEWithMD5AndDES" />
<property name="password" value="contactKey" />
</bean>
<bean id="hibernateEncryptor" class="org.jasypt.hibernate.encryptor.HibernatePBEStringEncryptor" lazy-init="false">
<!-- This property value must match "encryptorRegisteredName" used when defining hibernate user types -->
<property name="registeredName" value="jasyptHibernateEncryptor" />
<property name="encryptor" ref="stringEncryptor" />
</bean>`
This below coded added in hibernate mapping file
`<typedef name="encryptedString" class="org.jasypt.hibernate.type.EncryptedStringType">
<param name="encryptorRegisteredName">jasyptHibernateEncryptor</param>
</typedef>
We are using spring with Hibernate in to my application,but we want to implenting jasyptHibernateEncryptorin in to my application.
It's working fine when storing a new entry into database table and fetching the same entry, but problem here its how to encrypt my old data.
you create a new app that connects to the database, fetches all the existing rows and updates them one by one after encrypting the fields with your encryptor. After this update is done you can use the new typedef to handle these encrypted fields.
Ok, to elaborate more:
currently you mapped your entities/classes to a database with proterties NOT encrypted which looks something like this:
#Entity
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String name;
}
If you plan to swith to an encrypted type (jasypt) you need to first encrypt all the current
values in the database with a code that looks like this:
public class Exec {
public static void main(String[] args) {
SessionFactory sf = HibernateUtil.getSessionFactory(true);
Session session = null;
try {
session = sf.openSession();
//configure the jasypt string encryptor - different type use different encryptors
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setAlgorithm("PBEWithMD5AndDES");
encryptor.setPassword("123456");
encryptor.setKeyObtentionIterations(1000);
encryptor.initialize();
// get all unencrypted data from db and encrypt them - here just the name property of the Person is encrypted.
session.beginTransaction();
List<Person> persons = session.createQuery("select p from Person p").list();
for(Person pers : persons){
pers.setName(encryptor.encrypt(pers.getName()));
session.save(pers);
}
session.getTransaction().commit();
} catch (Exception ex) {
try {
ex.printStackTrace();
session.getTransaction().rollback();
} catch (Exception ex2) {
ex2.printStackTrace();
}
} finally {
session.close();
HibernateUtil.shutdown();
}
}
}
After you encrypted the values you need, switch the Person entity to use the encrypted type like this:
#org.hibernate.annotations.TypeDefs({
#org.hibernate.annotations.TypeDef(name="EncryptedString",
typeClass=EncryptedStringType.class,
parameters={#Parameter(name="algorithm",value="PBEWithMD5AndDES"),#Parameter(name="password",value="123456"),#Parameter(name="keyObtentionIterations",value="1000")})
})
#Entity
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Type(type="EncryptedString")
private String name;
public long getId() {
return id;
}
// ... getters and setters
}
Make sure that the parameters of the encryptors are the same in the definistion and in the code:
same algorithm, same password, same key obtenation iteration parameter.
After that you can use the Person entity as usual since the encryption is transparent to the java persistence code you use.
A working example of this code you can checkout from svn with this command:
svn checkout http://hibernate-jasypt-database-encryption.googlecode.com/svn/trunk/ hibernate-jasypt-database-encryption-read-only
Good luck !
I have one domain object that needs to be indexed by Hibernate Search. When I do a FullTextQuery on this object on my DEV machine, I get the expected results. I then deploy the app to a WAR and explode it to my PROD server (a VPS). When I perform the same "search" on my PROD machine, I don't get the expected results at all (it seems like some results are missing).
I've run LUKE to ensure that everything was properly indexed, and it appears that everything is where it should be... I'm new to Hibernate Search, so any help would be appreciated.
Here's my domain Object:
package com.chatter.domain;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.solr.analysis.LowerCaseFilterFactory;
import org.apache.solr.analysis.SnowballPorterFilterFactory;
import org.apache.solr.analysis.StandardTokenizerFactory;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Parameter;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TokenFilterDef;
import org.hibernate.search.annotations.TokenizerDef;
#Entity
#Table(name="faq")
#Indexed()
#AnalyzerDef(name = "customanalyzer",
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = {
#Parameter(name = "language", value = "English")
})
})
public class CustomerFaq implements Comparable<CustomerFaq>
{
private Long id;
#IndexedEmbedded
private Customer customer;
#Field(index=Index.TOKENIZED, store=Store.NO)
private String question;
#Field(index=Index.TOKENIZED, store=Store.NO)
private String answer;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
#ManyToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name="customer_id")
public Customer getCustomer()
{
return customer;
}
public void setCustomer(Customer customer)
{
this.customer = customer;
}
#Column(name="question", length=1500)
public String getQuestion()
{
return question;
}
public void setQuestion(String question)
{
this.question = question;
}
#Column(name="answer", length=1500)
public String getAnswer()
{
return answer;
}
public void setAnswer(String answer)
{
this.answer = answer;
}
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
CustomerFaq other = (CustomerFaq) obj;
if (id == null)
{
if (other.id != null) return false;
} else if (!id.equals(other.id)) return false;
return true;
}
#Override
public int compareTo(CustomerFaq o)
{
if (this.getCustomer().equals(o.getCustomer()))
{
return this.getId().compareTo(o.getId());
}
else
{
return this.getCustomer().getId().compareTo(o.getCustomer().getId());
}
}
}
Here's a snippet of my Customer domain object:
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Store;
import javax.persistence.Entity;
// ... other imports
#Entity
public class Customer
{
#Field(index=Index.TOKENIZED, store=Store.YES)
private Long id;
// ... other instance vars
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
And my persistence.xml:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<!-- value="create" to build a new database on each run; value="update" to modify an existing database; value="create-drop" means the same as "create" but also drops tables when Hibernate closes; value="validate" makes no changes to the database -->
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
<property name="hibernate.connection.charSet" value="UTF-8"/>
<!-- Hibernate Search configuration -->
<property name="hibernate.search.default.directory_provider"
value="filesystem" />
<property name="hibernate.search.default.indexBase" value="C:/lucene/indexes" />
</properties>
</persistence-unit>
</persistence>
And finally, here's the query that's being used in a DAO:
public List<CustomerFaq> searchFaqs(String question, Customer customer)
{
FullTextSession fullTextSession = Search.getFullTextSession(sessionFactory.getCurrentSession());
QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(CustomerFaq.class).get();
org.apache.lucene.search.Query luceneQuery = queryBuilder.keyword().onFields("question", "answer").matching(question).createQuery();
org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery(luceneQuery, CustomerFaq.class);
List<CustomerFaq> matchingQuestionsList = fullTextQuery.list();
log.debug("Found " + matchingQuestionsList.size() + " matching questions");
List<CustomerFaq> list = new ArrayList<CustomerFaq>();
for (CustomerFaq customerFaq : matchingQuestionsList)
{
log.debug("Comparing " + customerFaq.getCustomer() + " to " + customer + " -> " + customerFaq.getCustomer().equals(customer));
log.debug("Does list already contain this customer FAQ? " + list.contains(customerFaq));
if (customerFaq.getCustomer().equals(customer) && !list.contains(customerFaq))
{
list.add(customerFaq);
}
}
log.debug("Returning " + list.size() + " matching questions based on customer: " + customer);
return list;
}
It looks like the actual location where my software was looking for the indexBase was incorrect.
When I looked through the logs, I noticed that it was referring to two different locations when loading the indexBase.
One location that Hibernate Search was loading this indexBase was from "C:/Program Files/Apache Software Foundation/Tomcat 6.0/tmp/indexes", then a little later on in the logs (during the startup phase) I saw that it was also loading from the place I had set it to in my persistence.xml file ("C:/lucene/indexes").
So realizing this, I just changed the location in my persistence.xml file to match the location that it was (for some reason) also looking. Once those two matched up, BINGO, everything worked!
Just blind shot, if possible make your DEV env point to the PROD DB to see if you get the results that you're expecting.
Only to discard and be 100% sure that you're in front of the real problem :)
I can see that in your persistence.xml configuration that you're under Mysql. Googling some sql concepts about same queries in different environments I found that exists a cached mysql resultset from same queries, but this cached maybe changes depending on new variables from the environment like charset. Also you can disable this feature from your Mysql server.
Cited from Hibernate In Action - Bauer, C. King, G. Page 55 (Section 2.4.3 Logging)
But, especially in the face of asynchronous behavior, debugging Hibernate can quickly
get you lost. You can use logging to get a view of Hibernate’s internals.
We’ve already mentioned the *hibernate.show_sql* configuration parameter,
which is usually the first port of call when troubleshooting. Sometimes the SQL
alone is insufficient; in that case, you must dig a little deeper.
Hibernate logs all interesting events using Apache commons-logging, a thin
abstraction layer that directs output to either Apache log4j (if you put log4j.jar
in your classpath) or JDK1.4 logging (if you’re running under JDK1.4 or above and
log4j isn’t present). We recommend log4j, since it’s more mature, more popular,
and under more active development.
To see any output from log4j, you’ll need a file named log4j.properties in your
classpath (right next to hibernate.properties or hibernate.cfg.xml). This example
directs all log messages to the console:
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### root logger option ###
log4j.rootLogger=warn, stdout
### Hibernate logging options ###
log4j.logger.net.sf.hibernate=info
### log JDBC bind parameters ###
log4j.logger.net.sf.hibernate.type=info
### log PreparedStatement cache activity ###
log4j.logger.net.sf.hibernate.ps.PreparedStatementCache=info
With this configuration, you won’t see many log messages at runtime. Replacing
info with debug for the log4j.logger.net.sf.hibernate category will reveal the
inner workings of Hibernate. Make sure you don’t do this in a production environment—
writing the log will be much slower than the actual database access.
Finally, you have the hibernate.properties, hibernate.cfg.xml, and
log4j.properties configuration files.