What's new in iBATIS 3

Using iBATIS as a persistence framework in your application

iBATIS is a project used primarily for data access object (DAO) and object-relational mapping (ORM). You can use it to easily work with Java™ objects and databases. The update for iBATIS 3 beta 9 was posted 23 Jan 2010. iBATIS 3 is a completely rewritten version of iBATIS and includes some significant updates. This article introduces some of what's new in iBATIS 3.

Nathan A. Good, Senior Consultant and Freelance Developer, Enterprise Frameworks

Nathan GoodNathan A. Good lives in the Twin Cities area of Minnesota. Professionally, he does software development, software architecture, and systems administration. When he's not writing software, he enjoys building PCs and servers, reading about and working with new technologies, and trying to get his friends to make the move to open source software. He's written and co-written many books and articles, including Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach, and Foundations of PEAR: Rapid PHP Development.



02 March 2010

Also available in Japanese Portuguese

Depending on how long you've been writing Java code, you may remember the days when you wrote a lot of classes with logic inside methods that mapped data in Java objects to and from the data in a relational database management system (RDBMS). For the most part, this manual intervention is now strongly discouraged, in favor of using ORM tools as a best practice.

ORM tools allow you to configure the mapping between data elements in a relational database with Java object properties. When configured, these tools allow you to use your Java objects without worrying about the details of how the data in the properties of the Java classes are put away or retrieved, freeing you from writing, debugging, and handling errors in a lot of repetitious code.

This article introduces the new features in iBATIS 3, an ORM tool from the Apache Foundation that you can use to build Java applications that connect to databases. To get the most out of this article, you should have the Java Development Kit (JDK) V5 or newer and Eclipse V3.4 or newer. This article uses iBATIS 3 beta 9. The iBATIS site indicates that beta 9 is close to general availability (GA), so the examples in this article should also apply to the GA version when it's available.

Because the purpose of iBATIS is to do mapping to an RDBMS, a database is also required to get the full picture of the examples. The examples in this article use Apache Derby as the database of choice. It's important to note that, in addition to relieving you from writing repetitious Java code that uses Java Database Connectivity (JDBC), ORM tools provide the advantage of offering better abstraction from the data layer. With a few changes to the iBATIS configuration and references to the correct JDBC libraries, you should be able to use the examples in this article with other databases.

iBATIS Overview

iBATIS 3 is a persistence framework that allows you to configure mappings between the properties on your Java classes and table columns in an RDBMS. When configured, the framework handles the JDBC connection and assignments for you. You can configure iBATIS 3 using XML files. You can download the iBATIS framework as a compressed (ZIP) file archive from the iBATIS site (see Resources). Inside the archive is a Java Archive (JAR) file that you include in your Java project to provide the classes that you use.

Listing 1 contains an example of a Java class used by the example application.

Listing 1. The Automobile class used in the sample application
package com.ibm.developerWorks.examples.ibatis.model;

public class Automobile {

    private int id;
    private String make;
    private String model;
    private int year;

    public Automobile() {
        super();
    }

    public Automobile(final int id, final String make, final String model, 
        final int year) {

        super();
        this.id = id;
        this.make = make;
        this.model = model;
        this.year = year;
    }

    public int getId() {
        return id;
    }

    public String getMake() {
        return make;
    }

    public String getModel() {
        return model;
    }

    public int getYear() {
        return year;
    }

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

    public void setMake(final String make) {
        this.make = make;
    }

    public void setModel(final String model) {
        this.model = model;
    }

    public void setYear(final int year) {
        this.year = year;
    }

    @Override
    public String toString() {
        return "Automobile [id=" + id + ", make=" + make + ", model=" + model + ", 
				    year=" + year + "]";
    }
}

This Automobile class is a simple plain old Java object (POJO) that contains data used by the application. The iBATIS framework, when configured, is able to persist this object to the database or return it as a result of a method that selects it from the database.

Listing 2 shows a SQL script that creates the sample database table.

Listing 2. The SQL script for creating the automobiles table
CREATE TABLE automobiles (
    id INT NOT NULL,
    make VARCHAR(255) NOT NULL,
    model VARCHAR(255) NOT NULL,
    model_year INT NOT NULL
);

Execute this database script to create the table in your database. If you're using Derby as your database, you can run this script using the command-line utility that comes with Derby in the bin folder (see Listing 3). Before running the example, make sure you assign the DERBY_HOME variable to the full path to the directory in which Derby is installed and save the SQL script in a file called create.sql.

Listing 3. Using the Derby command-line ij tool to run create.sql
$ cd $DERBY_HOME/bin
$ ./ij
> connect 'jdbc:derby:/tmp/MyDB';
> run create.sql

Listing 4 shows the XML mapping file that allows you to map the properties in the Java class to the data columns in the data table.

Listing 4. The XML mapping file (automobile-mapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
    "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.ibm.developerWorks.examples.ibatis.model.Automobile">
    <resultMap type="Automobile" id="automobileResult">
        <result column="id" property="id" />
        <result column="make" property="make" />
        <result column="model" property="model" />
        <result column="model_year" property="year" />
    </resultMap>
    <select id="select" parameterType="int" resultType="Automobile"
        resultMap="automobileResult">
        select * from
        automobiles where id = #{id}
  </select>
    <insert id="insert" parameterType="Automobile">
        insert into automobiles (id,
        model, make, model_year)
        values (#{id}, #{model}, #{make}, #{year})
    </insert>
    <delete id="delete" parameterType="int">
        delete from automobiles where
        id = #{id}
  </delete>
    <delete id="deleteAll">
        delete from automobiles
  </delete>
</mapper>

The XML mapping file contains <select>, <insert>, and <delete> elements with code inside of them that looks like regular ANSI SQL. The XML element names correspond to the type of SQL statements you would expect them to — the <insert> elements correspond to SQL INSERT statements and so on. The parameters are defined in the SQL code by the notation #{parameter}, where parameter is the name of the field in your Java class. For instance, the Automobile object has a field called make, so you can pass the value stored in this field to the SQL statement using #{make}.

A new feature of iBATIS 3 is the ability to perform this same configuration using annotations on a Java interface. I explain how you can use Java 5 annotations instead of XML configuration files later (see Java 5 features).

Finally, Listing 5 demonstrates the XML configuration file for iBATIS 3 that specifies the name of the database, the type of driver to use, and other database properties, such as credentials. The names of the mapping files, such as the one shown in Listing 4, are listed in the <mappers> element of the configuration file.

Listing 5. The XML configuration file (ibatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//ibatis.apache.org//DTD Config 3.0//EN"
  "http://ibatis.apache.org/dtd/ibatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias type="com.ibm.developerWorks.examples.ibatis.model.Automobile"
            alias="Automobile" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" 
                    value="org.apache.derby.jdbc.EmbeddedDriver" />
                <property name="url" value="jdbc:derby:/tmp/MyDB" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="automobile-mapper.xml" />
    </mappers>
</configuration>

If you use the Java annotations method of defining the mapping between the objects and the database, you don't need this configuration file because iBATIS 3 provides a Java application program interface (API) that allows you to perform this configuration programmatically.


Advantages of using iBATIS

An advantage of using iBATIS is that the XML configuration enables it to be a good ORM framework for mapping objects to existing relational databases. With the Mapper classes and mapping files, emphasis is put on mapping objects to existing data structures, rather than conforming a data structure to the object structure. While the cost in time of configuration is more than that of using a framework that isolates developers more from the data structure, designing the database and object models independently does have its advantages. Good relational database practitioners and object model designers can have competing goals that make their respective implementations quite different from each other.

In the past, I've used iBATIS extensively in projects where the database uses a relational structure and stored procedures, and where developers had far less control over the design of the database.


Creating the sample Java project

To test the examples in this article, you need to create an empty Java project. In the new Java project, create a class that includes a main() method as shown in Listing 6.

Listing 6. The Main class
package com.ibm.developerWorks.examples.ibatis;

import java.io.IOException;

import javax.sql.DataSource;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.derby.jdbc.EmbeddedDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;

import com.ibm.developerWorks.examples.ibatis.data.AutomobileMapper;
import com.ibm.developerWorks.examples.ibatis.model.Automobile;

public class Main {

    private static final String CREATE = "create";
    private static final String DELETE = "delete";
    private static final String IBATIS_CONFIG = "ibatis-config.xml";
    private static final String SHOW = "show";

    @SuppressWarnings("static-access")
    private static Options createOptions() {
        Options options = new Options();
        options.addOption(CREATE, false, "creates the objects in the database");
        options.addOption(OptionBuilder.withArgName(SHOW).hasArg().withDescription(
                "shows the specified automobile").withLongOpt(SHOW).create());
        options.addOption(DELETE, false, "deletes all of the objects in database");
        return options;
    }

    private static SqlSessionFactory createSqlMapper() throws IOException {
        Reader reader = Resources.getResourceAsReader(IBATIS_CONFIG);
        return new SqlSessionFactoryBuilder().build(reader);
    }

    public static void main(final String[] args) {
        Options options = createOptions();
        try {
            CommandLine cmd = new GnuParser().parse(options, args);

            SqlSession session = createSqlMapper().openSession();
            
            try {
                if (cmd.hasOption(CREATE)) {
                    System.out.println("Creating the objects in the database...");
                    // Create the automobiles
                    session.insert(Automobile.class.getName() + ".insert", new
                        Automobile(1, "Toyota", "Tercel", 1993));
                    session.insert(Automobile.class.getName() + ".insert", new
                        Automobile(2, "Honda", "CR-V", 2000));
                    session.insert(Automobile.class.getName() + ".insert", new
                        Automobile(3, "Chevrolet", "Impala", 1964));
                    session.insert(Automobile.class.getName() + ".insert", new
                        Automobile(4, "Dodge", "Pickup", 1946));

                    session.commit();

                } else if (cmd.hasOption(SHOW)) {

                    Automobile auto = (Automobile) session.selectOne(
                        Automobile.class.getName() + ".select", cmd.getOptionValue(SHOW));
                    
                    if (auto == null) {
                        System.out.println("No matching results found!");
                    } else {
                        System.out.println(auto);
                    }

                } else if (cmd.hasOption(DELETE)) {

                    session.delete(Automobile.class.getName() + ".deleteAll");
                    session.commit();

                } else {
                    System.out.println("Doing nothing.");
                }

            } finally {
                session.close();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

To avoid lines of arbitrary argument parsing code, this main method uses the Apache Commons CLI project to parse the arguments sent to it from the command line. Using the Apache Commons CLI project enables the example to respond to different command-line arguments, such as --create, --delete, or --show. For more information about the Apache Commons CLI project, see Resources. This argument support allows the main() method to do a few sample operations with the objects and iBATIS.

This example uses the iBATIS Resources class to load the configuration from an XML file. The Resources class loads the file as a Reader and passes it to the SqlSessionFactoryBuilder. The SqlSessionFactoryBuilder is able to construct a SqlSessionFactory, which, in turn, is used to create the SqlSession objects that allow your code to interact with the database through the methods defined in the mapping classes.

Before you can compile and run the example code, you need to import the iBATIS 3, Apache Commons CLI, and Apache Derby JAR files (ibatis-3-core-x.jar, commons-cli-1.2.jar, and derby.jar).


Running the example

You can run this example by running the main class from Eclipse. If you want to add arguments to the Java call, open Run > Run Configurations and find the Java Application/Main run configuration. In the Arguments tab, specify the arguments you want to supply in Program Arguments (Figure 1 demonstrates).

Figure 1. Adding arguments to the run configuration
Screenshot shows the parameter '--show 3' being added in the applications Arguments tab on Eclipse

Alternatively, call the Java application from the command line, remembering to set the classpath to include the iBATIS 3, Apache Commons CLI, and Apache Derby JAR files. See Listing 7 for an example of how to call the application.

Listing 7. Running the application from the command line
$ java -classpath {jars} com.ibm.developerWorks.examples.ibatis.Main --create
Creating the objects in the database...

When you execute the Java application, you see that --create creates four new Automobile objects and adds them to the database in the automobiles table. Using the --delete argument deletes the objects from the database. Using --show with an ID runs the SQL script to get the matching database record, creates an Automobile object that has the data, and displays the results to the console.

After you get the XML configuration examples working, you're ready to see one of the key new features in iBATIS 3: Java annotation support.


Java 5 features

iBATIS 3 offers new changes that allow you to take advantage of Java 5 annotations. Using annotations, you can create mapper interfaces that allow you to do all of the mapping from the objects to the database in Java code. Consider the code shown in Listing 8 that shows the AutomobileMapper interface used for configuration, instead of the XML configuration.

Listing 8. The AutomobileMapper interface
package com.ibm.developerWorks.examples.ibatis.data;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import com.ibm.developerWorks.examples.ibatis.model.Automobile;

public interface AutomobileMapper {

	@Select("select id, make, model, model_year as \"year\" from automobiles 
	    where id = #{id}")
	Automobile selectAutomobile(final int id);
	
	@Insert("insert into automobiles(id,make,model,model_year) 
	    values (#{id}, #{make}, #{model}, #{year})")
	void insertAutomobile(final Automobile arg);
	
	@Delete("delete from automobiles")
	void deleteAll();
	
}

In the XML configuration for the Automobile mapper, the resultMap element is used to map the model_year database column with the year field on the Automobile object. This is a fairly easy mapping that you can do in the annotations simply by using the SQL feature of aliasing the column, which is done in the SQL defined in the @Select annotation.

The Select annotation in the AutomobileMapper interface maps the selectAutomobile method to the SQL that can be used to select a record from the automobiles table with the given value. The value is specified as the id parameter to the argument and is included in the SQL statement as #{id}, just as it is in the XML configuration. A strong advantage of using Java interfaces for mapping the SQL methods is the feedbac available to you in the Java editor in the form of compile-time errors. You can be sure that the methods are expecting and returning the correct types, whereas XML configuration usually requires that you execute the code before finding errors.

Additionally, iBATIS 3 now supports interface inheritance, which allows you to refine your Java interfaces to reduce code duplication.

The documentation for iBATIS cautions that annotations can be much simpler and easier to read for smaller, simpler projects. However, annotations are limited compared to the XML configuration. If your project includes complex objects or a complex database structure, consider using the XML configuration instead of the Java annotations.


Other API changes

The annotation-based configuration for iBATIS requires slightly different instantiation. Instead of using a Reader class that reads an XML configuration, you can add the mappers to the Configuration object, as shown in Listing 9.

Listing 9. The new Java code using annotation configuration

Click to see code listing

Listing 9. The new Java code using annotation configuration

package com.ibm.developerWorks.examples.ibatis;

// snipped imports

public class Main {

    // snipped constants declarations--didn't change

    // new method for creating data source private static DataSource createDataSource() { EmbeddedDataSource dataSource = new org.apache.derby.jdbc.EmbeddedDataSource(); dataSource.setDatabaseName("/tmp/MyDB"); return dataSource; }

    @SuppressWarnings("static-access")
    private static Options createOptions() {
		   // snipped... no changes
    }

    private static SqlSessionFactory createSqlMapper() throws IOException {DataSource
datasource = createDataSource(); TransactionFactory transaction = new JdbcTransactionFactory(); Configuration configuration = new Configuration(new Environment("development", transaction, datasource)); configuration.addMapper(AutomobileMapper.class);
        return new SqlSessionFactoryBuilder().build(configuration);
    }

    public static void main(final String[] args) {
        Options options = createOptions();
        try {
            CommandLine cmd = new GnuParser().parse(options, args);

            SqlSession session = createSqlMapper().openSession(); AutomobileMapper mapper = session.getMapper(AutomobileMapper.class);
            
            try {
                if (cmd.hasOption(CREATE)) {
                    System.out.println("Creating the objects in the database...");
                    // Create the automobiles mapper.insertAutomobile(new Automobile(1, "Toyota", "Tercel", 1993)); mapper.insertAutomobile(new Automobile(2, "Honda", "CR-V", 2000)); mapper.insertAutomobile( new Automobile(3, "Chevrolet", "Impala", 1964)); mapper.insertAutomobile(new Automobile(4, "Dodge", "Pickup", 1946));

                    session.commit();

                } else if (cmd.hasOption(SHOW)) {
 Automobile auto = mapper.selectAutomobile( Integer.parseInt(cmd.getOptionValue(SHOW)));

                    if (auto == null) {
                        System.out.println("No matching results found!");
                    } else {
                        System.out.println(auto);
                    }

                } else if (cmd.hasOption(DELETE)) {

                    mapper.deleteAll();
                    session.commit();

                } else {
                    System.out.println("Doing nothing.");
                }

            } finally {
                session.close();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

The AutomobileMapper supplied to the addMapper() method is the same Java interface shown back in Listing 8.

The Configuration object also requires a DataSource implementation. In this example, the DataSource implementation is created easily enough in the static createDataSource() method. However, in a production application, you should modify this to make the data source information — such as the name of the database — more dynamic. If you're building an application inside an application server or Web server that manages persistence, you can use Java Naming and Directory Interface (JNDI) to get the DataSource.


XML configuration changes

If you decide to use XML configuration instead of Java annotations for iBATIS while upgrading from a previous version of iBATIS, you will notice that there are some key differences in the XML between the old and new versions.

Previous versions of iBATIS use a parameterMap, which is similar to a resultMap, to map the parameters of the methods. However, the parameterMap element has been deprecated and should no longer be used. Instead, include the type of the object in the parameterType and access the properties on the type using the standard notation (for example, #{id} for the id field on a Java object).

The configuration and mapper files' root elements are updated to include new Document Type Definition (DTD) declarations, and some of the elements have been moved to provide better organization.

For more information about the iBATIS XML configuration changes, see Resources. Be aware that, as of the beta 9 version of iBATIS, the documentation for porting older versions of XML to newer versions is still in progress.


iBATIS migrations

The iBATIS Schema Migrations System (iBATIS Migrations) project is not a project for migrating older schemas of XML configuration to newer ones. Instead, it is a project aimed at making it easier to migrate your database changes while in development from one version of the database to another as the database evolves. You can use the tool to generate SQL scripts that allow you to apply changes automatically, which can greatly reduce errors.

For more information about the iBATIS Migrations project, see Resources.


Summary

iBATIS 3 is an ORM persistence framework that allows you to map the properties in Java objects to table columns in a database. The mapping-centric focus of iBATIS emphasizes mapping a solid object model with a solid relational database design.

New in iBATIS 3 is the use of Java annotations for mapping, which can introduce cleaner, more straight-forward mapping that is accessible in the Java source code for many projects. iBATIS 3 still offers the ability to map objects using XML configuration files. The dual configuration methods allow you to configure iBATIS using the easiest method for your project.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=469607
ArticleTitle=What's new in iBATIS 3
publish-date=03022010