SqlSession 获取 Mapper
SqlSession.getMapper() 是获取 Mapper 代理对象的核心方法。理解其工作机制和生命周期管理,有助于写出更健壮的代码。
getMapper 方法使用
基础用法
Java
// 获取 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 获取 Mapper 代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 调用接口方法
User user = mapper.selectById(1L);
System.out.println(user);
sqlSession.commit();
} finally {
sqlSession.close();
}
多个 Mapper 操作
Java
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
// 同一事务中的操作
User user = new User();
user.setUsername("test");
userMapper.insert(user);
Order order = new Order();
order.setUserId(user.getId());
orderMapper.insert(order);
sqlSession.commit();
} finally {
sqlSession.close();
}
注意:同一个 SqlSession 获取的多个 Mapper 共享同一事务,任一操作失败需整体回滚。
getMapper 内部工作原理
getMapper() 的调用链如下:
Java
sqlSession.getMapper(UserMapper.class)
↓
Configuration.getMapper(type, this)
↓
MapperRegistry.getMapper(type, this)
↓
MapperProxyFactory.newInstance(sqlSession)
↓
Proxy.newProxyInstance(classLoader, interfaces, mapperProxy)
↓
返回代理对象
核心源码分析
Java
// 1. SqlSession 调用
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// 2. Configuration 委托给 MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// 3. MapperRegistry 查找工厂并创建代理
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> factory = knownMappers.get(type);
if (factory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
return factory.newInstance(sqlSession);
}
// 4. MapperProxyFactory 创建代理
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy
);
}
MapperProxy 拦截器
代理对象的方法调用被 MapperProxy.invoke() 拦截:
Java
public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object 类方法直接调用,不走 SQL 逻辑
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 获取或缓存 MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行 SQL
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, key ->
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
);
}
}
生命周期管理
SqlSession 生命周期
| 范围 | 说明 | 推荐做法 |
|---|---|---|
| 请求级别 | 每个 HTTP 请求一个 SqlSession | 推荐,线程安全 |
| 方法级别 | 每个方法内创建和关闭 | 最安全,推荐 |
| 类级别 | 整个类共享一个 SqlSession | 不推荐,线程不安全 |
| 全局级别 | 全局单例 SqlSession | 错误,严重线程安全问题 |
Java
// 推荐:方法级别管理(try-finally)
public User getUserById(Long id) {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectById(id);
} finally {
sqlSession.close();
}
}
Spring 环境下的生命周期
在 Spring 环境中,SqlSession 由 Spring 容器管理,无需手动创建和关闭:
Java
@Service
public class UserService {
@Autowired
private UserMapper userMapper; // Spring 自动注入代理对象
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}
Spring 通过 SqlSessionTemplate 管理 SqlSession 的生命周期,每次方法调用时获取 SqlSession,执行后自动释放。
线程安全问题
SqlSession 不是线程安全的,不能在多线程间共享:
Java
// 错误:多线程共享 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.selectById(1L); // 线程不安全!
});
}
// 正确:每个线程独立 SqlSession
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
SqlSession session = sqlSessionFactory.openSession();
try {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.selectById(1L);
} finally {
session.close();
}
});
}
SqlSessionFactory 与 SqlSession 关系
text
SqlSessionFactory(线程安全,单例)
↓ openSession()
SqlSession(非线程安全,每次请求创建)
↓ getMapper()
Mapper 代理对象(无状态,可复用)
↓ 调用方法
执行 SQL
text
// SqlSessionFactory 通常在应用启动时创建一次
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 获取 SqlSession(非单例)
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
要点总结
- getMapper 通过 JDK 动态代理创建 Mapper 接口的代理对象
- 代理对象的方法调用被 MapperProxy 拦截,转为 SQL 执行
- SqlSession 不是线程安全的,不能在多线程间共享
- 推荐在方法级别管理 SqlSession 生命周期,使用 try-finally 确保关闭
- Spring 环境下由 SqlSessionTemplate 自动管理生命周期,无需手动操作
- SqlSessionFactory 是线程安全的单例,SqlSession 是每次请求创建的新实例
- 同一个 SqlSession 获取的多个 Mapper 共享同一事务
📝 发现内容有误?点击此处直接编辑