Appearance
MyBatis 高级特性
涵盖 TypeHandler、枚举处理、批量操作、存储过程调用等 MyBatis 高级用法,帮助你应对复杂业务场景。
一、TypeHandler(类型处理器)
作用:实现 Java 类型与 JDBC 类型之间的相互转换
内置 TypeHandler:
IntegerTypeHandler → int / Integer ↔ INTEGER
StringTypeHandler → String ↔ VARCHAR
DateTypeHandler → Date ↔ TIMESTAMP
BigDecimalTypeHandler → BigDecimal ↔ DECIMAL
BooleanTypeHandler → boolean ↔ BOOLEAN
...1.1 自定义 TypeHandler
场景: 将数据库中的逗号分隔字符串("1,2,3")转换为 Java 的 List<Long>。
java
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)
public class ListTypeHandler extends BaseTypeHandler<List<Long>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
List<Long> parameter, JdbcType jdbcType) throws SQLException {
// Java → 数据库:List<Long> → "1,2,3"
String value = parameter.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
ps.setString(i, value);
}
@Override
public List<Long> getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 数据库 → Java:String → List<Long>
String value = rs.getString(columnName);
return parseToList(value);
}
@Override
public List<Long> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return parseToList(value);
}
@Override
public List<Long> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return parseToList(value);
}
private List<Long> parseToList(String value) {
if (value == null || value.isEmpty()) {
return new ArrayList<>();
}
return Arrays.stream(value.split(","))
.map(Long::parseLong)
.collect(Collectors.toList());
}
}1.2 注册 TypeHandler
xml
<!-- 方式一:全局注册 -->
<typeHandlers>
<typeHandler handler="com.example.handler.ListTypeHandler"/>
<!-- 扫描包 -->
<package name="com.example.handler"/>
</typeHandlers>xml
<!-- 方式二:在 resultMap 中指定 -->
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="roleIds" column="role_ids"
typeHandler="com.example.handler.ListTypeHandler"/>
</resultMap>java
// 方式三:在字段上标注(MyBatis Plus 支持)
@TableField(typeHandler = ListTypeHandler.class)
private List<Long> roleIds;二、枚举处理
2.1 默认处理(EnumTypeHandler)
java
public enum Gender {
MALE, FEMALE
}java
// 实体类
public class User {
private Gender gender; // 数据库中存 "MALE" / "FEMALE"
}xml
<!-- 默认使用 EnumTypeHandler,存枚举的 name() -->
<insert id="insert">
INSERT INTO user (gender) VALUES (#{gender})
</insert>2.2 自定义枚举处理(EnumOrdinalTypeHandler)
java
// 数据库中存 0/1
public class User {
@EnumValue // MyBatis Plus 注解
private Gender gender;
}xml
<!-- 方式一:全局配置 -->
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.example.enums.Gender"/>
</typeHandlers>java
// 方式二:自定义枚举 TypeHandler
public class GenderTypeHandler extends BaseTypeHandler<Gender> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
Gender parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode()); // 存 code 值
}
@Override
public Gender getNullableResult(ResultSet rs, String columnName) throws SQLException {
int code = rs.getInt(columnName);
return Gender.fromCode(code); // 根据 code 获取枚举
}
@Override
public Gender getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int code = rs.getInt(columnIndex);
return Gender.fromCode(code);
}
@Override
public Gender getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int code = cs.getInt(columnIndex);
return Gender.fromCode(code);
}
}
// 枚举定义
public enum Gender {
MALE(1, "男"),
FEMALE(2, "女");
private final int code;
private final String desc;
Gender(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() { return code; }
public static Gender fromCode(int code) {
for (Gender gender : values()) {
if (gender.code == code) return gender;
}
throw new IllegalArgumentException("Unknown code: " + code);
}
}三、批量操作
3.1 批量插入
xml
<insert id="insertBatch">
INSERT INTO user (name, age, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email})
</foreach>
</insert>java
// 使用 ExecutorType.BATCH 提升性能
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public void batchInsert(List<User> users) {
// 方式一:使用 BATCH 执行器
SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory()
.openSession(ExecutorType.BATCH, false);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
}
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
throw e;
} finally {
sqlSession.close();
}
}
// 方式二:MyBatis Plus 的 saveBatch
// userService.saveBatch(users);3.2 批量更新
xml
<update id="updateBatch">
<foreach collection="list" item="user" separator=";">
UPDATE user
SET name = #{user.name}, age = #{user.age}
WHERE id = #{user.id}
</foreach>
</update>注意: 批量更新需要 JDBC 连接 URL 添加
allowMultiQueries=true。
3.3 批量删除
xml
<delete id="deleteByIds">
DELETE FROM user WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>四、存储过程调用
sql
-- 存储过程定义
DELIMITER //
CREATE PROCEDURE get_user_by_id(IN userId BIGINT, OUT userName VARCHAR(50))
BEGIN
SELECT name INTO userName FROM user WHERE id = userId;
END //
DELIMITER ;4.1 XML 方式
xml
<select id="callProcedure" statementType="CALLABLE" parameterType="map">
{call get_user_by_id(
#{userId, mode=IN, jdbcType=BIGINT},
#{userName, mode=OUT, jdbcType=VARCHAR}
)}
</select>java
// Mapper 接口
void callProcedure(Map<String, Object> params);
// 调用
Map<String, Object> params = new HashMap<>();
params.put("userId", 1L);
userMapper.callProcedure(params);
System.out.println(params.get("userName")); // 获取 OUT 参数4.2 注解方式
java
@Select("{call get_user_by_id(#{userId, mode=IN, jdbcType=BIGINT}, " +
"#{userName, mode=OUT, jdbcType=VARCHAR})}")
@Options(statementType = StatementType.CALLABLE)
void callProcedure(Map<String, Object> params);五、RowBounds 分页
java
// MyBatis 内置的逻辑分页(不推荐,性能差)
RowBounds rowBounds = new RowBounds(0, 10); // 偏移量,每页数量
List<User> users = sqlSession.selectList("com.example.mapper.UserMapper.selectAll",
null, rowBounds);注意: RowBounds 是逻辑分页,先查出全部数据再截取,性能极差。生产环境请使用 PageHelper 或 SQL 分页。
六、注解方式开发
java
@Mapper
public interface UserMapper {
// 查询
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
@Select("SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')")
List<User> selectByName(String name);
// 新增(返回自增主键)
@Insert("INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
// 修改
@Update("UPDATE user SET name=#{name}, age=#{age}, email=#{email} WHERE id=#{id}")
int update(User user);
// 删除
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Long id);
// 动态 SQL(使用 script 标签)
@Select("<script>" +
"SELECT * FROM user" +
"<where>" +
"<if test='name != null'>AND name = #{name}</if>" +
"<if test='age != null'>AND age = #{age}</if>" +
"</where>" +
"</script>")
List<User> selectByCondition(User user);
// 一对一关联
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "orders", column = "id",
many = @Many(select = "com.example.mapper.OrderMapper.selectByUserId"))
})
User selectWithOrders(Long id);
}七、SQL 注入器(MyBatis Plus)
MyBatis Plus 提供了 SQL 注入器,可以自定义全局方法。
java
// 自定义 SQL 注入器
@Component
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(configuration, mapperClass);
methodList.add(new DeleteAllMethod());
return methodList;
}
}
// 自定义方法
public class DeleteAllMethod extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass,
TableInfo tableInfo) {
String sql = "DELETE FROM " + tableInfo.getTableName();
String method = "deleteAll";
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
}
}八、速记总结
高级特性速记:
TypeHandler → 类型转换(Java ↔ JDBC)
枚举处理 → 自定义枚举 TypeHandler
批量操作 → foreach + BATCH 执行器
存储过程 → statementType="CALLABLE"
注解开发 → @Select/@Insert/@Update/@Delete
分页 → PageHelper(推荐)> RowBounds
速记口诀:
类型转换 TypeHandler,枚举处理自定义,
批量操作用 foreach,存储过程 CALLABLE,
注解开发简单用,复杂 SQL 还是 XML 好。九、面试要点
| 问题 | 答案要点 |
|---|---|
| TypeHandler 的作用? | Java 类型和 JDBC 类型的双向转换 |
| 枚举怎么处理? | 自定义 EnumTypeHandler 或 EnumOrdinalTypeHandler |
| 批量操作如何提升性能? | 使用 ExecutorType.BATCH |
| 注解和 XML 怎么选? | 简单 SQL 用注解,复杂 SQL 用 XML |
