Mybatis中SQL和Java类实例之间是怎么关联上的?
用过mybatis的人都知道mybatis的特点就是sql写在配置文件中,使用者使用的时候只需要调相对应的接口方法,或者是ibatis那种调配置文件中的ID。
那mabatis怎么实现调用一个空接口或者调用配置文件中的ID来执行sql的呢?
如果要解读源码,那这篇文章实在太长,这里只做一个小实验演示,并且指出小实验中代码与mybatis代码间的关系。
反射技术是一些java框架经常用到的技术,使用反射可以在不改变源代码的情况下改变代码运行的流程方式。
jdk提供了一个生成接口的实现类,其方法调用内容都来自于指定的接口实现类的方法,也就是说,你在你的代码里写的mapper接口,在mybatis中看来都会被转到mybatis自定义的真正执行类,想一想为什么接口方法名和ID一致?
代码
废话不多说,看看jdk提供的方法吧。
ProxyFactory.java
首先看看这个实例生成的方法,ProxyFactory.java
:
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
代码:
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
:
/**
* @author gavin
* @version V1.0
*/
public interface FakeInterface {
void hello();
void goodbye();
void other();
}
ExcuteInterface.java
最后,来写一个main方法ExcuteInterface.java
:
/**
* @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中取到的字符串了。
正在说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。