mybatis——sql.xml解析及接口映射器
mybatis-conf.xml解析:主要弄清楚sql.xml的解析成什么了,为后面直接执行+接口映射器做准备。
一、配置文件
mybatis-conf.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> <!-- 属性:定义配置外在化 --> <properties resource="db.properties" /> <!-- 环境:配置mybatis的环境 --> <environments default="dev"> <!-- 环境变量:可以配置多个环境变量,比如使用多数据源时,就需要配置多个环境变量 --> <environment id="dev"> <!-- 事务管理器 --> <transactionManager type="JDBC"></transactionManager> <!-- 数据源 --> <dataSource type="POOLED"> <property name="driver" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> </dataSource> </environment> </environments> <!-- 映射器:指定映射文件或者映射类 --> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
userMapper.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="com.app.aop.transactional.mapper.UserMapper"> <resultMap id="BaseResultMap" type="com.app.aop.transactional.model.User"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="user_id" jdbcType="BIGINT" property="userId" /> <result column="user_name" jdbcType="VARCHAR" property="userName" /> </resultMap> <sql id="Base_Column_List"> id, user_id, user_name </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from user where id = #{id,jdbcType=BIGINT} </select> </mapper>
二、xml解析生成Configureration对象
xml配置解析主要是下面两行代码:
// 读取mybatis-config.xml文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 初始化mybatis,创建SqlSessionFactory类的实例 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
new SqlSessionFactoryBuilder().build(inputStream):解析mybatis-conf.xml并生成sqlSessionFactory对象
/* org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties) */ 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. } } }
Configration对象是通过XMLConfigBuilder.parse()解析的
/* org.apache.ibatis.builder.xml.XMLConfigBuilder#parse */ private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration());//生成一个new Configuration()对象 ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); configuration.typeAliasRegistry<string,Class> 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")); //重点看看sql.xml的解析成了什么 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
mapperElement(root.evalNode("mappers)):解析sql.xml到Configuration对象中
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"); //将package下多个sql.xml解析到Configuration对象中, //便于理解,研究下面的单个sql.xml解析 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); //sql.xml解析到Configuration对象中 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); //sql.xml解析到Configuration对象中 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); //接口直接绑定,直接接口方法注解@select等解析成sql configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
① sql.xml解析
/* org.apache.ibatis.builder.xml.XMLMapperBuilder#parse */ public void parse() { if (!configuration.isResourceLoaded(resource)) { //解析sql.xml(根节点<mapper>) //将<insert><delete><update><select>解析成一个MappedStatement对象 //然后放入到configuration对象的mappedStatements容器中(Map类型) //<namespace+id , mappedStatement> configurationElement(parser.evalNode("/mapper")); //打标 configuration.addLoadedResource(resource); //加入到configuration.mapperRegistry.knownMappers容器中(Map类型) //type = class.forName(namespace) //Map<type,new MapperProxyFactory<T>(type)> bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
解析生成的mappedStatement对象的属性。
public void parseStatementNode() { //<sql>标签中的id属性 String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); //判断对应的databaseId中是否已存在id=namespace+id if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); //<sql>中的parameterMap属性 参数Map String parameterMap = context.getStringAttribute("parameterMap"); //<sql>中的paramterType属性 参数类型 String parameterType = context.getStringAttribute("parameterType"); //解析parameterType //① 类型别名typeAlias直接TYPE_ALIASES.get(paramterType) //② 不是别名Class.forName(paramterType) Class<?> parameterTypeClass = resolveClass(parameterType); //<sql>中resultMap属性 返回参数Map String resultMap = context.getStringAttribute("resultMap"); //<sql>中resultType属性 返回参数类型 String resultType = context.getStringAttribute("resultType"); //<sql>中lang属性 不常用,指定Diver类型常用的mysql String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); //返回类型 别名转class,不是别名直接Class.forName(type) Class<?> resultTypeClass = resolveClass(resultType); //<sql>的resultSetType 不常用 String resultSetType = context.getStringAttribute("resultSetType"); //<sql>的statementType属性,编译类型,默认PreparedStatement StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); //是否<select>标签 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //<sql>的flushCache属性,是否清除缓存 默认<select>:false ; 非<select>:true boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); //<sql>的userCache属性,是否使用缓存 默认<select>:true;非<select> : false boolean useCache = context.getBooleanAttribute("useCache", isSelect); //<sql>的resultOrdered属性 不常用 boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) //sql语句解析生成sql,三部分 sql语句+parameterMapping+parameterObject SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } //创建mappedStatement对象,并初始化变量 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
② 注解解析:
sql.xml解析时bindMapperForNamespace的主题逻辑也是执行addMapper(type)。
/* org.apache.ibatis.binding.MapperRegistry#addMapper */ 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 { //加入到configuration.mapperRegistry.knownMappers容器中(Map类型) //type = class.forName(namespace) //Map<type,new MapperProxyFactory<T>(type)> knownMappers.put(type, new MapperProxyFactory<T>(type)); // 将type中methods上的注解@Insert@Delete@Update@Select转化为一个MapperStatement对象放入到configuration.mappedStatements容器中 //<type.getName+method.getName , mapperStatement> MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
小结一下sql的解析:支持sql.xml解析,支持接口解析,下面type.getName == namespace、method.getName == id
① 解析sql为一个MapperStatement对象,然后以<namespace+id , mapperStatement>的映射关系存储到configuration的容器mappedStatements中
② new了一个MapperProxyFactory对象,然后以<type , mapperProxyFactory>的映射关系存储到容器configuration.mapperRegistry.knownMappers中
三、接口映射器
看过sql的解析,然后来研究下sql执行时映射关系。
// 操作数据库方法一(ibatis主要使用方式):获得xml映射文件中定义的操作语句 User s = session.selectOne("com.app.aop.transactional.mapper.UserMapper.selectByPrimaryKey", 1L); // 打印Student对象 System.out.println(s); // 操作数据库方法二(mybatis主要使用方式):获得mapper接口的代理对象 UserMapper sm = session.getMapper(UserMapper.class); // 直接调用接口的方法,查询id为1的Student数据 User s2 = sm.selectByPrimaryKey(1L); // 打印Peson对象 System.out.println(s2);
方法一:很简单就是从configuration.mappedStatements.get(namespace+id),然后执行.主要看方法二:接口映射器实现
1、接口映射器实现
sessiong.getMapper()-->configuration.getMapper()-->configuration.mapperRegistry.getMapper()
/* 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*/ public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { //实际用JDK动态代理生成了一个代理对象proxy object //mapperProxy是InvokeHandler实例 //那么target object是什么 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
MapperProxy.invoke() : 动态代理方法执行,好像没有target object 主要是proxy object 中每个method对象都代理sqlsession的一个行为。
例如:userMapper.selectUserById(1L)实际proxyObject.selectUserById(1L)--->sqlSession.selete("com.app.aop.transactional.mapper.UserMapper.selectByPrimaryKey",1L);
/* org.apache.ibatis.binding.MapperProxy#invoke */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { //Object的方法直接执行mapperProxy的方法 return method.invoke(this, args); } else if (isDefaultMethod(method)) { //静态方法直接执行 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //new MapperMethod(mapperInterface, method, configuration) //实际new sqlCommond(mapperInterface, mehod, configuration) //sqlCommond.name = mapperStatement.getId //sqlCommond.type = ms.getSqlCommondType()--insert|delete|update|select final MapperMethod mapperMethod = cachedMapperMethod(method); //sqlSession根据sqlCommondType执行对应sql,例如当sqlCommondType == select时 //sqlsession.select(sqlCommond.name) return mapperMethod.execute(sqlSession, args); }
mapperMethod.execute():sql执行
/* 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); 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; }
四、总结
1、<Configuration>标签实际解析成了一个Configuration对象
2、mapper.xml实际是<configuration>里的一个<mapper>标签,mapper.xml中多个的<select|delete|update|insert>解析成对应的多个MappedStatement对象放入到configuration.mappedStatements(Map<namespace+id, mappedStatement)中
3、接口映射器的实质是通过JDK动态代理,生成一个mapperInterface接口的代理对象,InvokeHandler实例是new MapperProxy(mapperInterface),调用接口方法实际是调用代理对象的方法,在MapperProxy.invoke方法中调用具体的sqlSession.selectOne(namespace+id, parameters),找到上面mappedStatement然后执行sql。