注册/加载过程
- 首先读取配置文件
String resource ="mybatis-config.xml";
InputStream inputStream =null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory =null;
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
- 解析xml 在org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)方法注册xml并解析。把配置文件解析成几个重要的对象:
Configuration(Mybatis配置)
Environment(可以设置为多个环境,用于隔离)
DataSource(数据源)
JdbcTransactionFactory(事务工厂)
MapperRegistry(就是存储Mapper接口的类)
执行流程:
- 在
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);这一行开始解析xml。进入build方法,返回对象是DefaultSqlSessionFactory。这个工厂很简单,返回一个sqlSession对象,生成JdbcTransactionFactory,创建数据源PooledDataSourceFactory,此过程涉及到触发了
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
静态代码,从而mysql的驱动被加载。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
}
...
}
- 看parser.parse()方法,返回对象是Configuration,调用parseConfiguration方法。
private void parseConfiguration(XNode root) {
try {
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器 todo 关闭是不是为null
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
重点看第7,10.第7的environmentsElement()里面实现比较简单,就是new 事务管理器TransactionFactory和数据源DataSourceFactory。 接下来是mapperElement()方法,看一下都干了什么。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
// 这里是生成mapper的实现类的包
configuration.addMappers(mapperPackage);
...
进入configuration.addMappers(mapperPackage)方法,最终是调用MapperRegistry.addMappers()方法
public void addMappers(String packageName, Class<?> superType) {
//查找包下所有是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);
}
}
public <T> void addMapper(Class<T> type) {
//mapper必须是接口!才会添加
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<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
//如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
主要做了把Mapper.class加到knownMappers中,并value是MapperProxyFactory代理工厂,其实说白了就是JDK代理,客户端使用代理类的时候,调用Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);生成一个代理类,内部类Proxy#0。某个Mapper的实现类为Proxy#0。接下来把Mapper.xml解析了。
解析xml过程
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {// 判断resource是否被解析过了
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
- MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); 新建并把config,type关联
- 注册一些sql的注解,比如Select,Insert,放在mapper接口的方法上的
- parser.parse();
- 查找xml文件并加载
- 解析xml成MappedStatement,过程比较复杂,简而言之,把接口的方法和sql语句对应起来,并存储在org.apache.ibatis.session.Configuration#mappedStatements中。我们看一下MappedStatement的结构:
- 主要有id,sqlSource,parameterMap,resultMaps,sqlCommandType
小结
注册/解析过程,完成了数据库字段和JavaBean的关系映射,简称ORM。把数据库驱动和数据源配置好,MapperRegistry(已注册的接口)和MappedStatement(sql语句)配置在Configuration中。以便执行的时候使用。
sql执行过程
1. sqlSession=sqlSessionFactory.openSession();
2. RoleMapper roleMapper=sqlSession.getMapper(RoleMapper.class);
3. Role role=roleMapper.getRole(1L);
第一步:
<cache/>)关系差不多是这样子,图画地有点丑...
- stmt = prepareStatement(handler, ms.getStatementLog());执行进入prepareStatement()方法
- 得到一个Connection,执行器有Transaction,Transaction可以得到数据库的Connection。
- 由于我们设置了连接池,所以在池中获取一个,如果没有则新建一个,使用完毕返回池中。
- 得到连接之后,会prepare,跟jdbc那个一样,先prepare。
- 然后填充参数。
- 开始真正执行进入handler.query(stmt, resultHandler),下图
excute方法,由于PreparedStatement对象被代理了,所以走到PreparedStatementLogger类 由于使用了反射,Idea无法进入具体相关实现类,statement对象是com.mysql.cj.jdbc.ClientPreparedStatement,method是java.sql.PreparedStatement#execute也就是statement对象的execute方法。这里得到数据库的数据。 7.获取到数据之后,包装成返回类型的对象。
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
//一般resultMaps里只有一个元素
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
整个过程已完成。
- 回到开始的地方
try {
sqlSession=sqlSessionFactory.openSession();
RoleMapper roleMapper=sqlSession.getMapper(RoleMapper.class);
Role role=roleMapper.getRole(1L);
log.debug("sql 已经被执行");
System.out.println(role.getId()+":"+role.getRoleName()+":"+role.getNote());
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
e.printStackTrace();
}finally {
sqlSession.close();
}
提交事务,由于这次是查询,最后是没提交的。如果是update的话,那肯定会提交。若有异常则回滚。关于事务的提交方式,可以参考下面链接。
MYSQL 事务处理主要有两种方法:
1、用 BEGIN, ROLLBACK, COMMIT来实现
BEGIN 开始一个事务
ROLLBACK 事务回滚
COMMIT 事务确认
2、直接用 SET 来改变 MySQL 的自动提交模式:
SET AUTOCOMMIT=0 禁止自动提交
SET AUTOCOMMIT=1 开启自动提交
对于一个MYSQL数据库(InnoDB),事务的开启与提交模式无非下面这两种情况:
1>若参数autocommit=0,事务则在用户本次对数据进行操作时自动开启,在用户执行commit命令时提交,用户本次对数据库开始进行操作到用户执行commit命令之间的一系列操作为一个完整的事务周期。若不执行commit命令,系统则默认事务回滚。总而言之,当前情况下事务的状态是自动开启手动提交。
2>若参数autocommit=1(系统默认值),事务的开启与提交又分为两种状态:
①手动开启手动提交:当用户执行start transaction命令时(事务初始化),一个事务开启,当用户执行commit命令时当前事务提交。从用户执行start transaction命令到用户执行commit命令之间的一系列操作为一个完整的事务周期。若不执行commit命令,系统则默认事务回滚。
②自动开启自动提交:如果用户在当前情况下(参数autocommit=1)未执行start transaction命令而对数据库进行了操作,系统则默认用户对数据库的每一个操作为一个孤立的事务,也就是说用户每进行一次操作系都会即时提交或者即时回滚。这种情况下用户的每一个操作都是一个完整的事务周期。
参考来源:
总结
这次学习mybatis源码,总的来说比较简单,我们可以对比出来,使用JDBC 和 Mybatis的区别。也学了很多,特别是反射,工厂,代理,装饰者。这些都是设计模式啊,日常都会涉及到,但是叫你使用起来也是有难度的。个人看法,对于Mybatis这种框架,只需要把核心流程理解了就可以了,如果要你开发Mybatis框架的话,那再进行深入探究。
接下来的话,有空看看mybatis-spring的集成。