最近debug了一下mybatis,记录一下过程。
两个xml:
mybatis-config.xml:
<configuration>
<properties resource="datasource.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/Learn_resourceMapper.xml"/>
</mappers>
</configuration>
Learn_resourceMapper.xml:
<mapper namespace="com.mall.dao.Learn_resourceMapper">
<resultMap id="BaseResultMap" type="com.mall.pojo.Learn_resource">
<constructor>
<idArg column="id" javaType="java.lang.Long" jdbcType="BIGINT" />
<arg column="author" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="title" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="url" javaType="java.lang.String" jdbcType="VARCHAR" />
</constructor>
</resultMap>
<sql id="Base_Column_List">
id, author, title, url
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from learn_resource
where id = #{id,jdbcType=BIGINT}
</select>
</mapper>
dao:
public interface Learn_resourceMapper {
Learn_resource selectByPrimaryKey(Long id);
}
test:
public class TestMain {
@Test
public void test1() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
Learn_resourceMapper mapper = sqlSession.getMapper(Learn_resourceMapper.class);
Learn_resource learn_resource = mapper.selectByPrimaryKey(1056L);
System.out.println(learn_resource.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
}
开始debug,第三句进来看看 。
//org.apache.ibatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); ①
return build(parser.parse()); ②
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
看看①这句话干了什么:
//org.apache.ibatis.builder.xml.XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration()); //初始了一个Configuration
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
可以看出构造了一个XPathParser,并且将mybatis-config.xml的转换成的inputStream 赋值给了XPathParser的document属性。
然后XMLConfigBuilder持有了这个了XPathParser。
然后看看②这句话干了什么: build(parser.parse());
首先看parser.parse()
//org.apache.ibatis.builder.xml.XMLConfigBuilder
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true; //表示解析过
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parser.evalNode(“/configuration”) :这是解析了xml中的configuration下的内容。
接着看parseConfiguration():这是一个解析xml全过程。 解析的每一步内容最后都是加载到Configuration持有的属性、对象上。
Configuration可以说是mybatis最重要的类之一,负责组装框架的配置内容。
//类org.apache.ibatis.builder.xml.XMLConfigBuilder
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
propertiesElement(root.evalNode("properties")); //<properties resource="datasource.properties"/> 给configuration持有的Properties赋值
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers")); //mappers.xml文件
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
具体来看看 mapperElement(root.evalNode(“mappers”));
//org.apache.ibatis.builder.xml.XMLConfigBuilder
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
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) {
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) {
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.");
}
}
}
}
}
这次代码走的是
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
有没有觉得跟上面很类似,也是一个inputStream 、Builder。进这个构造方法看看。
//org.apache.ibatis.builder.xml.XMLMapperBuilder
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
就是一脉相承。目的就是解析Learn_resourceMapper.xml文件。看看怎么解析的好了,进入 mapperParser.parse();
//方法org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
先看 configurationElement(parser.evalNode(“/mapper”));
//org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace); //com.mall.dao.Learn_resourceMapper
cacheRefElement(context.evalNode("cache-ref")); //null
cacheElement(context.evalNode("cache")); //null
parameterMapElement(context.evalNodes("/mapper/parameterMap")); //null
resultMapElements(context.evalNodes("/mapper/resultMap")); //给configuration的resultMaps添加元素
sqlElement(context.evalNodes("/mapper/sql")); //sql片段
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)):
给configuration的mappedStatements添加元素。
ms.id = com.mall.dao.Learn_resourceMapper.selectByPrimaryKey
再看 bindMapperForNamespace();
//org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
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) {
if (!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);
}
}
}
}
这个boundType就是dao层的接口。看看 configuration.addMapper(boundType);
//org.apache.ibatis.session.Configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
再进去。
//org.apache.ibatis.binding.MapperRegistry
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<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
添加了dao的一个mapper接口和它的代理类到Configuration的MapperRegistry的HashMap中。
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource(); //已加载过
configuration.addLoadedResource(resource);//resource:三个
//mappers/Learn_resourceMapper.xml
//interface com.mall.dao.Learn_resourceMapper(mapper接口
//namespace:com.mall.dao.Learn_resourceMapper(mapper.xml)
assistant.setCurrentNamespace(type.getName());
parseCache(); //null
parseCacheRef(); //null
Method[] methods = type.getMethods(); //接口里的方法
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method); //主要是加载接口文件中用注解方法的sql语句,本例中无
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
一起都加载的差不多了,让我们回到最初的起点。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
看看怎么加载configutation到sqlsession的
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
来到test文件的
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession是mybatis最重要的类之一,看看怎么开启回话的
//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//Executor的SIMPLE类型
public ExecutorType getDefaultExecutorType() {
return defaultExecutorType;
}
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//datasource
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//org.apache.ibatis.transaction.jdbc.JdbcTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//org.apache.ibatis.executor.SimpleExecutor
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();
}
}
最后返回了接口SqlSession的实现类DefaultSqlSession;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
至此,mybatis的三大最重要类都出现了:分别是SqlSession、Configuration、Executor。
回到test.java
try {
Learn_resourceMapper mapper = sqlSession.getMapper(Learn_resourceMapper.class);
Learn_resource learn_resource = mapper.selectByPrimaryKey(1056L);
System.out.println(learn_resource.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
先从Configuration里面的MapperRegistry里面得到Learn_resourceMapper的代理类。
//org.apache.ibatis.binding.MapperRegistry#getMapper
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);
}
}
//org.apache.ibatis.binding.MapperProxyFactory#newInstance
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//org.apache.ibatis.binding.MapperProxyFactory#newInstance
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//org.apache.ibatis.binding.MapperProxy#MapperProxy
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession; //DefaultSqlSession
this.mapperInterface = mapperInterface; //com.mall.dao.Learn_resourceMapper
this.methodCache = methodCache; //null
}
调用: Learn_resource learn_resource = mapper.selectByPrimaryKey(1056L);
实质上就是在调用org.apache.ibatis.binding.MapperProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) { //false
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
org.apache.ibatis.binding.MapperProxy#cachedMapperMethod
构造了MapperMethod ,并添加进了methodCache。
MapperMethod的command:
name: com.mall.dao.Learn_resourceMapper.selectByPrimaryKey;
type:select
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT: //进了这一条
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else { //进了这一条
Object param = method.convertArgsToSqlCommandParam(args); //para参数为1056
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
查找一条实质就是查找list中的第一条
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//查找到了id为com.mall.dao.Learn_resourceMapper.selectByPrimaryKey的ms.
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
query方法经过一系列的跳转到了org.apache.ibatis.executor.SimpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
其中的:org.apache.ibatis.executor.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//打开了数据库连接 PooledConnection
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//给sql语句赋值
handler.parameterize(stmt);
return stmt;
}
org.apache.ibatis.executor.statement.PreparedStatementHandler#query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行sql
ps.execute();
//给实体类赋值
return resultSetHandler.<E> handleResultSets(ps);
}
结束。
整个流程引用一张流程图: