-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: 'select from table where column = null' -> bad mysql sql
PostPosted: Mon Nov 17, 2008 12:18 pm 
Newbie

Joined: Mon Nov 17, 2008 10:40 am
Posts: 3
Hello all,

I'm writing a little app that uses straight JPA calls, with Hibernate as the provider and MySQL as the database.

I specify org.hibernate.dialect.MySQLInnoDBDialect as hibernate.dialect (see persistence.xml below).

The problem I'm running into is, when selecting against null values, Hibernate generates sql that looks more-or-less like this (actual generated sql included below):
select * from book where title = null

Which will never return a result in mysql, since null is never equal to anything.

Correct sql would look like this:
select * from book where title is null

I think I've isolated this to a Hibernate problem, as it goes away when I switch to Toplink.

Is this a Hibernate bug? or a bug in my code? (Based on past experience I'm betting on the latter)


Thank you for any help.

--Dave


Hibernate version:
Hibernate Annotations 3.4.0.GA

Mapping documents:
persistence.xml:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="JPA-ScratchPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpascratch.dao.Library</class>
    <class>jpascratch.dao.Book</class>
    <properties>
            <property name="hibernate.connection.url" value="jdbc:mysql://10.0.64.200:3306/test"/>
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.password" value="password"/>
            <property name="hibernate.connection.username" value="username"/>
            <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
    </properties>
  </persistence-unit>
</persistence>


Code between sessionFactory.openSession() and session.close():
Code:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jpascratch;

import java.util.HashMap;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Persistence;
import javax.persistence.Query;
import jpascratch.dao.Book;
import org.apache.log4j.Logger;

/**
*
* @author dhoyt
*/
public class DriverBugDemo {
    private static Logger LOG = Logger.getLogger(DriverBugDemo.class.getCanonicalName());

    public void demoTheBug() {

        LOG.debug("begin.");
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA-ScratchPU", new HashMap());
        EntityManager em = emf.createEntityManager();

        // some book titles
        String titles[] = {"Frankenstein", "War and Peace", "The Inspector Ggnural", null};

        // make some books out of them.
        EntityTransaction et = em.getTransaction();
        et.begin();
        for (String title : titles) {
            Book b = new Book();
            b.setTitle(title);
            em.persist(b);
        }
        et.commit();

        // now, try to look them up.
        Query namedQ = em.createNamedQuery("Book.findByTitle");
        String title = null;

        try {
            // War and Peace should be fine using hql named query.
            title = "War and Peace";
            namedQ.setParameter("title", title);

            Book b = (Book) namedQ.getSingleResult();
            LOG.debug("Found book with id " + b.getId() + " and title " + b.getTitle());

            title = null;

            // now to test a null parameter first using a native query
            // returns the book we made earlier.
            Query nativeQuery = em.createNativeQuery("select * from book where title " + (title == null ? "is null" : "=" + title), Book.class);
            b = (Book) nativeQuery.getSingleResult();
            LOG.debug("Found book with id " + b.getId() + " and title " + b.getTitle());

            // then again using the hql named query
            // this will throw NoResultException.
            // looking at the show_query=true output, we find that
            // it's because of this sql:  select .... where title=null
            // which should read ... where title is null
            namedQ = em.createNamedQuery("Book.findByTitle");
            namedQ.setParameter("title", title);
            b = (Book) namedQ.getSingleResult();
            LOG.debug("Found book with id " + b.getId() + " and title " + b.getTitle());

        } catch (NoResultException nre) {
            LOG.debug("sorry, no book by title " + title);
        } catch (NonUniqueResultException nure) {
            LOG.debug("Too many results.  Please narrow your query.");
        }

    }
}





Persisted class

Code:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package jpascratch.dao;

import java.io.Serializable;
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.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

/**
*
* @author dhoyt
*/
@Entity
@Table(name = "book")
@NamedQueries({@NamedQuery(name = "Book.findById", query = "SELECT b FROM Book b WHERE b.id = :id"), @NamedQuery(name = "Book.findByTitle", query = "SELECT b FROM Book b WHERE b.title = :title")})
public class Book implements Serializable {
    private static final long serialVersionUID = 1L;
   
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Integer id;
   
    @Column(name = "title")
    private String title;
   
    @ManyToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
    @JoinColumn(name = "library_id", referencedColumnName = "id")
    private Library libraryId;

    public Book() {
    }

    public Book(Integer id) {
        this.id = id;
    }

    public Book(Integer id, String title) {
        this.id = id;
        this.title = title;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Library getLibraryId() {
        return libraryId;
    }

    public void setLibraryId(Library libraryId) {
        this.libraryId = libraryId;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Book)) {
            return false;
        }
        Book other = (Book) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "jpascratch.dao.Book[id=" + id + "]";
    }

}


Name and version of the database you are using:
mysql 5.051a

The generated SQL (show_sql=true):
Hibernate: insert into book (library_id, title) values (?, ?)
Hibernate: insert into book (library_id, title) values (?, ?)
Hibernate: insert into book (library_id, title) values (?, ?)
Hibernate: insert into book (library_id, title) values (?, ?)
Hibernate: select book0_.id as id1_, book0_.library_id as library3_1_, book0_.title as title1_ from book book0_ where book0_.title=? limit ?
and title War and Peace
Hibernate: select * from book where title is null limit ?
and title null
Hibernate: select book0_.id as id1_, book0_.library_id as library3_1_, book0_.title as title1_ from book book0_ where book0_.title=? limit ?


Debug level Hibernate log excerpt:
For some reason I'm not able to override the log-level of hibernate from my log4j.properties (or .xml).

Here is the output from the class listed above:

Code:
1282 [main] DEBUG jpascratch.DriverBugDemo  - Found book with id 142 and title War and Peace
1294 [main] DEBUG jpascratch.DriverBugDemo  - Found book with id 144 and title null
1297 [main] DEBUG jpascratch.DriverBugDemo  - sorry, no book by title null


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 17, 2008 3:09 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
The structure of your named query doesn't change just because you give the :title parameter a null value, so it is expected that the last query doesn't return any result. If you want to support querying for both null and not-null values with the same query you should do something like this:

Code:
... WHERE b.title = :title OR (b.title is null AND :title is null)


I don't know anything about Toplink, but if the original query works, then it is probably a custom "feature" of Toplink. MySQL has a similar feature where you can use <=> as a null-safe equal, but I bet this doesn't work with HQL queries.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.