All Products
Search
Document Center

Lindorm:Develop applications using the Java ORM framework MyBatis

Last Updated:Mar 28, 2026

LindormTable is compatible with the MySQL protocol, so you can use the MyBatis Object-Relational Mapping (ORM) framework to build Java applications. MyBatis separates SQL from application code by letting you define data access logic in mapper interfaces, keeping business logic free of embedded SQL.

By the end of this tutorial, you will have:

  • Added MyBatis and MySQL JDBC driver dependencies to a Maven project

  • Configured mybatis-config.xml to connect to LindormTable

  • Defined a mapper interface with create, read, upsert, and delete operations

  • Run a working demo against LindormTable

Prerequisites

Before you begin, ensure that you have:

Set up the project

Step 1: Add dependencies

In your Maven project, add the following dependencies to the dependencies block in pom.xml:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.14</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.3.0</version>
</dependency>

Step 2: Configure the connection

In the resources folder, create mybatis-config.xml with the connection settings for LindormTable:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://<MySQL-compatible endpoint>:33060/<database>?sslMode=disabled&amp;allowPublicKeyRetrieval=true&amp;useServerPrepStmts=true&amp;useLocalSessionState=true&amp;rewriteBatchedStatements=true&amp;cachePrepStmts=true&amp;prepStmtCacheSize=100&amp;prepStmtCacheSqlLimit=50000000"/>
                <property name="username" value="<username>"/>
                <property name="password" value="<password>"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="org.example.UserMapper"/>
    </mappers>
</configuration>

Replace the following placeholders:

PlaceholderDescription
<MySQL-compatible endpoint>The MySQL-compatible endpoint of your instance. To get this value, see View endpoints.
<database>The name of the database to connect to. If omitted, the client connects to the default database.
<username>The database username.
<password>The database password. To reset it, see Change a user password.

Choosing the right endpoint

Access typeEndpoint to use
From an Elastic Compute Service (ECS) instance in the same virtual private cloud (VPC)VPC address of the MySQL-compatible endpoint
From the internetPublic address of the MySQL-compatible endpoint

If your application runs on an ECS instance, use the VPC endpoint for better security and lower latency. To connect over the internet, first enable the public endpoint: in the console, go to Database Connection > Wide Table Engine, and click Enable Public Endpoint.

For details on the connection string parameters and why they improve performance, see Connection configuration.

Step 3: Define the data model

Create a User class to represent rows in the demo table:

package org.example;

import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.sql.Timestamp;

public class User {
  private int userId;
  private String userName;
  private double height;
  private long score;
  private Timestamp createTime;
  private Date birthday;
  private byte[] digest;

  public User(int userId, String userName, double height, long score,
      Timestamp createTime, Date birthday, byte[] digest) {
    this.userId = userId;
    this.userName = userName;
    this.height = height;
    this.score = score;
    this.createTime = createTime;
    this.birthday = birthday;
    this.digest = digest;
  }

  public int getUserId() { return userId; }
  public void setUserId(int userId) { this.userId = userId; }

  public String getUserName() { return userName; }
  public void setUserName(String userName) { this.userName = userName; }

  public double getHeight() { return height; }
  public void setHeight(double height) { this.height = height; }

  public long getScore() { return score; }
  public void setScore(long score) { this.score = score; }

  public Timestamp getCreateTime() { return createTime; }
  public void setCreateTime(Timestamp createTime) { this.createTime = createTime; }

  public Date getBirthday() { return birthday; }
  public void setBirthday(Date birthday) { this.birthday = birthday; }

  public byte[] getDigest() { return digest; }
  public void setDigest(byte[] digest) { this.digest = digest; }

  @Override
  public String toString() {
    return "User{" + "userId=" + userId + ", userName='" + userName + '\'' +
        ", height=" + height + ", score=" + score + ", createTime=" + createTime +
        ", birthday=" + birthday + ", digest=" + new String(digest, StandardCharsets.UTF_8) + '}';
  }
}

Step 4: Create the mapper

Create a UserMapper interface. Each method is annotated with the SQL statement it runs against LindormTable:

package org.example;

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

import java.util.List;

public interface UserMapper {

  @Update("create table if not exists demo_user(`id` INT, `name` VARCHAR, `height` DOUBLE, `score` BIGINT, `createtime` TIMESTAMP, `birthday` DATE, digest VARBINARY, primary key(id))")
  void createUserTable();

  @Update("drop table if exists demo_user")
  void dropUserTable();

  @Insert("upsert into demo_user(`id`,`name`,`height`,`score`,`createtime`,`birthday`,`digest`) values(#{userId},#{userName},#{height},#{score},#{createTime},#{birthday},#{digest})")
  int upsertUser(User user);

  @Delete("delete from demo_user where `id` = #{userId}")
  int deleteUser(@Param("userId") int userId);

  @Select("select * from demo_user where `id` = #{userId}")
  User selectOneUser(@Param("userId") int userId);

  @Select("select * from demo_user")
  List<User> selectAllUser();
}

All write operations use upsert instead of insert — this is LindormTable's preferred syntax for inserting or updating rows in a single statement.

Step 5: Write the application code

Load the configuration, open a SqlSession, and run CRUD operations through the mapper:

package org.example;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.sql.Timestamp;

public class MybatisDemo {
  public static void main(String[] args) throws Exception {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    try (SqlSession session = sqlSessionFactory.openSession()) {
      UserMapper mapper = session.getMapper(UserMapper.class);

      // Create the table
      mapper.createUserTable();

      // Verify the table is empty
      System.out.println(mapper.selectAllUser());

      // Insert two users
      User user1 = new User(1, "zhangsan", 1.8, 100,
          new Timestamp(System.currentTimeMillis()),
          Date.valueOf("1995-03-02"),
          "hello".getBytes(StandardCharsets.UTF_8));
      User user2 = new User(2, "lisi", 1.7, 90,
          new Timestamp(System.currentTimeMillis()),
          Date.valueOf("1996-08-02"),
          "world".getBytes(StandardCharsets.UTF_8));

      mapper.upsertUser(user1);
      mapper.upsertUser(user2);

      // Query all users and a single user
      System.out.println(mapper.selectAllUser());
      System.out.println(mapper.selectOneUser(1));

      // Delete user1 and verify
      mapper.deleteUser(1);
      System.out.println(mapper.selectAllUser());

      // Update user2's score using upsert and verify
      user2.setScore(99);
      mapper.upsertUser(user2);
      System.out.println(mapper.selectAllUser());

      // Clean up
      mapper.dropUserTable();
    }
  }
}

Complete example

Download the full sample project: mybatis-demo.zip.

After a successful run, the output looks like this:

[User{userId=1, userName='zhangsan', height=1.8, score=100, createTime=2023-12-02 09:39:17.63, birthday=1995-03-02, digest=hello}, User{userId=2, userName='lisi', height=1.7, score=90, createTime=2023-12-02 09:39:17.63, birthday=1996-08-02, digest=world}]
User{userId=1, userName='zhangsan', height=1.8, score=100, createTime=2023-12-02 09:39:17.63, birthday=1995-03-02, digest=hello}
[User{userId=2, userName='lisi', height=1.7, score=90, createTime=2023-12-02 09:39:17.63, birthday=1996-08-02, digest=world}]
[User{userId=2, userName='lisi', height=1.7, score=99, createTime=2023-12-02 09:39:17.63, birthday=1996-08-02, digest=world}]

FAQ

Why does selectAllUser() throw "ExecutionPlaceholder cannot be cast to java.util.List"?

The full error is:

### Cause: java.lang.ClassCastException: class org.apache.ibatis.executor.ExecutionPlaceholder cannot be cast to class java.util.List

SqlSession and Mapper objects in MyBatis are not thread-safe. This error occurs when multiple threads access the same SqlSession or Mapper concurrently. Make sure each thread creates its own SqlSession and obtains its own mapper from it.

What is the difference between #{} and ${} placeholders?

#{}${}
ImplementationJDBC PreparedStatement precompiled parameter bindingDirect string replacement
SQL injection riskNoneYes
PerformanceHigh — execution plan is reusableLow — each statement is re-parsed
Use whenBinding dynamic values such as WHERE conditionsInjecting SQL fragments such as table names or ORDER BY columns

Use #{} for all parameter values. Reserve ${} for dynamic SQL structure where parameterized binding is not possible.

Important

#{} only produces a true parameterized query when useServerPrepStmts=true is set in the connection string. Without it, the MySQL JDBC driver rewrites the statement into a string literal at runtime, bypassing precompilation. For details, see Connection configuration.

What's next