欢迎光临
我们一直在努力

第一篇:MyBatis 到底解决了什么问题?为什么 JDBC 会被它淘汰?

Mapper 接口为什么没有实现类,却能执行 SQL?

如果你刚接触 MyBatis。

可能不会觉得这有什么问题。

但如果你认真想一下,其实这件事非常离谱。

我们平时写 Java:

public interface UserService {

User getById(Long id);

}

接口是不能直接执行的。

因为接口没有实现。

所以必须写:

@Service
public class UserServiceImpl implements UserService {

@Override
public User getById(Long id) {
return null;
}
}

然后才能调用:

userService.getById(1L);

这是所有 Java 开发都知道的事情。


但到了 MyBatis。

事情突然变了。

我们只写一个接口:

@Mapper
public interface UserMapper {

User selectById(Long id);

}

再写一条 SQL:

<select id="selectById">
select * from user where id = #{id}
</select>

然后直接调用:

userMapper.selectById(1L);

竟然成功执行了。


问题来了。

接口没有实现类。

那到底是谁执行了这个方法?


先思考一个问题

假设让你设计 MyBatis。

用户写:

userMapper.selectById(1L);

你会怎么实现?

最简单的方法当然是:

public class UserMapperImpl implements UserMapper {

@Override
public User selectById(Long id) {

return jdbcTemplate.queryForObject(...);
}

}

但是很快你会发现。

项目里可能有:

100个Mapper

1000个Mapper

甚至5000个Mapper

难道每个 Mapper 都生成一个实现类?

显然不现实。


MyBatis 的答案:动态代理

MyBatis 发现。

所有 Mapper 干的事情其实都一样。

无非就是:

接收参数

找到对应SQL

执行SQL

返回结果

既然逻辑一样。

那根本没必要生成大量实现类。

只需要生成一个代理对象即可。


代理对象到底是什么?

假设你调用:

userMapper.selectById(1L);

实际上执行的并不是:

UserMapper

而是:

com.sun.proxy.$ProxyXX

类似这样:

UserMapper mapper = proxy;

当方法被调用时:

mapper.selectById(1L);

最终进入:

invoke()

方法。

public Object invoke(
Object proxy,
Method method,
Object[] args) {
}

这里才是真正的入口。


MyBatis 怎么知道执行哪条 SQL?

假设调用:

userMapper.selectById(1L);

MyBatis 会拿到:

method.getName()

结果:

selectById

然后结合:

UserMapper.class

拼出唯一标识:

com.demo.mapper.UserMapper.selectById

这个字符串非常重要。

因为在启动时。

MyBatis 已经把 XML 中的 SQL 全部解析好了。

例如:

<select id="selectById">
select * from user where id = #{id}
</select>

最终会保存成:

com.demo.mapper.UserMapper.selectById
↓
SQL

这样就建立起了映射关系。


真正执行 SQL 的是谁?

很多人以为:

MapperProxy 负责执行 SQL。

其实不是。

MapperProxy 只是中间人。

它最终会调用:

SqlSession

源码链路大概如下:

MapperProxy
↓
MapperMethod
↓
SqlSession
↓
Executor
↓
JDBC
↓
Database

真正执行 SQL 的核心其实是:

Executor

后面我们会详细分析。


看一眼源码

Mapper 的代理对象创建入口:

MapperProxyFactory

核心代码:

protected T newInstance(MapperProxy<T> mapperProxy) {

return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[]{mapperInterface},
mapperProxy);
}

是不是很熟悉?

上一篇 Spring 系列讲动态代理时。

我们已经见过类似代码。

本质上都是:

JDK Dynamic Proxy


为什么这样设计?

如果不用动态代理。

假设系统有:

300个Mapper

就需要:

300个实现类

维护成本极高。

而使用动态代理以后:

一个 MapperProxy

就能代理所有 Mapper 接口。

这也是 MyBatis 设计最巧妙的地方。


总结

很多人以为:

userMapper.selectById()

是在调用接口。

实际上不是。

真正执行的是 MyBatis 创建的代理对象。

整个流程如下:

Mapper接口
↓
JDK动态代理
↓
MapperProxy
↓
MapperMethod
↓
SqlSession
↓
Executor
↓
数据库

所以:

Mapper 接口之所以没有实现类还能执行 SQL,本质上是因为 MyBatis 在运行期间为它创建了动态代理对象。

这也是整个 MyBatis 框架最核心的设计之一。


上一篇:

《Spring 系列完结》

下一篇:

《Mapper 接口为什么没有实现类,却能执行 SQL?》

赞(0)
未经允许不得转载:171主机测评 » 第一篇:MyBatis 到底解决了什么问题?为什么 JDBC 会被它淘汰?
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址