四、结果映射
4.1 基础映射
<!-- 简单映射 -->
<resultMap id="simpleMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="age" column="age"/>
</resultMap>
<!-- 类型处理器指定 -->
<resultMap id="typedMap" type="User">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time"
jdbcType="TIMESTAMP" javaType="java.util.Date"/>
</resultMap>
4.2 关联映射(一对一)
java
// 实体类
public class User {
private Long id;
private String username;
private UserDetail detail; // 一对一关联
// getter/setter
}
public class UserDetail {
private Long id;
private Long userId;
private String address;
private String phone;
// getter/setter
}
xml
<!-- 嵌套查询(延迟加载) -->
<resultMap id="userWithDetail" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<association property="detail" column="id"
select="com.example.mapper.UserDetailMapper.selectByUserId"/>
</resultMap>
<!-- 嵌套结果 -->
<resultMap id="userWithDetailResult" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<association property="detail" javaType="UserDetail">
<id property="id" column="detail_id"/>
<result property="address" column="address"/>
<result property="phone" column="phone"/>
</association>
</resultMap>
<select id="selectUserWithDetail" resultMap="userWithDetailResult">
SELECT u.id, u.username, d.id as detail_id, d.address, d.phone
FROM user u
LEFT JOIN user_detail d ON u.id = d.user_id
WHERE u.id = #{id}
</select>
4.3 集合映射(一对多)
public class Department {
private Long id;
private String name;
private List<User> users; // 一对多关联
// getter/setter
}
xml
<!-- 嵌套查询 -->
<resultMap id="deptWithUsers" type="Department">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="users" column="id"
select="com.example.mapper.UserMapper.selectByDeptId"
ofType="User"/>
</resultMap>
<!-- 嵌套结果 -->
<resultMap id="deptWithUsersResult" type="Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
<collection property="users" ofType="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="age" column="age"/>
</collection>
</resultMap>
<select id="selectDeptWithUsers" resultMap="deptWithUsersResult">
SELECT d.id as dept_id, d.name as dept_name,
u.id as user_id, u.username, u.age
FROM department d
LEFT JOIN user u ON d.id = u.dept_id
WHERE d.id = #{id}
</select>
4.4 鉴别器映射
xml
<!-- 根据字段值映射不同的结果 -->
<resultMap id="vehicleMap" type="Vehicle">
<id property="id" column="id"/>
<result property="type" column="type"/>
<discriminator javaType="int" column="type">
<case value="1" resultMap="carMap"/>
<case value="2" resultMap="truckMap"/>
</discriminator>
</resultMap>
<resultMap id="carMap" type="Car" extends="vehicleMap">
<result property="doorCount" column="door_count"/>
</resultMap>
<resultMap id="truckMap" type="Truck" extends="vehicleMap">
<result property="loadCapacity" column="load_capacity"/>
</resultMap>
五、注解开发
5.1 基础注解
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserAnnotationMapper {
// @Select 注解
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
// 多个参数
@Select("SELECT * FROM user WHERE username = #{username} AND age = #{age}")
User selectByUsernameAndAge(@Param("username") String username,
@Param("age") Integer age);
// @Insert 注解
@Insert("INSERT INTO user (username, age, create_time) VALUES (#{username}, #{age}, NOW())")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
// @Update 注解
@Update("UPDATE user SET username = #{username}, age = #{age} WHERE id = #{id}")
int update(User user);
// @Delete 注解
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Long id);
// 返回 Map
@Select("SELECT * FROM user WHERE id = #{id}")
@MapKey("id")
Map<Long, User> selectAsMap();
// 动态 SQL 使用 Script 注解
@Select({
"<script>",
"SELECT * FROM user",
"<where>",
" <if test='username != null'>",
" AND username LIKE CONCAT('%', #{username}, '%')",
" </if>",
" <if test='age != null'>",
" AND age = #{age}",
" </if>",
"</where>",
"</script>"
})
List<User> findByCondition(@Param("username") String username,
@Param("age") Integer age);
}
5.2 结果映射注解
// @Results 注解
@Select("SELECT id, username, age FROM user WHERE id = #{id}")
@Results(id = "userResultMap", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(property = "age", column = "age")
})
User selectById(Long id);
// 引用结果映射
@Select("SELECT * FROM user WHERE username = #{username}")
@ResultMap("userResultMap")
User selectByUsername(String username);
// 一对一关联
@Select("SELECT u.id, u.username, d.address FROM user u LEFT JOIN user_detail d ON u.id = d.user_id WHERE u.id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "detail", column = "id",
one = @One(select = "com.example.mapper.UserDetailMapper.selectByUserId"))
})
User selectUserWithDetail(Long id);
// 一对多关联
@Select("SELECT * FROM department WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "users", column = "id",
many = @Many(select = "com.example.mapper.UserMapper.selectByDeptId"))
})
Department selectDeptWithUsers(Long id);
5.3 动态 SQL 注解
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.annotations.DeleteProvider;
// SQL 构建器
public class UserSqlProvider {
public String selectByCondition(Map<String, Object> params) {
return new SQL() {
{
SELECT("*");
FROM("user");
if (params.get("username") != null) {
WHERE("username LIKE #{username}");
}
if (params.get("age") != null) {
WHERE("age = #{age}");
}
ORDER_BY("id DESC");
}}.toString();
}
public String insert(User user) {
return new SQL() {
{
INSERT_INTO("user");
VALUES("username", "#{username}");
VALUES("age", "#{age}");
VALUES("create_time", "NOW()");
}}.toString();
}
public String update(User user) {
return new SQL() {
{
UPDATE("user");
if (user.getUsername() != null) {
SET("username = #{username}");
}
if (user.getAge() != null) {
SET("age = #{age}");
}
SET("update_time = NOW()");
WHERE("id = #{id}");
}}.toString();
}
}
// 使用 Provider
public interface UserProviderMapper {
@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(Map<String, Object> params);
@InsertProvider(type = UserSqlProvider.class, method = "insert")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@UpdateProvider(type = UserSqlProvider.class, method = "update")
int update(User user);
}
六、高级特性
6.1 缓存机制
一级缓存(SqlSession 级别):
public class FirstLevelCacheDemo {
public void testFirstLevelCache() {
try (SqlSession session = MyBatisUtil.getSqlSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询,从数据库查询
User user1 = mapper.selectById(1L);
// 第二次查询,从一级缓存获取
User user2 = mapper.selectById(1L);
// 返回同一个对象
System.out.println(user1 == user2); // true
// 执行更新操作会清空缓存
mapper.update(new User(1L, "新名字", 30));
// 再次查询,从数据库查询
User user3 = mapper.selectById(1L);
// 手动清空缓存
session.clearCache();
}
}
}
二级缓存(Mapper 级别):
xml
<!-- 开启二级缓存 -->
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
<!-- Mapper XML 中配置二级缓存 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 基础缓存配置 -->
<cache/>
<!-- 高级缓存配置 -->
<cache
eviction="LRU" <!-- 缓存淘汰策略 -->
flushInterval="60000" <!-- 刷新间隔 60秒 -->
size="512" <!-- 缓存对象数量 -->
readOnly="false"/> <!-- 是否只读 -->
</mapper>
java
// 实体类需要实现 Serializable
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// ...
}
// 在 Mapper 接口上配置缓存
@CacheNamespace(eviction = FifoCache.class, flushInterval = 60000, size = 512)
public interface UserMapper {
// ...
}
// 使用参照缓存
@CacheNamespaceRef(UserMapper.class)
public interface RoleMapper {
// 使用 UserMapper 的缓存配置
}
6.2 延迟加载
xml
<!-- 配置延迟加载 -->
<configuration>
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 延迟加载触发方法 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
</configuration>
xml
<!-- 映射文件中配置延迟加载 -->
<resultMap id="userWithOrders" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<!-- fetchType 覆盖全局配置 -->
<collection property="orders" column="id"
select="com.example.mapper.OrderMapper.selectByUserId"
fetchType="lazy"/>
</resultMap>
java
// 使用代理对象触发加载
public class LazyLoadingDemo {
public void testLazyLoading() {
try (SqlSession session = MyBatisUtil.getSqlSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);
// 此时只加载了用户信息
System.out.println(user.getUsername());
// 访问订单时触发加载
List<Order> orders = user.getOrders(); // 执行额外查询
// 判断是否已加载
boolean loaded = PersistenceUtils.isLoaded(user.getOrders());
}
}
}
6.3 插件机制
java
// 实现 Interceptor 接口
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取参数
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
// 分页逻辑
if (parameter instanceof Page) {
Page page = (Page) parameter;
// 获取原始 SQL
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql();
// 添加分页 SQL
String pageSql = sql + " LIMIT " + page.getOffset() + "," + page.getLimit();
// 创建新的 BoundSql
BoundSql newBoundSql = new BoundSql(
ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
// 创建新的 MappedStatement
MappedStatement newMs = copyMappedStatement(ms, newBoundSql);
args[0] = newMs;
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 读取配置
String dialect = properties.getProperty("dialect");
}
private MappedStatement copyMappedStatement(MappedStatement ms, BoundSql boundSql) {
// 复制 MappedStatement 的实现
// ...
return ms;
}
}
配置插件:
xml
<configuration>
<plugins>
<plugin interceptor="com.example.plugin.PageInterceptor">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
6.4 批量操作
java
public class BatchDemo {
// 使用 ExecutorType.BATCH
public void batchInsert(List<User> users) {
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
}
session.commit();
}
}
// 使用 foreach 批量插入
public void batchInsertWithForeach(List<User> users) {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.batchInsert(users);
session.commit();
}
}
// 批量更新
public void batchUpdate(List<User> users) {
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.update(user);
}
session.commit();
}
}
// 批量删除
public void batchDelete(List<Long> ids) {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.deleteByIds(ids); // 使用 foreach 实现
session.commit();
}
}
}