前言
公司使用的是一个自研的orm框架,这也导致我工作三四年了对目前市场上的主流orm框架认识浅显。虽然orm框架的底层原理都是基于jdbc来封装实现的,但是封装的思路不同,使用的方便程度和运行效率也有天壤之别。mybais是目前市场上使用比较多的一款orm框架,平时在使用的时候都是在XXXMapper.xml中定义好sql语句,然后编写XXXMapper.java接口,然后就可以使用了。但是仔细思考发现,我们仅仅是定义了接口,并没有定义实现类,程序是如何实现方法的调用并执行sql语句的呢?
mybatis如何找到Mapper.xml文件
mybatis最初设计时,并没有考虑到要和spring去集成,所以原生的提供了基于配置文件的使用方式。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 打印sql日志 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="poolMaximumActiveConnections" value="10"/>
<property name="poolMaximumIdleConnections" value="5"/>
<property name="poolTimeToWait" value="20000"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/cz/entity/TestCaseMapper.xml"></mapper>
<mapper resource="com/cz/entity/AutoCollectMethodParamsMapper.xml"></mapper>
</mappers>
</configuration>
在使用的时候程序中指定配置文件的路径,如下:
public class Test1 {
public static void main(String[] args) throws IOException {
// 1、获取配置文件
InputStream inputStream = Resources.getResourceAsStream("com/cz/resource/mybatis-config.xml");
// 2、构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3、开启会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4、操作Mapper接口
//List<String> list1 = sqlSession.getMapper(AutoCollectMethodParamsMapper.class).selectByRequestHashCode("655839017");
// 5、流式查询 一、直接使用sqlSession.selectCursor("selectAll");指定方法名,如果多个mapper中都有selectAll这个方法,会报错
/* Cursor<AutoCollectMethodParams> selectByRequestHashCode = sqlSession.selectCursor("selectAll");
Iterator<AutoCollectMethodParams> iterator = selectByRequestHashCode.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().getRequestHash());
}*/
// 6、流式查询 二、
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
Cursor<TestCase> autoCollectMethodParamsWithBLOBs = mapper.selectAll();
Iterator<TestCase> iterator1 = autoCollectMethodParamsWithBLOBs.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next().getCaseName());
}
}
}
程序在执行new SqlSessionFactoryBuilder().build(inputStream);时会解析mybatis-config.xml中的mapper并将mapper添加到HashMap保存。
解析mybatis-config.xml
解析mapper.xml
解析出mapper中的方法名,以及sql等
解析完成之后,此时xml中的各种数据已经被保存到了内存中,接下来开始使用。
sqlSession.getMapper(AutoCollectMethodParamsMapper.class).selectByRequestHashCode("655839017");
这里getMapper其实获取到的就是上面addMapper进去的代理对象。那么代理对象中的方法是什么时候设置进去的?
下面模拟mybatis写一个例子:
package com.cz.proxy;
/**
* @program: Reids
* @description: 测试Mybatis
* @author: Cheng Zhi
* @create: 2023-05-07 19:58
**/
public interface Test1Mapper {
public String select();
public void insert();
}
模拟mybatis代理类
package com.cz.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @program: Reids
* @description: 动态代理类
* @author: Cheng Zhi
* @create: 2023-05-07 19:59
**/
public class TestMapperProxy<T> implements InvocationHandler {
Map map;
TestMapperProxy(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print(method.getName());
System.out.print(":");
// 这里模拟处理sql
System.out.println(map.get("sql"));
return null;
}
}
package com.cz.proxy;
import java.lang.reflect.Proxy;
/**
* @program: Reids
* @description:
* @author: Cheng Zhi
* @create: 2023-05-07 20:32
**/
public class TestMapperProxyFactory<T> {
Class mapperInterface;
TestMapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
protected T newInstance(TestMapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
}
package com.cz.proxy;
import java.util.Map;
/**
* @program: Reids
* @description:
* @author: Cheng Zhi
* @create: 2023-05-07 20:23
**/
public class TestSqlSession {
Map map;
TestSqlSession(Map map) {
this.map = map;
}
public <T> T getMapper(Class<T> type) {
return new TestMapperProxyFactory<T>(type).newInstance(new TestMapperProxy<T>(map));
}
}
package com.cz.proxy;
import java.util.HashMap;
import java.util.Map;
/**
* @program: Reids
* @description:
* @author: Cheng Zhi
* @create: 2023-05-07 20:22
**/
public class TestSqlSessionFactory {
Map map;
TestSqlSessionFactory(Map map) {
this.map = map;
}
public TestSqlSession openSqlSession() {
return new TestSqlSession(map);
}
}
package com.cz.proxy;
import java.util.HashMap;
import java.util.Map;
/**
* @program: Reids
* @description: 模拟mybatis的SqlSessionFactory
* @author: Cheng Zhi
* @create: 2023-05-07 20:17
**/
public class TestSqlSessionFactoryBuilder {
public TestSqlSessionFactory builder() {
String id = "select";
String sql = "select * from ca_bill";
Map map = new HashMap();
// 这里理论上应该从文件中获取,这里只是模拟,写死
map.put("id", id);
map.put("sql", sql);
return new TestSqlSessionFactory(map);
}
}
测试类:
package com.cz.proxy;
/**
* @program: Reids
* @description:
* @author: Cheng Zhi
* @create: 2023-05-07 20:39
**/
public class Main {
public static void main(String[] args) {
TestSqlSessionFactory builder = new TestSqlSessionFactoryBuilder().builder();
TestSqlSession testSqlSession = builder.openSqlSession();
Test1Mapper mapper = testSqlSession.getMapper(Test1Mapper.class);
mapper.select();
}
}
运行结果:
从上面的例子可以看出来,其实并没有类去真实的实现TestMapper接口中的方法,只是在代理类中,根据mapper.xml中的内容做为了方法的内容来处理。综上就是mybatis如何通过代理实现sql语句的执行。