(一)MyBatis源码解析之配置文件

时间:2022-09-09 17:16:23

使用mybatis进行数据库查询的代码如下:

public class MessageDaoDemo {
@Test
public void queryMessageList() throws IOException{
SqlSession sqlSession=getSessionFactory().openSession();
MessageMapperDao messageMapper = sqlSession.getMapper(MessageMapperDao.class);
Message msg = new Message();
msg.setCommand("555");
msg.setDescription("2");
List<Message> message = messageMapper.queryMessageList(msg.getCommand(),msg.getDescription());
for(Message m :message){
System.out.println(m.toString());
}
}

private SqlSessionFactory getSessionFactory() {
SqlSessionFactory sessionFactory = null;
String resource = "mybatis-config.xml";
try {
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
//String resource1 = "src/main/resources/mybatis-config.xml";
//File file = new File(resource1);
//sessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
return sessionFactory;
}
}
首先mybatis必须通过SqlSession来进行持久化操作,想要拿到SqlSession就要拿到SqlSessionFactory,而SqlSessionFactory对象是通过SqlSessionFactoryBuilder创建出来的,所以我们来看看SqlSessionFactoryBuilder是如何构建SqlSessionFactory的,SqlSessionFactory通过build()方法来构建SqlSessionFactory,build()有很多重载方法,重载的方法可以分为3类,一类是通过字符流Reader对象读取总的配置文件,另一类是通过字节流InputStream对象读取总的配置文件,最后一类是用总配置文件mybatis-config.xml中的信息创建一个Configuration对象,然后构建一个SqlSessionFactory,一般来说我们选择前两类中的一个来构建SqlSessionFactory,因为前两类会自动为我们创建Configuration对象。上面代码分别列出了使用Reader和使用InputStream是如何读取mybatis配置文件的。

(一)MyBatis源码解析之配置文件

图1

Mybatis使用dom来解析xml配置文件。在Mybatis中由XmlConfigBuilder负责总的配置文件的解析工作,XmlConfigBuilder的继承结构图如图1所示。mybatis总配置文件共有11个标签(全部在代码中),这11个标签的顺序是固定的,标签的顺序在mybatis-3-config.dtd文件中定义,在mybatis配置文件的头部引用了mybatis-3-config.dtd,其中最常用的标签有4个,分别是properties,typeAliases,environments,mapper标签,

 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 {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
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"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

下面来分别看看这些标签的作用然后通过源码来看看mybatis是如何使用这些标签的

properties标签:

properties配置如下

<properties resource="dbConfig.properties">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</properties>

dbConfig.properties配置如下

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf8
username=root
password=root

解析properties标签的代码在XMLConfigBuilder类的propertiesElement(...)方法中(参数省略),代码如下,在代码1 处将properties中配置的属性以键值对的形式存储到defaults中,这个时候value中的值还是以${...}的形式存储,接下来因为resource属性的值不为空,所以会用外部的properties文件中的内容直接覆盖defaults中对应的记录,所以如果我们将数据库连接的信息是从外部引入的,property标签中的value配置成什么样都是无所谓的,但是为了规范最好还是按照大家都熟悉的方式来写,用外部文件进行替换之后,mybatis会将这些信息存储到configuration的variables属性中,configuration是一个十分重要的对象,mybatis将配置文件中的所有信息都存放到该对象中

  private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//将properties中配置的属性以键值对的形式存储到Properties中
Properties defaults = context.getChildrenAsProperties();// 1
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
//用外部的peoperties文件覆盖property属性的值
defaults.putAll(Resources.getResourceAsProperties(resource)); // 2
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables(); // 3
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//将property属性中配置的键值对全部存储到defaults中
configuration.setVariables(defaults); //4
}
}

typeAliases标签:

typeAliases配置如下

<typeAliases>
<typeAlias type="net.klq.Message" alias="message"/>
<package name="net.klq.bean"/>
</typeAliases>
typeAliases配置有两种形式,一种是package,另一种是typeAlias,名字上就可以看出来,第一种是为包下所有的类按默认的规则生成别名,第二种是为特定的类生成指定的别名,具体代码如下 mybatis会根据类的全路径名为我们生成一个对应的Class对象,并将这些信息存放到typeAliasRegistry对象中,该对象是Configuration对象(存放所有配置信息的对象)的一个属性,这些类的全名和别名会简化我们在存放SQL配置文件中的开发。

  private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//为某个包下的所有类生成别名,如果包名为net.snail,该包下有个Message类,则该类生成的别名为message
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
//将类的全路径名和别名的对应信息存到typeAliasRegistry对象中
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
值得一提的是mybatis已经默认帮我们生成了很多别名与全名的对应关系

在创建TypeAliasRegistry对象时

 public TypeAliasRegistry() {
registerAlias("string", String.class);

registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);

registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);

registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);

registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);

registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);

registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);

registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);

registerAlias("ResultSet", ResultSet.class);
}
还有在创建configuration时,mybatis已经为我们初始化好了一些对应关系
  public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}

environments标签:

environmets标签配置如下:

<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

environments标签可以有多个environment子标签,在environments标签中我们通过default属性来让那个标签生效,这为我们在多套不同的环境中切换提供了方便我们还可以通过在调用SqlSessionFactoryBuilder的build(...)方法时指定让那个environment生效

  public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}

来看看源码在解析environments标签时为我们做了哪些工作,解析environments源码如下

  private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));// 1
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); // 2
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
在代码1处取到了transactionManager标签的内容,上面我们配置了type="JDBC",在transactionManagerElement(...)会去TypeAliasRegistry对象中找是否有和"JDBC"对应的类(和TypeAliasRegistry对象相关的内容上面已经讲过),mybatis在解析typeAliases标签时已经为我们创建了和"JDBC"别名对应的类JdbcTransactionFactory.class,transactionManagerElement(...)方法会根据该class对象通过反射创建一个JdbcTransactionFactory对象并返回,代码如下

  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.");
}
解析dataSource的过程是类似的,mybatis在解析typeAliases标签时已经为我们创建了和"POOLED"别名对应的类PooledDataSourceFactory.class,解析完后mybatis会将environments标签中的信息存放到configuration对象的environment属性中。在解析dataSource标签的时候,dataSource标签中的属性是以EL表达式${...}的形式存放的,mybatis使用GenericTokenParser类来解析${...}并用实际的值来进行替换。

防止篇幅过长,本篇文章我们就讲述这三个标签,其余标签会在接下来的文章中继续分析!!!