Implementing Java Persistence with MyBatis: Core Concepts and Usage
Core Concepts of MyBatis
MyBatis is a persistence framework for Java that enables custom SQL, stored procedures, and advanced object-relational mapping. Unlike full ORM tools like Hibernate, it focuses on flexible SQL control through configuration and ennotations, bridging Java objects and database records.
Configuration File
The central configuration file (mybatis-config.xml) defines database connections, transaction management, and mappers. Example:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<environments default="prod">
<environment id="prod">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/demo_db"/>
<property name="username" value="admin"/>
<property name="password" value="secret"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/demo/mapper/AppUserMapper.xml"/>
</mappers>
</configuration>
Mapper Definitions
Mapper XML files map SQL statements to Java methods. Example AppUserMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.demo.mapper.AppUserMapper">
<select id="fetchUserById" parameterType="int" resultType="com.demo.model.AppUser">
SELECT * FROM app_users WHERE id = #{userId}
</select>
<insert id="addUser" parameterType="com.demo.model.AppUser">
INSERT INTO app_users (full_name, contact_email) VALUES (#{fullName}, #{contactEmail})
</insert>
<update id="modifyUser" parameterType="com.demo.model.AppUser">
UPDATE app_users SET full_name = #{fullName}, contact_email = #{contactEmail} WHERE id = #{id}
</update>
<delete id="removeUser" parameterType="int">
DELETE FROM app_users WHERE id = #{userId}
</delete>
</mapper>
Implementation Steps
1. Database Table and Model
Create a table app_users:
CREATE TABLE app_users (
id INT AUTO_INCREMENT PRIMARY KEY,
full_name VARCHAR(255) NOT NULL,
contact_email VARCHAR(255) NOT NULL
);
Corresponding Java model:
package com.demo.model;
public class AppUser {
private int id;
private String fullName;
private String contactEmail;
// Getters and setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public String getContactEmail() { return contactEmail; }
public void setContactEmail(String contactEmail) { this.contactEmail = contactEmail; }
}
2. Mapper Interface
Define interface matching the XML mapper:
package com.demo.mapper;
import com.demo.model.AppUser;
import java.util.List;
public interface AppUserMapper {
AppUser fetchUserById(int userId);
void addUser(AppUser user);
void modifyUser(AppUser user);
void removeUser(int userId);
List<AppUser> queryUsers(AppUser filter);
}
3. Usage Example
Execute operations via SqlSession:
package com.demo;
import com.demo.mapper.AppUserMapper;
import com.demo.model.AppUser;
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.Reader;
public class MyBatisDemo {
public static void main(String[] args) throws Exception {
try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
try (SqlSession session = factory.openSession()) {
AppUserMapper mapper = session.getMapper(AppUserMapper.class);
// Add user
AppUser newUser = new AppUser();
newUser.setFullName("Alice Smith");
newUser.setContactEmail("alice@example.com");
mapper.addUser(newUser);
// Fetch user
AppUser fetched = mapper.fetchUserById(newUser.getId());
System.out.printf("Fetched: %s (%s)\n", fetched.getFullName(), fetched.getContactEmail());
// Modify user
fetched.setFullName("Alice Johnson");
mapper.modifyUser(fetched);
// Remove user
mapper.removeUser(fetched.getId());
session.commit();
}
}
}
}
Advnaced Features
Dynamic SQL
Generate conditional SQL using tags like <if>:
<select id="queryUsers" resultType="com.demo.model.AppUser">
SELECT * FROM app_users
<where>
<if test="fullName != null">AND full_name = #{fullName}</if>
<if test="contactEmail != null">AND contact_email = #{contactEmail}</if>
</where>
</select>
Caching Mechanisms
- First-level cache: Enabled by default, scoped to
SqlSession. - Second-level cache: Mapper-scoped, enabled via
<cache/>in mapper XML:
Enable globally in<mapper namespace="com.demo.mapper.AppUserMapper"> <cache/> <!-- SQL mappings --> </mapper>mybatis-config.xml:<settings> <setting name="cacheEnabled" value="true"/> </settings>
Plugin System
Intercept SQL execution phases. Example logger plugin:
package com.demo.plugin;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Properties;
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class SqlExecutionLogger implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("SQL execution started");
Object result = invocation.proceed();
System.out.println("SQL execution completed");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties props) {}
}
Register in mybatis-config.xml:
<plugins>
<plugin interceptor="com.demo.plugin.SqlExecutionLogger"/>
</plugins>