基本概念
MyBatis 使用 Mapper Proxy 动态代理实现对数据库操作的封装,用户只需定义一个接口(Mapper 接口),在接口方法上通过注解或 XML 文件指定 SQL 语句的执行逻辑,而不需要手动实现接口的方法。
- Mapper 接口:定义了数据库操作方法的接口,例如 insertUser, getUserById 等。
- 动态代理:在运行时生成接口实现类的实例,将方法调用映射为对应的 SQL 操作。
MyBatis 的 Mapper Proxy 是一个实现了数据访问层接口的动态代理机制,用于在运行时将接口方法映射到特定的 SQL 语句执行逻辑。
主要组件
- SqlSession:MyBatis 的核心接口,用于执行 SQL 语句和管理事务。Mapper Proxy 通过 SqlSession 来执行数据库操作。
- Mapper Proxy:MyBatis 提供的动态代理实现类,用于代理 Mapper 接口。
- Mapper Proxy Factory:MapperProxyFactory 是用于创建 MapperProxy 对象的工厂类。每个 Mapper 接口对应一个 MapperProxyFactory 实例。
优势与局限性
优势
- 解耦:开发者只需定义接口和 SQL,无需关注具体实现。
- 简洁性:代码量减少,尤其适合简单的 CRUD 操作。
- 动态性:基于动态代理,无需生成实际的实现类。
局限性
- 性能开销:动态代理在运行时生成对象,性能略低于手写实现类。
- 可读性:当 Mapper 接口较多或方法复杂时,调试和维护可能变得困难。
- 复杂逻辑限制:复杂查询和多表关联操作需要依赖 XML 或其他方式实现。
额外扩展
- 使用 XML 配置或注解配置定义 SQL。
- 可以通过插件(Interceptors)扩展 MapperProxy 的行为,例如记录 SQL 执行时间、日志输出等。
运行机制
获取 Mapper 的实例
当通过 SqlSession.getMapper(Class type) 获取一个 Mapper 接口的实例时,MyBatis 会执行以下步骤:
- 代理工厂创建代理对象:MyBatis 使用 MapperProxyFactory 来生成一个 MapperProxy 的动态代理对象。
- MapperProxyFactory 内部调用 JDK 的动态代理工具生成代理类。
- 生成的代理类实现了用户定义的 Mapper 接口。
- 返回代理对象:该代理对象在表面上是用户定义的接口的实现类,但其方法调用会被拦截并处理。
方法调用拦截
当调用 Mapper 接口中的方法时,实际上会触发 MapperProxy 的 invoke 方法:
- 判断方法所属:MapperProxy 会首先检查方法是否是 Object 类的方法(例如 toString、hashCode 等),这些方法会直接调用 Object 的实现。
- 获取 SQL 映射:如果方法是 Mapper 接口的方法,MapperProxy 会通过 cachedMapperMethod 方法查找或创建 MapperMethod。MapperMethod 是一个内部工具类,表示 Mapper 接口方法和 SQL 语句的映射关系。
- 执行 SQL 操作:通过 MapperMethod.execute() 方法调用对应的 SQL。
SQL 执行
MapperMethod 执行时会调用 SqlSession 提供的数据库操作方法,例如:
- selectOne:执行单条记录查询
- selectList:执行多条记录查询
- insert:执行插入操作
- update:执行更新操作
- delete:执行删除操作
Mapper 代理
示例代码:
public class WzkicuPage01 {
public static void main(String[] args) throws Exception {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
PageHelper.startPage(2, 1);
List<WzkUser> dataList = userMapper.selectList();
for (WzkUser wzk : dataList) {
System.out.println(wzk);
}
sqlSession.close();
}
}
MyBatis 初始化时对接口的处理,MapperRegistry 是 Configuration 中的一个属性,它内部维护一个 HashMap 用于存放 mapper 接口的工厂类,每个接口对应一个工厂类。
<mappers>
<mapper resource="mapper.xml"/>
<mapper resource="OrderMapper.xml"/>
<mapper resource="UserMapper.xml"/>
<mapper resource="RoleMapper.xml"/>
<mapper resource="UserCacheMapper.xml"/>
</mappers>
当解析 mappers 标签的时候,会将对应的配置文件中的增删改查封装成 MappedStatement 对象,存入到 MappedStatements 中。
getMapper 源码解析
// DefaultSqlSession 中的 getMapper
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
// Configuration 中的 getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry 中的 getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从 MapperRegistry 中的 HashMap 中获取 MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 通过动态代理工厂生成实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MapperProxyFactory 类中的 newInstance 方法
public T newInstance(SqlSession sqlSession) {
// 创建 JDK 动态代理的 Handler 类
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用重载方法
return newInstance(mapperProxy);
}
// MapperProxy 类,实现了 InvocationHandler 接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
// 省略其他实现部分
}
invoke
在动态代理之后,执行时在 MapperProxy 中的 invoke 方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 1. 如果是 Object 类中定义的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 2. 如果是默认方法,调用默认方法
else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
// 3. 捕获并解包异常
throw ExceptionUtil.unwrapThrowable(t);
}
// 4. 获取 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 5. 执行 MapperMethod 对应的方法
return mapperMethod.execute(sqlSession, args);
}
execute 方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// Determine the type of operation based on the command's type
switch (command.getType()) {
case INSERT:
result = executeInsert(sqlSession, args);
break;
case UPDATE:
result = executeUpdate(sqlSession, args);
break;
case DELETE:
result = executeDelete(sqlSession, args);
break;
case SELECT:
result = executeSelect(sqlSession, args);
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// Check for null result and primitive return type, throw exception if necessary
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName() +
"' attempted to return null from a method with a primitive return type(" + method.getReturnType() + ").");
}
return result;
}
private Object executeInsert(SqlSession sqlSession, Object[] args) {
Object param = method.convertArgsToSqlCommandParam(args);
int rowCount = sqlSession.insert(command.getName(), param);
return rowCountResult(rowCount);
}
private Object executeUpdate(SqlSession sqlSession, Object[] args) {
Object param = method.convertArgsToSqlCommandParam(args);
int rowCount = sqlSession.update(command.getName(), param);
return rowCountResult(rowCount);
}
private Object executeDelete(SqlSession sqlSession, Object[] args) {
Object param = method.convertArgsToSqlCommandParam(args);
int rowCount = sqlSession.delete(command.getName(), param);
return rowCountResult(rowCount);
}
private Object executeSelect(SqlSession sqlSession, Object[] args) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
return null;
} else if (method.returnsMany()) {
return executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
return executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
return executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
Object result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
return Optional.ofNullable(result);
}
return result;
}
}