mybatis在开发中作为一个ORM框架使用的比较多,所谓ORM指的是Object Relation Mapping,直译过来就是对象关系映射,这个映射指的是java中的对象和数据库中的记录的映射,也就是一个java对象映射数据库中的一条记录。了解了mybatis的背景及作用下面看mybatis的使用及从源码分析启动过程。
一、概述
要使用mybatis必须要引入mybatis的jar包,由于我这里需要查看源码,使用的mybatis源码作为依赖。首先需要下载源码,可执行从github上下载,mybatis下载下来是maven工程,按照maven导入的方式进行导入即可,详细的步骤在这里不在赘述。
引入了mybatis的依赖便可以开发mybatis的程序,我这里使用的源码版本为:3-3.4.x版本。
1、核心配置文件
mybatis核心配置文件,一般命名为mybatis-config.xml,说是核心配置文件一点也不错,这个文件包含了使用mybatis的时候的所有配置,只有正确加载了此文件,mybatis才可正常工作。下面是mybatis-config.xml文件,
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> settings> <!-- 设置日志输出为LOG4J --> setting name="logImpl" value="LOG4J" /> 将以下画线方式命名的数据库列映射到 Java 对象的驼峰式命名属性中= "mapUnderscoreToCamelCase"="true" /> </简化类命名空间 --> typeAliases> > environments default="development"environment id> transactionManager type="JDBC" /> dataSource ="UNPOOLED"> property ="driver"="com.MysqL.jdbc.Driver" /> ="url" value="jdbc:MysqL://127.0.0.1:3306/test" ="username"="user" ="password"dataSourceenvironmentenvironmentsmappers常规做法mapper resource="cn/com/mybatis/dao/UserMapper.xml"="cn/com/mybatis/dao/MenuMapper.xml"第二种做法 <package name="cn.com.mybatis.dao"/> >
上面是一个mybatis-config.xml文件的实例,在configuration标签中配置了mappers、settings、environments等标签,这些标签代表的意思及如何解析在后面会详细分析。
这里sql的配置方式有注解和映射文件两种方式,这里采用映射文件的方式,所以在mybatis-config.xml文件中配置了Mapper文件,下面看UserMapper.xml文件,
xml version="1.0" encoding="UTF-8" DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"namespace="cn.com.mybatis.dao.UserMapper"> select ="selectUser" resultType="hashmap"> select * from e_user selectmapper>
上面的UserMapper.xml只有一个select标签,另外在mapper标签中配置了namespace属性,这个属性很关键,代表的是一个应映射文件对应的接口。下面看UserMapper接口,
package cn.com.mybatis.dao; import java.util.HashMap; java.util.List; public interface UserMapper { public List<HashMap> selectUser(); }
细心的读者会发现接口中的方法名和映射文件中的select标签的id是一样的,没错这里必须是一致,必须一一对应,至于为什么要保持一致,后面会通过源码分析,并且在一同一个namespace中不能包含同名的方法,也就是映射文件中的id不允许重复。
有了上面的这些配置,便可以开始mybatis之旅了,下面看下每个文件的位置,
二、详述
上面已经把mybatis的环境及代码已经分析了,下面看测试代码,
cn.com.mybatis.test; java.io.IOException; java.io.InputStream; org.apache.ibatis.io.Resources; org.apache.ibatis.session.sqlSession; org.apache.ibatis.session.sqlSessionFactory; org.apache.ibatis.session.sqlSessionFactoryBuilder; cn.com.mybatis.dao.UserMapper; class TestMybatis { static void main(String[] args) throws IOException { // TODO Auto-generated method stub //加载核心配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//生成一个sqlSessionFactoryBuilder对象 sqlSessionFactoryBuilder builder = new sqlSessionFactoryBuilder();
//创建一个sqlSessionFactory对象 sqlSessionFactory factory = builder.build(inputStream);
//获得一个sqlSession对象 sqlSession session=factory.openSession();
//获得一个UserMapper UserMapper userMapper=session.getMapper(UserMapper.); List<HashMap> users=userMapper.selectUser(); System.out.println(users.size()); } }
上面的代码,即使用mybatis的过程,下面来分析。
1、读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
这里就一句话,把mybatis-config.xml转化为InputStream对象,这里mybatis-config.xml文件是在类路径下(WEB-INF/classes)下,这里Resources类是如何读取文件,后续详细分析,只要明白这里会获得InputStream就好。
2、创建sqlSessionFactoryBuilder
下面需要创建一个sqlSessionFactoryBuilder对象,看这个类名可以猜到应该使用的是建造者模式,
看下具体的sqlSessionFactoryBuilder类,下面是其所有的方法,
可以看到都是build方法,那么其构造方法也就是默认的。在sqlSessionFactoryBuilder类中所有的方法都是build方法,这是标准的建造者模式,可以看到返回值都是sqlSessionFactory。在mybatis中很多地方都使用了建造者模式,后边会进行专门的分析。
sqlSessionFactory factory = builder.build(inputStream);
调用的sqlSessionFactoryBuilder的build(InputStream)方法,
public sqlSessionFactory build(InputStream inputStream) { return build(inputStream,null,1)">null); }
sqlSessionFactory build(InputStream inputStream,String environment,Properties properties) { try { XMLConfigBuilder parser = XMLConfigBuilder(inputStream,environment,properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building sqlSession.",e); } finally { ErrorContext.instance().reset(); { inputStream.close(); } (IOException e) { Intentionally ignore. Prefer prevIoUs error. } } }
在上面的代码中,使用inputStream生成一个XMLConfigBuilder,这里又是一个建造者模式,看XMLConfigBuilder的构造方法,
XMLConfigBuilder(InputStream inputStream,Properties props) {
//调用下面的构造方法 this(new XPathParser(inputStream,1)">true,props,1)"> XMLMapperEntityResolver()),props); } private XMLConfigBuilder(XPathParser parser,Properties props) {
//初始化父类BaseBuilder类的configuration super( Configuration()); ErrorContext.instance().resource("sql Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
下面看其parse()方法,此方法构造的是在这里建造的对象是Configuration对象,
Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true;
//解析mybatis-config.xml文件中的<configuration>标签,把该标签中的内容 parseConfiguration(parser.evalNode("/configuration")); configuration; }
从上面的代码中,可以看出调用parseConfiguration方法,这个方法就是解析<configuration>标签,如下,
private void parseConfiguration(XNode root) { { issue #117 read properties first 解析properties标签 propertiesElement(root.evalNode("properties")); 解析settings标签 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); 解析别名标签,例<typeAlias alias="user" type="cn.com.bean.User"/> typeAliasesElement(root.evalNode("typeAliases"解析插件标签 pluginElement(root.evalNode("plugins"解析objectFactory标签,此标签的作用是mybatis每次创建结果对象的新实例时都会使用ObjectFactory,如果不设置 则默认使用DefaultObjectFactory来创建,设置之后使用设置的 objectFactoryElement(root.evalNode("objectFactory"解析objectWrapperFactory标签 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"解析reflectorFactory标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); read it after objectFactory and objectWrapperFactory issue #631 解析environments标签 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers"解析<mappers>标签 mapperElement(root.evalNode("mappers")); } new BuilderException("Error parsing sql Mapper Configuration. Cause: " + e,e); } }
上面的方法后面会逐一进行分析,主要就是解析核心配置文件mybatis-config.xml中的配置,并放到Configuration对象中。
再回到上面的build(parse.parse())方法,其定义如下,
从上面的代码,可以看到使用configuration生成一个DefaultsqlSessionFactory对象。
3、创建sqlSessionFactory
上面分析到sqlSessionFactoryBuilder最后会返回一个DefaultsqlSessionFactory对象,
DefaultsqlSessionFactory(Configuration configuration) { this.configuration = configuration; }
可以看到,把Configuration对象直接赋值给了DefaultsqlSessionFactory对象的configuration属性。
4、创建sqlSession
sqlSession session=factory.openSession();
上面的代码调用factory的openSession()方法,也就是DefaultsqlSesssionFactory的openSession()方法,
@Override sqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(),1)">); }
private sqlSession openSessionFromDataSource(ExecutorType execType,TransactionIsolationLevel level,1)">boolean autoCommit) { Transaction tx = { 获得mybatis核心配置文件中的environment信息,包括dataSource id trasacationFactory final Environment environment = configuration.getEnvironment(); 获得transactionFactory,如果environment中没有则使用ManagedTransactionFactory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit); final Executor executor = configuration.newExecutor(tx,execType); DefaultsqlSession(configuration,executor,autoCommit); } (Exception e) { closeTransaction(tx); may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + { ErrorContext.instance().reset(); } }
上面的方法返回了一个DefaultsqlSession对象,具体的过程就是使用上面的参数构造一个DefaultsqlSession对象。
5、获取Mapper对象
使用下面的代码获取一个Mapper对象,有了Mapper对象便可以调用方法,进行数据库操作,
UserMapper userMapper=session.getMapper(UserMapper.class);
上面的代码从DefaultsqlSession中调用getMapper返回一个UserMapper对象,这里UserMapper是一个代理对象,至于为什么是代理对象,先不分析,先了解其过程,
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type,1)">); }
可以看出是从DefaultsqlSession的Configuration中获得该Mapper,下面继续看,
type,sqlSession sqlSession) {
//返回的是下面的方法 mapperRegistry.getMapper(type,sqlSession); } 调用此方法 @SuppressWarnings("unchecked") sqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == ) { new BindingException("Type " + type + " is not known to the MapperRegistry."); } mapperProxyFactory.newInstance(sqlSession); } new BindingException("Error getting mapper instance. Cause: " +T newInstance(sqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,mapperInterface,methodCache); newInstance(mapperProxy); }
下面看newInstance方法,
@SuppressWarnings("unchecked"protected T newInstance(MapperProxy<T> mapperProxy) {
//JDK动态代理 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),1)"> Class[] { mapperInterface },mapperProxy); }
到这里我们可以看到最终返回的是一个代理对象,而且是JDK动态代理的一个对象,从我们只编写了接口也可以猜出这里返回的应该是一个JDK动态代理的类,因为JDK动态代理要求必须有接口。
6、执行操作
List<HashMap> users=userMapper.selectUser();
从上面的分析制定useMapper是代理对象,那么代理类便是上面的MapperProxy类,那么执行selectUser方法,便会执行MapperProxy的invoke方法,那么该类肯定也会实现InvocationHandler接口,下面,
在看其invoke方法,
public Object invoke(Object proxy,Method method,Object[] args) Throwable { if (Object..equals(method.getDeclaringClass())) { return method.invoke(else (isDefaultMethod(method)) { invokeDefaultMethod(proxy,method,args); } } (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用的是execute方法 mapperMethod.execute(sqlSession,args); }
从上面可以看到调用的是mapperMethod.execute方法,并且把sqlSession方法作为参数传进去。那也就是说最后调用的sqlSession的方法,下面看,
可以看到调用的sqlSession的方法,从这里大体可以看出sqlSession是个重要的类。
三、总结
上面分析了mybatis的启动过程,包括加载核心配置文件(mybatis-config.xml)、sqlSessionFactory、sqlSession、执行操作数据库方法。这里仅仅分析了其执行过程,很多细节后续会一一分析,像加载配置文件、Configuration类、DefaultsqlSession以及如何通过接口找到对应的Mapper文件等内容。
有不正之处,欢迎指正。