Mybatis中SQL和Java类实例之间是怎么关联上的?

用过mybatis的人都知道mybatis的特点就是sql写在配置文件中,使用者使用的时候只需要调相对应的接口方法,或者是ibatis那种调配置文件中的ID。

那mabatis怎么实现调用一个空接口或者调用配置文件中的ID来执行sql的呢?

如果要解读源码,那这篇文章实在太长,这里只做一个小实验演示,并且指出小实验中代码与mybatis代码间的关系。

反射技术是一些java框架经常用到的技术,使用反射可以在不改变源代码的情况下改变代码运行的流程方式。

jdk提供了一个生成接口的实现类,其方法调用内容都来自于指定的接口实现类的方法,也就是说,你在你的代码里写的mapper接口,在mybatis中看来都会被转到mybatis自定义的真正执行类,想一想为什么接口方法名和ID一致?

代码

废话不多说,看看jdk提供的方法吧。

ProxyFactory.java

首先看看这个实例生成的方法,ProxyFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.lang.reflect.Proxy;

/**
* @author gavin
* @version V1.0
*/
public class ProxyFactory<T> {

/**
* 注意这里传入的class一定是接口的class,不能是类的class
* @param t
* @return
*/
protected T newInstance(Class t){
return (T) Proxy.newProxyInstance(t.getClassLoader(),new Class[]{t},new InvocationHandlerImpl());
}
}

InvocationHandlerImpl.java

在上边代理生成类里,我们指定了接口实现类来具体执行方法,实现类InvocationHandlerImpl.java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
* @author gavin
* @version V1.0
*/
public class InvocationHandlerImpl implements InvocationHandler {
static Map<String, String> map = new HashMap<>();

static {
map.put("hello", "正在说hello");
map.put("goodbye", "正在说goodbye");
}

/**
* 功能是打印特定文字
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String str = map.get(method.getName());
if (str == null) {
System.out.println("未实现 "+method.getName());
} else {
System.out.println(str);
}
return null;
}
}

在这个实现类中,我们根据方法名来决定从map中取哪个字符串并打印出来。

FakeInterface.java

接下来,我们写一个接口FakeInterface.java

1
2
3
4
5
6
7
8
9
10
11
/**
* @author gavin
* @version V1.0
*/
public interface FakeInterface {
void hello();

void goodbye();

void other();
}

ExcuteInterface.java

最后,来写一个main方法ExcuteInterface.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author gavin
* @version V1.0
*/
public class ExcuteInterface {
public static void main(String[] args) {
ProxyFactory<FakeInterface> proxyFactory = new ProxyFactory();
FakeInterface fakeInterface = proxyFactory.newInstance(FakeInterface.class);

fakeInterface.hello();
fakeInterface.goodbye();
fakeInterface.other();
}
}

运行结果

运行结果不言自明,应该就是我们在map中取到的字符串了。

1
2
3
正在说hello
正在说goodbye
未实现 other

Mapper的实现

mapper的实现就是基于jdk提供的这个实现方法,从使用者自定义的接口中获取方法名,入参和出参,然后综合判断后执行对应的sql。

实际上,mybatis是有一个MapperMethod类来执行sql的。具体的执行就是MapperMethod的sqlSession执行具体sql。

知道了这个原理,我们也能自己写一个简单版的sql执行器了。

  • 在配置文件中配置key和sql。
  • 在代码运行第一步加载key和sql到InvocationHandler接口实现类中的map中。
  • InvocationHandler接口实现类中设置上SqlStatement
  • 从proxyFactory中获取接口实现类。
  • 执行接口方法,获取相应数据。

SqlSession在SqlSessionTemplate的实现

SqlSessionTemplate是ibatis中的写法,具体可以看源码。

SqlSessionTemplate类并没有真正实现SqlSession接口,而是又写了一个InvocationHandler接口实现内部类,在这个实现内部类中通过外部类SqlSessionTemplate的SqlSessionFactory获取到真正的SqlSession用来执行。

说白了SqlSessionTemplate就是给spring准备的,mybatis不想为了sping再写一个SqlSession。