传统JDBC和Mybatis相比的弊病
1.数据库连接创建,释放频繁造成西戎资源的浪费,从而影响系统性能,使用数据库连接池可以解决问题。
2.sql语句在代码中硬编码,造成代码的不已维护,实际应用中sql的变化可能较大,sql代码和java代码没有分离开来维护不方便。
3.使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中的where子句的条件不确定,同样是修改不方便
4.对结果集中解析存在硬编码问题,sql的变化导致解析代码的变化,系统维护不方便。
5、JDBC没有提供缓存,增加了数据库压力。
mybatis整体架构图
mybatis功能架构分为三层:
API接口层:
提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层完成具体的数据处理。其核心是SqlSession接口。
数据处理层:
负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次工作。
配置解析流程:
在Mybatis初始化过程中,会加载mybatis-config.xml配置文件、映射文件以及Mapper接口中的信息,解析后的配置信息会形成相应的对象并保存到Configuration对象中。利用该configuration对象创建SqlSessionFactory对象。待Mybatis初始化之后。开发人员可以通过初始化得到SqlSessionFactory创建SqlSession对象并完成数据库操作。
Configuration对象
是一个所有配置型的的容器对象。
SQL解析(sqlsource)
对应的是scripting模块。Mybatis中的scripting模块,会根据用户传入的实参,解析映射文件中定义的SQL节点,并形成数据库可执行的SQL语句。之后会处理SQL语句中的占位符,绑定用户传入的实参
负责根据用户传递的parameterObject,动态生成SQL语句,将信息封装到BoundSql对象中并返回
SQL执行(executor)
基础支撑层:
负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取成最基础的组件。为上层的数据处理层提供最基础的支撑。
一个mybatis简单的例子
1
2 /***
3 *
4 *
5 */
6 public class App {
7 public static void main(String[] args) {
8 String resource = "mybatis‐config.xml";
9 Reader reader;
10 try {
11 //将XML配置文件构建为Configuration配置类
12 reader = Resources.getResourceAsReader(resource);
13 // 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
14 SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
15 // 数据源 执行器 DefaultSqlSession
16 SqlSession session = sqlMapper.openSession();
17 try {
18 // 执行查询 底层执行jdbc
19 //User user = (User)session.selectOne("com.tuling.mapper.selectById", 1);
20
21 UserMapper mapper = session.getMapper(UserMapper.class);
22 System.out.println(mapper.getClass());
23 User user = mapper.selectById(1L);
24 System.out.println(user.getUserName());
25 } catch (Exception e) {
26 e.printStackTrace();
27 }finally {
28 session.close();
29 }
30 } catch (IOException e) {
31 e.printStackTrace();
32 }
33 }
34 }
mybatis执行分为以下四个步骤:
1、执行从配置文件(通常是XML文件)得到SessionFactory;
2、从SessionFactory得到SqlSession;
3、通过SqlSession进行CRUD和事务的操作;
4、执行完相关操作之后关闭Session。
mybatis启动流程分析:
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
加载配置文件,并且解析配置文件,把配置文件的内容解析到configuration对象中。
mybatis中有很多build方法和xxxbuild类,是因为mybatis用到了build模式来创建对象。build模式是指创建对象的过程比较复杂,不能再一个简单的构造方法中完成,就需要用到build模式。
通过上面代码发现,创建SqlSessionFactory的代码在SqlSessionFactoryBuilder中,进去一探究竟:
///整个过程就是将配置文件解析成Configration对象,然后创建SqlSessionFactory的过程
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
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.
}
}
}
// 到这里配置文件已经解析成了Configuration
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
在上面代码中的
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
其实内部是创建了一个XMLParser对象
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
在XMLParser对象中有以下的这些属性:
private final Document document; private boolean validation; private EntityResolver entityResolver; private Properties variables; private XPath xpath;
其中xpath就是Java提供用来解析XML文件,而xml的内容都放在了document对象中,而解析document文件得到的信息都是放在node对象中。
xml文件中的每个节点都是用一个node对象表示,所以解析配置文件都是对node对象操作。比如可以获得节点的名字、值、以及下面的子节点
builder.append(node.getNodeName());
NamedNodeMap attributeNodes = node.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
String value = PropertyParser.parse(attribute.getNodeValue(), null);
//System.out.println(attribute.getNodeName()+":"+ value);
builder.append(" ");
builder.append(attribute.getNodeName());
builder.append("=\"");
builder.append(value);
builder.append("\"");
}
}
NodeList nodeList = node.getChildNodes();
得到XMLConfigBuilder对象之后就去执行 parse()方法,去解析XML文件:
先判断是否解析过,如果已经解析过就抛异常
public Configuration parse() {
/**
* 若已经解析过了 就抛出异常
*/
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
/**
* 设置解析标志位
*/
parsed = true;
/**
* 解析我们的mybatis-config.xml的
* 节点
* <configuration>
*
*
* </configuration>
*/
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
具体执行的是parseConfiguration():XNode是经过Mybatis封装的node,XNode中有toString方法可以输出当前节点说封装的内容
private void parseConfiguration(XNode root) {
try {
/**
* 解析 properties节点
* <properties resource="mybatis/db.properties" />
* 解析到org.apache.ibatis.parsing.XPathParser#variables
* org.apache.ibatis.session.Configuration#variables
*/
propertiesElement(root.evalNode("properties"));
/**
* 解析我们的mybatis-config.xml中的settings节点
* 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
* <settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
..............
</settings>
*
*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
/**
* 基本没有用过该属性
* VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
解析到:org.apache.ibatis.session.Configuration#vfsImpl
*/
loadCustomVfs(settings);
/**
* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
* SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
* 解析到org.apache.ibatis.session.Configuration#logImpl
*/
loadCustomLogImpl(settings);
/**
* 解析我们的别名
* <typeAliases>
<typeAlias alias="Author" type="cn.tulingxueyuan.pojo.Author"/>
</typeAliases>
<typeAliases>
<package name="cn.tulingxueyuan.pojo"/>
</typeAliases>
解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
*/
typeAliasesElement(root.evalNode("typeAliases"));
/**
* 解析我们的插件(比如分页插件)
* mybatis自带的
* Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
*/
pluginElement(root.evalNode("plugins"));
/**
* todo
*/
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 设置settings 和默认值
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
/**
* 解析我们的mybatis环境
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="root"/>
<property name="password" value="Zw726515"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
* 解析到:org.apache.ibatis.session.Configuration#environment
* 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
*/
environmentsElement(root.evalNode("environments"));
/**
* 解析数据库厂商
* <databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySql" value="mysql" />
</databaseIdProvider>
* 解析到:org.apache.ibatis.session.Configuration#databaseId
*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/**
* 解析我们的类型处理器节点
* <typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
*/
typeHandlerElement(root.evalNode("typeHandlers"));
/**
* 最最最最最重要的就是解析我们的mapper
*
resource:来注册我们的class类路径下的
url:来指定我们磁盘下的或者网络资源的
class:
若注册Mapper不带xml文件的,这里可以直接注册
若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
-->
<mappers>
<mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
<mapper class="com.tuling.mapper.DeptMapper"></mapper>
<package name="com.tuling.mapper"></package>
-->
</mappers>
* package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
2.
*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
在执行parseConfiguration()过程中会执行具体的方法去解析不同的组件例如解析properties属性到configuration的variables属性里面去:
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
以及最重要的是解析mapper属性,mapper的配置方式一共有四种,在源码中可以看到分别是package、resource、url、class
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
/**
* 获取我们mappers节点下的一个一个的mapper节点
*/
for (XNode child : parent.getChildren()) {
/**
* 判断我们mapper是不是通过批量注册的
* <package name="com.tuling.mapper"></package>
*/
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
/**
* 判断从classpath下读取我们的mapper
* <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
*/
String resource = child.getStringAttribute("resource");
/**
* 判断是不是从我们的网络资源读取(或者本地磁盘得)
* <mapper url="D:/mapper/EmployeeMapper.xml"/>
*/
String url = child.getStringAttribute("url");
/**
* 解析这种类型(要求接口和xml在同一个包下)
* <mapper class="com.tuling.mapper.DeptMapper"></mapper>
*
*/
String mapperClass = child.getStringAttribute("class");
/**
* 我们得mappers节点只配置了
* <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
*/
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
/**
* 把我们的文件读取出一个流
*/
InputStream inputStream = Resources.getResourceAsStream(resource);
/**
* 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
*/
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
/**
* 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
*/
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
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<?> 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.");
}
}
}
}
}