mybatis初始化mysql_MyBatis初始化

1. 准备工作

为了看清楚MyBatis的整个初始化过程,先创建一个简单的Java项目,目录结构如下图所示:

8a67ef383cbabdf8a426820f6f873382.png

1.1 Product 产品实体类

public classProduct {private longid;privateString productName;privateString productContent;privateString price;private intsort;private intfalseSales;private longcategory_id;private bytetype;private bytestate;//PS:省略setter、getter函数

1.2 ProductMapper 产品持久化接口

public interfaceProductMapper {/*** 查询所有的产品

*@return

*/ListselectProductList();

}

1.3 ProductMapper.xml 产品映射文件

select * from product

1.4 db.properties 数据库配置文件

driver=com.mysql.jdbc.Driver

url=jdbc:mysql://127.0.0.1:3306/waimai?useUnicode=true&characterEncoding=utf8

username=root

password=xxxxxx

1.5 mybatis.xml MyBatis的配置文件

/p>

PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-config.dtd">

1.6 Main 主函数

public classMain {public static void main(String[] args) throwsIOException {

String resource= "mybatis.xml";

InputStream inputStream=Resources.getResourceAsStream(resource);

SqlSessionFactory sqlSessionFactory= newSqlSessionFactoryBuilder().build(inputStream);

SqlSession sqlSession=sqlSessionFactory.openSession();try{

ProductMapper productMapper= sqlSession.getMapper(ProductMapper.class);

List productList =productMapper.selectProductList();for(Product product : productList) {

System.out.printf(product.toString());

}

}finally{

sqlSession.close();

}

}

}

2. MyBatis初始化过程

2.1 获取配置文件

当系统初始化时,首先会读取配置文件,并将其解析成InputStream

String resource = "mybatis.xml";

InputStream inputStream= Resources.getResourceAsStream(resource);

2.2 创建SqlSessionFactoryBuilder对象

从 SqlSessionFactoryBuilder的名字中可以看出, SqlSessionFactoryBuilder是用来创建 SqlSessionFactory对象的。

来看一下SqlSessionFactoryBuilder源码:

9a8ba0077db6b4196fa62dd5474c842e.png

SqlSessionFactoryBuilder中只有一些重载的build函数,这些build函数的入参都是MyBatis配置文件的输入流,返回值都是SqlSessionFactory;由此可见,SqlSessionFactoryBuilder的作用很纯粹,就是用来通过配置文件创建SqlSessionFactory对象的。

2.3 SqlSessionFactory创建过程

下面具体来看一下,build函数是如何创建SqlSessionFactory对象的。

publicSqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try{

XMLConfigBuilder parser= newXMLConfigBuilder(inputStream, environment, properties);returnbuild(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.

}

}

}

2.3.1 构造XMLConfigBuilder对象

build函数首先会构造一个XMLConfigBuilder对象,从名字上大致可以猜到,该对象是用来解析XML配置文件的。下面来看一下XMLConfigBuilder的体系结构。

d7828152ad671ed33eda91b6d784d5cc.png

XMLxxxBuilder是用来解析XML配置文件的,不同类型XMLxxxBuilder用来解析MyBatis配置文件的不同部位。比如:XMLConfigBuilder用来解析MyBatis的配置文件,XMLMapperBuilder用来解析MyBatis中的映射文件(如上文提到的ProductMapper.xml),XMLStatementBuilder用来解析映射文件中的SQL语句。

这些XMLxxxBuilder都有一个共同的父类—— BaseBuilder。这个父类维护了一个全局的 Configuration对象,MyBatis的配置文件解析后就以Configuration对象的形式存储。

当创建 XMLConfigBuilder对象时,就会初始化Configuration对象,并且在初始化Configuration对象的时候,一些别名会被注册到Configuration的typeAliasRegistry容器中。

privateXMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(newConfiguration());

ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment =environment;this.parser =parser;

}

publicConfiguration() {

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);

……

}

2.3.2 解析配置文件

当有了XMLConfigBuilder对象之后,接下来就可以用它来解析配置文件了。

private voidparseConfiguration(XNode root) {try{//解析节点

propertiesElement(root.evalNode("properties"));//解析节点

Properties settings = settingsAsProperties(root.evalNode("settings"));

loadCustomVfs(settings);//解析节点

typeAliasesElement(root.evalNode("typeAliases"));//解析节点

pluginElement(root.evalNode("plugins"));//解析节点

objectFactoryElement(root.evalNode("objectFactory"));

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));//解析节点

reflectorFactoryElement(root.evalNode("reflectorFactory"));

settingsElement(settings);//解析节点

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);

}

}

从上述代码中可以看到,XMLConfigBuilder会依次解析配置文件中的、< settings >、< environments>、< typeAliases >、< plugins >、< mappers >等属性。下面介绍下几个重要属性的解析过程。

节点的定义如下:

节点的解析过程:

/*** @Param context 节点*/

private void propertiesElement(XNode context) throwsException {if (context != null) {//获取节点的所有子节点

Properties defaults =context.getChildrenAsProperties();//获取节点上的resource属性

String resource = context.getStringAttribute("resource");//获取节点上的url属性

String url = context.getStringAttribute("url");//resource和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) {//获取resource属性值对应的properties文件中的键值对,并添加至defaults容器中

defaults.putAll(Resources.getResourceAsProperties(resource));

}else if (url != null) {//获取url属性值对应的properties文件中的键值对,并添加至defaults容器中

defaults.putAll(Resources.getUrlAsProperties(url));

}//获取configuration中原本的属性,并添加至defaults容器中

Properties vars =configuration.getVariables();if (vars != null) {

defaults.putAll(vars);

}

parser.setVariables(defaults);//将defaults容器添加至configuration中

configuration.setVariables(defaults);

}

}

首先读取节点下的所有节点,并将每个节点的name和value属性存入Properties中。

然后读取节点上的resource、url属性,并获取指定配置文件中的name和value,也存入Properties中。(PS:由此可知,如果resource节点上定义的属性和properties文件中的属性重名,那么properties文件中的属性值会覆盖resource节点上定义的属性值。)

最终,携带所有属性的 Properties对象会被存储在 Configuration对象中。

2.3.2.2 节点的解析过程

节点的定义如下:

节点的解析过程:

属性的解析过程和 属性的解析过程极为类似,这里不再赘述。最终,所有的setting属性都被存储在Configuration对象中。

2.3.2.3 属性的解析过程

属性的定义方式有如下两种:

方式1:

方式2:

采用这种方式时,MyBatis会为指定包下的所有类起一个别名,该别名为首字母小写的类名。

节点的解析过程如下:

private voidtypeAliasesElement(XNode parent) {if (parent != null) {//遍历下的所有子节点

for(XNode child : parent.getChildren()) {//若当前结点为

if ("package".equals(child.getName())) {//获取上的name属性(包名)

String typeAliasPackage = child.getStringAttribute("name");//为该包下的所有类起个别名,并注册进configuration的typeAliasRegistry中

configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);

}//如果当前结点为< typeAlias >

else{//获取alias和type属性

String alias = child.getStringAttribute("alias");

String type= child.getStringAttribute("type");//注册进configuration的typeAliasRegistry中

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);

}

}

}

}

}

如果节点下定义了节点,那么MyBatis会给该包下的所有类起一个别名(以类名首字母小写作为别名)

如果节点下定义了节点,那么MyBatis就会给指定的类起指定的别名。

这些别名都会被存入configuration的typeAliasRegistry容器中。

2.3.2.4 节点的解析过程

节点的定义方式有如下四种:

方式1:

方式2:

方式3:

方式4:

节点的解析过程如下:

private void mapperElement(XNode parent) throwsException {if (parent != null) {//遍历下所有子节点

for(XNode child : parent.getChildren()) {//如果当前节点为

if ("package".equals(child.getName())) {//获取的name属性(该属性值为mapper class所在的包名)

String mapperPackage = child.getStringAttribute("name");//将该包下的所有Mapper Class注册到configuration的mapperRegistry容器中

configuration.addMappers(mapperPackage);

}//如果当前节点为

else{//依次获取resource、url、class属性

String resource = child.getStringAttribute("resource");

String url= child.getStringAttribute("url");

String mapperClass= child.getStringAttribute("class");//解析resource属性(Mapper.xml文件的路径)

if (resource != null && url == null && mapperClass == null) {

ErrorContext.instance().resource(resource);//将Mapper.xml文件解析成输入流

InputStream inputStream =Resources.getResourceAsStream(resource);//使用XMLMapperBuilder解析Mapper.xml,并将Mapper Class注册进configuration对象的mapperRegistry容器中

XMLMapperBuilder mapperParser = newXMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());

mapperParser.parse();

}//解析url属性(Mapper.xml文件的路径)

else if (resource == null && url != null && mapperClass == null) {

ErrorContext.instance().resource(url);

InputStream inputStream=Resources.getUrlAsStream(url);

XMLMapperBuilder mapperParser= newXMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());

mapperParser.parse();

}//解析class属性(Mapper Class的全限定名)

else if (resource == null && url == null && mapperClass != null) {//将Mapper Class的权限定名转化成Class对象

Class> mapperInterface =Resources.classForName(mapperClass);//注册进configuration对象的mapperRegistry容器中

configuration.addMapper(mapperInterface);

}else{throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");

}

}

}

}

}

MyBatis会遍历下所有的子节点,如果当前遍历到的节点是,则MyBatis会将该包下的所有Mapper Class注册到configuration的mapperRegistry容器中。

如果当前节点为,则会依次获取resource、url、class属性,解析映射文件,并将映射文件对应的Mapper Class注册到configuration的mapperRegistry容器中。

其中,节点的解析过程如下:

XMLMapperBuilder mapperParser = newXMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());

mapperParser.parse();

在解析前,首先需要创建XMLMapperBuilder,创建过程如下:

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, MapsqlFragments) {//将configuration赋给BaseBuilder

super(configuration);//创建MapperBuilderAssistant对象(该对象为MapperBuilder的协助者)

this.builderAssistant = newMapperBuilderAssistant(configuration, resource);this.parser =parser;this.sqlFragments =sqlFragments;this.resource =resource;

}

首先会初始化父类BaseBuilder,并将configuration赋给BaseBuilder;

然后创建MapperBuilderAssistant对象,该对象为XMLMapperBuilder的协助者,用来协助XMLMapperBuilder完成一些解析映射文件的动作。

当有了XMLMapperBuilder后,便可进入解析的过程:

public voidparse() {//若当前的Mapper.xml尚未被解析,则开始解析//PS:若节点下有相同的节点,那么就无需再次解析了

if (!configuration.isResourceLoaded(resource)) {//解析节点

configurationElement(parser.evalNode("/mapper"));//将该Mapper.xml添加至configuration的LoadedResource容器中,下回无需再解析

configuration.addLoadedResource(resource);//将该Mapper.xml对应的Mapper Class注册进configuration的mapperRegistry容器中

bindMapperForNamespace();

}

parsePendingResultMaps();

parsePendingCacheRefs();

parsePendingStatements();

}

configurationElement函数

private voidconfigurationElement(XNode context) {try{//获取节点上的namespace属性,该属性必须存在,表示当前映射文件对应的Mapper Class是谁

String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");

}//将namespace属性值赋给builderAssistant

builderAssistant.setCurrentNamespace(namespace);//解析节点

cacheRefElement(context.evalNode("cache-ref"));//解析节点

cacheElement(context.evalNode("cache"));//解析节点

parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析节点

resultMapElements(context.evalNodes("/mapper/resultMap"));//解析节点

sqlElement(context.evalNodes("/mapper/sql"));//解析sql语句

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

}catch(Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause: " +e, e);

}

}

resultMapElements函数

该函数用于解析映射文件中所有的节点,这些节点会被解析成ResultMap对象,存储在Configuration对象的resultMaps容器中。

节点定义如下:

节点的解析过程:

private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throwsException {

ErrorContext.instance().activity("processing " +resultMapNode.getValueBasedIdentifier());//获取上的id属性

String id = resultMapNode.getStringAttribute("id",

resultMapNode.getValueBasedIdentifier());//获取上的type属性(即resultMap的返回值类型)

String type = resultMapNode.getStringAttribute("type",

resultMapNode.getStringAttribute("ofType",

resultMapNode.getStringAttribute("resultType",

resultMapNode.getStringAttribute("javaType"))));//获取extends属性

String extend = resultMapNode.getStringAttribute("extends");//获取autoMapping属性

Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");//将resultMap的返回值类型转换成Class对象

Class> typeClass =resolveClass(type);

Discriminator discriminator= null;//resultMappings用于存储下所有的子节点

List resultMappings = new ArrayList();

resultMappings.addAll(additionalResultMappings);//获取并遍历下所有的子节点

List resultChildren =resultMapNode.getChildren();for(XNode resultChild : resultChildren) {//若当前节点为,则将它的子节点们添加到resultMappings中去

if ("constructor".equals(resultChild.getName())) {

processConstructorElement(resultChild, typeClass, resultMappings);

}//若当前节点为,则进行条件判断,并将命中的子节点添加到resultMappings中去

else if ("discriminator".equals(resultChild.getName())) {

discriminator=processDiscriminatorElement(resultChild, typeClass, resultMappings);

}//若当前节点为、、,则将其添加到resultMappings中去

else{//PS:flags仅用于区分当前节点是否是或,因为这两个节点的属性名为name,而其他节点的属性名为property

List flags = new ArrayList();if ("id".equals(resultChild.getName())) {

flags.add(ResultFlag.ID);

}

resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));

}

}//ResultMapResolver的作用是生成ResultMap对象,并将其加入到Configuration对象的resultMaps容器中(具体过程见下)

ResultMapResolver resultMapResolver = newResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);try{returnresultMapResolver.resolve();

}catch(IncompleteElementException e) {

configuration.addIncompleteResultMap(resultMapResolver);throwe;

}

}

ResultMapResolver这个类很纯粹,有且仅有一个函数resolve,用于构造ResultMap对象,并将其存入Configuration对象的resultMaps容器中;而这个过程是借助于MapperBuilderAssistant.addResultMap完成的。

publicResultMap resolve() {return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);

}

sqlElement函数

该函数用于解析映射文件中所有的节点,并将这些节点存储在当前映射文件所对应的XMLMapperBuilder对象的sqlFragments容器中,供解析sql语句时使用。

${alias}.id,${alias}.username,${alias}.password

buildStatementFromContext函数

该函数会将映射文件中的sql语句解析成MappedStatement对象,并存在configuration的mappedStatements。

2.3.3 创建SqlSessionFactory对象

publicSqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try{

XMLConfigBuilder parser= newXMLConfigBuilder(inputStream, environment, properties);returnbuild(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.

}

}

}

回过头来再看一下SqlSessionFactory的build函数,刚才说了半天,介绍了XMLConfigBuilder解析映射文件的过程,解析完成之后parser.parse()函数会返回一个包含了映射文件解析结果的configuration对象,紧接着,这个对象将作为参数传递给另一个build函数,如下:

publicSqlSessionFactory build(Configuration config) {return newDefaultSqlSessionFactory(config);

}

这个函数将configuration作为参数,创建了DefaultSqlSessionFactory对象。

DefaultSqlSessionFactory是接口SqlSessionFactory的一个实现类,SqlSessionFactory的体系结构如下图所示:

76bb035da3575926e97b3ba23433a72c.png

此时,SqlSessionFactory创建完毕!

转自:https://blog.csdn.net/u010425776/article/details/78207890

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值