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.xmlto connect to LindormTableDefined a mapper interface with create, read, upsert, and delete operations
Run a working demo against LindormTable
Prerequisites
Before you begin, ensure that you have:
The MySQL compatibility feature enabled on your LindormTable instance. For details, see Enable the MySQL compatibility feature.
JDK 1.8 or later installed
Your client IP address added to the whitelist of your Lindorm instance. For details, see Configure whitelists.
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&allowPublicKeyRetrieval=true&useServerPrepStmts=true&useLocalSessionState=true&rewriteBatchedStatements=true&cachePrepStmts=true&prepStmtCacheSize=100&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:
| Placeholder | Description |
|---|---|
<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 type | Endpoint 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 internet | Public 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.ListSqlSession 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?
#{} | ${} | |
|---|---|---|
| Implementation | JDBC PreparedStatement precompiled parameter binding | Direct string replacement |
| SQL injection risk | None | Yes |
| Performance | High — execution plan is reusable | Low — each statement is re-parsed |
| Use when | Binding dynamic values such as WHERE conditions | Injecting 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.
#{} 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
MyBatis documentation — mapper XML syntax, dynamic SQL, and advanced configuration
Connect via Java JDBC — connection string parameters and performance tuning for LindormTable