要学习框架,必须先对其有整体的认识。
下面为一篇介绍Mybatis整体架构的文章
http://www.cnblogs.com/mengheng/p/3739610.html
先从基本的使用方法看起
1.1 项目结构
1.2 mybatis-config.xml
mybatis的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="useGeneratedKeys" value="true" />
<setting name="defaultExecutorType" value="REUSE" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="jdbc" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mldn" />
<property name="username" value="root" />
<property name="password" value="mypassword" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.example.mapper.UserMapper" />
</mappers>
</configuration>
1.3 Mapper
package com.example.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import com.example.pojo.User;
public interface UserMapper {
@Select("SELECT * FROM user WHERE userid = #{userid}")
public User getUserById(String userid);
@Select("SELECT * FROM user")
public List<User> getAllUsers();
}
1.4 POJO
package com.example.pojo;
public class User {
private String username;
private String userid;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [username=" + username + ", userid=" + userid
+ ", password=" + password + "]";
}
}
1.5 测试
package test.mybatis;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisUtil {
public static final String RESOURCE = "mybatis-config.xml";
private static SqlSessionFactory sqlSessionFactory;
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader(RESOURCE);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
package test;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import com.example.mapper.UserMapper;
import com.example.pojo.User;
import test.mybatis.MyBatisUtil;
public class Main {
private static SqlSessionFactory sqlSessionFactory;
static {
sqlSessionFactory = MyBatisUtil.getSqlSessionFactory();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
SqlSession session = sqlSessionFactory.openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUserById("12345678");
System.out.println(user);
} finally {
session.close();
}
}
}
1.6总结
以上为不使用Spring的情况下,MyBatis简单的使用方法。
SqlSessionFactory和SqlSession是实现数据库操作的核心类(接口),在org.apache.ibatis.session包下,该包的类结构为
Configuration类是封装了MyBatis基本配置的类了。
从上面的类关系图可以看出,Configuration与SqlSessionFactory是有关联的,那么看看两者是如何关联在一起。
2.1 SqlSessionFactory的对象获得
public static final String RESOURCE = "mybatis-config.xml";
private static SqlSessionFactory sqlSessionFactory;
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader(RESOURCE);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
而SqlSessionFactoryBuilder.build(Reader)方法,最后调用内部的bulid(Reader,String,Properties)
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
最后返回
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
3.1配置文件的解析
通常使用mybatis都会写两个配置文件。
mybatis-config.xml
mapper.xml(我现在都基本不写了,使用注解完成)
从2.1可以看出,mybatis-config.xml是由XMLConfigBuilder进行解析,然后通过parse()方法获得Configuration对象。
看一下大致的类关系图
MyBatis对XML配置文件的解释使用的是DOM
3.2 XMLConfigBuilder
Configuration对象的获得通过XMLconfigBuilder.parse()
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
然后通过paresConfiguration(XNode)方法对Configuration对象进行配置
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("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"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
写过mybatis-config.xml文件的话,对上面的关键字都不陌生。
XMLConfigBuilder对这些节点一个个的解析然后配置Configuration。
下面,主要根据前面所写的mybatis-config.xml文件,分析一下XMLConfigBuilder中的
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments"));
mapperElement(root.evalNode("mappers"));
3.2.1 Environment和Configuration简介
想要了解上面三个方法,首先要对类Environment和Configuration有基本的了解。
3.2.1.1 Environment
package org.apache.ibatis.mapping;
import javax.sql.DataSource;
import org.apache.ibatis.transaction.TransactionFactory;
/** * @author Clinton Begin */
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException("Parameter 'id' must not be null");
}
if (transactionFactory == null) {
throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}
public static class Builder {
private String id;
private TransactionFactory transactionFactory;
private DataSource dataSource;
public Builder(String id) {
this.id = id;
}
public Builder transactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
return this;
}
public Builder dataSource(DataSource dataSource) {
this.dataSource = dataSource;
return this;
}
public String id() {
return this.id;
}
public Environment build() {
return new Environment(this.id, this.transactionFactory, this.dataSource);
}
}
public String getId() {
return this.id;
}
public TransactionFactory getTransactionFactory() {
return this.transactionFactory;
}
public DataSource getDataSource() {
return this.dataSource;
}
}
可以看出,Environment类就是把数据源,事务管理封装起来的一个POJO。
3.2.1.2 Configuration
Configuration代码篇幅十分长,就不贴出来了,它有很多属性,主要关于mybatis的环境配置属性,其中包括Environment。
方法主要是Getter/Setter,和isXX,hasXX的一些判断方法。(这是主要,当然还有其他的方法)
而现在所要分析的XMLConfigBuilder中的配置Configuration的方法,就是使用Configuration的Setter方法进行配置。
3.2.2 settingsElement(root.evalNode(“settings”));
mybatis-config.xml中对应的节点
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="useGeneratedKeys" value="true" />
<setting name="defaultExecutorType" value="REUSE" />
</settings>
方法主体(主要对上面使用到的节点进行了注释)
private void settingsElement(XNode context) throws Exception {
if (context != null) {
Properties props = context.getChildrenAsProperties();
// 检查settings下的子节点的名称是否与Configuration.class中的属性对应
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
//是否开启查询缓存,默认为true
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
//设置是否使用自增长主键(仅对insert,update操作起作用),默认为false
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
//插入空值时,需要指定JdbcType
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
}
3.2.3 environmentsElement(root.evalNode(“environments”));
mybatis-config.xml中对应的节点
<environments default="development">
<environment id="development">
<transactionManager type="jdbc" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mldn" />
<property name="username" value="root" />
<property name="password" value="mypassword" />
</dataSource>
</environment>
</environments>
方法主体
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//environment属性是一个字符串
if (environment == null) {
//如果environment属性为空,则获取defalut的值
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
/* * isSpecifiedEnvironment(String)方法为判断当前environment的值与传入的字符串是否相等 * 所以若然environment本身为空,default的值与id的值不一致,将不会进行下面的配置 */
if (isSpecifiedEnvironment(id)) {
//方法transactionManagerElement(XNode)会根据type的值(示例中type=jdbc)实例化一个对应的TransactionFactory对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
/* * 方法dataSourceElement(XNode)会根据type的值(示例中type=POOLED)实例化一个对应的DataSourceFactory对象, * 然后根据property的值配置DataSourceFactory的属性 */
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
//实例化Environment对象,并传入Configuration对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
方法transactionManagerElement(XNode)
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
方法dataSourceElement(XNode)
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
3.2.4 mapperElement(root.evalNode(“mappers”));
mybatis-config.xml对应的节点
<mappers>
<mapper class="com.example.mapper.UserMapper" />
</mappers>
方法主体
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//查看是否有package子节点,有的话,直接将所指定的包名传入configuration(即将包中的所有Mapper传入)
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//分别获取mapper子节点名称resource,url,class的属性,并根据是哪一项不为空执行下面不同的代码
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<?>对象
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.");
}
}
}
}
方法Resources.classForName(String)最终调用的是ClassLoaderWrapper.classForName(String)
/*
* Attempt to load a class from a group of classloaders
*
* @param name - the class to load
* @param classLoader - the group of classloaders to examine
* @return the class
* @throws ClassNotFoundException - Remember the wisdom of Judge Smails: Well, the world needs ditch diggers, too.
*/
Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {
for (ClassLoader cl : classLoader) {
if (null != cl) {
try {
Class<?> c = Class.forName(name, true, cl);
if (null != c) {
return c;
}
} catch (ClassNotFoundException e) {
// we'll ignore this until all classloaders fail to locate the class
}
}
}
throw new ClassNotFoundException("Cannot find class: " + name);
}
配置的分析,到这里基本结束了。
其实,这篇文章所写的亦只是基础的一部分,还有很多属性是没有讲到的,想要完全透彻,还得要继续学习。