1 了解项目结构
- annotations 注解
提供在Mapper 接口上使用注解进行CRUD操作,虽然方便但是不能很好的解耦,更推荐在XML文件写SQL
- binding
在调用 SqlSession 相应方法执行数据库操作时,需要指定映射文件中定义的 SQL 节点,如果出现拼写错误,我们只能在运行时才能发现相应的异常。Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。
注意: 开发人员无须编写自定义 Mapper 接口的实现,MyBatis 会自动为其创建动态代理对象。
一般在系统抛出 BindingException 我们就需要去检查namespace,SQL的id是否与系统里的命名一一对应。
- builder 配置解析
MyBatis 初始化时,会加载
mybatis-config.xml
配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。 - cache 缓存
MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓 存模块实现的。
注意: MyBatis 中自带的这两级缓存与 MyBatis 以及整个应用是运行在同一个 JVM 中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用 Redis、Memcache 等缓存产品。
- cursor 执行结果的游标
- datasource 数据源
数据源是开发常用的组件之一,开源的数据源都提供了比较丰富的功能,例如,连接池功能、检测连接状态等,选择性能优秀的数据源组件对于提升 ORM 框架乃至整个应用的性能都是非常重要的。
MyBatis提供了相应的数据源实现,也提供了与第三方数据源集成的接口,这些功能都位于数据源模块之中。
- exceptions 异常
定义了 MyBatis 专有的 PersistenceException 和 TooManyResultsException 异常。
- executor SQL执行器
主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给 StatementHandler完成。
- io 资源加载
对类加载器进行封装,确定类加载器的使用顺序,并提供加载类文件以及其他资源的功能,
- jdbc JDBC 单元测试工具类。
- lang Java版本
- logging 日志
无论开发、测试还是生产环境日志在整个系统中地位极其重要。
该模块可以很好的帮我们集成第三方日志框架。
mapping SQL解析
例如:
<resultMap>
( ResultSet 的映射规则) 会被解析成 ResultMap 对象。<result>
(属性映射)会被解析成 ResultMapping 对象。之后,利用该 Configuration 对象创建 SqlSessionFactory对象。待 MyBatis 初始化之后,开发人员可以通过初始化得到 SqlSessionFactory 创建 SqlSession 对象并完成数据库操作。
- parsing 解析
初始化对mybatis-config.xml 以及映射配置文件进行解析
处理SQL语句中的占位符
- plugin 插件
MyBatis 提供了插件接口,我们可以通过添加用户自定义插件的方式对 MyBatis 进行扩展。用户自定义插件也可以改变 Mybatis 的默认行为,例如,我们可以拦截 SQL 语句并对其进行重写。由于用户自定义插件会影响 MyBatis 的核心行为,在使用自定义插件之前,开发人员需要了解 MyBatis 内部的原理,这样才能编写出安全、高效的插件。
- reflection
对 Java 原生的反射进行了良好的封装,提了更加简洁易用的 API,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。
- scripting 动态SQL,会根据用户传入的实参,解析映射文件中定义的动态 SQL 节点,并形成数据库可执行的 SQL 语句。之后会处理 SQL 语句中的占位符,绑定用户传入的实参。
- session
核心是 SqlSession 接口,该接口中定义了 MyBatis 暴露给应用程序调用的 API,也就是上层应用与 MyBatis 交互的桥梁。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。
- transaction 事务
MyBatis对数据库事物进行抽象,其自身提供了响应的事物接口和简单实现
通常情况下,MyBatis会与Spring进行集成,并由Spring管理框架。
type 类型
- MyBatis 为简化配置文件提供了别名,这里就需要该模块去做类型转换
- Java类型与JDBC类型的相互转化
- utils 工具类
2 搭建测试类
2.1 引入 mysql 连接依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
2.2 mybatis-config.xml文件
在测试包下,建自己的测试包配置mybatis-config.xml文件
<?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>
<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://newAliyun:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/apache/ibatis/zhjtest/MbGoodsMapper.xml"/>
</mappers>
</configuration>
2.3 建立实体
对应的在数据库建一张表
package org.apache.ibatis.zhjtest;
import java.math.BigDecimal;
import java.util.Date;
public class MbGoods {
private String id;
private String name;
private BigDecimal price;
private String description;
private String type;
private Integer status;
private String createBy;
private Date createTime;
private String updateBy;
private Date updateTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "MbGoods{" +
"id='" + id + ''' +
", name='" + name + ''' +
", price=" + price +
", description='" + description + ''' +
", type='" + type + ''' +
", status=" + status +
", createBy='" + createBy + ''' +
", createTime=" + createTime +
", updateBy='" + updateBy + ''' +
", updateTime=" + updateTime +
'}';
}
}
2.4 写Mapper接口
package org.apache.ibatis.zhjtest;
import org.apache.ibatis.annotations.Param;
public interface MbGoodsMapper {
MbGoods selectById(@Param("id") String id);
}
2.5 写Mapper接口对应的XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.zhjtest.MbGoodsMapper">
<select id="selectById" resultType="org.apache.ibatis.zhjtest.MbGoods">
select * from mb_goods where id = #{id}
</select>
</mapper>
2.6 编写测试类
package org.apache.ibatis.zhjtest;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.Reader;
public class MyTest {
private static SqlSessionFactory sqlSessionFactory;
@Test
public void test01() throws IOException {
//1、创建SqlSessionFactory
String resource = "org/apache/ibatis/zhjtest/mybatis-config.xml";
final Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
//2、获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//3、获取mapper
MbGoodsMapper mapper = sqlSession.getMapper(MbGoodsMapper.class);
//4、执行数据库操作,并处理结果集
MbGoods goods = mapper.selectById("1");
System.out.println(goods);
}
}
3 执行流程分析
第一步就是加载配置文件
通过IO包下资源加载类加载配置文件
final Reader reader Resources.getResourceAsReader(resource);
第二步通过SqlSessionFactoryBuilder建造者模式构建SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader)
先需要将XML内容读取到XMLConfigBuilder转换成文档对象Document赋值给当前对象的文档属性
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(entityResolver); builder.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { // NOP } }); return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } }
- 再解析文档XPathParser解析成一个一个节点
- 把内容都构建在Configuration对象
构建会话工厂
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
- 得到SqlSessionFactory
第三步通过工厂构建SqlSession
由上一步我们获得的是DefaultSqlSessionFactory所以本步通过默认实现来构建SqlSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 环境 final Environment environment = configuration.getEnvironment(); /** 获取一个事物工厂 会根据配置文件是否指定事物工厂生成,如果没有则使用自己的事物管理 经常我们会将事物交给Spring来管理 private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } return environment.getTransactionFactory(); } */ final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); /** public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) { this.dataSource = ds; this.level = level; this.closeConnection = closeConnection; } */ tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); /** // 根据配置文件指定类型,构建一个执行器 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 判断是否开启缓存 if (cacheEnabled) { executor = new CachingExecutor(executor); } /** 插件拦截器 如果有相关接口,通过代理模式生成代理对象返回 public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } */ executor = (Executor) interceptorChain.pluginAll(executor); return executor; } */ final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
根据返回确定使用哪种实现方式
第四步获取对应mapper
读取XML文件时会将命名空间添加到Map中,通过动态代理为接口创建对象
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); // xml处理绑定命名空间 private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { // ignore, bound type is not required } if (boundType != null && !configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } 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 { // 代理工厂 knownMappers.put(type, new MapperProxyFactory<>(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); // XML解析 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 根据接口名判断是否右对应的代理实现,此处抛异常很可能是接口名与命名空间不符 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
- 第五步执行数据库操作返回结果集,并记录在缓存数据