mybatis 学习

这篇博客是我学习mybatis的心得。也是对自己的鞭策。
这些我想一步步的解答:)每天记录一下自己的学习心得
如下代码:

String resource = "config/ibatis/configuration.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}


首先 那我就看Resources这个类是如何解析资源的吧。
Resources 这个类 位于 org.apache.ibatis 包下面 包下面的类如下所示:
[img]http://dl.iteye.com/upload/attachment/611204/16cd6729-21c1-31f0-ada9-d9ebd1165e88.jpg[/img]
实际上是调用 ClassLoaderWrapper的getResourceAsReader方法
如下所示:

public static Reader getResourceAsReader(String resource) throws IOException {
Reader reader;
if (charset == null) {
//得到inputStreamReader
reader = new InputStreamReader(getResourceAsStream(resource));
} else {
reader = new InputStreamReader(getResourceAsStream(resource), charset);
}
return reader;
}


实际上调用的主要是:ClassLoaderWrapper的getResourceAsStream方法

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {

// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);

// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) returnValue = cl.getResourceAsStream("/" + resource);

if (null != returnValue) return returnValue;
}
}
return null;
}

在这里呢,我们发现classLoader 这里传入了多个classLoader 如下:

return getResourceAsStream(resource, new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
ClassLoader.getSystemClassLoader()
});

这里应该是为了防止找不到相应的配置文件而采取的措施
[color=red]需要复习一下类的加载机制了。[/color]
[quote]
JVM的类加载是通过ClassLoader以及其子类完成。

[img]http://dl.iteye.com/upload/attachment/611208/7778d9c9-fc6b-3a67-bbd6-32a0a3bf434b.jpg[/img]
打印出传递的5个classLoader 发现结果如下:
[img]http://dl.iteye.com/upload/attachment/611214/b459c4fd-5315-37e0-aa3a-4871fc64f0b8.jpg[/img]
发现后面三个classLoader是一样的 也就是[color=red]System class loader [/color]

[/quote]
好了 现在已经得到配置文件 reader对象了 ,如下解析呢 看下一步
关键在于 [color=red]SqlSessionFactoryBuilder[/color]这个类


//============================= 上面是方法重载==================================================
public SqlSessionFactory build(Reader reader, String environment, Properties props) {
try {
//通过XMLConfigBuilder 解析 这个类是解析 configuration.xml的关键
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, props);
Configuration config = parser.parse();
return build(config);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
// 关闭 reader 所以不需要在程序中关闭
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

这个时候需要看一看[color=red]XMLConfigBuilder[/color]的代码

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
super(new Configuration()); //创建一个新的Configuration对象
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
//创建xpath解析器,需要验证 主要是创建xml的document 便于引用
this.parser = new XPathParser(reader, true, new XMLMapperEntityResolver(), props);
}

//======================================= 以上三个构造方法(reader,environment,props) ====================================================

public Configuration parse() {// 解析xml
if (parsed) {//若没有解析 则抛出异常
throw new BuilderException("Each MapperConfigParser can only be used once.");
}
parsed = true; //改变是否解析过的状态
//应该是查找configuration节点 然后开始解析配置
parseConfiguration(parser.evalNode("/configuration")); //将解析后的configuration后返回
return configuration;
}



实际上 关键是 parseConfiguration 来解析这个节点


/**
* 根据根节点来解析
* @param root
*
* <configuration>
<!-- properties文件 -->
<properties resource="config/properties/db.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.connection.driver_class}"/>
<property name="url" value="${db.connection.url}"/>
<property name="username" value="${db.connection.username}"></property>
<property name="password" value="${db.connection.password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 活动的sqlxml -->
<mapper resource="config/ibatis/test.xml" />
</mappers>
</configuration>
*/
private void parseConfiguration(XNode root) {
try {
typeAliasesElement(root.evalNode("typeAliases")); //注册别名
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
propertiesElement(root.evalNode("properties")); //属性文件
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); //解析environments节点 ,这里是非常关键的
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));//解析mapper文件
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}


[color=darkred]那么如何解析typeAliases节点呢?[/color]
看如下代码:

/**
* 解析别名
* @param parent
*/
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String alias = child.getStringAttribute("alias"); //别名
String type = child.getStringAttribute("type"); //类的路径
try {
Class clazz = Resources.classForName(type); //加载类
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}


TypeAliasRegistry 类型命名注册的方法 : [color=red]registerAlias[/color]
TypeAliasRegistry类里定义了一个hashMap
如:

private final HashMap<String, Class> TYPE_ALIASES = new HashMap<String, Class>();

这里调用核心的方法:


/**
* 注册别名
* @param alias 别名的名称 需要放入map中
* @param value value 相当于类的class
*/
public void registerAlias(String alias, Class value) {
assert alias != null;
String key = alias.toLowerCase();//小写
if (TYPE_ALIASES.containsKey(key) && !TYPE_ALIASES.get(key).equals(value.getName()) && TYPE_ALIASES.get(alias) != null) {
if (!value.equals(TYPE_ALIASES.get(alias))) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(alias).getName() + "'."); //已经存在映射关系,抛出异常
}
}
TYPE_ALIASES.put(key, value);
}


[color=red]这里最关键的节点是: enviroment[/color]

/** 解析environments节点
* <environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.connection.driver_class}"/>
<property name="url" value="${db.connection.url}"/>
<property name="username" value="${db.connection.username}"></property>
<property name="password" value="${db.connection.password}"></property>
</dataSource>
</environment>
</environments>
***/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) { //没有传递environment
environment = context.getStringAttribute("default"); //得到default属性的名称
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id"); //id属性
if (isSpecifiedEnvironment(id)) { //id是否和默认的eviroment相同 ,若相同 则进行比较
//当前是JDBC factory 以后还有其他的实现类 如下的方法解析transactionManager的节点
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//得到数据工厂 得到数据源
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); //解析dataSource节点
Environment.Builder environmentBuilder = new Environment.Builder(id, txFactory, dsFactory.getDataSource());
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}


[color=indigo]那么数据工厂怎么来处理节点的参数呢?[/color]

/** 解析 dataSource 元素 **/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");//POOLED,UNPOLLED
/**类似如下所示:
* <dataSource type="POOLED">
<property name="driver" value="${db.connection.driver_class}"/>
<property name="url" value="${db.connection.url}"/>
<property name="username" value="${db.connection.username}"></property>
<property name="password" value="${db.connection.password}"></property>
若是pooled可以有其他的属性 如最小的链接数
</dataSource>
*/
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props); //设置属性
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

主要的是方法factory.setProperties(props);
这里 我们可以看到 DataSourceFactory 有一个实现类UnpooledDataSourceFactory
如下代码实现:

/** 传入的参数是DataSource的配置项 **/
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
MetaObject metaDataSource = MetaObject.forObject(dataSource); //可以代理的为对象添加属性
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {//若属性名称是 以driver开头的
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);//得到dataSource的节点属性集合
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue); //此方法可以动态的调用set方法 很奇妙 ,明天继续看哈哈
} else {
throw new DataSourceException("Unkown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}


这里的关键是一个MetaObject对象的用法。

Author author = new Author();
MetaObject metaDataSource = MetaObject.forObject(author);
metaDataSource.setValue("username","111111");

如上代码: MetaObject可以为对象赋值某一个属性 username ,所以 111111

这里需要详细介绍一下:MapWrapper 以及 BeanWrapper两个类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值