Mybatis是支持定制化SQL、存储过程和高级映射的持久层框架。主要完成两件事:
mybatis的主要目的就是管理执行SQL是参数的输入和输出,编写SQL和结果集的映射是mybatis的主要优点
SqlSessionFactory的创建是mybatis的第一步,SqlSession完成数据库增删改查。我们先来看看二者的创建
首先创建SqlSessionFactoryBudiler对象,在调用builder方法读取mybatis配置文件,并创建SqlSessionFactory:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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);
}
}
不管是调用SqlSessionFactoryBuilder哪个build重载方法,最后调用的都是上面的两种,这两种的区别只是采用不同的流读取配置文件,最后都会调用build(Configuration config)创建SqlSessionFactory接口的实现类对象
当创建SqlSessionFactory完成后下一步就是创建SqlSession:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
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);
//这一步可以发现每个SqlSession会配一个Executor
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();
}
}
}
接下来我们看看SqlSession的源码:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
SqlSession的所有增删改查操作,最后都会落到上面三个方法,而最后的SQL执行都是使用Executor执行。
接下来我们在看看Executor中的query()和update方法,Executor接口有一个抽象实现类BaseExecutor,我们调用的query()和update()方法实际属于该类,而该类的query()和update()最后都落到三个子类SimpleExecutor、ReuseExecutor、BatchExecutor中。这里我们选SimpleExecutor看看
public class SimpleExecutor extends BaseExecutor {
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
//把锅甩给StatementHandler
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//把锅甩给StatementHandler
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
}
上面prepareStatement()方法内部调用StatementHandler的prepare()方法,这一般是我们定制插件的拦截方法。
可以发现最后都交给StatementHandler处理。StatementHandler有三个实现类:PreparedStatementHandler、CallableStatementHandler、RoutingStatementHandler,我们选择PreparedStatementHandler看一下:
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler. handleResultSets(ps);
}
}
好了,这里就是我们熟悉的JDBC操作了。
到了这了大家可能还有一个疑问,我调用的是DAO接口中的方法,和上面这些好像没关系。别急接下来我们就来看看二者是怎么联系起来的
在mybatis和Spring集合使用中,使用DAO时我们一般使用@Autowired注入,但是大家有没有一个疑问,DAO是一个接口,接口是不能创建对象的,这个是怎么完成的呢?
先来看第一个疑惑:使用mybatis操作数据库时,方法是这样的:
SqlSessionFactory sessionFactory = null;
String resource = "mybatis-conf.xml";
sessionFactory = new SqlSessionFactoryBuilder().build(Resources
.getResourceAsReader(resource));
SqlSession sqlSession = sessionFactory.openSession();
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println("jdk proxy mapper : "+mapper);
UserPO userPo = mapper.getUserByUsername("zhangsan");
如何获取SqlSession上面我们已经了解了,这里重点关注session.getMapper(UserMapper.class)这一句
上图描述了getMapper()这个方法的过程,我们来看MapperRegistry中的这个方法:
public class MapperRegistry {
private final Configuration config;
private final Map, MapperProxyFactory>> knownMappers = new HashMap, MapperProxyFactory>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
konwMapper会在创建MapperRegistry对象是初始化,key是我们定义的Dao的class对象,value是根据各Dao接口的class对象创建的MapperProxyFactory、接下来我们看MapperProxyFactory:
public class MapperProxyFactory {
//我们的Dao接口的class对象
private final Class mapperInterface;
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
到了这一步我们明白了,这是通过动态代理创建Dao接口的动态类的对象,而对接口所有方法的调用,最后都会回到调用mapperProxy的invoke方法上(这就是JDK动态代理)。我们去看看mapperProxy对象的invoke方法:
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断你调用的是否是已实现的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
if判断我们调用的方法是否是对象中的,我们调用的都是接口的方法,所以直接走mapperMethod.execute()。mapperMethod标识我们调用接口中的那个方法:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
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 if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}
}
好,这里就和上面的SqlSession接上了,最后这些操作都会归为SqlSession中的update、selectList、select操作了。而这其实我们已经知道了,最后SqlSession其实是交给Statement执行SQL命令了。
下面来解决另外一个疑惑@Autowired注入的是个什么鬼?
注入的其实就是动态代理创建的接口实现类型的对象,不过mybatis-spring把sqlSession.getMapper()这个动作交个了MapperFactoryBean。而这个实在Spring扫描dao层的时候,为每一个接口分别创建一个MapperFactoryBean:
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
private Class mapperInterface;
public MapperFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
mapperInterface就是dao的class对象,因为实现了FactoryBean接口,因此通过@Autowired获取对象时,实际是调用getObject方法获取对象,而这里有回到了sqlSession.getMapper()。到此终于和上面接上了