MyBatis Annotation-Based Development: A Comprehensive Guide
Introduction to MyBatis Annotation Development
MyBatis annotation-based development has become the preferred approach in modern Java projects, particularly those built with Spring Boot. This method allows developers to bypass the cumbersome XML configuration files and write data access layers in a more "Java-native" style, resulting in cleaner code and improved development efficiency.
Why Choose Annotation Development? Is XML Obsolete?
XML-based mapping is powerful and flexible, especially for complex dynamic SQL scenarios. However, it has some drawbacks:
- Multiple files: Each Mapper requires both a Java interface and an XML mapping file, making management scattered.
- Navigation challenges: Jumping from a Java method to its corresponding XML SQL statement in the IDE can be less convenient than reading code directly.
- Overhead for simple queries: A simple
SELECT * FROM user WHERE id = ?still requires a complete XML configuration.
Annotation development addresses these issues by embedding SQL statements directly into Mapper interface methods using annotations instead of XML tags.
Benefits include:
- Code cohesion: SQL and its corresponding Java method are written together, making everything immediately visible.
- Reduced file count: No separate XML files needed; a single Java file handles everything.
- Faster development: For small to medium projects or simple CRUD operations, annotation development is significantly faster.
This is a choice, not a replacement. In enterprise development, both approaches are often combined: annotations for simple, static SQL, and XML for complex queries requiring dynamic concatenation.
Core Annotations for CRUD Operations
MyBatis provides a set of core annotations corresponding to SQL operations:
@Select: Executes query operations (SELECT)@Insert: Executes insert operations (INSERT)@Update: Executes update operations (UPDATE)@Delete: Executes delete operations (DELETE)
These annotations take a string array as their value, containing the SQL statement directly.
Practical Implementation: Converting XML to Annotations
Let's convert a UserMapper from XML-based to annotation-based step by step.
Project Structure
mybatis-cache-demo/
└── src/
└── main/
├── java/
│ └── com/
│ └── example/
│ ├── entity/
│ │ └── User.java
│ ├── mapper/
│ │ ├── UserMapper.java (XML version for reference)
│ │ └── UserAnnotationMapper.java (annotation version)
│ └── test/
│ └── AnnotationTest.java
└── resources/
├── mappers/
│ └── UserMapper.xml
└── mybatis-config.xml
Step 1: Create UserAnnotationMapper Interface
Define the SQL statements using annotations instead of XML.
package com.example.mapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.*;
public interface UserAnnotationMapper {
/**
* Query operation using @Select annotation
* MyBatis automatically binds the method parameter to the SQL placeholder
*/
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(Integer id);
/**
* Insert operation using @Insert annotation
* @Options configures additional options like retrieving auto-generated keys
*/
@Insert("INSERT INTO user(username, password) VALUES(#{username}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
/**
* Update operation using @Update annotation
* @Param annotation names parameters for SQL reference
*/
@Update("UPDATE user SET username = #{newUsername} WHERE id = #{id}")
int updateUsername(@Param("id") Integer id, @Param("newUsername") String newUsername);
/**
* Delete operation using @Delete annotation
*/
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Integer id);
}
Step 2: Register the Mapper in mybatis-config.xml
For annotation-based Mappers, use <mapper class="..."> instead of <mapper resource="...">.
<mappers>
<!-- XML-based Mapper registration -->
<mapper resource="mappers/UserMapper.xml"/>
<!-- Annotation-based Mapper registration -->
<mapper class="com.example.mapper.UserAnnotationMapper"/>
</mappers>
MyBatis automatically recognizes annotations on the interface and generates the dynamic proxy without requiring an XML file.
Step 3: Test the Implementation
package com.example.test;
import com.example.entity.User;
import com.example.mapper.UserAnnotationMapper;
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.IOException;
import java.io.InputStream;
public class AnnotationTest {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession(false)) {
UserAnnotationMapper mapper = session.getMapper(UserAnnotationMapper.class);
// --- Testing @Select ---
System.out.println("--- Testing @Select ---");
User foundUser = mapper.findById(1);
System.out.println("Found User: " + foundUser);
// --- Testing @Insert ---
System.out.println("\n--- Testing @Insert ---");
User newUser = new User();
newUser.setUsername("annotationUser");
newUser.setPassword("anno123");
System.out.println("Before insert, newUser id: " + newUser.getId());
mapper.insertUser(newUser);
System.out.println("After insert, newUser id (auto-generated): " + newUser.getId());
// --- Testing @Update ---
System.out.println("\n--- Testing @Update ---");
int updatedRows = mapper.updateUsername(newUser.getId(), "updatedAnnotationUser");
System.out.println("Updated rows: " + updatedRows);
User updatedUser = mapper.findById(newUser.getId());
System.out.println("After update: " + updatedUser);
// --- Testing @Delete ---
System.out.println("\n--- Testing @Delete ---");
int deletedRows = mapper.deleteById(newUser.getId());
System.out.println("Deleted rows: " + deletedRows);
session.commit();
}
}
}
Execution Results
Running AnnotationTest.java demonstrates all CRUD operations working successfully via annotations. Notably, the @Insert test shows how @Options retrieves the auto-generated ID from the database and sets it back into the newUser object.
Advanced Annotations for Enterprise Applications
While simple CRUD operations work well with annotations, more complex scenarios like column-to-property mapping and one-to-many/many-to-one associations require additional annotations.
Using @Results and @Result for Column Mapping
These annotations replace the XML <resultMap> functionality.
@Results: Container similar to<resultMap>, can be referenced by other queries via itsidattribute.@Result: Maps a single column to a property. Setid = truefor primary key fields.
Enterprise example: Database columns with underscores, Java properties in camelCase
Assume the user table has columns user_name and user_pass, while the User entity uses username and password.
public interface UserAnnotationMapper {
/**
* Manual mapping using @Results and @Result
*/
@Results(id = "userResultMap", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "user_name"),
@Result(property = "password", column = "user_pass")
})
@Select("SELECT id, user_name, user_pass FROM user WHERE id = #{id}")
User findByIdWithResultMap(Integer id);
/**
* Reuse mapping with @ResultMap annotation
*/
@ResultMap("userResultMap")
@Select("SELECT id, user_name, user_pass FROM user")
List<User> findAll();
}
Best practice: Instead of manual mapping for each query, enable automatic camelCase mapping in mybatis-config.xml:
<setting name="mapUnderscoreToCamelCase" value="true"/>
Using @One and @Many for Associations
These annotations handle one-to-one and one-to-many relationships, replacing XML <association> and <collection>.
Enterprise example: Retrieving user with all orders (one-to-many)
First, create the Order entity and update User:
@Data
public class Order implements Serializable {
private Integer orderId;
private String orderName;
private Integer userId; // foreign key
}
@Data
public class User implements Serializable {
private Integer id;
private String username;
private List<Order> orders; // one user has many orders
}
Then define the association query in UserAnnotationMapper:
public interface UserAnnotationMapper {
@Select("SELECT * FROM `order` WHERE user_id = #{userId}")
List<Order> findOrdersByUserId(Integer userId);
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(
property = "orders",
javaType = List.class,
column = "id",
many = @Many(select = "com.example.mapper.UserAnnotationMapper.findOrdersByUserId")
)
})
User findUserWithOrders(Integer id);
}
Execution flow: When calling findUserWithOrders(1):
- MyBatis executes
SELECT * FROM user WHERE id = 1 - Gets the
idvalue from the result - Uses this
idas a parameter to callfindOrdersByUserId(1) - Sets the returned order list into the
Userobject'sordersproperty - Returns the complete
Userobject with orders
Using @SelectProvider for Dynamic SQL
The annotation value attribute only supports static SQL strings. For dynamic SQL, MyBatis provides @SelectProvider (and similar provider annotations).
Create a Provdier class with a method that returns a SQL string. This method can receive parameters and use Java logic (if-else, StringBuilder) to build the SQL dynamically.
Example: Dynamic user search
// Provider class
public class UserSqlProvider {
public static String findUserByCondition(Map<String, Object> params) {
return new SQL() {{
SELECT("*");
FROM("user");
if (params.get("username") != null) {
WHERE("username like #{username}");
}
if (params.get("email") != null) {
WHERE("email = #{email}");
}
}}.toString();
}
}
// Mapper interface
public interface UserAnnotationMapper {
@SelectProvider(type = UserSqlProvider.class, method = "findUserByCondition")
List<User> findUserByCondition(Map<String, Object> params);
}
Consideration: While Providers enable dynamic SQL, their logic in Java code is less intuitive than XML's dynamic SQL tags. Therefore, XML is generally preferred for complex dynamic SQL.
Annotation vs XML: Making the Right Choice
Enterprise Development Best Practice
Use annotations for simple, static SQL to accelerate development; use XML for complex, dynamic SQL to enable easier maintenance and optimization. These approaches are complementary allies, not competing alternatives.
Now you can convert existing examples to annotations and experiment with association queries and dynamic SQL Providers to understand the differences and trade-offs between these two development approaches.