精华内容
下载资源
问答
  • MyBatis原理

    2019-12-30 16:55:48
    MyBatis原理 原理 MyBatis启动时,解析mybatis的配置文件,并且从指定路径下解析mapper.xml配置文件 把每条sql语句映射成MappedStatement 然后把MappedStatement存放到Configuration的一个mappedStatements属性...

    MyBatis原理

    原理

    • MyBatis启动时,解析mybatis的配置文件,并且从指定路径下解析mapper.xml配置文件
      • 把每条sql语句映射成MappedStatement
    • 然后把MappedStatement存放到Configuration的一个mappedStatements属性中(mappedStatements是一个HashMap),key为namespace + id,value为MappedStatement
    • 当要执行sql语句的时候,从mappedStatements这个map中通过id找到MappedStatement
    • 获取MappedStatement对应sql语句、查询参数
    • 查看一级缓存中有没有数据,有则直接返回
    • 缓存没有数据,则查询数据库
      • 通过调用原生的jdbc方法,执行sql语句,获取到结果,删除旧缓存
    • 把结果放到一级缓存,返回结果

    接口方式

    思考一个问题,通常的Mapper接口,我们可以不实现方法,却可以使用。这是为什么呢?答案很简单 动态代理

    开始之前介绍一下MyBatis初始化时对接口的处理:

    • 当判断解析到接口时,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的class对象,value = 此接口对应的MapperProxyFactory对象。
    • 当执行sql的时候,通过MapperProxyFactory生成动态代理实例(JDK动态代理)
    • 通过代理实例,将请求转发给invoker方法
    • 在invoke()方法中,最终SqlSession中的方法执行sql语句

    MyBatis缓存

    背景

    一次数据库会话中,可能会执行的重复的查询语句,极短时间内重复查询,结果往往是一样的,但是造成了数据库资源的浪费。

    为了解决这个问题,MyBatis使用了一级缓存。MyBatis在sqlSession对象(表示会话的对象)中建立了一个简单的缓存,将每次查询的结果缓存起来。如果下次有完全一样的查询,直接返回结果。

    一级缓存

    特点

    • 一级缓存默认是开启的

    • 作用域是session级别,每个sqlSession各自拥有一级缓存,各自隔离。

    • 第一次查询结果换以key value的形式存起来,如果有相同的key进来,直接返回value,这样有助于减轻数据库的压力。

    • 缓存的key格式如下:

      • cache key: id + sql + limit + offset 
        
    • 当commit或者rollback的时候会清除缓存,并且当执行insert、update、delete的时候也会清除缓存。

    二级缓存

    特点

    • 一个namespace(mapper.xml)就会有一个缓存
    • 不同的sqlSession之间的二级缓存是共享的
    • 实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的,也就是要求实现Serializable接口
      • 因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样
      • 如果存储在内存中的话,实测不序列化也可以的。
    • 一般为了避免出现脏数据,所以我们可以在每一次的insert | update | delete操作后都进行缓存刷新
    展开全文
  • mybatis原理

    2019-08-10 10:22:00
    一、静态代理 可以为一个接口生成一个代理类,代理类去操作这个接口的具体实现类 二、动态代理 ...三、mybatis原理 1. mybatis使用动态代理,生成了接口的代理类 org.apache.ibatis.binding.Mapper...

    一、静态代理

    可以为一个接口生成一个代理类,代理类去操作这个接口的具体实现类

     

    二、动态代理

    1. 可以为多个接口生成同一个代理类,代理类去操作这个接口的具体实现类

    2. 通过拦截器方法的回调,对目标target方法进行增强

     

    三、mybatis原理

    1. mybatis使用动态代理,生成了接口的代理类 org.apache.ibatis.binding.MapperProxy

    2. 代理类也做了实现类的工作,通过xml和实现类的映射关系,执行sql

    3. mybatis使用方法的全限定名作为key,去xml寻找唯一的sql去执行,因此接口Mapper内的方法不能重载

     

    四、可以使用JDK动态代理自定义一个MapperProxy,跟mybatis的源码比较

     

    转载于:https://www.cnblogs.com/june0816/p/6709154.html

    展开全文
  • MyBatis原理分析(通俗易懂)

    万次阅读 多人点赞 2019-06-07 16:10:20
    MyBatis原理分析MyBatis工作流程简述原生MyBatis原理分析初始化工作解析配置文件配置类方式执行SQLSqlSession API方式接口方式 真正掌握一个框架源码分析是少不了的~ 在讲解整合Spring的原理之前理解原生的MyBatis...

    真正掌握一个框架源码分析是少不了的~

    在讲解整合Spring的原理之前理解原生的MyBatis执行原理是非常有必要的

    MyBatis工作流程简述

    传统工作模式:

    public static void main(String[] args) {
    		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    		SqlSession sqlSession = factory.openSession();
    		String name = "tom";
    		List<User> list = sqlSession.selectList("com.demo.mapper.UserMapper.getUserByName",params);
    }
    
    1. 创建SqlSessionFactoryBuilder对象,调用build(inputstream)方法读取并解析配置文件,返回SqlSessionFactory对象
    2. 由SqlSessionFactory创建SqlSession 对象,没有手动设置的话事务默认开启
    3. 调用SqlSession中的api,传入Statement Id和参数,内部进行复杂的处理,最后调用jdbc执行SQL语句,封装结果返回。

    使用Mapper接口:
    由于面向接口编程的趋势,MyBatis也实现了通过接口调用mapper配置文件中的SQL语句

    public static void main(String[] args) {
    		//前三步都相同
    		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    		SqlSession sqlSession = factory.openSession();
    		//这里不再调用SqlSession 的api,而是获得了接口对象,调用接口中的方法。
    		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    		List<User> list = mapper.getUserByName("tom");
    }
    

    原生MyBatis原理分析

    初始化工作

    解析配置文件

    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    //这一行代码正是初始化工作的开始。
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    

    进入源码分析:

    // 1.我们最初调用的build
    public SqlSessionFactory build(InputStream inputStream) {
    	//调用了重载方法
        return build(inputStream, null, null);
      }
    
    // 2.调用的重载方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          //  XMLConfigBuilder是专门解析mybatis的配置文件的类
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          //这里又调用了一个重载方法。parser.parse()的返回值是Configuration对象
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } //省略部分代码
      }
    

    下面进入对配置文件解析部分:

    首先对Configuration对象进行介绍:

    Configuration对象的结构和xml配置文件的对象几乎相同。

    回顾一下xml中的配置标签有哪些:

    properties(属性),settings(设置),typeAliases(类型别名),typeHandlers(类型处理器),objectFactory(对象工厂),mappers(映射器)等

    Configuration也有对应的对象属性来封装它们:
    (图片来自:https://blog.csdn.net/luanlouis/article/details/37744073)在这里插入图片描述
    也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部的属性中。

    //在创建XMLConfigBuilder时,它的构造方法中解析器XPathParser已经读取了配置文件
    //3. 进入XMLConfigBuilder 中的 parse()方法。
    public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        //parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
        parseConfiguration(parser.evalNode("/configuration"));
        //最后返回的是Configuration 对象
        return configuration;
    }
    
    //4. 进入parseConfiguration方法
    //此方法中读取了各个标签内容并封装到Configuration中的属性中。
    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          loadCustomLogImpl(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
    
    

    到此对xml配置文件的解析就结束了(下文会对部分解析做详细介绍),回到步骤 2. 中调用的重载build方法。

    // 5. 调用的重载方法
    public SqlSessionFactory build(Configuration config) {
    	//创建了DefaultSqlSessionFactory对象,传入Configuration对象。
        return new DefaultSqlSessionFactory(config);
      }
    

    配置类方式

    发散一下思路,既然解析xml是对Configuration中的属性进行复制,那么我们同样可以在一个类中创建Configuration对象,手动设置其中属性的值来达到配置的效果。

    执行SQL

    先简单介绍SqlSession

    SqlSession是一个接口,它有两个实现类:DefaultSqlSession(默认)和SqlSessionManager(弃用,不做介绍)
    SqlSession是MyBatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要close。
    在这里插入图片描述
    SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执行器,

    Executor:

    Executor也是一个接口,他有三个常用的实现类BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器,默认)。

    SqlSession API方式

    继续分析,初始化完毕后,我们就要执行SQL了:

    		SqlSession sqlSession = factory.openSession();
    		String name = "tom";
    		List<User> list = sqlSession.selectList("com.demo.mapper.UserMapper.getUserByName",params);
    

    获得sqlSession

    //6. 进入openSession方法。
      public SqlSession openSession() {
      	//getDefaultExecutorType()传递的是SimpleExecutor
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }
    
    //7. 进入openSessionFromDataSource。
    //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
    //openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
    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);
          //根据参数创建指定类型的Executor
          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();
        }
      }
    

    执行sqlsession中的api

    //8.进入selectList方法,多个重载方法。
    public <E> List<E> selectList(String statement) {
        return this.selectList(statement, null);
    }
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          //根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
          MappedStatement ms = configuration.getMappedStatement(statement);
          //调用Executor中的方法处理
          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();
        }
     }
    

    介绍一下MappedStatement

    • 作用: MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句。
    • **初始化过程:**回顾刚开始介绍的加载配置文件的过程中,会对mybatis-config.xml中的各个标签都进行解析,其中有 mappers标签用来引入mapper.xml文件或者配置mapper接口的目录。
     <select id="getUser" resultType="user" >
        select * from user where id=#{id}
      </select>
    

    这样的一个select标签会在初始化配置文件时被解析封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中,mappedStatements 是一个HashMap,存储时key = 全限定类名 + 方法名,value = 对应的MappedStatement对象

    • 在configuration中对应的属性为
    Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
    
    • 在XMLConfigBuilder中的处理:
      private void parseConfiguration(XNode root) {
        try {
          // 省略其他标签的处理
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    

    继续源码中的步骤,进入 executor.query()

    //此方法在SimpleExecutor的父类BaseExecutor中实现
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    	//根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
        BoundSql boundSql = ms.getBoundSql(parameter);
        //为本次查询创建缓存的Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
     
    //进入query的重载方法中
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          clearLocalCache();
        }
        List<E> list;
        try {
          queryStack++;
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
          	// 如果缓存中没有本次查找的值,那么从数据库中查询
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
    
    //从数据库查询
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          // 查询的方法
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);
        }
        // 将查询结果放入缓存
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    
    // SimpleExecutor中实现父类的doQuery抽象方法
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          // 传入参数创建StatementHanlder对象来执行查询
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          // 创建jdbc中的statement对象
          stmt = prepareStatement(handler, ms.getStatementLog());
          // StatementHandler进行处理
          return handler.query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    
    // 创建Statement的方法
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        //条代码中的getConnection方法经过重重调用最后会调用openConnection方法,从连接池中获得连接。
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
      }
    //从连接池获得连接的方法
    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
          log.debug("Opening JDBC Connection");
        }
        //从连接池获得连接
        connection = dataSource.getConnection();
        if (level != null) {
          connection.setTransactionIsolation(level.getLevel());
        }
        setDesiredAutoCommit(autoCommit);
      }
    
    
    //进入StatementHandler进行处理的query,StatementHandler中默认的是PreparedStatementHandler
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        //原生jdbc的执行
        ps.execute();
        //处理结果返回。
        return resultSetHandler.handleResultSets(ps);
      }
    

    接口方式

    回顾一下写法:

    public static void main(String[] args) {
    		//前三步都相同
    		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    		SqlSession sqlSession = factory.openSession();
    		
    		//这里不再调用SqlSession 的api,而是获得了接口对象,调用接口中的方法。
    		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    		List<User> list = mapper.getUserByName("tom");
    }
    

    思考一个问题,通常的Mapper接口我们都没有实现的方法却可以使用,是为什么呢?答案很简单 动态代理


    开始之前介绍一下MyBatis初始化时对接口的处理:MapperRegistry是Configuration中的一个属性,它内部维护一个HashMap用于存放mapper接口的工厂类,每个接口对应一个工厂类。mappers中可以配置接口的包路径,或者某个具体的接口类。

    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
      <mapper class="com.demo.mapper.UserMapper"/>
      <package name="com.demo.mapper"/>
    </mappers>
    
    • 当解析mappers标签时,它会判断解析到的是mapper配置文件时,会再将对应配置文件中的增删改查标签一 一封装成MappedStatement对象,存入mappedStatements中。(上文介绍了)
    • 当判断解析到接口时,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的字节码对象,value = 此接口对应的MapperProxyFactory对象。
    //MapperRegistry类
    public class MapperRegistry {
      private final Configuration config;
      //这个类中维护一个HashMap存放MapperProxyFactory
      private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
    
      //解析到接口时添加接口工厂类的方法
      public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            //重点在这行,以接口类的class对象为key,value为其对应的工厂对象,构造方法中指定了接口对象
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    }
    

    正文:
    进入sqlSession.getMapper(UserMapper.class)中

    //DefaultSqlSession中的getMapper
    public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
    }
    
    //configuration中的给getMapper
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
    
    //MapperRegistry中的getMapper
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    	//从MapperRegistry中的HashMap中拿MapperProxyFactory
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
        }
    }
    
    //MapperProxyFactory类中的newInstance方法
     public T newInstance(SqlSession sqlSession) {
     	// 创建了JDK动态代理的Handler类
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // 调用了重载方法
        return newInstance(mapperProxy);
      }
    
    //MapperProxy类,实现了InvocationHandler接口
    public class MapperProxy<T> implements InvocationHandler, Serializable {
      
      //省略部分源码	
    
      private final SqlSession sqlSession;
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache;
      
      // 构造,传入了SqlSession,说明每个session中的代理对象的不同的!
      public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
      }
      
      //省略部分源码
    }
    
    //重载的方法,由动态代理创建新示例返回。
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
     }
    

    在动态代理返回了示例后,我们就可以直接调用mapper类中的方法了,说明在MapperProxy中的invoke方法中已经为我们实现了方法。

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          //判断调用是是不是Object中定义的方法,toString,hashCode这类非。是的话直接放行。
          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);
        // 重点在这:MapperMethod最终调用了执行的方法
        return mapperMethod.execute(sqlSession, args);
      }
    
    
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //判断mapper中的方法类型,最终调用的还是SqlSession中的方法
        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);
              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原理

    2020-08-25 16:36:22
    简介 Mybatis是一款非常优秀的半自动化持久层框架,主要着力点在于 ...Mybatis技术原理 Mybatis的工作内容主要包括以下两个部分: 加载配置,构建SqlSessionFactory。 使用SqlSessionFactory构建SqlSession,由S...
    1. 简介
      Mybatis是一款非常优秀的半自动化持久层框架,主要着力点在于 POJO 与 SQL 之间的映射关系。然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定 POJO 。

    2. Mybatis技术原理

    Mybatis的工作内容主要包括以下两个部分:

    加载配置,构建SqlSessionFactory。
    使用SqlSessionFactory构建SqlSession,由SqlSession完成跟数据库的交互操作。

        构建SqlSessionFactory的流程如下图所示, 首先把平台所有的Mybatis相关配置信息加载到一个Configuration对象中,再把这个Configuration对象作为构建参数,完成SqlSessionFactory对象的创建。Mybatis的常用配置来源包括 sql文件、Mybatis配置文件、以及java编码配置, 但这些配置来源都是非必须的。无论是哪种配置来源,Mybatis最终的目的就是为了构建一个Configuration对象,只要以满足这个目的为前提,任何配置来源都可以找到相应的替换方式。Configuration对象包含的配置非常复杂,但我们需要重点关注的配置项并不多,一般采用默认配置即可,在开发中需要更改或新增默认配置时,可以参考xml的配置方式:http://www.mybatis.org/mybatis-3/zh/configuration.html
    

    在实际项目中,一般不会直接采用xml的配置方式,但通过上面的配置文档,可以系统了解总共有哪些配置项,再按对应的替换方式进行配置即可。比如Springboot整合Mybatis的项目,直接在application.properties中也可以对Mybatis大部分的参数进行配置。
    在这里插入图片描述

        了解Configuration对象是Mybatis开发中排查问题非常重要的一环。 这里自定义两个对比词汇: 1) 运行环境    2)外观环境(或逻辑环境)。其中,Configuration对象可称为Mybatis的 运行环境 , 而 sql文件、Mybatis配置文件 这些被 称为 Mybatis的外观环境(或逻辑环境)。 在大多数情况下,查看外观环境是我们排查问题的首选,因为便于观察和判断,所以排查问题也相对迅速。然而,只理解外观环境是不够的,举几个例子:
    
        1. A同事发现 运行项目时某个配置文件找不到, 但是查看项目发现该配置文件是有的,多次重启项目还是无法解决这个问题;
    
        2. B同事发现 运行项目时 某个类缺失, 在pom文件中查找依赖,发现已经引入对应的jar包,jar包中也存在相应的类,多次检查后还是未发现问题;
    
        3. C同事在开发时发现 页面数据跟数据库的数据对不上,怀疑连错了库,于是反复检查 config.properties的数据库配置,startup的jar包是否引入,最后还是无奈求助。
    
        
    
        上述例子在日常开发中屡见不鲜,究其原因,还是习惯于对外观环境进行排查,而忽略对运行环境的查看。同样的,如果不了解Configuration对象,  在Mybatis的开发过程中,我们同样只能依赖于外观环境,在特定环境下排查问题将会非常艰难。
    
    
    
        在完成SqlSessionFactory的构建后,接下来只要构建SqlSession,就可进行跟数据库交互了。Mybatis构建SqlSession并进行数据库交互的流程如下:
    

    在这里插入图片描述

        由于SqlSession是非线程安全的,上述的Mybatis原生流程,每次调用时都需要手工创建SqlSession,获得Mapper并调用,开发时十分不方便。在项目中更常用的是下述的Mybatis-Spring式的调用流程:
    

    在这里插入图片描述

        这种方式与Mybatis原生的流程相比,区别在于项目启动时即创建Mapper代理对象, 使用的SqlSession也同样是代理对象,在真正进行数据库交互时创建一个新的SqlSession对象,来代替当前对象执行目标方法即可。在项目中此方式实用了许多,使用时直接注入Mapper并调用Mapper的方法即可。
    
        SqlSession执行数据库交互的流程如下:
    

    在这里插入图片描述

        上述执行流程中, SqlSession调用Executor执行数据库交互, Executor控制着 预编译、参数设置、结果处理 等流程, 而StatementHandler则为每个流程提供功能支持,对于其中的参数设置和结果处理,StatementHandler分别委托给了ResultSetHandler和ParameterHandler 进行处理。Executor、StatementHandler、ResultSetHandler、ParameterHandler 这四个对象主导了Mybatis执行数据库交互的流程,为Mybatis的四大核心对象,Mybatis允许我们对这四个对象的方法进行拦截、通过动态代理技术增强功能,这便是Mybatis的插件技术,对应的拦截器又被称作插件。
    
        讨论点:  如何利用插件技术制作常用的分页插件,  分页插件应该拦截哪个对象?
    
    1. Mybatis使用技巧
      3.1 自定义类型处理器
      在Configuration对象创建时,默认会注册一系列类型处理器:
      在这里插入图片描述
      虽然大量常用的类型都能够通过这些处理器进行处理, 但仍有一部分情况需要我们自定义类型处理器,比如json字段需要转为java中的对象,java中的Calendar类型支持,枚举值转换等。支持Calendar类型的自定义类型处理器如下所示:

    在这里插入图片描述

        类型处理器可通过指定包名进行集中注册:
    
        mybatis:
    
                typeHandlersPackage: com.hikvision.ctm01punishment.web.modules.typehandlers
    

    3.2 执行器的选择
    Mybatis并列的执行器有以下三种(CachingExecutor跟其他三种可联合使用,不算并列):

        SIMPLE 为普通的执行器;
    
        REUSE 执行器会重用预处理语句(prepared statements);
    
        BATCH 执行器将重用语句并执行批量更新。
    
        以上为三种处理器官方的定义,默认情况下, Mybatis的执行器使用SIMPLE。单看上面的定义肯定有些模糊, 接下来依次进行说明:
    
        首先,SIMPLE执行器在接到数据库交互请求后,无论是查询还是修改都是以下步骤:
    
        1. 控制StatementHandler准备预处理语句;
        2. 控制StatementHandler设置请求参数;
        3. 调用StatementHandler的select/update方法;
    
        相应的,BATCH 执行器在接到查询请求时,同样使用以上步骤,而在接到更新请求时,会把预处理语句缓存起来,并且调用StatementHandler的batch方法,把预处理语句加入到批量更新列表,待事务完成时统一提交。而REUSE 执行器不管是查询还是更新, 都会把已创建的预处理语句缓存起来,遇到相同sql则取出来复用。
       有了上述理论基础后,我们再来讨论以上处理器分别在何时能发挥最大的作用。这里就涉及到处理器的生命周期,按工作流程中的介绍,处理器由SqlSession进行调用,而Executor作为SqlSession的成员变量,其生命周期与SqlSession一致,即事务中复用Executor, 非事务环境下每次交互新建 Executor。了解这点之后,对执行器的选择可归纳为以下几点:
        1.在同一个事务中有大量的相同更新时,如在事务中往同一张表中插入大量数据,应使用BATCH 执行器(如果数据量太多需分批调用事务方法、防止内存溢出)。
        2.同一个事务中 有大量相同查询语句(参数不同,如果参数相同时一级缓存会返回之前的查询对象), 或者大量相同更新语句,但是不想批量提交的,应使用REUSE 执行器;
        3.项目中无上述两种情况时,可以考虑使用默认的 SIMPLE执行器。
    
        在springboot项目中,可通过以下方式指定执行器:
        mybatis:
                executor-type: batch
    
    展开全文
  • mybatis 原理

    2019-10-04 12:36:47
    什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于...
  • Mybatis 原理 运行图 SqlSession接收开发人员提供Statement Id 和参数.并返回操作结果ExecutorMyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护StatementHandler封装了JDBC Statement操作,...
  • Mybatis原理学习

    2020-02-18 22:00:24
    Mybatis原理学习Mybatis原理学习Mybatis原理学习
  • mybatis原理.xmind

    2020-06-09 12:41:02
    该思维导图主要是对MyBatis原理知识进行了整理,通过对底层的分析,能够实现手写一个实现核心功能的简单MyBats,内容包括MyBatis整体架构和流程的分析、SQL的解析过程、手写解析流程、手写执行流程、看源码、MyBatis...
  • mybatis原理分析

    2020-03-11 22:16:36
    mybatis原理分析
  • MyBatis原理分析

    2019-11-05 20:28:00
    MyBatis原理分析

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,698
精华内容 4,679
关键字:

mybatis原理