Mybaits整体体系图
Mybaits功能架构
- API接口层:提供给外部使用的接口API,程序员可以通过这些本地API来操作数据库。
- 数据处理层:负责具体的SQL处理、SQL解析、SQL执行和执行结果映射处理...
- 基础支撑层:通用基础功能,包括连接管理、事务管理、配置加载、缓存处理...
mybatis对传统的JDBC的解决方案
- 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库链接。
- Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
- 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
- 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
本文使用的全局配置文件
<?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>
<!--SqlSessionFactoryBuilder中配置的配置文件的优先级最高;config.properties配置文件的优先级次之;properties标签中的配置优先级最低 -->
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="username"/>
<property name="password" value="password"/>
</properties>
<!--一些重要的全局配置-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果-->
<!--如果某些查询数据量非常大,不应该允许查出所有数据-->
<property name="pageSizeZero" value="true"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.59.97.10:3308/Test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<mappers>
<!-- 1、必须保证接口名称和XML名称相同,还必须在同一个包中。-->
<package name = "com.cangqiong"/>
<!-- 2、不保证同接口同包。-->
<mapper resouse = "mapper/Test.xml"/>
<!-- 3、保证接口名称和XML名称相同,还必须在同一个包中。-->
<mapper class= "com.cangqiong.Test"/>
<!-- 4、使用本地路径或者网络路径。-->
<mapper url= "file:E/test/TestMapper.xml"/>
</mappers>
</configuration>
本文使用的Mybatis使用方式
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader;
try {
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory,默认使用DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 数据源执行器,默认使用DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询,底层执行jdbc
//User user = (User)session.selectOne("com.zhangwei.mapper.selectById", 1);
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(mapper.getClass());
User user = mapper.selectById(1L);
System.out.println(user.getUserName());
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
解析全局配置源码分析
/**
* 全局配置类解析入口
* 源码位置:org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader)
*/
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
/**
* 全局配置类解析入口,三个参数的构造方法
* 源码位置:org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader, String, Properties)
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 继续java中sax的方向进行解析,Mybatis对其进行了增强
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 具体的即系流程
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
/**
* 解析配置文件的外部方法
* 源码位置:org.apache.ibatis.builder.xml.XMLConfigBuilder.parse()
*/
public Configuration parse() {
// 判断是否解析过,解析过直接抛异常。所以Mybatis的配置文件只有一个并且只能解析一次!
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标记已经解析了,下次进来抛异常
parsed = true;
// 解析configuration节点,配置文件的核心
parseConfiguration(parser.evalNode("/configuration"));
// 返回解析好的数据
return configuration;
}
/**
* 解析配置文件的最核心的方法,解析配置文件的configuration节点
* 源码位置:org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader, String, Properties)
*/
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
// 解析properties标签,并set到Configration对象中
// 在properties配置属性后,在Mybatis的配置文件中就可以使用${key}的形式使用了。
propertiesElement(root.evalNode("properties"));
// 解析setting标签的配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 添加vfs的自定义实现,这个功能不怎么用
loadCustomVfs(settings);
// 配置类的别名,配置后就可以用别名来替代全限定名
// mybatis默认设置了很多别名,参考附录部分
typeAliasesElement(root.evalNode("typeAliases"));
// 解析拦截器和拦截器的属性,set到Configration的interceptorChain中
// MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
// Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
// ParameterHandler (getParameterObject, setParameters)
// ResultSetHandler (handleResultSets, handleOutputParameters)
// StatementHandler (prepare, parameterize, batch, update, query)
pluginElement(root.evalNode("plugins"));
// Mybatis创建对象是会使用objectFactory来创建对象,一般情况下不会自己配置这个objectFactory,使用系统默认的objectFactory就好了
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 设置在setting标签中配置的配置
settingsElement(settings);
// 解析环境信息,包括事物管理器和数据源,SqlSessionFactoryBuilder在解析时需要指定环境id,如果不指定的话,会选择默认的环境;
// 最后将这些信息set到Configration的Environment属性里面
environmentsElement(root.evalNode("environments"));
// 数据源ID,用于区分不同的数据库,目前很少用
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。解析typeHandler。
// 简单说就是JAVA类型与数据库类型的对应关系
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析Mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
Mapper的解析
/**
* mapper信息解析入口
* 源码位置:org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement(XNode)
*/
private void mapperElement(XNode parent) throws Exception {
// 解析的节点不为null
if (parent != null) {
// 遍历每个节点
for (XNode child : parent.getChildren()) {
// package的解析方式
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// resouse的解析方式
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// url的解析方式
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// class的解析方式
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException(
"A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
包路径解析Mapper源码分析
/**
* 包路径解析Mapper的入口
* 源码位置:org.apache.ibatis.session.Configuration.addMappers(String)
*/
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
/**
* 继续调用
* 源码位置:org.apache.ibatis.binding.MapperRegistry.addMappers(String)
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
/**
* 继续调用
* 源码位置:org.apache.ibatis.binding.MapperRegistry.addMappers(String, Class<?>)
*/
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 得到所有的包路径内的文件
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// 遍历包中的所有文件
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* 添加单个Mapper
* 源码位置:org.apache.ibatis.binding.MapperRegistry.addMapper(Class<T>)
*/
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 {
// 添加到缓存。。值是一个MapperProxyFactor,后面会使用jdk的动态代理去参数代理对象
knownMappers.put(type, new MapperProxyFactory<T>(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);
// 解析mapper文件
parser.parse();
// 设置解析成功
loadCompleted = true;
} finally {
// 解析不成功,移除掉缓存!
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/**
* 解析mapper文件
* 源码位置:org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parse()
*/
public void parse() {
// 得到mapper路径
String resource = type.toString();
// 是否已经解析mapper对应的xml
if (!configuration.isResourceLoaded(resource)) {
// 根据文件接口名,获取对应的xml文件
loadXmlResource();
// 标记位已经解析
configuration.addLoadedResource(resource);
// 设置解析的nameSpace
assistant.setCurrentNamespace(type.getName());
// 解析缓存
parseCache();
parseCacheRef();
// 得到里面的方法,看是不是用了注解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
// 判断是不是用了注解
if (!method.isBridge()) {
// 用了注解的解析为MappedStatement
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 解析方法
parsePendingMethods();
}
加载XML文件源码分析
/**
* 加载XML文件
* 源码位置:org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.loadXmlResource()
*/
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 更觉接口名称找xml的名称
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
// 读取文件
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
// 创建解析器
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(),
xmlResource, configuration.getSqlFragments(), type.getName());
// 解析XML
xmlParser.parse();
}
}
}
/**
* 解析XML文件
* 源码位置:org.apache.ibatis.builder.xml.XMLMapperBuilder.parse()
*/
public void parse() {
// 判断当前的mapper是否在加载过
if (!configuration.isResourceLoaded(resource)) {
// 加载mapper节点
configurationElement(parser.evalNode("/mapper"));
// 资源保存在configuration中
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
/**
* 加载mapper节点
* 源码位置:org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XNode)
*/
private void configurationElement(XNode context) {
try {
// 得到节点中namespace的值
String namespace = context.getStringAttribute("namespace");
// 没有值,抛异常
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 将namespace进行存储(里面判断是否与类名相同)
builderAssistant.setCurrentNamespace(namespace);
// 解析缓存引用
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache标签。二级缓存
cacheElement(context.evalNode("cache"));
// 解析parameterMap。3.5之后不推荐使用了
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析resultMap节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析增删改查节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e,
e);
}
}
二级缓存解析逻辑
/**
* 源码位置:org.apache.ibatis.builder.xml.XMLMapperBuilder.cacheElement(XNode)
*/
private void cacheElement(XNode context) throws Exception {
if (context != null) {
// 解析cache节点的type属性
String type = context.getStringAttribute("type", "PERPETUAL");
// 根据别名或者完整限定名加载class
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 获取缓存过期策略,默认是LRU
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 设置刷新间隔
Long flushInterval = context.getLongAttribute("flushInterval");
// 得到缓存的大小
Integer size = context.getIntAttribute("size");
// 判断是不是只读
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 得到调用方式
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// 构建缓存,加入到缓存链,组成装饰者模式
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
解析增删改查节点
/**
* 源码位置:org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(List<XNode>)
*/
private void buildStatementFromContext(List<XNode> list) {
// 解析增删改查节点
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
/**
* 解析增删改查节点
* 源码位置:org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(List<XNode>, String)
*/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 循环每一个节点!
for (XNode context : list) {
// 创建XMLStatement构建器对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant,
context, requiredDatabaseId);
try {
// 解析具体的节点:将所有属性解析出来,sql解析出来
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
结束语
- 获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!
- 目前已经完成了并发编程、MySQL、spring源码、Mybatis的源码。可以在公众号下方菜单点击查看之前的文章!
- 接下来的目标是深入分析JVM、tomcat、redis
- 这个公众号,无广告!!!每日更新!!!