面试题
一类是存储类对象
,一类是操作类对象
也就是说我们Mybatis的存储类对象的作用就是读取我们写的所有配置文件,将其封装成对象
操作类对象就是用来操作这个配置对象的,使用这些操作类对象与DB进行交互(建立连接发SQL拿结果等)
运行流程图
使用的是XPath方式
还有DOM SAX两种方式
XPathParser---->XNode
主配置文件(mybatis-config.xml)以及所有的XxxMapper.xml文件
进行解析然后封装在Configuration
对象中,然后创建一个SqlSession的工厂,将封装好的Configuration传入 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
Mybatis对于Mapper文件的封装是基于每一个增删改查标签进行的封装,每一个(select|update|insert|delete)标签都封装成一个MappedStatement对象,然后将所有MappedStatement对象都设置在Configuration对象的一个Map属性中,key就是这个标签的namespace+标签id,value就是这个MappedStatement
//environments标签
protected Environment environment;
//标签
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected boolean useActualParamName = true;
// 标签
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
.....
/*所有的增删改查标签被封装成了一个Map `
- key就是这个标签的namespace+标签id,
- value就是这个标签被封装成的MappedStatement对象
*/
protected final Map<String, MappedStatement> mappedStatements
//所有的缓存信息
protected final Map<String, Cache> caches
/*所有的resultMap信息 跟MappedStatement类似
key就是namespace+id value就是这个resultMap对象
*/
protected final Map<String, ResultMap> resultMaps
...
里面包含了标签的 id 各种属性 SQL语句等
private Configuration configuration; //我们的Configuration对象
private String id; //标签的id
private Integer timeout; //超时属性
/*
语句类型就是JDBC提供的有三种 默认是PreparedStatement
Statement 普通的,有SQL注入风险 弃用
PreparedStatement 预编译 默认使用
callableStatement 处理存储过程
*/
private StatementType statementType;
//返回类型处理
private ResultSetType resultSetType;
/*
https://blog.csdn.net/lqzkcx3/article/details/78370567 关于SqlSource
https://blog.csdn.net/lqzkcx3/article/details/78370497 关于BoundSql
是一个接口 有四种实现类,具体看文章
主要是构建一条SQL语句
*/
private SqlSource sqlSource; //下面讲 里面存的是SQL语句 进行参数传入等
private boolean useCache; //是否使用二级缓存
MappedStatement对于标签中SQL语句的封装
SQL语句以及对应的参数被封装在BoundSql
类中
private String sql; //封装的SQL语句 经过?替换后的SQL语句
private List<ParameterMapping> parameterMappings; //SQL的参数列表中参数
private Object parameterObject; //传入的参数值
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;
//ParameterHandler的创建
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object parameterObject,
BoundSql boundSql)
//ResultSetHandler的创建
public ResultSetHandler newResultSetHandler(Executor executor,
MappedStatement mappedStatement,
RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler,
BoundSql boundSql)
//StatementHandler的创建
public StatementHandler newStatementHandler(Executor executor,
MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler
resultHandler, BoundSql boundSql)
//newExecutor对象的创建
public Executor newExecutor(Transaction transaction)
//newExecutor对象的创建
public Executor newExecutor(Transaction transaction, ExecutorType eecutorType)
是一个接口
,Mybatis是处理功能的核心入口,SqlSession中的功能底层都是调用的Executor中的这些方法,只不过对其进行了封装
增删改查+事务处理+处理缓存
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
//增删改
int update(MappedStatement ms, Object parameter)
//查询
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql)
//事务
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
//缓存
void clearLocalCache();
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
核心的三个实现类
(JDBC中的addBatch操作)
(前提:SQL语句+参数不能改变)
//Configuration类中的配置
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
在那些位置可以指定Executor的类型
1、在主配置文件的settings标签中可以指定
2、在SqlSessionFactory创建SqlSession时可以传一个Executor类型参数
StatementHandler是Mybatis封装了JDBC的Statement,真正Mybatis进行数据库访问操作增删改查的核心
,Executor对其进行了包装,Executor的增删改查的底层都是StatementHandler帮其完成的
public interface StatementHandler {
Statement prepare(Connection var1, Integer var2) throws SQLException;
void parameterize(Statement var1) throws SQLException;
//批量操作
void batch(Statement var1) throws SQLException;
//增删改
int update(Statement var1) throws SQLException;
//查 参数有一个ResultHandler处理返回结果 进行封装
<E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;
<E> Cursor<E> queryCursor(Statement var1) throws SQLException;
//SQL语句
BoundSql getBoundSql();
//参数处理
ParameterHandler getParameterHandler();
}
三种主要实现(对应StatementType):
(默认)
预编译 PreparedStatement
一些具体的操作--底层都是JDBC的操作
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
public int update(Statement statement) throws SQLException {
//获取PreparedStatement对象 --原生JDBC的操作步骤
PreparedStatement ps = (PreparedStatement) statement;
//执行 --原生JDBC的操作步骤
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
//获取PreparedStatement对象 --原生JDBC的操作步骤
PreparedStatement ps = (PreparedStatement) statement;
//批量执行 --原生JDBC的操作
ps.addBatch();
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//获取PreparedStatement对象 --原生JDBC的操作步骤
PreparedStatement ps = (PreparedStatement) statement;
//执行 --原生JDBC的操作步骤
ps.execute();
//获取查询结果 使用ResultSetHandler进行结果的封装
return resultSetHandler.<E> handleResultSets(ps);
}
}
在StatementHandler执行SQL语句前,使用ParameterHandler填充预编译参数值
preparedStatement.set(int index,Obj)
封装了JDBC原生的ResultSet
在StatementHandler执行SQL语句后,使用ResultSetHandler封装返回结果
在ParameterHandler和ResultSetHandler处理数据时,底层其实就是使用的TypeHandler(类型处理器)来进行数据库与Java类型的转换,
public interface TypeHandler<T> {
//在ParameterHandler设置预编译参数时 就是使用的这个方法 设置参数+类型转换
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//在ResultSetHandler处理结果封装时,就是使用的下面的方法 封装对象+类型转换
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
SqlSessionFactory是一个接口,我们一般使用的都是它的实现类DefaultSqlSessionFactory
SqlSession也是一个接口,我们一般使用的都是它的实现类DefaultSqlSession
源码流程解析
我们先来看一下它的源码,定义了一个Configuration
类,然后在其构造器中为其赋值,就是我们读取主配置文件及Mapper文件后封装的Configuration对象
public class DefaultSqlSessionFactory implements SqlSessionFactory {
//声明了Configuration属性 在build()时为其赋值
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
然后我们要拿到一个session对象,使用的是SqlSessionFactory的openSession()方法,有很多重载的方法,我们这里就以最常用无参的来演示
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
//将我们 configuration对象的ExecutorType传入,获取一个Session对象
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
}
点进openSessionFromDataSource()方法,最终就是返回了一个DefaultSqlSession对象
,传入了
Configuration
配置对象
executor
执行器(默认的是SimpleExecutor)
autoCommit
是否自动提交
public class DefaultSqlSessionFactory implements SqlSessionFactory {
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);
//最终返回了一个 DefaultSqlSession对象
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();
}
}
我们点进DefaultSqlSession类,看到了上一步创建DefaultSqlSession对象使用的构造器,也发现了其实在DefaultSqlSession中声明了 Configuration和Executor
,调用构造器其实就是给这两个属性赋值。
也可以说明,在SqlSession被创建的时候,Executor就被创建了!!
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private executor;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
总结
Configuration和Executor也就初始化完成了
我们有两种方式执行增删改查
//1、通过获取接口的代理调用方法 动态代理 MapperProxy
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
Employee employee = mapper.getEmpById(1);
//1、直接使用sqlSession的增删改查方法
session.insert(xxx);
那么这两种方式有什么区别吗? 第一种方式就是对于第二种的封装
我们打开SqlSession的源码,发现里面定义了很多的方法,基本上都是关于
增删改查+缓存+事务
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
}
我们使用第一种方式执行,debug进入,发现增删改查的底层就是调用的SqlSession的方法,所以说我们接下来直接研究SqlSession的这些增删改查时如何执行的即可
sqlSession.insert(command.getName(), param)
sqlSession.update(command.getName(), param)
sqlSession.update(command.getName(), param)
....
执行过程解析
我们知道SqlSession是一个接口,它有一个实现类DefaultSqlSession,我们点进DefaultSqlSession就以一个查询方法为例,看一下执行流程,
我们发现,SqlSession调用的增删改查方法其实是Executor中的方法!!!!
public class DefaultSqlSession implements SqlSession {
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//调用的确实是Executor的方法 query()方法
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 int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
//调用的是 executor的update方法
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
然后我们追踪一下在Executor中是怎样执行的
我们点进update方法,是接口中的方法,BaseExecutor对其进行了重写,
public abstract class BaseExecutor implements Executor {
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
}
调用的是doUpdate方法,这个doUpdate是由典型的三种Executor实现的,我们点进SimpleExecutor中看一下doUpdate方法,
我们发现,方法里面声明了原生的Statement,然后通过MappedStatement对象获取了Configuration,又通过Configuration创建了StatementHandler对象,最终是StatementHandler执行了此次的update操作
public class SimpleExecutor extends BaseExecutor {
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//创建了StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
}
最后,我们点进PreparedStatementHandler的update方法中,发现最终是由StatementHandler执行的方法。
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
public int update(Statement statement) throws SQLException {
//JDBC原生操作
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;
}
}
总结:
Executor是在创建SqlSession时就创建的
StatementHandler是在执行增删改查方法时在Executor中被Configuration对象创建。
最终的原生JDBC操作就是由StatementHandler封装执行的。
ResultSetHandler ParameterHandler是在StatementHandler创建时创建的(在BaseStatementHandler的构造器赋值)
所以,也印证了我们上面的执行流程图。
SqlSession——>Executor——>StatementHandler——>JDBC原生操作
使用SqlSessison调用getMapper()方法时,通过动态代理为我们的Mapper接口产生了一个实现类,那么底层是如何实现的呢?
XxxMapper mapper = sqlSession.getMapper(XxxMapper.class);
通过上面的研究我们可以发现,实际上通过代理调用增删改查的方式的
底层还是调用的SqlSession的增删改查方法
。那么我们不妨先简单的实现一下这个功能
动态代理的核心就是InvocationHandler
,我们需要实现此接口来为原始方法增加额外功能
要生成代理对象,肯定要调用这个方法,类加载器 实现的接口 以及额外功能InvocationHandler,前两个都好说,第三我们需要自定义一个InvocationHandler
EmployeeDao dao = (EmployeeDao) Proxy.newProxyInstance(ClassLoader,interfaces,InvocationHandler)
分析:这时的动态代理属于是无中生有式
,即我们没有目标类,我们的目的就是直接造一个接口的实现类即可,在实现类中调用SqlSession的增删改查方法即可,这也就是核心
public class MyMapperMethod implements InvocationHandler {
//注入一个SqlSession为了调用增删改查方法
private SqlSession sqlSession;
//注入接口的类对象 因为我们在使用SqlSession的增删改查方法时,
//第一个参数就是 接口的全类名+id id就是方法名 我们可以通过method获取
//所以我们需要接口的全类名 就把接口的类对象注入
private Class daoClass;
public MyMapperMethod(SqlSession sqlSession, Class daoClass) {
this.sqlSession = sqlSession;
this.daoClass = daoClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object one = sqlSession.selectOne(daoClass.getName() + "." + method.getName(),1);
return one;
}
}
大致的思路就是上面的代码,核心就是我们得把接口的类对象传入,因为SqlSession调方法的时候需要 接口的全类名+方法名(即namespace+id)
所以我们在调用时就可以生成一个代理类对象,
EmployeeDao dao = (EmployeeDao) Proxy.newProxyInstance(Test1.class.getClassLoader(),
new Class[]{EmployeeDao.class},
new MyMapperMethod(session,EmployeeDao.class));
这只是一个大体的思路,Mybatis的实现比这个要复杂的多,要考虑参数,调的是什么方法等,但是核心的思路是一致的。
那么,Mybatis是如何实现的呢?
Mybatis使用了这两个类为我们的接口创建实现类
通过MapperProxyFactory工厂将我们的MapperProxy(额外功能)传入,底层调用Proxy的newProxyInstance方法为我们创建了一个代理类对象
MapperProxy核心源码
public class MapperProxy<T> implements InvocationHandler, Serializable {
//SqlSession
private final SqlSession sqlSession;
//接口的类对象
private final Class<T> mapperInterface;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
MapperProxyFactory创建代理类对象源码
public class MapperProxyFactory<T> {
protected T newInstance(MapperProxy<T> mapperProxy) {
//通过此方法 创建一个代理了类对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//通过传入 sqlsession和接口创建一个MapperProxy 然后传入上面的方法创建一个代理类
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
总结
获取代理对象的核心就是MapperProxy,就是一个额外功能处理器,将SqlSession和接口类对象传入然后调用SqlSession的方法。然后通过MapperProxyFactory创建一个代理对象
在方法执行之前,实际上,Mybatis会将我们的方法封装成一个MapperMethod
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
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);
}
}
//这里 会将我们的方法封装成一个MapperMethod 然后执行的是MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
我们看一下MapperMethod的内部结构
public class MapperMethod {
//SQL语句标签 后面要根据标签不同执行不同的方法(判断是增删改查)
private final SqlCommand command;
//SQL的返回值类型 (查询时返回值的情况)
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);
}