Mapper 代理方式初始化
首先修改一下 SqlSession 获取代理对象方式,即通过 getMapper() 来拿到动态代理对象
public class MybatisTest {
@Test
public void test2 ( ) throws IOException {
InputStream resourceAsStream = Resources . getResourceAsStream ( "sqlMapConfig.xml" ) ;
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ( ) . build ( resourceAsStream) ;
SqlSession sqlSession = sqlSessionFactory. openSession ( ) ;
UserMapper mapperProxy = sqlSession. getMapper ( UserMapper . class ) ;
. . .
}
}
修改 sqlMapConfig.xml 引入配置文件的方式
< configuration>
...
< mappers>
< package name = " com.itheima.mapper" />
</ mappers>
</ configuration>
把 UserMapper.xml 放到和 com.itheima.mapper.UserMapper 同一个目录,同时修改一下命名空间,然后就可以学习 MyBatis 的代理方式
<! DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
< mapper namespace = " com.itheima.mapper.UserMapper" >
< cache> </ cache>
< select id = " findByCondition" resultType = " com.itheima.pojo.User" useCache = " true" >
SELECT id, name FROM user WHERE id = #{id}
</ select>
</ mapper>
问题
<package name=“com.itheima.mapper”/> 是如何进行解析的?
首先解析配置文件还是从 SqlSessionFactoryBuilder 的 build() 开始,方法内会先创建 XMLConfigBuilder ,然后开始解析 sqlMapConfig.xml 配置文件
public class SqlSessionFactoryBuilder {
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) {
}
}
}
public SqlSessionFactory build ( Configuration config) {
return new DefaultSqlSessionFactory ( config) ;
}
}
parse() 会继续先解析 /configuration 节点,然后从根节点开始找到每个节点进行解析
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private final XPathParser parser;
. . .
public Configuration parse ( ) {
if ( parsed) {
throw new BuilderException ( "Each XMLConfigBuilder can only be used once." ) ;
}
parsed = true ;
parseConfiguration ( parser. evalNode ( "/configuration" ) ) ;
return configuration;
}
}
这次我们更关注解析 /mappers 这个基点
public class XMLConfigBuilder extends BaseBuilder {
. . .
private void parseConfiguration ( XNode root) {
try {
. . .
mapperElement ( root. evalNode ( "mappers" ) ) ;
} catch ( Exception e) {
throw new BuilderException ( "Error parsing SQL Mapper Configuration. Cause: " + e, e) ;
}
}
获取 /mappers 标签,然后遍历 /mappers 标签的子标签,也就是现在我们配置的 /package 标签,从标签中拿到 name 属性,即 com.itheima.mapper,然后开始将包下所有的 mapper 接口以及它的代理工厂对象存储到 Configuration 的一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
public abstract class BaseBuilder {
protected final Configuration 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" ) ;
configuration. addMappers ( mapperPackage) ;
} else {
. . .
}
}
}
}
Configuration 对象会交由 MapperRegistry 来添加到 Map 集合
public class Configuration {
. . .
protected final MapperRegistry mapperRegistry = new MapperRegistry ( this ) ;
. . .
public void addMappers ( String packageName) {
mapperRegistry. addMappers ( packageName) ;
}
}
MapperRegistry 先创建解析工具类 ResolverUtil ,根据 packageName 找到该包下所有的 Mapper 接口文件,然后将 Mapper 接口添加到 MapperRegistry 中
public class MapperRegistry {
. . .
public void addMappers ( String packageName, Class < ? > superType) {
ResolverUtil < Class < ? > > resolverUtil = new ResolverUtil < > ( ) ;
resolverUtil. find ( new ResolverUtil. IsA ( superType) , packageName) ;
Set < Class < ? extends Class < ? > > > mapperSet = resolverUtil. getClasses ( ) ;
for ( Class < ? > mapperClass : mapperSet) {
addMapper ( mapperClass) ;
}
}
}
ResolverUtil 找接口时,先把 packageName 改成 com/itheima/mapper,然后查找所有的 .class 文件,然后加载这个文件存入 matches 中
public class ResolverUtil < T > {
private Set < Class < ? extends T > > matches = new HashSet < > ( ) ;
. . .
protected String getPackagePath ( String packageName) {
return packageName == null ? null : packageName. replace ( '.' , '/' ) ;
}
public ResolverUtil < T > find ( Test test, String packageName) {
String path = getPackagePath ( packageName) ;
try {
List < String > children = VFS. getInstance ( ) . list ( path) ;
for ( String child : children) {
if ( child. endsWith ( ".class" ) ) {
addIfMatching ( test, child) ;
}
}
} catch ( IOException ioe) {
log. error ( "Could not read package: " + packageName, ioe) ;
}
return this ;
}
protected void addIfMatching ( Test test, String fqn) {
try {
String externalName = fqn. substring ( 0 , fqn. indexOf ( '.' ) ) . replace ( '/' , '.' ) ;
ClassLoader loader = getClassLoader ( ) ;
if ( log. isDebugEnabled ( ) ) {
log. debug ( "Checking to see if class " + externalName + " matches criteria [" + test + "]" ) ;
}
Class < ? > type = loader. loadClass ( externalName) ;
if ( test. matches ( type) ) {
matches. add ( ( Class < T > ) type) ;
}
} catch ( Throwable t) {
log. warn ( "Could not examine class '" + fqn + "'" + " due to a "
+ t. getClass ( ) . getName ( ) + " with message: " + t. getMessage ( ) ) ;
}
}
}
得到类集合之后,先判断对应的类是否已经加入过 knownMappers ,如果是,则抛出异常,否则就加入 knownMappers 中,其中 key 为 type ,value 为 MapperProxyFactory 代理工厂,如果接口中有使用 @Select 或者配置了 Mapper.xml ,就需要使用 MapperAnnotationBuilder 进一步解析
public class MapperRegistry {
private final Configuration config;
private final Map < Class < ? > , MapperProxyFactory < ? > > knownMappers = new HashMap < > ( ) ;
. . .
public < T > boolean hasMapper ( Class < T > type) {
return knownMappers. containsKey ( 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) ) ;
MapperAnnotationBuilder parser = new MapperAnnotationBuilder ( config, type) ;
parser. parse ( ) ;
loadCompleted = true ;
} finally {
if ( ! loadCompleted) {
knownMappers. remove ( type) ;
}
}
}
}
}
解析 Mapper.xml 时,先获取 mapper 接口的全路径(即 interface com.itheima.mapper.UserMapper)。如果 Configuration 对象从来没加载过,就创建 XMLMapperBuilder 来解析。然后遍历 type 的每个方法,都解析构成 MappedStatement 对象,存入 Configuration 对象中
public class MapperAnnotationBuilder {
private final Configuration configuration;
private final MapperBuilderAssistant assistant;
private final Class < ? > type;
. . .
public void parse ( ) {
String resource = type. toString ( ) ;
if ( ! configuration. isResourceLoaded ( resource) ) {
loadXmlResource ( ) ;
configuration. addLoadedResource ( resource) ;
assistant. setCurrentNamespace ( type. getName ( ) ) ;
parseCache ( ) ;
parseCacheRef ( ) ;
for ( Method method : type. getMethods ( ) ) {
if ( ! canHaveStatement ( method) ) {
continue ;
}
if ( getAnnotationWrapper ( method, false , Select . class , SelectProvider . class ) . isPresent ( )
&& method. getAnnotation ( ResultMap . class ) == null ) {
parseResultMap ( method) ;
}
try {
parseStatement ( method) ;
} catch ( IncompleteElementException e) {
configuration. addIncompleteMethod ( new MethodResolver ( this , method) ) ;
}
}
}
parsePendingMethods ( ) ;
}
void parseStatement ( Method method) {
final Class < ? > parameterTypeClass = getParameterType ( method) ;
. . .
assistant. addMappedStatement (
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
null ,
parameterTypeClass,
resultMapId,
getReturnType ( method) ,
resultSetType,
flushCache,
useCache,
false ,
keyGenerator,
keyProperty,
keyColumn,
statementAnnotation. getDatabaseId ( ) ,
languageDriver,
options != null ? nullOrEmpty ( options. resultSets ( ) ) : null ) ;
} ) ;
}
}
总结