一、SqlSessionFactory
我们在学习初学MyBatis的时候,一般都会写一个MyBatisUtil,以获取SqlSession。其中一开始的片段一般是这样的:
InputStream is = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
我们知道,这里最终创建了一个SqlSessionFactory对象,但是期间到底发生了什么呢?
1.首先,从代码中我们可以知道,Resources加载全局配置文件xml,把它解析成了一个输入流
2.调用SqlSessionFactoryBuilder对象的build()创建factory。
我们来看看build()是怎么写的:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 然后返回一个DefaultSqlSessionFactory
return build(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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我们来看parse()方法:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// parse()在解析什么呢?
// 看"properties"、"typeAliases"、"environments"有没有让你联想到什么?
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
2.1、综上,我们看到程序先利用输入流创建了一个XMLConfigBuilder对象,然后调用其parse()得到一个Configuration对象。即先解析配置文件输入流,然后把配置信息封装到Configuration对象中
2.2、利用 Configuration 创建 DefaultSqlSessionFactory
一顿操作好复杂,其实简单来说,就是mybatis.xml --> Configuration对象(读取mybatis配置文件到Configuration对象,用于创建SqlSessionFactory)
我们的 mybatis.xml 文件中有这样一段
<environments default="default">
<environment id="default">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/ssm" />
<property name="username" value="root" />
<property name="password" value="0" />
</dataSource>
</environment>
</environments>
然后,Configuration类的构造函数
public Configuration(Environment environment) {
this();
this.environment = environment;
}
是不是很眼熟呢?
二、SqlSession
SqlSessionFactory有了,然后我们来生产SqlSession
SqlSession session = factory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 从Configuration对象中获取到environment信息,用于创建 TransactionFactory
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// transaction用于提交、回滚事务等操作,里面封装了数据库连接 Connection和数据源 DataSource
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// executor是MyBatis的sql执行器,相当于JDBC中的 Statement对象
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
我们看到,程序先用TransactionFactory创建了一个Transaction对象,再用它封装了一个Executor对象,最后封装出了一个DefaultSqlSession
我们说 mybatis 是对 JDBC 的封装,大家可以对比下 JDBC 对应的类封装在了哪里
Class.forName("com.mysql.jdbc.Driver");
Connection ct = DriverManager.getConnection("jdbc:mysql://localhost:3306/ssm", "root","0");
PreparedStatement ps = connection.prepareStatement("select * from users where sex=?");
ps.setString(1, "男");
ResultSet rs = ps.executeQuery();
三、综述
1、mybatis 运行时,先通过 Resources 加载核心配置文件 mybatis.xml ,然后通过 xmlConfigBulider 解析,解析完成后把结果放入 configuration 中,并把它作为参数传入到 build() 中,返回一个DefaultSqlSessionFactory
2、当调用 openSession() 来获取 SqlSession 时,程序会创建 transaction 和 executor 用于后续执行操作。
四、Mapper
得到 SqlSession 对象之后,我们可以利用它来创建 Mapper 对象,继而进行 CRUD 操作。
源码追踪
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1); //在这里打断点并debug
debug 模式进入内部方法
// 可以看到这里使用了动态代理技术
// 亦即其实得到的 XXXMapper对象是一个动态代理对象,最终调用的是代理的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 进入 ————————————————
return mapperMethod.execute(sqlSession, args);
}
execute():
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 进入 ——————————————————
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
selectOne():
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// 进入 ————————————————
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
selectList():
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 进入 ——————————————————
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里再追下去就是Executor了(封装了Statement等),之后就是JDBC的内容了…