Posts Tagged ‘JPA’

Using “use index” With Hibernate/MySQL

Wednesday, October 7th, 2009

MySQL has the notion of specifying a specific index to use when invoking queries in order to override the default provided by MySQL. Typically, MySQL selects the most optimized index based on the query. However, in some cases, it fails to pick properly reducing performance. In those cases, you need to use the keyword “use index” to select the index. For example:

SELECT [FIELDS] FROM my_table TABLE USE INDEX (my_index) WHERE [predicate]

The problem with this is when moving to an ORM-solution such as Hibernate. Being provider-independent, there is no capability natively within Hibernate and HQL to use provider-specific features such as “use index”. However, Hibernate does allow extension points to do everything from adding provider-specific functions to completely rewriting the SQL statements themselves. In this article, I will demonstrate the capability to semi-add “use index” to hibernate.

Before reading take note that this is, IMO, a really ugly hack to make this work. Unfortunately, in my use case, I had the desire to keep my query caches within HQL in line with the timestamps cache created by hibernate for table updates. The easiest way to actually utilize “use index” is to use a native SQL query rather than HQL. Native SQL queries within hibernate can contain any vendor-specific functionality. Further, there may be better ways at doing this, but I had not found any. Thus, this is my hack and so without further ado, let’s get to it.

Hibernate has two extension points that we will use to provide this functionality. The first is a custom dialect used to add specific functions, keywords, etc. The second is an interceptor to intercept various functionality such as preparing SQL statements. When I initially went down this path, I tried to just create and register a custom function in a custom dialect and then invoke it such as:

SELECT [FIELDS] FROM MyTable TABLE useindex(my_index)

However, from what I understand, you may only use custom functions within the where, group, and order clauses, not the from clause. So, I decided to try the interceptor and modify the SQL before getting added to the SQL statement. Unfortuately, Hibernate only passes in the SQL so there is no way to know whether the specified query wanted an index or not. So, I decided to mix the two by using a custom function within a where statement that I could then find in the SQL in order to inject the proper syntax.

The first step here is to create a custom dialect to register our custom function. The easiest way to do this is to just extend the dialect you are already using so that you merely add functionality to your existing dialect. For example, in my persistence.xml file (NOTE: I am using JPA with Hibernate, but this would work with plain Hibernate and session factory configuration as well), I specify my dialect via:

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />

So, I just need to create a new class and extend that MySQL5InnoDBDialect. Within the constructor, I just need to register the simple useindex marker function. The result is:

package com.znet.hibernate.dialect;
 
import org.hibernate.Hibernate;
import org.hibernate.dialect.function.StandardSQLFunction;
 
public class MySQL5InnoDBDialect extends org.hibernate.dialect.MySQL5InnoDBDialect
{
    public MySQL5InnoDBDialect()
    {
        super();
        registerFunction("useindex", new StandardSQLFunction("useindex", Hibernate.BOOLEAN));
    }
}

We use the org.hibernate.dialect.function.StandardSQLFunction class to create a quick function that expects the specified function name and expects a return type of boolean. To use this dialect, just update the configuration to specify the newly created class.

<property name="hibernate.dialect" value="com.znet.hibernate.dialect.MySQL5InnoDBDialect" />

This function will in the end take two parameters. The first parameter will denote the table to associate the index to. The second parameter will denote the actual index [eg: useindex(table, my_index)]. This will allow us to mark the resultant SQL with both the index and associated table. Thus, we can properly restructure the generated SQL accordingly. We could now use this function within HQL such as:

String hql1 = "select table from MyTable table where useindex(table, my_index) is true and table.feature is not null";
// result: select [omitted] from my_table table where useindex(table.id, my_index)=1 and table.feature is not null";
// note that hibernate automatically resolves table into table.id within the function

Now that we know what the SQL gets generated as, let’s build an interceptor to transform the SQL. org.hibernate.Interceptors can perform all types of functionality within Hibernate. For our case, we just care about transforming the SQL via the onPrepareStatement(String sql) method. The first step is to create our interceptor. The easiest way is to just extend EmptyInterceptor and merely override the methods you care about. EmptyInterceptor provides empty stubs for all implemented methods.

package com.znet.hibernate.interceptor;
 
import org.hibernate.EmptyInterceptor;
 
public class IndexInterceptor extends EmptyInterceptor
{
    private static final long serialVersionUID = 1L;
 
    @Override
    public String onPrepareStatement(String sql)
    {
        while (true)
        {
            // check if function specified
            int idx = sql.indexOf("useindex(");
            if (idx < 0) { break; }
 
            // find end of function
            int endidx = sql.indexOf(")=1", idx);
            if (endidx < idx) 
            { 
                throwError("expected useindex(table, index) is true"); 
            }
 
            // get both parameters
            String[] params = sql.substring(idx + 9, endidx).split(",");
            if (params.length != 2) 
            {
                throwError("expected 2 parameters to useindex(table, index)");
            }
 
            // trim parameters and verify
            String tableId = params[0].trim(); 
            String indexHint = params[1].trim();
            if (tableId.length() == 0 || indexHint.length() == 0)
            {
                throwError("invalid parameters to useindex(table, index)");
            }
 
            // find actual table name minus id
            int dotIdx = tableId.indexOf('.');
            if (dotIdx < 0)
            {
                throwError("invalid table name in useindex(table, index)");
            }
 
            // find table name within declaration
            String tableName = tableId.substring(0, dotIdx);
            int tableIdx = sql.indexOf(" " + tableName + " ");
            if (tableIdx < 0)
            {
                throwError("unknown table name in useindex(table, index)");
            }
 
            // remove useindex function from predicate
            String predicate = sql.substring(endidx + 3);
            if (predicate.startsWith(" and ")) 
            { 
                predicate = predicate.substring(5); 
            }
 
            // inject use index after table declaration
            sql = sql.substring(0, tableIdx + 2 + tableName.length()) +
                  "use index (" + indexHint + ") " + 
                  sql.substring(tableIdx + 2 + tableName.length(), idx) +
                  predicate;
        }
 
        return sql;
    }
 
    protected void throwError(String message)
    {
        throw new IllegalStateException(message);
    }
}

There is quite a bit of logic there to go into depth, but the general idea is that it searches for each useindex(table, index)=1 declaration within the SQL statement. For each instance, it tracks back to the table declaration (via from or join) and appends the “use index(index)” statement to the table declaration. Finally, it removes the function from the where statement. So, going back to our example above, we end up with:

String hql1 = "select table from MyTable table where useindex(table, my_index) is true and table.feature is not null";
// before interceptor: select [omitted] from my_table table where useindex(table.id, my_index)=1 and table.feature is not null";
// after interceptor:  select [omitted] from my_table table use index (my_index) where table.feature is not null

Before that will actually work, though, we need to register the interceptor with Hibernate. There are multiple ways to do this depending on whether you are using straight Hibernate, Hibernate with Spring, JPA with Hibernate, etc. I will demonstate JPA with Hibernate via persistence.xml, but for other examples, see this article.

<property name="hibernate.ejb.interceptor" value="com.znet.hibernate.interceptor.IndexInterceptor" />

That’s it. You can now force the “use index” declaration within any HQL by simply adding useindex(table, index) is true to the where statement. Note that it should be the first statement in the where statement. In theory, although I have not tested this very much, the following complex example should work too:

String  hql = "select table from MyTable table " +
                     "inner join table.relative relative, " +
                    "OtherTable other " +
                     "where useindex(table,table_index) is true and " +
                     "useindex(relative,relative_index) is true and " +
                     "useindex(other, other_index) is true and " +
                     "relative.feature1 = other.feature2";

As I stated, this is just an example that I am using and I have in no way tested it to a large extent outside simple examples. If you do take an attempt to use it and find issues, feel free to post a comment and I will look into fixing or helping. Otherwise feel free to use this in any way that helps you.

Eclipse Galileo (3.5) Released - My Favorite Features

Wednesday, June 24th, 2009

Eclipse Galileo has officially been released and with it comes multitudes of changes. Below are my favorites from the Platform, JDT, and Web Tools projects.

For more information, including screenshots, see:

Mac OSX Builds Use Cocoa and Sheets

The platform now uses native cocoa-build SWT widgets, rather than the obsolete Carbon. This brings the platform more in line with current Mac OSX technologies. Further, it uses Sheets for most popup dialogs to follow the OSX user interface guidelines.

Comparing Microsoft Word Documents

Comparing Microsoft Word documents in Eclipse now uses the native Microsoft Word compare support to show changes between versions. For any project that stores Word documents in source control, this feature may be nice to compare changes between versions…especially in terms of merging updates.

Open Resource Dialog

The Open Resource Dialog now allows the choice of what editor to use by right clicking to the context menu. This is a huge, yet small, benefit. Often times you may want to use a different editor than the default. This new change makes that much easier. For example, if you have custom named configuration files in XML that have an official editor (ie: JSF), you can right click to select the specific editor rather than the default XML editor.

Improved Installation/Plugin Management

Eclipse has greatly improved how plugins are added and managed, especially when adding new plugin sites.

Open Implementation Hyperlink

An Open Implementation hyperlink has been added for overridable methods, which directly opens the implementation in case there’s only one, or shows all the concrete implementations for that method in the hierarchy of its declaring type, using the quick type hierarchy. By default, the hyperlink appears when you hold down the Ctrl key while hovering over an overridable method. You can configure the modifier for this hyperlink on the General > Editors > Text Editors > Hyperlinking preference page.

This feature is a huge bonus, especially when you are navigating through source files trying to process the flow. If the class declares a type as an interface (which is usually the case), then normally you wil ctrl+click to the interface, which is obviously not what you want. This new feature allows you to pick the concrete class that you want to jump to making debugging easier.

Javadoc Viewer and Hover

The Javadoc view and hovers now support the {@inheritDoc} tag and add links to overridden methods (like the Javadoc tool). Moreover, the message for deprecated elements now shows up on top, and relative links are supported (for example, to embed images from the “doc-files” directory or to link to {@docRoot}).

This feature should make viewing javadoc inline when hovering an element easier to follow to click through to.

New JPA Entity Generation from Tables

Dali has a new Entity Generation wizard via a contribution from Oracle. This functionality includes a new wizard that offers additional configuration options.

This makes setting up JPA projects, tables, and entities much easier.

XSLT Source Editing

Content assistance is now available for the XSLT mode attribute. This attribute is on the xsl:template and xsl:apply-templates elements. The assistance will find all available modes that have been defined in the current stylesheet as well as any imported or included stylesheets.

Content Assistance is available for the name attribute on the xsl:call-template element. This will provide proposals of available named templates that can be called. This searches the current stylesheet as well as any imported or included stylesheets.

Content assistance is available for the href attribute on xsl:include and xsl:import elements. This will search the current project for any XSLT stylesheet that is available, and provide it as a possible proposal. It is limited in scope to the current project.

The XSL Tools Editor now supports the Templates View. This allows for drag and drop support of XPath templates into the editor. Users may also use this view to create and maintain new or existing templates.

XSLT Debugger

The XSLT debugger now supports a result view. The view will show the output that has been generated to the current break point. As a user steps through code the view will be updated as well.

During debugging of an XSLT Stylesheet, variables that contain NodeSets are now expandable, allowing inspection of the contents of the Nodes carried