About this book

Storing and retrieving data to and from relational databases is a very common requirement, and is a crucial part of many applications. Even though Java provides a JDBC API for database access, it is not very effective and involves writing the boilerplate code again and again. Getting data out of a database, populating into Java objects, and persisting data from Java objects into a database using JDBC involves a lot of repetitive coding and is a very tedious process. MyBatis takes the simplest approach of leveraging the existing knowledge and power of Java and SQL yet provides powerful features to make data persistence implementation easy. Java Persistence with MyBatis 3 is a practical, hands-on guide that provides you with a number of clear step-by-step exercises, which will help you to understand how MyBatis works and how to use it for your real application needs. With MyBatis, you will learn how to use the MyBatis framework effectively through simple instructions. Java Persistence with MyBatis 3 will highlight the cumbersome process of data persistence using plain JDBC and will also show you how easy it is to implement the same using MyBatis. You will also take a deeper look into the implementation of MyBatis with XML and Annotation-based Mappers, and finally learn how to integrate MyBatis with a Spring framework. You will learn how to map Complex SQL query results to Java beans using XML and Annotation-based Mappers, including One-To-Many and Many-To-Many relationships. You will also learn how to use TypeAliases and Custom Type Handlers, and build dynamic queries using XML and SqlProvider annotations. It progresses to detailed instructions on integrating MyBatis with Spring and leveraging Spring’s Annotation-based transaction handling mechanism, which further simplifies the usage of MyBatis.With Java Persistence with MyBatis 3, you will learn how to use the MyBatis framework effectively through simple instructions.

Publication date:
June 2013
Publisher
Packt
Pages
132
ISBN
9781782166801

 

Chapter 1. Getting Started with MyBatis

In this chapter, we will cover the following topics:

  • What is MyBatis?

  • Why MyBatis?

  • Installing and configuring MyBatis

  • Sample domain model

 

What is MyBatis?


MyBatis is an open source persistence framework that simplifies the implementation of the persistence layer by abstracting a lot of JDBC boilerplate code and provides a simple and easy-to-use API to interact with the database.

MyBatis was formerly known as iBATIS and was started by Clinton Begin in 2002. MyBatis 3 is a complete redesign of iBATIS, with annotations and Mapper support.

The main reason for the popularity of MyBatis is its simplicity and ease of use. In Java applications, the persistence layer involves populating Java objects with data loaded from the database using SQL queries, and persisting the data in Java objects into the database using SQL.

MyBatis makes using SQL easy by abstracting low-level JDBC code, automating the process of populating the SQL result set into Java objects, and persisting data into tables by extracting the data from Java objects.

If you are currently using iBATIS and want to migrate to MyBatis, you can find the step-by-step instructions on the official MyBatis website at https://code.google.com/p/mybatis/wiki/DocUpgrade3.

 

Why MyBatis?


There are many Java-based persistence frameworks, however MyBatis became popular because of the following reasons:

  • It Eliminates a lot of JDBC boilerplate code

  • It has a low learning curve

  • It works well with legacy databases

  • It embraces SQL

  • It provides support for integration with Spring and Guice frameworks

  • It provides support for integration with third-party cache libraries

  • It induces better performance

Eliminates a lot of JDBC boilerplate code

Java has a Java DataBase Connectivity (JDBC) API to work with relational databases. But JDBC is a very low-level API, and we need to write a lot of code to perform database operations.

Let us examine how we can implement simple insert and select operations on a STUDENTS table using plain JDBC.

Assume that the STUDENTS table has STUD_ID, NAME, EMAIL, and DOB columns.

The corresponding Student JavaBean is as follows:

package com.mybatis3.domain;
import java.util.Date;
public class Student
{
  private Integer studId;
  private String name;
  private String email;
  private Date dob;
  // setters and getters
}

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

The following StudentService.java program implements the SELECT and INSERT operations on the STUDENTS table using JDBC.

public Student findStudentById(int studId)
{
  Student student = null;
  Connection conn = null;
  try{
//obtain connection
    conn = getDatabaseConnection();
    String sql = "SELECT * FROM STUDENTS WHERE STUD_ID=?";
//create PreparedStatement
    PreparedStatement pstmt = conn.prepareStatement(sql);
//set input parameters
    pstmt.setInt(1, studId);
    ResultSet rs = pstmt.executeQuery();
//fetch results from database and populate into Java objects
    if(rs.next())  {
      student = new Student();
      student.setStudId(rs.getInt("stud_id"));
      student.setName(rs.getString("name"));
      student.setEmail(rs.getString("email"));
      student.setDob(rs.getDate("dob"));
    }
  } catch (SQLException e){
    throw new RuntimeException(e);
  }finally{
//close connection
    if(conn!= null){
      try {
        conn.close();
      } catch (SQLException e){ }
    }
  }
  return student;
}
public void createStudent(Student student)
{
  Connection conn = null;
  try{
//obtain connection    
    conn = getDatabaseConnection();
    String sql = "INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) VALUES(?,?,?,?)";
//create a PreparedStatement
    PreparedStatement pstmt = conn.prepareStatement(sql);
//set input parameters
    pstmt.setInt(1, student.getStudId());
    pstmt.setString(2, student.getName());
    pstmt.setString(3, student.getEmail());
    pstmt.setDate(4, new java.sql.Date(student.getDob().getTime()));
    pstmt.executeUpdate();

  } catch (SQLException e){
    throw new RuntimeException(e);
  }finally{
//close connection
    if(conn!= null){
      try {
        conn.close();
      } catch (SQLException e){ }
    }
  }
}

protected Connection getDatabaseConnection() throws SQLException
{
  try{
    Class.forName("com.mysql.jdbc.Driver");
    return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "admin");
  } catch (SQLException e){
    throw e;
  } catch (Exception e){
    throw new RuntimeException(e);
  }
}

There is a lot of duplicate code in each of the preceding methods, for creating a connection, creating a statement, setting input parameters, and closing the resources, such as the connection, statement, and result set.

MyBatis abstracts all these common tasks so that the developer can focus on the really important aspects, such as preparing the SQL statement that needs to be executed and passing the input data as Java objects.

In addition to this, MyBatis automates the process of setting the query parameters from the input Java object properties and populates the Java objects with the SQL query results as well.

Now let us see how we can implement the preceding methods using MyBatis:

  1. Configure the queries in a SQL Mapper config file, say StudentMapper.xml.

    <select id="findStudentById" parameterType="int" resultType=" Student">
      SELECT STUD_ID AS studId, NAME, EMAIL, DOB 
      FROM STUDENTS WHERE STUD_ID=#{Id}
    </select>
    
    <insert id="insertStudent" parameterType="Student">
      INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) VALUES(#{studId},#{name},#{email},#{dob})
    </insert>
  2. Create a StudentMapper interface.

    public interface StudentMapper
    {
      Student findStudentById(Integer id);
      void insertStudent(Student student);
    }
  3. In Java code, you can invoke these statements as follows:

    SqlSession session = getSqlSessionFactory().openSession();
    StudentMapper mapper = session.getMapper(StudentMapper.class);
    // Select Student by Id
    Student student = mapper.selectStudentById(1);
    //To insert a Student record
    mapper.insertStudent(student);

That's it! You don't need to create the Connection, PrepareStatement, extract, and set parameters and close the connection by yourself for every database operation. Just configure the database connection properties and SQL statements, and MyBatis will take care of all the ground work.

Don't worry about what SqlSessionFactory, SqlSession, and Mapper XML files are. These concepts will be explained in detail in the coming chapters.

Along with these, MyBatis provides many other features that simplify the implementation of persistence logic.

  • It supports the mapping of complex SQL result set data to nested object graph structures

  • It supports the mapping of one-to-one and one-to-many results to Java objects

  • It supports building dynamic SQL queries based on the input data

Low learning curve

One of the primary reasons for MyBatis' popularity is that it is very simple to learn and use because it depends on your knowledge of Java and SQL. If developers are familiar with Java and SQL, they will find it fairly easy to get started with MyBatis.

Works well with legacy databases

Sometimes we may need to work with legacy databases that are not in a normalized form. It is possible, but difficult, to work with these kinds of legacy databases with fully-fledged ORM frameworks such as Hibernate because they attempt to statically map Java objects to database tables.

MyBatis works by mapping query results to Java objects; this makes it easy for MyBatis to work with legacy databases. You can create Java domain objects following the object-oriented model, execute queries against the legacy database, and map the query results to the Java objects.

Embraces SQL

Full-fledged ORM frameworks such as Hibernate encourage working with entity objects and generate SQL queries under the hood. Because of this SQL generation, we may not be able to take advantage of database-specific features. Hibernate allows to execute native SQLs, but that might defeat the promise of a database-independent persistence.

The MyBatis framework embraces SQL instead of hiding it from developers. As MyBatis won't generate any SQLs and developers are responsible for preparing the queries, you can take advantage of database-specific features and prepare optimized SQL queries. Also, working with stored procedures is supported by MyBatis.

Supports integration with Spring and Guice frameworks

MyBatis provides out-of-the-box integration support for the popular dependency injection frameworks Spring and Guice; this further simplifies working with MyBatis.

Supports integration with third-party cache libraries

MyBatis has inbuilt support for caching SELECT query results within the scope of SqlSession level ResultSets. In addition to this, MyBatis also provides integration support for various third-party cache libraries, such as EHCache, OSCache, and Hazelcast.

Better performance

Performance is one of the key factors for the success of any software application. There are lots of things to consider for better performance, but for many applications, the persistence layer is a key for overall system performance.

  • MyBatis supports database connection pooling that eliminates the cost of creating a database connection on demand for every request.

  • MyBatis has an in-built cache mechanism which caches the results of SQL queries at the SqlSession level. That is, if you invoke the same mapped select query, then MyBatis returns the cached result instead of querying the database again.

  • MyBatis doesn't use proxying heavily and hence yields better performance compared to other ORM frameworks that use proxies extensively.

Note

There are no one-size-fits-all solutions in software development. Each application has a different set of requirements, and we should choose our tools and frameworks based on application needs. In the previous section, we have seen various advantages of using MyBatis. But there will be cases where MyBatis may not be the ideal or best solution.

If your application is driven by an object model and wants to generate SQL dynamically, MyBatis may not be a good fit for you. Also, if you want to have a transitive persistence mechanism (saving the parent object should persist associated child objects as well) for your application, Hibernate will be better suited for it.

 

Installing and configuring MyBatis


We are assuming that the JDK 1.6+ and MySQL 5 database servers have been installed on your system. The installation process of JDK and MySQL is outside the scope of this book.

At the time of writing this book, the latest version of MyBatis is MyBatis 3.2.2. Throughout this book, we will use the MyBatis 3.2.2 version.

Even though it is not mandatory to use IDEs, such as Eclipse, NetBeans IDE, or IntelliJ IDEA for coding, they greatly simplify development with features such as handy autocompletion, refactoring, and debugging. You can use any of your favorite IDEs for this purpose.

This section explains how to develop a simple Java project using MyBatis:

  • By creating a STUDENTS table and inserting sample data

  • By creating a Java project and adding mybatis-3.2.2.jar to the classpath

  • By creating the mybatis-config.xml and StudentMapper.xml configuration files

  • By creating the MyBatisSqlSessionFactory singleton class

  • By creating the StudentMapper interface and the StudentService classes

  • By creating a JUnit test for testing StudentService

Creating a STUDENTS table and inserting sample data

Create a STUDENTS table and insert sample records in the MySQL database using the following SQL script:.

CREATE TABLE STUDENTS
(
stud_id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(50) NOT NULL,
  email varchar(50) NOT NULL,
  dob date DEFAULT NULL,
  PRIMARY KEY (stud_id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

/*Sample Data for the students table */
insert  into students(stud_id,name,email,dob) 
values (1,'Student1','[email protected]','1983-06-25');

insert  into students(stud_id,name,email,dob) 
values (2,'Student2','[email protected]','1983-06-25');

Creating a Java project and adding mybatis-3.2.2.jar to the classpath

Let us create a Java project and configure MyBatis JAR dependencies.

  1. Create a Java project named mybatis-demo.

  2. If you are not using a build tool, such as Maven or Gradle, with dependency management capabilities, you need to download the JAR files and add them to the classpath manually.

  3. You can download the MyBatis distribution mybatis-3.2.2.zip from http://code.google.com/p/mybatis/. This bundle contains the mybatis-3.2.2.jar file and its optional dependent jars such as the slf4j/log4j logging jars.

  4. We will use the SLF4J logging framework along with the log4j binding for logging. The mybatis-3.2.2.zip file contains the slf4j dependency jars as well.

  5. Extract the ZIP file and add the mybatis-3.2.2.jar, lib/slf4j-api-1.7.5.jar, lib/slf4j-log4j12-1.7.5.jar, and lib/log4j-1.2.17.jar JARS to the classpath.

  6. You can download the JUnit JAR file from http://junit.org/ and the driver from http://www.mysql.com/downloads/connector/j/.

  7. Add junit-4.11.jar and mysql-connector-java-5.1.22.jar to the classpath.

  8. If you are using Maven, configuring these jar dependencies is much simpler. In your pom.xml file add the following dependencies:

    <dependencies>
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.2.2</version>
      </dependency>
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.22</version>
        <scope>runtime</scope>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.5</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.5</version>
        <scope>runtime</scope>
      </dependency>
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
        <scope>runtime</scope>
      </dependency>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
      </dependency>
    </dependencies>
  9. Create the log4j.properties file and put it in the classpath.

    log4j.rootLogger=DEBUG, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n

Creating the mybatis-config.xml and StudentMapper.xml configuration files

Let us create MyBatis' main configuration file mybatis-config.xml with database connection properties, type aliases, and so on, and create the StudentMapper.xml file containing mapped SQL statements.

  1. Create the mybatis-config.xml file to configure database connection properties, SQL Mapper files, type aliases, and so on, and put it in the classpath.

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    <typeAliases>
      <typeAlias alias="Student" type="com.mybatis3.domain.Student"/>
    </typeAliases>
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
    <property name="username" value="root"/>
    <property name="password" value="admin"/>
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <mapper resource="com/mybatis3/mappers/StudentMapper.xml"/>
    </mappers>
    </configuration>
  2. Create the SQL Mapper XML file StudentMapper.xml and put it in the classpath under the com.mybatis3.mappers package.

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.mybatis3.mappers.StudentMapper">
      <resultMap type="Student" id="StudentResult">
        <id property="studId" column="stud_id"/>
        <result property="name" column="name"/>
        <result property="email" column="email"/>
        <result property="dob" column="dob"/>
      </resultMap>
    
      <select id="findAllStudents" resultMap="StudentResult">
        SELECT * FROM STUDENTS
    </select>
      
    <select id="findStudentById" parameterType="int" resultType="Student">
      SELECT STUD_ID AS STUDID, NAME, EMAIL, DOB FROM STUDENTS WHERE STUD_ID=#{Id}
    </select>
    
    <insert id="insertStudent" parameterType="Student">
      INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) 
      VALUES(#{studId },#{name},#{email},#{dob})
    </insert>
    </mapper>

The preceding StudentMapper.xml file contains the mapped statements that can be invoked using the statement ID along with the namespace.

Creating the MyBatisSqlSessionFactory singleton class

Create the MyBatisSqlSessionFactory.java class to instantiate and hold the SqlSessionFactory singleton object.

package com.mybatis3.util;
import java.io.*;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
public class MyBatisSqlSessionFactory
{
  private static SqlSessionFactory sqlSessionFactory;
  
  public static SqlSessionFactory getSqlSessionFactory() {
    if(sqlSessionFactory==null) {
      InputStream inputStream;
      try {
        inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      } catch (IOException e) {
        throw new RuntimeException(e.getCause());
      }
    }
    return sqlSessionFactory;
  }

  public static SqlSession openSession() {
    return getSqlSessionFactory().openSession();
  }

}

In the preceding code snippet, we have created the SqlSessionFactory object that will be used to get SqlSession and execute mapped statements.

Creating the StudentMapper interface and the StudentService classes

Let us create a StudentMapper interface with method signatures similar to mapped SQL statements and a StudentService.java class that contains the implementation of business operations.

  1. First, create the JavaBean Student.java.

    package com.mybatis3.domain;
    import java.util.Date;
    public class Student
    {
      private Integer studId;
      private String name;
      private String email;
      private Date dob;
      // setters and getters
    }
  2. Create a Mapper interface StudentMapper.java with the same method signatures as the mapped statements in StudentMapper.xml.

    package com.mybatis3.mappers;
    import java.util.List;
    import com.mybatis3.domain.Student;
    public interface StudentMapper
    {
      List<Student> findAllStudents();
      Student findStudentById(Integer id);
      void insertStudent(Student student);
    }
  3. Now create StudentService.java to implement database operations on the STUDENTS table.

    package com.mybatis3.services;
    import java.util.List;
    import org.apache.ibatis.session.SqlSession;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import com.mybatis3.domain.Student;
    import com.mybatis3.mappers.StudentMapper;
    import com.mybatis3.util.MyBatisSqlSessionFactory;
    public class StudentService
    {
      private Logger logger = LoggerFactory.getLogger(getClass());
      
      public List<Student> findAllStudents()
      {
        SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
          try {
          StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
          return studentMapper.findAllStudents();
        } finally {
    //If sqlSession is not closed 
    //then database Connection associated this sqlSession will not be returned to pool 
    //and application may run out of connections.
          sqlSession.close();
        }
      }
      
      public Student findStudentById(Integer studId)
      {
        logger.debug("Select Student By ID :{}", studId);
        SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
        try {
          StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
          return studentMapper.findStudentById(studId);
        } finally {
          sqlSession.close();
        }
      }
      
      public void createStudent(Student student)
      {
        SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
        try {
          StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
          studentMapper.insertStudent(student);
          sqlSession.commit();
        } finally {
          sqlSession.close();
        }
      }
    }

You can also execute mapped SQL statements without using Mapper interfaces. An example is as follows:

Student student = (Student)sqlSession. 
selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", studId);

However, it is best practice to use Mapper interfaces so that we invoke mapped statements in a type-safe manner.

Creating a JUnit test for testing StudentService

Create a JUnit test class StudentServiceTest.java to test the StudentService methods.

package com.mybatis3.services;
import java.util.*;
import org.junit.*;
import com.mybatis3.domain.Student;
public class StudentServiceTest
{
  private static StudentService studentService;
  
  @BeforeClass
  public static void setup(){
    studentService = new StudentService();
  }
  @AfterClass
  public static void teardown(){
    studentService = null;
  }
  
  @Test
  public void testFindAllStudents() {
    List<Student> students = studentService.findAllStudents();
    Assert.assertNotNull(students);
    for (Student student : students) {
      System.out.println(student);
    }
  }
  
  @Test
  public void testFindStudentById() {
    Student student = studentService.findStudentById(1);
    Assert.assertNotNull(student);
    System.out.println(student);
  }
  
  @Test
  public void testCreateStudent() {
    Student student = new Student();
    int id = 3;
    student.setStudId(id);
    student.setName("student_"+id);
    student.setEmail("student_"+id+"gmail.com");
    student.setDob(new Date());
    studentService.createStudent(student);
    Student newStudent = studentService.findStudentById(id);
    Assert.assertNotNull(newStudent);
  }
}

How it works

First, we have configured the main MyBatis configuration file, mybatis-config.xml, with the JDBC connection parameters and configured the Mapper XML files that contain the SQL statement's mappings.

We have created the SqlSessionFactory object using the mybatis-config.xml file. There should be only one instance of SqlSessionFactory per database environment, so we have used a singleton pattern to have only one instance of SqlSessionFactory.

We have created a Mapper interface, StudentMapper, with method signatures that are the same as those of the mapped statements in StudentMapper.xml. Note that the StudentMapper.xml namespace value is set to com.mybatis3.mappers.StudentMapper, which is a fully qualified name of the StudentMapper interface. This enables us to invoke mapped statements using the Mapper interface.

In StudentService.java, we have created a new SqlSession in each method and closed it after the method completes. Each thread should have its own instance of SqlSession. Instances of SqlSession objects are not thread safe and should not be shared. So the best scope for SqlSession is the method scope. From a web application perspective, SqlSession should have a request scope.

 

Sample domain model


In this section, we will discuss the sample domain model that represents an e-learning application that will be used throughout the book.

An e-learning system enables students to enroll for courses and take lessons through web-based mediums, such as virtual classes or desktop-sharing systems.

The tutors who are interested in teaching courses through an e-learning system can register with the system and announce the course details that they are going to teach.

The course details include course name, description, and duration. Students from across the globe can register and enroll for the courses that they want to learn.

The e-learning system provides a course search functionality where you can search for the available courses by course name, tutor, start date, or end date.

The following diagram represents the database schema for our e-learning application:

 

Summary


In this chapter, we discussed about MyBatis and the advantages of using MyBatis instead of plain JDBC for database access. We learned how to create a project, install MyBatis jar dependencies, create a MyBatis configuration file, and configure SQL mapped statements in Mapper XML files. We created a Service class to insert and get data from the database using MyBatis. We created a JUnit test case for testing Service.

In the next chapter, we will discuss bootstrapping MyBatis using XML and Java-API-based approaches in detail.

About the Author

  • K. Siva Prasad Reddy

    K. Siva Prasad Reddy is a Senior Software Engineer living in Hyderabad, India, and having more than seven years of experience in developing enterprise applications with Java and JavaEE technologies. Siva is a Sun Certified Java Programmer and has a lot of experience in server-side technologies such as Java, JavaEE, Spring, Hibernate, MyBatis, JSF, PrimeFaces, and WebServices (SOAP/REST). Siva is also the author of Java Persistence with MyBatis 3, Packt Publishing. Siva normally shares the knowledge he has acquired on his blog at www.sivalabs.in. If you want to find out more about his work, you can follow him on Twitter (@sivalabs) and GitHub (https://github.com/sivaprasadreddy).

    Browse publications by this author

Latest Reviews

(2 reviews total)
Perfect! Molto professionale.
Good
Book Title
Access this book and the full library for FREE
Access now