mybatis 操作数据库的过程
// 第一步:读取mybatis-config.xml配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); // 第二步:构建SqlSessionFactory(框架初始化) SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().bulid(); // 第三步:打开sqlSession SqlSession session = sqlSessionFactory.openSession(); // 第四步:获取Mapper接口对象(底层是动态代理) AccountMapper accountMapper = session.getMapper(AccountMapper.class); // 第五步:调用Mapper接口对象的方法操作数据库; Account account = accountMapper.selectByPrimaryKey(1);
通过调用 session.getMapper (AccountMapper.class) 所得到的 AccountMapper 是一个动态代理对象,所以执行
accountMapper.selectByPrimaryKey (1) 方法前,都会被 invoke () 拦截,先执行 invoke () 中的逻辑。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 要执行的方法所在的类如果是Object,直接调用,不做拦截处理 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); //如果是默认方法,也就是java8中的default方法 } else if (isDefaultMethod(method)) { // 直接执行default方法 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 从缓存中获取MapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
从 methodCache 获取对应 DAO 方法的 MapperMethod
MapperMethod 的主要功能是执行 SQL 语句的相关操作,在初始化的时候会实例化两个对象:SqlCommand(Sql 命令)和 MethodSignature(方法签名)。
/** * 根据Mapper接口类型、接口方法、核心配置对象 构造MapperMethod对象 * @param mapperInterface * @param method * @param config */ public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); // 将Mapper接口中的数据库操作方法(如Account selectById(Integer id);)封装成方法签名MethodSignature this.method = new MethodSignature(config, mapperInterface, method);
new SqlCommand()调用 SqlCommand 类构造方法:
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { // 获取Mapper接口中要执行的某个方法的方法名 // 如accountMapper.selectByPrimaryKey(1) final String methodName = method.getName(); // 获取方法所在的类 final Class<?> declaringClass = method.getDeclaringClass(); // 解析得到Mapper语句对象(对配置文件中的<mapper></mapper>中的sql语句进行封装) MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { // 如com.bjpowernode.mapper.AccountMapper.selectByPrimaryKey name = ms.getId(); // SQL类型:增 删 改 查 type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); this.methodCache.put(method, mapperMethod); } return mapperMethod; }
调用 mapperMethod.execute (sqlSession, args)
在 mapperMethod.execute () 方法中,我们可以看到:mybatis 定义了 5 种 SQL 操作类型:
insert/update/delete/select/flush。其中,select 操作类型又可以分为五类,这五类的返回结果都不同,分别对应:
・返回参数为空:executeWithResultHandler ();
・查询多条记录:executeForMany (),返回对象为 JavaBean
・返参对象为 map:executeForMap (), 通过该方法查询数据库,最终的返回结果不是 JavaBean,而是 Map
・游标查询:executeForCursor ();关于什么是游标查询,自行百度哈;
・查询单条记录: sqlSession.selectOne (),通过该查询方法,最终只会返回一条结果;
通过源码追踪我们可以不难发现:当调用 mapperMethod.execute () 执行 SQL 语句的时候,无论是
insert/update/delete/flush,还是 select(包括 5 种不同的 select), 本质上时通过 sqlSession 调用的。在 SELECT 操作中,虽然调用了 MapperMethod 中的方法,但本质上仍是通过 Sqlsession 下的 select (), selectList (), selectCursor (), selectMap () 等方法实现的。
而 SqlSession 的内部实现,最终是调用执行器 Executor(后面会细说)。这里,我们可以先大概看一下 mybatis 在执行 SQL 语句的时候的调用过程:
以accountMapper.selectByPrimaryKey (1) 为例:
・调用 SqlSession.getMapper ():得到 xxxMapper (如 UserMapper) 的动态代理对象;
・调用
accountMapper.selectByPrimaryKey (1):在 xxxMapper 动态代理内部,会根据要执行的 SQL 语句类型 (insert/update/delete/select/flush) 来调用 SqlSession 对应的不同方法,如 sqlSession.insert ();
・在 sqlSession.insert () 方法的实现逻辑中,又会转交给 executor.query () 进行查询;
・executor.query () 又最终会转交给 statement 类进行操作,到这里就是 jdbc 操作了。
有人会好奇,为什么要通过不断的转交,SqlSession->Executor->Statement,而不是直接调用 Statement 执行 SQL 语句呢?因为在调用 Statement 之前,会处理一些共性的逻辑,如在 Executor 的实现类 BaseExecutor 会有一级缓存相关的逻辑,在 CachingExecutor 中会有二级缓存的相关逻辑。如果直接调用 Statement 执行 SQL 语句,那么在每个 Statement 的实现类中,都要写一套一级缓存和二级缓存的逻辑,就显得冗余了。这一块后面会细讲。
// SQL命令(在解析mybatis-config.xml配置文件的时候生成的) private final SqlCommand command; public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 从command对象中获取要执行操作的SQL语句的类型,如INSERT/UPDATE/DELETE/SELECT switch (command.getType()) { // 插入 case INSERT: { // 把接口方法里的参数转换成sql能识别的参数 // 如:accountMapper.selectByPrimaryKey(1) // 把其中的参数"1"转化为sql能够识别的参数 Object param = method.convertArgsToSqlCommandParam(args); // sqlSession.insert(): 调用SqlSession执行插入操作 // rowCountResult(): 获取SQL语句的执行结果 result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } // 更新 case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); // sqlSession.insert(): 调用SqlSession执行更新操作 // rowCountResult(): 获取SQL语句的执行结果 result = rowCountResult(sqlSession.update(command.getName(), param)); break; } // 删除 case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); // sqlSession.insert(): 调用SqlSession执行更新操作 // rowCountResult(): 获取SQL语句的执行结果 result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } // 查询 case SELECT: // method.returnsVoid(): 返参是否为void // method.hasResultHandler(): 是否有对应的结果处理器 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { // 查询多条记录 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { // 查询结果返参为Map result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { // 以游标的方式进行查询 result = executeForCursor(sqlSession, args); } else { // 参数转换 转成sqlCommand参数 Object param = method.convertArgsToSqlCommandParam(args); // 执行查询 查询单条数据 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } 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;
标签:Mybatis,resultType,数据库,JavaBean,属性,icode9 来源:
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。