本文将通过阅读源码的方法分析session.getMapper(Class)的实现原理。
系列文档:
- mybatis源码之创建SqlSessionFactory代码分析
- mybatis源码之创建SqlSessionFactory代码分析 - mapper xml解析
- mybatis源码之执行查询SQL代码分析
- mybatis源码之执行insert代码分析
- mybatis源码之mapper接口扫描原理分析
- mybatis源码之集成spring原理
- mybatis源码之集成springboot原理
- mybatis源码之集成mybatis-plus源码
- mybatis源码之mybatis-plus执行查询(基础篇完结)
- MybatisPlusAutoConfiguration源码分析
- Autowired注入Service变成了biaomidou的Mapper代理
示例代码
mapper接口
public interface BlogMapper {
Blog selectBlogById(Integer id);
int insertBlog(Blog blog);
List<Blog> selectBlogByParameter(BlogSearchParameter parameter);
}
xml mapper配置文件
就是一个普通的配置文件,有两点需要注意:
- namespace与接口的全类名对应
- SQL的id与接口的方法名对应。
<mapper namespace="org.net5ijy.mybatis.test.mapper.BlogMapper">
<resultMap id="BlogResultMap" type="org.net5ijy.mybatis.test.entity.Blog">
<id column="id" property="id"/>
<id column="title" property="title"/>
<id column="content" property="content"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<insert id="insertBlog" parameterType="org.net5ijy.mybatis.test.entity.Blog"
useGeneratedKeys="true" keyProperty="id">
insert into blog
(title, content, create_time, update_time)
values
(#{title}, #{content}, now(), now())
</insert>
<select id="selectBlogById" resultMap="BlogResultMap" parameterType="java.lang.Integer">
select * from blog where id = #{id}
</select>
<!-- other sql configuration -->
</mapper>
主配置文件
<configuration>
<properties resource="jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 这里还是引入xml mapper配置文件 -->
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
</configuration>
测试类
public class BlogMapperTest2 {
private SqlSession session;
private BlogMapper blogMapper;
@Before
public void before() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 注意这里的session还是非自动提交事务的
this.session = sessionFactory.openSession();
this.blogMapper = this.session.getMapper(BlogMapper.class);
}
@After
public void after() {
this.session.close();
}
@Test
public void testInsertBlog() {
Blog blog = new Blog();
blog.setTitle("spring学习");
blog.setContent("spring深入 - 源码分析");
int rows = this.blogMapper.insertBlog(blog);
System.out.println(rows);
System.out.println(blog.getId());
}
@Test
public void testSelectBlogById() {
Blog blog = this.blogMapper.selectBlogById(1);
System.out.println(blog);
}
// other test case
}
源码分析
入口
BlogMapper blogMapper = this.session.getMapper(BlogMapper.class);
执行的是DefaultSqlSession的getMapper(Class)方法:
public <T> T getMapper(Class<T> type) {
return configuration.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) {
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);
}
}
方法中的knownMappers是一个成员变量,Map结构保存Class -> MapperProxyFactory映射关系:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
此时knownMappers中是存在元素的,就是我们要get的BlogMapper.class与其对应的MapperProxyFactory的映射。
那么这个knownMappers是什么时候put值的呢?
在addMapper(Class)方法中有knownMappers的put操作,只要知道addMapper方法在哪里被调用,就可以知道knownMappers的初始化入口了。
addMapper(Class)方法
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
经过debug知道了addMapper(Class)方法被Configuration的addMapper(Class type)方法调用,而Configuration的addMapper(Class type)方法又被XMLMapperBuilder的bindMapperForNamespace方法和XMLConfigBuilder的mapperElement方法调用,因为我们在主配置文件不是使用mapperClass方式配置的mapper标签,所以在当前示例应该执行的是XMLMapperBuilder的bindMapperForNamespace方法,bindMapperForNamespace方法被XMLMapperBuilder.parse方法调用:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
这段代码很眼熟,就是之前初始化阶段解析主配置文件的逻辑。
解析mapper接口和statement:
addMapper(Class)方法的这两行代码值得注意一下:
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
// parser.parse()
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 此时xml mapper配置文件都解析完成了,这个方法的逻辑就不会执行了
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 缓存,不展开分析
parseCache();
parseCacheRef();
// 遍历所有方法
for (Method method : type.getMethods()) {
// 判断方法是否可以转为statement
// 基本都可以的
if (!canHaveStatement(method)) {
continue;
}
// 我们的接口方法没有使用注解方式,所以这个分支进不来
// 后续的注解mapper分析再做展开
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
// 方法转statement
// 核心是根据方法标注的Select、Update、Insert、Delete、SelectProvider、
// UpdateProvider、InsertProvider、DeleteProvider等注解创建statement
// 我们使用的不是注解方法,不展开分析
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
回到mapperRegistry.getMapper方法
这段代码很重要,所以此处又一次记录:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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(sqlSession)
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 创建java代理
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy);
}
简单介绍一下Proxy.newProxyInstance方法创建java的参数:
- ClassLoader - 类加载器对象
- Class<?>[] - 代理的interface
- InvocationHandler - 处理器
重点在InvocationHandler参数,这是一个接口:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
代理不是我们讨论的内容,我们看一下MapperProxy类的invoke方法实现,核心的代码就在这个方法里面。
MapperProxy类
核心在invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 执行这个分支
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// cachedInvoker方法
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
// 因为方法都不是default的,所以执行else分支
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 执行这里,创建PlainMethodInvoker对象返回
return new PlainMethodInvoker(
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
// MapperMethodInvoker接口
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
// PlainMethodInvoker类
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(
Object proxy, Method method,
Object[] args, SqlSession sqlSession) throws Throwable {
// mapper方法在这里执行
return mapperMethod.execute(sqlSession, args);
}
}
MapperMethod类
MapperMethod类的execute方法负责执行mapper方法,如下:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
// 插入
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
// 更新
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
// 删除
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("...");
}
return result;
}
}
以查询多条数据为例:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 将原始的方法参数转为Statement需要的参数形式
Object param = method.convertArgsToSqlCommandParam(args);
// 查询数据
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// 这个分支是非List返回值的处理
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
// 数组类型
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
可以看到内部就是使用的sqlSession.selectList方法做的查询操作,后续的逻辑我们在执行SQL源码分析中做过分析了,此处分析到这里就可以了。
其余的insert、update操作基本相同。