在了解mybatis之前,我们先来看看原始jdbc操作数据库需要哪些步骤:
Class.forName("com.mysql.jdbc.Driver"); //1.加载驱动
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/kkb","root","123"
); //2.获取数据库链接
con.setAutoCommit(false)
PreparedStatement ps = con.preparedStatement(sql); // 3.得到数据库操作对象
ps.setInt(); // 4.设置参数
ps.setString();
try{
int num= ps.executeUpdate();
ResultSet rs= ps.executeQuery(); //5.执行sql,得到结果
ps.executeBatch();
con.commit(); //6. 提交事物
}catch(SQLException ex){
con.rollback();
}
con.close(); //7.关闭链接
mybatis作为一个数据持久层框架,说白了就是对上面几个步骤的封装,然我们只需要关心sql的编写,其它的事,都是一些公共的操作,我们只需要简单的配置,它就可以帮我们完成了。
下面对源码的解析,大体的方向也就是看mybatis对应上述的步骤,都是怎么封装的。
1.加载配置文件,生成Configuration对象
Configuration对象是mybatis配置在内存中的载体。
这里的源码,多余的内容被我删了,只留下了主要的处理逻辑。
//SqlSessionFactoryBuilder.build()方法,该方法返回SqlSessionFactory(工厂模式)
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//reader 配置文件的数据流
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
}
}
public Configuration parse() {//解析配置文件的方法
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//具体解析配置,然后设置到Configuration对象中
//这里采用了建造者模式,一步步完善Configuration的属性
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
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);
// 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);
}
}
2.创建执行器对象Executor
Executor是个接口,它有两个实现类BaseExecutor和CachingExecutor
- BaseExecutor(适配器 模式)有三个子类:
* SimpleExecutor 普通执行器
* ReuseExecutor 可重用执行器
* BatchExecutor 批量执行器
具体看下面源码,会根据不同情况创建不同的Executor.
通过第一步的SqlSessionFactory.openSession()就会创建一个SqlSession,
下面看看openSession()干了啥 (采用的DefaultSqlSessionFactory的openSession())
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
//创建了事物管理器
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
**//创建执行器**
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
}
//这个地方判断执行器类型:BATCH 批量; REUSE 可重用;simple 普通执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//是否开启了缓存 默认开启
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//加入了拦截器逻辑
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//最后返回了DefaultSqlSession 调用构造函数填充属性
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
3.创建StatmentHandler对象,设置参数会用ParmeterHandler,结果映射会用到ResultHandler
StatmentHandler是个接口 下面有两个子类BaseStatmentHandler和RoutingStatementHandler;
SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler继承了BaseStatmentHandler。
对比本文最开始列出来的几个操作数据的步骤,1加载驱动 2获取数据库连接(mybatis会初始化数据库连接池),因为不是核心,我这就不去贴源码了。
这个statment对象是在什么时候创建的呢?看看执行器对象SimpleExecutor就知道了
执行查询主要分为三步
//用执行器对象执行某个数据库操作的时候,就会创建statmentHandler对象 ,下面只列出了query操作
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//1. 创建StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//2. sql预编译后,返回PrepareStatment
stmt = prepareStatement(handler, ms.getStatementLog());
//3. 通过statment对象执行真正的数据库查询操作
return handler.<E>query(stmt, resultHandler);
}
}
第一步 创建mybatis 的StatmentHandler对象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//根据不同的类型创建不同的StatmentHandler对象
switch (ms.getStatementType()) {
case STATEMENT://sql没有参数
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED://sql有参数
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE://采用 call()
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
第二.1步 创建数据库对象PrepareStatment对象 相当于最上面的 3.得到数据库操作对象。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//2.1 预编译后 返回 prepareStatement
stmt = handler.prepare(connection, transaction.getTimeout());
// 2.2设置sql参数 (源码在下一步)
handler.parameterize(stmt);
return stmt;
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//这个方法 就是通过数据库连接创建真正的数据 PrepareStatment对象了
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);//设置超时时间
setFetchSize(statement);//查询条数
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {//是否主键自动生成
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
//这里就是调用本文最上面步骤3的代码了 预编译
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
第二.2步 给PrepareStatment对象赋值 相当于最上面的 4.设置参数
调用这个方法给参数赋值handler.parameterize(stmt);
@Override
public void parameterize(Statement statement) throws SQLException {
//赋值 调用的是 parameterHandler
parameterHandler.setParameters((PreparedStatement) statement);
}
//主要的逻辑就是将java参数类型转成数据库类型 不同的类型调用不同的Typehandler赋值
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
}
}
//关注最下面的typeHandler.setParameter(ps, i + 1, value, jdbcType)方法即可
//如果是数组调用的就是
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setArray(i, (Array) parameter);
}
第三步 执行查询 将结果映射成java对象
//执行查询
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//这个地方也很熟悉上,对应最上面的第五步,执行sql
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
}
//TODO resultSetHandler 源码
mybatis面向接口编程,采用的jdk动态代理
看看 session.getMapper(),获取操作数据库的dao对象了(代理模式)
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
try {
//一看这个名字,就猜到是代理工厂了
return mapperProxyFactory.newInstance(sqlSession);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//这个代码就很熟悉了啥 采用jdk动态代理,对mapper接口进行代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
//调用上面紧挨着的方法
return newInstance(mapperProxy);
}
}
到这里,已经大体分析完了mybatis的整个查询过程,具体某些方法的执行细节,就需要大家自己去debug看了,其它update,delete操作也和这个流程差不多。后面会分析mybatis的缓存和事务。