Mybatis原理
写在前面:好久没有写了,这阵子忙着面试,然后过年,然后过完年交接工作,来到新的公司工作有三周了,新工作也算有点轨迹了;下面就接着搬运吧;
一、回顾JDBC的调用
在分析mybatis原理之前,我们先来回顾一下jdbc是怎么调用的
public class TestJDBC {
private static String url = "jdbc:mysql://127.0.0.1:3306/hello";
private static String userName = "root";
private static String pwd = "root";
public static void main(String[] args) {
String sql = "select now() date1 from dual";
Connection connection =null;
Statement statement = null;
ResultSet resultSet =null;
try {
//1、加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库连接
connection = DriverManager.getConnection(url, userName, pwd);
//3、准备statement
statement = connection.createStatement();
//4、执行sql查询
statement.execute(sql);
//5、获取查询结果
resultSet = statement.getResultSet();
while (resultSet.next()){
System.out.println(resultSet.getString(1));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6、关闭资源
try {
if (resultSet !=null){
resultSet.close();
}
if(statement !=null){
statement.close();
}
if (connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
====================================================================================
这里再说一下preparedstatement和statement的区别
1、PreparedStatement继承自Statement,都是接口;
2、preparedStatement可以用占位符,是预编译的,批处理比Statement效率高;
3、由于preparedStatement是预编译的,所以它可以防止sql注入;sql注入问题传送门
二、Mybatis的原理
下面就通过代码的方式看一下从配置文件到SQL执行的调用流程
public class TestMain {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
/**
* 1、通过XmlConfigBuilder解析配置XML,解析后的信息放入到单例Configuration
* XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
* var5 = this.build(parser.parse());
* 2、使用Configuration去创建一个DefaultSqlSessionFactory(这是单线程是使用的SqlSessionFactory)
* public SqlSessionFactory build(Configuration config) {
* return new DefaultSqlSessionFactory(config);
* }
* 3、构建Configuration是一个很重要的过程
*/
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session =null;
try {
/**
* public SqlSession openSession() {
* return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
* }
* private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
* Transaction tx = null;
* DefaultSqlSession var8;
* try {
* Environment environment = this.configuration.getEnvironment();
* 这里的TransactionFactory尤其要注意一下,
* 在我们做多数据源管理的时候,如果需要重写这里的newTransaction()
* 而Transaction也要自定义;
* TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
* tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
* 这里可以看到关于事务的信息被放到了执行器中,将来获取connection连接的时候,会使用到
* Executor executor = this.configuration.newExecutor(tx, execType);
* var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
* } catch (Exception var12) {
* this.closeTransaction(tx);
* throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
* } finally {
* ErrorContext.instance().reset();
* }
*
* return var8;
* }
*/
session = sqlSessionFactory.openSession();
/**
* sqlSession获取mapper使用了动态代理技术
* 1、使用Configuration来获取mapper对象
* public <T> T getMapper(Class<T> type) {
* return this.configuration.getMapper(type, this);
* }
* 2、mapperRegistry是Configuration中的一个mapper的注册器
* public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
* return this.mapperRegistry.getMapper(type, sqlSession);
* }
* 3、通过代理工厂获取代理对象
* public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
* //这个应该是在之前Configuration中创建好的map中的信息
* MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
* if (mapperProxyFactory == null) {
* throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
* } else {
* try {
*
* return mapperProxyFactory.newInstance(sqlSession);
* } catch (Exception var5) {
* throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
* }
* }
* }
* 4、这里使用了JDK动态代理,mapperProxy实现了invocationHandler接口
* protected T newInstance(MapperProxy<T> mapperProxy) {
* return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
* }
*
* public T newInstance(SqlSession sqlSession) {
* MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
* return this.newInstance(mapperProxy);
* }
* 5、看一下具体怎么运行的
* public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
* try {
* //这里判断mapper是不是一个类,这里不是,所以不会调用invoke;
* if (Object.class.equals(method.getDeclaringClass())) {
* return method.invoke(this, args);
* }
*
* if (this.isDefaultMethod(method)) {
* return this.invokeDefaultMethod(proxy, method, args);
* }
* } catch (Throwable var5) {
* throw ExceptionUtil.unwrapThrowable(var5);
* }
*
* MapperMethod mapperMethod = this.cachedMapperMethod(method);
* //会走这里
* return mapperMethod.execute(this.sqlSession, args);
* }
* 6、这里根据command命令选择不同的执行
* public Object execute(SqlSession sqlSession, Object[] args) {
* Object param;
* Object result;
* switch(this.command.getType()) {
* case INSERT:
* param = this.method.convertArgsToSqlCommandParam(args);
* result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
* break;
* case UPDATE:
* param = this.method.convertArgsToSqlCommandParam(args);
* result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
* break;
* case DELETE:
* param = this.method.convertArgsToSqlCommandParam(args);
* result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
* break;
* case SELECT:
* if (this.method.returnsVoid() && this.method.hasResultHandler()) {
* this.executeWithResultHandler(sqlSession, args);
* result = null;
* } else if (this.method.returnsMany()) {
* result = this.executeForMany(sqlSession, args);
* } else if (this.method.returnsMap()) {
* result = this.executeForMap(sqlSession, args);
* } else if (this.method.returnsCursor()) {
* result = this.executeForCursor(sqlSession, args);
* } else {
* param = this.method.convertArgsToSqlCommandParam(args);
* result = sqlSession.selectOne(this.command.getName(), param);
* }
* break;
* case FLUSH:
* result = sqlSession.flushStatements();
* break;
* default:
* throw new BindingException("Unknown execution method for: " + this.command.getName());
* }
*
* if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
* throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
* } else {
* return result;
* }
* }
* 7、重点看一下executeForMany方法:这里可以看到最终还是sqlSession去执行查询,这里如果继续跟下去,会发现其实是Executor再干活
* private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
* Object param = this.method.convertArgsToSqlCommandParam(args);
* List result;
* if (this.method.hasRowBounds()) {
* RowBounds rowBounds = this.method.extractRowBounds(args);
* result = sqlSession.selectList(this.command.getName(), param, rowBounds);
* } else {
* result = sqlSession.selectList(this.command.getName(), param);
* }
*
* if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
* return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
* } else {
* return result;
* }
* }
*
*
*/
StudentMapper mapper = session.getMapper(StudentMapper.class);
Student student = mapper.getStudentById(1);
System.out.println(student);
}catch (Exception e){
e.printStackTrace();
}finally {
session.close();
}
}
}
=======================================================================================================================
上面说到一直跟下去会跟到SimpleExecutor这个Executor中,下面看一下Executor内部执行原理;
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//这里主要是获取数据库连接,并准备执行查询的Statement;
stmt = this.prepareStatement(handler, ms.getStatementLog());
//执行SQL并返回处理结果,这里通过PreparedStatement进行查询,并使用resultSetHandler处理返回结果
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
//这里获取数据库连接
Connection connection = this.getConnection(statementLog);
//这里准备执行SQL的statement;
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
//准备参数,这里往下看的话,其实是DefaultParameterHandler在处理
handler.parameterize(stmt);
return stmt;
}
这里可以看到获取数据源连接是从transaction中获取的,也就是我们在创建sqlSessionFactory的时候创建的Executor时,注入的transaction
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = this.transaction.getConnection();
return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}
参考书籍:《JavaEE 互联网轻量级框架整合开发》