详解MyBatis Mapper 代理实现数据库调用原理
1.Mapper代理层执行
Mapper代理上执行方法调用时,调用被委派给MapperProxy来处理。
publicclassMapperProxyimplementsInvocationHandler,Serializable{ privatefinalSqlSessionsqlSession; privatefinalClass mapperInterface; privatefinalMap methodCache; publicMapperProxy(SqlSessionsqlSession,Class mapperInterface,Map methodCache){ this.sqlSession=sqlSession; this.mapperInterface=mapperInterface; this.methodCache=methodCache; } publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{ if(Object.class.equals(method.getDeclaringClass())){ try{ returnmethod.invoke(this,args); }catch(Throwablet){ throwExceptionUtil.unwrapThrowable(t); } } //接口里声明的方法,转换为MapperMethod来调用 finalMapperMethodmapperMethod=cachedMapperMethod(method); //与Spring集成时此处的sqlSession仍然SqlSessionTemplate returnmapperMethod.execute(sqlSession,args); } privateMapperMethodcachedMapperMethod(Methodmethod){ MapperMethodmapperMethod=methodCache.get(method); if(mapperMethod==null){ mapperMethod=newMapperMethod(mapperInterface,method,sqlSession.getConfiguration()); methodCache.put(method,mapperMethod); } returnmapperMethod; } }
MapperMethod根据mapperInterface.getName()+"."+method.getName()从Configuration对象里找到对应的MappedStatement,从而得到要执行的SQL操作类型(insert/delete/update/select/flush),然后调用传入的sqlSession实例上的相应的方法。
publicObjectexecute(SqlSessionsqlSession,Object[]args){ Objectresult; if(SqlCommandType.INSERT==command.getType()){ //把参数转换为SqlSession能处理的格式 Objectparam=method.convertArgsToSqlCommandParam(args); //在sqlSession上执行并处理结果 result=rowCountResult(sqlSession.insert(command.getName(),param)); }elseif(SqlCommandType.UPDATE==command.getType()){ Objectparam=method.convertArgsToSqlCommandParam(args); result=rowCountResult(sqlSession.update(command.getName(),param)); ...省略
如果上述方法传入的是SqlSessionTemplate,那么这些方法调用会被SqlSessionInterceptor拦截,加入与Spring事务管理机制协作的逻辑,具体可以看这篇文章MyBatis事务管理,这里不再展开,最终会调用到DefaultSqlSession实例上的方法。
2.会话层的执行过程
SqlSession里声明的所有方法的第一个参数如果是Stringstatement,则都是mapperInterface.getName()+"."+method.getName(),表示要调用的SQL语句的标识符。通过它从configuration找到MappedStatement。
会话层最主要的逻辑是进行参数的包装,获取对应的MappedStatement,然后调用持有的Executor的方法去执行。
publicclassDefaultSqlSessionimplementsSqlSession{ privateConfigurationconfiguration; privateExecutorexecutor; privatebooleanautoCommit; privatebooleandirty; privateList>cursorList; publicDefaultSqlSession(Configurationconfiguration,Executorexecutor,booleanautoCommit){ this.configuration=configuration; this.executor=executor; this.dirty=false; this.autoCommit=autoCommit; } public List selectList(Stringstatement,Objectparameter,RowBoundsrowBounds){ try{ MappedStatementms=configuration.getMappedStatement(statement); returnexecutor.query(ms,wrapCollection(parameter),rowBounds,Executor.NO_RESULT_HANDLER); }catch(Exceptione){ throwExceptionFactory.wrapException("Errorqueryingdatabase.Cause:"+e,e); }finally{ ErrorContext.instance().reset(); } }
3.Executor执行的过程
我们知道JDBC里有三种数据库语句:java.sql.Statement/PreparedStatement/CallableStatement,每种语句的执行方式是不一样的,MyBatis创建了StatementHandler抽象来表示数据库语句的处理逻辑,有对应的三种具体实现:SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler。
RoutingStatementHandler是个门面模式,构建时根据要执行的数据库语句类型实例化SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler中的一个类作为目标delegate,并把调用都转给这个delegate的方法。
不同的handler实现实现了对应的:数据库语句的创建、参数化设置、执行语句。
通过这层抽象,MyBatis统一了Executor里的执行流程,以下以SimpleExecutor的流程为例:
1.对于传入的MappedStatementms,得到Configurationconfiguration。
2.configuration通过ms的语句类型得到一个RoutingStatementHandler的实例(内部有个delegate可以委派)handler,并用InterceptorChain对handler实例进行装饰。
3.通过SimpleExecutor持有的Transaction实例获取对应的数据库连接connection。
4.handler通过数据库连接初始化数据库语句java.sql.Statement或其子类stmt,设置超时时间和fetchSize。
5.用handler对stmt进行参数化处理(比如PreparedStatement设置预编译语句的参数值)。
6.handler执行相应的stmt完成数据库操作。
7.用ResultSetHandler对结果集进行处理。ResultSetHandler会调用TypeHandler来进行Java类型与数据库列类型之间转换。
//SimpleExecutor publicList doQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{ Statementstmt=null; try{ Configurationconfiguration=ms.getConfiguration(); //创建handler来负责具体的执行 StatementHandlerhandler=configuration.newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql); //创建数据库语句 stmt=prepareStatement(handler,ms.getStatementLog()); //执行数据库操作 returnhandler. query(stmt,resultHandler); }finally{ closeStatement(stmt); } } //Configuration publicStatementHandlernewStatementHandler(Executorexecutor,MappedStatementmappedStatement,ObjectparameterObject,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql){ StatementHandlerstatementHandler=newRoutingStatementHandler(executor,mappedStatement,parameterObject,rowBounds,resultHandler,boundSql); statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler); returnstatementHandler; } //RoutingStatementHandler publicRoutingStatementHandler(Executorexecutor,MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql){ //根据SQL语句的执行方式创建对应的handler实例 switch(ms.getStatementType()){ caseSTATEMENT: delegate=newSimpleStatementHandler(executor,ms,parameter,rowBounds,resultHandler,boundSql); break; casePREPARED: delegate=newPreparedStatementHandler(executor,ms,parameter,rowBounds,resultHandler,boundSql); break; caseCALLABLE: delegate=newCallableStatementHandler(executor,ms,parameter,rowBounds,resultHandler,boundSql); break; default: thrownewExecutorException("Unknownstatementtype:"+ms.getStatementType()); } } privateStatementprepareStatement(StatementHandlerhandler,LogstatementLog)throwsSQLException{ //创建数据库连接 Connectionconnection=getConnection(statementLog); //创建数据库语句 Statementstmt=handler.prepare(connection,transaction.getTimeout()); //参数化设置 handler.parameterize(stmt); returnstmt; } protectedConnectiongetConnection(LogstatementLog)throwsSQLException{ Connectionconnection=transaction.getConnection(); if(statementLog.isDebugEnabled()){ returnConnectionLogger.newInstance(connection,statementLog,queryStack); }else{ returnconnection; } } //BaseStatementHandler publicStatementprepare(Connectionconnection,IntegertransactionTimeout)throwsSQLException{ ErrorContext.instance().sql(boundSql.getSql()); Statementstatement=null; try{ //由具体的子类来创建对应的Statement实例 statement=instantiateStatement(connection); //通用参数设置 setStatementTimeout(statement,transactionTimeout); setFetchSize(statement); returnstatement; }catch(SQLExceptione){ closeStatement(statement); throwe; }catch(Exceptione){ closeStatement(statement); thrownewExecutorException("Errorpreparingstatement.Cause:"+e,e); } } //PreparedStatementHandler protectedStatementinstantiateStatement(Connectionconnection)throwsSQLException{ Stringsql=boundSql.getSql(); if(mappedStatement.getKeyGenerator()instanceofJdbc3KeyGenerator){ String[]keyColumnNames=mappedStatement.getKeyColumns(); if(keyColumnNames==null){ returnconnection.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS); }else{ returnconnection.prepareStatement(sql,keyColumnNames); } }elseif(mappedStatement.getResultSetType()!=null){ returnconnection.prepareStatement(sql,mappedStatement.getResultSetType().getValue(),ResultSet.CONCUR_READ_ONLY); }else{ returnconnection.prepareStatement(sql); } } //PreparedStatementHandler publicvoidparameterize(Statementstatement)throwsSQLException{ parameterHandler.setParameters((PreparedStatement)statement); }
4.问题
只在XML里定义SQL、没有对应的Java接口类能否使用MyBatis?
答:可以,通过SqlSession的方法来调用XML里的SQL语句。
Mapper接口类里可以进行方法重载吗?
答:不能,因为MyBatis里根据类名+“.”+方法名来查找SQL语句,重载会导致这样的组合出现多条结果。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。