Lazy Loading using MyBatis 3 with Java

hemantvsn picture hemantvsn · Jun 3, 2014 · Viewed 9.7k times · Source

I am using Mybatis (3.2.7 version) as an ORM framework for my JAVA project. As I'm from JPA background, I was keen to explore the LAZYLOADING supported by Mybatis. But I couldn't get across anything substantial.
(I am configuring MYBATIS using JAVA API and annotations solely for querying purpose)

As per the Mybatis documentation: 1. lazyLoadingEnabled: default value=TRUE

Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. This value can be superseded for an specific relation by using the fetchType attribute on it.

2. aggressiveLazyLoading : default value=TRUE

When enabled, an object with lazy loaded properties will be loaded entirely upon a call to any of the lazy properties. Otherwise, each property is loaded on demand.

Using the following attributes, I tried the following code:

a. JAVA Classes :

Feedback.java

public class Feedback implements Serializable {
private static final long serialVersionUID = 1L;

private int id;
private String message;

   /**
   * while loading Feedback, I want sender object to be lazily loaded
   */
private User sender;
private boolean seen;

// getters and setters
}

User.java

public class User implements Serializable, {
private static final long serialVersionUID = 1L;
private int id;
private String email;

// getters and setters
}

b. DB schema:

Feedback table

                Table "public.feedback"

  Column | Type      |    Modifiers                       
-------------+-----------+-------------------------------------------------------
 id          | integer   | PRIMARY KEY
 seen        | boolean   | not null
 sender_id   | integer   | FOREIGN KEY (sender_id) REFERENCES users(id)
 message     | text      | 

User Table:

                Table "public.users"

Column   | Type     |     Modifiers                      
-------------+----------+----------------------------------------------------
id          | integer  | PRIMARY KEY
email       | text     | 

c. Configuring MyBatis via JAVA API:

DataSource dataSource = new PGSimpleDataSource();
        ((PGSimpleDataSource) dataSource).setServerName("localhost");
        ((PGSimpleDataSource) dataSource).setDatabaseName(dbName);
        ((PGSimpleDataSource) dataSource).setPortNumber(5432);
        ((PGSimpleDataSource) dataSource).setUser(new UnixSystem().getUsername());
        ((PGSimpleDataSource) dataSource).setPassword("");

        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment(dbName, transactionFactory, dataSource);
        Configuration configuration = new Configuration(environment);
             configuration.addMapper(FeedbackMapper.class);

            //
             configuration.setAggressiveLazyLoading(false);
             sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

d. Querying DB and DB Queries in Feedbackmapper:

d.1 Code in Feedbackmapper:

@Select("SELECT f.id, f.message, f.seen, f.sender_id FROM feedback f WHERE f.id= #{feedbackId}")
@Results(value = { 
        @Result(property = "id", column = "id"),
        @Result(property = "sender", column = "sender_id", javaType = User.class, one = @One(select = "getUser", fetchType=FetchType.DEFAULT))
})
public Feedback getFeedback(@Param("feedbackId") int feedbackId);

@Select("SELECT id, email FROM users WHERE id=#{id}")
public User getUser(int id);

d.2: Code to invoke the queries in feedbackMapper

    // setup Mybatis session factory and config
    Feedback feedback =feedbackMapper.getFeedback(70000);
    System.out.println(feedback);

But still the "sender" object is populated upon querying the getFeedback(id). I expect the sender object shouldn't be populated immediately but only when I call getSender() on the fetched feedback object . Please help.

My recent Observations:

Mybatis team has indeed got it wrong in their documentation ie in documentation:

  1. lazyLoadingEnabled: default value=TRUE

  2. aggressiveLazyLoading : default value=TRUE

    But looking at their source code:

     protected boolean lazyLoadingEnabled = false;
     protected boolean aggressiveLazyLoading = true;
    

    **However that being corrected, the results are not affected and lazy loading isnt working :( **

Answer

hemantvsn picture hemantvsn · Jun 12, 2014

I think I found a way to enable the lazyloading (though not cent-percent sure):

  • MyBatis documentation has following setting in configuration:

Setting : lazyLoadTriggerMethods

Description : Specifies which Object's methods trigger a lazy load

Valid Values : A method name list separated by commas

Default: equals,clone,hashCode,toString

  • As per the source code, this thing maps properly to what is given in documentation:

    public class Configuration {
    // other attributes + methods
    protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new         
    String[] { "equals", "clone", "hashCode", "toString" }));
    }
    
    • I have changed the Mybatis configuration to follows:

      TransactionFactory transactionFactory = new JdbcTransactionFactory();
      Environment environment = new Environment(dbName, transactionFactory,  dataSource);
      Configuration configuration = new Configuration(environment);
      
      /**
      *This is the cause why LAZY LOADING is working now
      */
        configuration.getLazyLoadTriggerMethods().clear();
      
        ///////////////////////////////////////////////////
        configuration.setLazyLoadingEnabled(true);
        configuration.setAggressiveLazyLoading(false);
      
    • The queries in Mapper (mostly unchanged):

      @Select("SELECT id, message, seen, sender_id 
      FROM feedback WHERE f.id= #{feedbackId}")
      @Results(value = { 
         @Result(property = "id", column = "id"),
         @Result(property = "sender", column = "sender_id", javaType = User.class, one = @One(select = "getUser"))
      
      // Set fetchType as DEFAULT or LAZY or don't set at all-- lazy loading takes place
      // Set fetchType as EAGER --sender Object is loaded immediately
      
      })
      public Feedback getFeedback(@Param("feedbackId") int feedbackId);
      
      @Select("SELECT id, email FROM users WHERE id=#{id}")
      public User getUser(int id);
      

- JAVA Code to invoke mapper

        FeedbackMapper mapper = sqlSession.getMapper(FeedbackMapper.class);
        Feedback feedback =mapper.getFeedback(69999);                   
        System.out.println("1. Feedback object before sender lazily load: \n"+ feedback);
        System.out.println("2. Sender loaded explicitly \n" +feedback.getSender());
        System.out.println("3. Feedback object after sender loading \n" + feedback);
  • Output of the CODE

1. Feedback object before sender lazily load:

{id : 69999,  message : message123, sender : null, seen : false}

2. Sender loaded explicitly 

{id : 65538 , email: [email protected]}

3. Feedback object after sender loading:

{id : 69999, message : message123, sender : {id : 65538, email : [email protected]},
 seen : false}

  • Though this works satisfactorily, upon doing

configuration.getLazyLoadTriggerMethods().clear();

However to lack of documentation in Mybatis, I'm not sure, whether this is associated with any drawbacks as such.