精华内容
下载资源
问答
  • 深入理解mybatis原理

    2018-03-29 23:28:59
    MyBatis 是目前非常流行的 ORM 框架,它的功能很强大,然而其实现却 比较简单、优雅。
  • 深入浅出Mybatis原理与实战
  • 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原理

    2017-10-18 19:42:21
    mybatis是什么? mybatis是一个持久层框架,mybatis是一个不完全的ORM框架。sql语句需要程序员 自己去编写,但是mybatis也有映射(输入参数映射、输出结果映射)。 mybatis入门门槛不高,学习成本低,让程序员把...
        mybatis是什么?
    

        mybatis是一个持久层框架,mybatis是一个不完全的ORM框架。sql语句需要程序员

    自己去编写,但是mybatis也有映射(输入参数映射、输出结果映射)。

        mybatis入门门槛不高,学习成本低,让程序员把精力放在sql语句上,对sql语句优

    化非常方便,适用与需求变化较多项目,比如互联网项目。

        mybatis框架执行过程:

    1、配置mybatis的配置文件,SqlMapConfig.xml(名称不固定)

    2、通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂

    SqlSessionFactory在实际使用时按单例方式。

    3、通过SqlSessionFactory创建SqlSession

    SqlSession是一个面向用户接口(提供操作数据库方法),实现对象是线程不安全的,建议sqlSession应用场合在方法体内。

    4、调用sqlSession的方法去操作数据。

    如果需要提交事务,需要执行SqlSession的commit()方法。

    5、释放资源,关闭SqlSession

     

    mybatis开发dao的方法:

    1、原始dao 的方法

    需要程序员编写dao接口和实现类

    需要在dao实现类中注入一个SqlSessionFactory工厂。

     

    2、mapper代理开发方法(建议使用)

    只需要程序员编写mapper接口(就是dao接口)

    程序员在编写mapper.xml(映射文件)和mapper.java需要遵循一个开发规范:

    1、mapper.xml中namespace就是mapper.java的类全路径。

    2、mapper.xml中statement的id和mapper.java中方法名一致。

    3、mapper.xml中statement的parameterType指定输入参数的类型和mapper.java的方法输入 参数类型一致。

    4、mapper.xml中statement的resultType指定输出结果的类型和mapper.java的方法返回值类型一致。

     

    SqlMapConfig.xml配置文件:可以配置properties属性、别名、mapper加载。。。

     

    输入映射:

    parameterType:指定输入参数类型可以简单类型、pojo、hashmap。。

    对于综合查询,建议parameterType使用包装的pojo,有利于系统 扩展。

     

    输出映射:

    resultType:

    查询到的列名和resultType指定的pojo的属性名一致,才能映射成功。

    reusltMap:

    可以通过resultMap完成一些高级映射。

    如果查询到的列名和映射的pojo的属性名不一致时,通过resultMap设置列名和属性名之间的对应关系(映射关系)。可以完成映射。

    高级映射:

    将关联查询的列映射到一个pojo属性中。(一对一)

    将关联查询的列映射到一个List<pojo>中。(一对多)
    展开全文
  • SpringBoot整合MyBatis原理

    千次阅读 2019-05-25 11:26:53
    SpringBoot整合MyBatis原理 一. 自动配置 SpringBoot提供了MyBatis的自动配置类MybatisAutoConfiguration,可以自动注册SqlSessionFactory、SqlSessionTemplate等组件,开发人员只需在配置文件中指定相关属性即可。 ...

    SpringBoot整合MyBatis原理

    一. 自动配置

    SpringBoot提供了MyBatis的自动配置类MybatisAutoConfiguration,可以自动注册SqlSessionFactory、SqlSessionTemplate等组件,开发人员只需在配置文件中指定相关属性即可。

    @Configuration
    @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
    @ConditionalOnBean(DataSource.class)
    @EnableConfigurationProperties(MybatisProperties.class)
    @AutoConfigureAfter(DataSourceAutoConfiguration.class)
    public class MybatisAutoConfiguration {
    
      private static Log log = LogFactory.getLog(MybatisAutoConfiguration.class);
    
      @Autowired
      private MybatisProperties properties;
    
      @Autowired(required = false)
      private Interceptor[] interceptors;
    
      @Autowired
      private ResourceLoader resourceLoader = new DefaultResourceLoader();
    
      @Autowired(required = false)
      private DatabaseIdProvider databaseIdProvider;
    
      //检查配置文件路径
      @PostConstruct
      public void checkConfigFileExists() {
        if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
          Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
          Assert.state(resource.exists(), "Cannot find config location: " + resource
                       + " (please add config file or check your Mybatis " + "configuration)");
        }
      }
    
      //注册SqlSessionFactory
      @Bean
      @ConditionalOnMissingBean
      public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
          factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        factory.setConfiguration(properties.getConfiguration());
        if (!ObjectUtils.isEmpty(this.interceptors)) {
          factory.setPlugins(this.interceptors);
        }
        if (this.databaseIdProvider != null) {
          factory.setDatabaseIdProvider(this.databaseIdProvider);
        }
        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
          factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
          factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
          factory.setMapperLocations(this.properties.resolveMapperLocations());
        }
    
        return factory.getObject();
      }
    
      //注册SqlSessionTemplate
      @Bean
      @ConditionalOnMissingBean
      public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
          return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
          return new SqlSessionTemplate(sqlSessionFactory);
        }
      }
    
      //注册ClassPathMapperScanner,进行Mapper的自动扫描,也可以使用@MapperScan代替
      public static class AutoConfiguredMapperScannerRegistrar
        implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
        private BeanFactory beanFactory;
    
        private ResourceLoader resourceLoader;
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
          log.debug("Searching for mappers annotated with @Mapper'");
    
          ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    
          try {
            if (this.resourceLoader != null) {
              scanner.setResourceLoader(this.resourceLoader);
            }
    
            List<String> pkgs = AutoConfigurationPackages.get(this.beanFactory);
            for (String pkg : pkgs) {
              log.debug("Using auto-configuration base package '" + pkg + "'");
            }
    
            scanner.setAnnotationClass(Mapper.class);
            scanner.registerFilters();
            scanner.doScan(StringUtils.toStringArray(pkgs));
          } catch (IllegalStateException ex) {
            log.debug("Could not determine auto-configuration " + "package, automatic mapper scanning disabled.");
          }
        }
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
          this.beanFactory = beanFactory;
        }
    
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
          this.resourceLoader = resourceLoader;
        }
      }
    
      //如果程序中没有显式注入MapperFactoryBean,就打印日志
      @Configuration
      @Import({ AutoConfiguredMapperScannerRegistrar.class })
      @ConditionalOnMissingBean(MapperFactoryBean.class)
      public static class MapperScannerRegistrarNotFoundConfiguration {
    
        @PostConstruct
        public void afterPropertiesSet() {
          log.debug(String.format("No %s found.", MapperFactoryBean.class.getName()));
        }
      }
    
    }
    

    SpringBoot为我们自动注册了相应的组件:

    1. SqlSessionFactoryBean:用于构建MyBatis的SqlSessionFactory
    2. SqlSessionTemplate:MyBatis的代理类,将SqlSession与Spring的事务进行了整合
    3. ClassPathMapperScanner:提供MyBatis的Mapper的自动扫描

    二. SqlSessionFactoryBean

    SqlSessionFactoryBean是一个工厂Bean,其作用就是加载用户自定义的配置,然后使用MyBatis的API创建一个SqlSessionFactory,具体逻辑如下:

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
      Configuration configuration;
    
      XMLConfigBuilder xmlConfigBuilder = null;
      if (this.configuration != null) {
        configuration = this.configuration;
        if (configuration.getVariables() == null) {
          configuration.setVariables(this.configurationProperties);
        } else if (this.configurationProperties != null) {
          configuration.getVariables().putAll(this.configurationProperties);
        }
      } else if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
      } else {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
        }
        configuration = new Configuration();
        configuration.setVariables(this.configurationProperties);
      }
    
      if (this.objectFactory != null) {
        configuration.setObjectFactory(this.objectFactory);
      }
    
      if (this.objectWrapperFactory != null) {
        configuration.setObjectWrapperFactory(this.objectWrapperFactory);
      }
    
      if (this.vfs != null) {
        configuration.setVfsImpl(this.vfs);
      }
    
      if (hasLength(this.typeAliasesPackage)) {
        String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                                                               ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeAliasPackageArray) {
          configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                                                               typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
          }
        }
      }
    
      if (!isEmpty(this.typeAliases)) {
        for (Class<?> typeAlias : this.typeAliases) {
          configuration.getTypeAliasRegistry().registerAlias(typeAlias);
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Registered type alias: '" + typeAlias + "'");
          }
        }
      }
    
      if (!isEmpty(this.plugins)) {
        for (Interceptor plugin : this.plugins) {
          configuration.addInterceptor(plugin);
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Registered plugin: '" + plugin + "'");
          }
        }
      }
    
      if (hasLength(this.typeHandlersPackage)) {
        String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                                                                  ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeHandlersPackageArray) {
          configuration.getTypeHandlerRegistry().register(packageToScan);
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
          }
        }
      }
    
      if (!isEmpty(this.typeHandlers)) {
        for (TypeHandler<?> typeHandler : this.typeHandlers) {
          configuration.getTypeHandlerRegistry().register(typeHandler);
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Registered type handler: '" + typeHandler + "'");
          }
        }
      }
    
      if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
        try {
          configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException e) {
          throw new NestedIOException("Failed getting a databaseId", e);
        }
      }
    
      if (this.cache != null) {
        configuration.addCache(this.cache);
      }
    
      if (xmlConfigBuilder != null) {
        try {
          xmlConfigBuilder.parse();
    
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
          }
        } catch (Exception ex) {
          throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    
      if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
      }
    
      configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    
      if (!isEmpty(this.mapperLocations)) {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
    
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                                                     configuration, mapperLocation.toString(), configuration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
    
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
          }
        }
      } else {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
        }
      }
    
      return this.sqlSessionFactoryBuilder.build(configuration);
    }
    

    三. SqlSessionTemplate

    SqlSessionTemplate是Spring提供的一个对MyBatis的SqlSession的一个增强类,它的作用就是将SqlSession与当前的事务所绑定,而且是线程安全的,一个SqlSessionTemplate可以被多个dao所共享。

    SqlSessionTemplate基于动态代理模式,内部委托了一个SqlSession对象,并且在其基础上进行了增强。代码如下:

    //传入的SqlSessionFactory
    private final SqlSessionFactory sqlSessionFactory;
    
    //Executor类型
    private final ExecutorType executorType;
    
    //SqlSession的动态代理
    private final SqlSession sqlSessionProxy;
    
    private final PersistenceExceptionTranslator exceptionTranslator;
    
    
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                              PersistenceExceptionTranslator exceptionTranslator) {
    
      notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
      notNull(executorType, "Property 'executorType' is required");
    
      this.sqlSessionFactory = sqlSessionFactory;
      this.executorType = executorType;
      this.exceptionTranslator = exceptionTranslator;
      
      //创建SqlSession的动态代理
      this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
    }
    

    在创建SqlSessionTemplate传入SqlSessionFactory,而通过SqlSessionFactory创建的SqlSession其实是一个动态代理类,其增强的逻辑在SqlSessionInterceptor中定义,如下:

    private class SqlSessionInterceptor implements InvocationHandler {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取当前事务下绑定的SqlSession,由于Spring通过ThreadLocal将线程与事务绑定,所以也可以认为获取的是当前线程绑定的SqlSession
        SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
        try {
          //执行SqlSession的方法
          Object result = method.invoke(sqlSession, args);
          
          //如果当前在非事务环境下允许,则强制commit一下,因为有些数据库要求在close()方法前要先调用commit()或rollback()
          if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            sqlSession.commit(true);
          }
          return result;
        } catch (Throwable t) {
          Throwable unwrapped = unwrapThrowable(t);
          throw unwrapped;
        } finally {
          //关闭sqlSession
          if (sqlSession != null) {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          }
        }
      }
    }
    

    这样一来,Spring就将事务与SqlSession整合到了一起。

    四. Mapper自动扫描

    通过@MapperScan注解,可以指定Mapper自动扫描的包路径。自动扫描的处理是通过ClassPathMapperScanner实现的,其doScan()方法如下:

    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
      if (beanDefinitions.isEmpty()) {
        logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
      } else {
        processBeanDefinitions(beanDefinitions);
      }
    
      return beanDefinitions;
    }
    

    ClassPathMapperScanner会将所有的Mapper扫描进来,并且将每个Mapper包装成一个类型为MapperFactoryBean的BeanDefinition,注册到IoC容器中。
    MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,是一个BeanFactoryPostProcessor,它的功能就是在容器启动阶段动态向容器中注册BeanDefinition。经过MapperScannerConfigurer处理后,所有Mapper接口的BeanDefinition就以MapperFactoryBean的形式注册到Spring IoC容器中了。 代码可见ClassPathMapperScanner#processBeanDefinitions():

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
    
          if (logger.isDebugEnabled()) {
            logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
          }
    
          // the mapper interface is the original class of the bean
          // but, the actual class of the bean is MapperFactoryBean
          //这里实际注册的BeanDefinition是Mapper接口所对应的MapperFactoryBean
          definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
          definition.setBeanClass(this.mapperFactoryBean.getClass());
    
          definition.getPropertyValues().add("addToConfig", this.addToConfig);
    
          boolean explicitFactoryUsed = false;
          if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionFactory != null) {
            definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
            explicitFactoryUsed = true;
          }
    
          if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
            if (explicitFactoryUsed) {
              logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionTemplate != null) {
            if (explicitFactoryUsed) {
              logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
          }
    
          if (!explicitFactoryUsed) {
            if (logger.isDebugEnabled()) {
              logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            }
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
          }
        }
      }
    

    五. MapperFactoryBean

    MapperFactoryBean也是一个工厂Bean,可以通过它创建Mapper实例:

    @Override
    public T getObject() throws Exception {
      return getSqlSession().getMapper(this.mapperInterface);
    }
    

    这样一来,所有程序中定义的Mapper就都以Bean的形式加载到Spring的IoC容器中了。

    六. Spring对MyBatis生命周期的修改

    1. SqlSession

      在MyBatis原生API中,SqlSession的生命周期是方法级别的,在每个方法中创建一个新的SqlSession实例,用完了之后就销毁。而Spring对MyBatis原生的SqlSession进行了事务的增强,通过一个单例的SqlSessionTemplate,保证每次获取的SqlSession其实都是与当前线程绑定的。

    2. Mapper

      MyBatis原生API中,通过SqlSession#getMapper()获取到的Mapper,是一个MapperProxy动态代理类,它的生命周期与SqlSession一致,都是方法级别的。但是Spring通过MapperFactoryBean创建的Mapper是单例的,也就是将其声明周期提升到了全局级别。

    七. MyBatis一级缓存失效问题

    我们知道MyBatis有两级缓存,其中一级缓存是默认开启的。但是在Spring整合了MyBatis后,却经常出现一级缓存失效的问题,其原因是在SqlSessionTemplate对SqlSession进行了代理后,在非事务环境下,每次执行完SqlSession的操作后都会进行一次commit(),且最后后关闭SqlSession,因此会清空一级缓存(强调下仅在非事务环境下,在事务中调用不会出现这个问题),源码如下:

    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          SqlSession sqlSession = getSqlSession(
              SqlSessionTemplate.this.sqlSessionFactory,
              SqlSessionTemplate.this.executorType,
              SqlSessionTemplate.this.exceptionTranslator);
          try {
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
              //在非事务环境下执行,会强制commit()
              sqlSession.commit(true);
            }
            return result;
          } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
              // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
              closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
              sqlSession = null;
              Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
              if (translated != null) {
                unwrapped = translated;
              }
            }
            throw unwrapped;
          } finally {
          	//关闭SqlSession
          	//在非事务环境下,就是直接调用SqlSession.close()方法
          	//在事务环境下,并不会关闭SqlSession,而仅是对当前事务的引用计数-1
            if (sqlSession != null) {
              closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
          }
        }
      }
    

    八. SpringBoot整合MyBatis简单演示

    1. 引入MyBatis的Starter依赖

      <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.1.1</version>
      </dependency>
      
      
    2. 在application.properties中增加MyBatis相关配置

      #数据源配置
      spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
      spring.datasource.url=jdbc:mysql://localhost:3306/test
      spring.datasource.driver-class-name=com.mysql.jdbc.Driver
      spring.datasource.username=xxx
      spring.datasource.password=xxx
      
      #MyBatis配置
      mybatis.type-aliases-package=xxx
      mybatis.mapper-locations=classpath:mappers/*.xml
      
    3. 编写Mapper和对应的映射文件(或注解)

    4. 开启Mapper自动扫描,并指定包路径

      @MapperScan("xxx.mapper")
      
    展开全文
  • mybatis原理.xmind

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

    2019-09-17 16:52:26
    1.What is MybatisMyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和...

    1.What is Mybatis?

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。上面是官网的解释,简单的说就是如何把Java中的对象映射到数据库中,把数据库中的记录映射成为Java对象的一个过程,并且封装好jdbc相关的功能,从mybatis的运作机理中可以发现其实它只是一个半ORM框架,因为在实现增删改查操作的时候需要我们自己手动编写数据库语句。

    2.Mybatis应用(整合SpringBoot)

    SpringBoot作为现如今主流的开发框架,为你提供非常便捷简单的项目搭建流程,再加上丰富工具集成而且out-of-the-box(开箱即用),不需要繁琐的配置,大大的提升了开发效率,SpringBoot本文不多做叙述,有兴趣的可以自行了解,下面开始使用SpringBoot+Mybatis做一简单的demo,实现快速使用mybatis来构建你的Dao层。

    2-1.创建SpringBoot空项目,引入相关依赖和项目结构

     

    2-2.自定义数据源配置类,完成数据源的自定义配置

    @Configuration
    @MapperScan(basePackages = DataSourceConfig.PACKAGE, sqlSessionFactoryRef = "mybatisSqlSessionFactory")
    public class DataSourceConfig {
    
        //项目中的mapper接口包路径
        static final String PACKAGE = "com.lzl.demo.mapper";
        //mapper接口对应的xml文件的所在路径
        private static final String MAPPER_LOCATION = "classpath:mapper/*.xml";
    
        @Value("${datasource.master.url}")
        private String url;
    
        @Value("${datasource.master.username}")
        private String user;
    
        @Value("${datasource.master.password}")
        private String password;
    
        @Value("${datasource.master.driverClassName}")
        private String driverClass;
    
        /**
         * 自定义数据源配置
         * @return
         */
        @Bean(name = "mybatisDataSource")
        public DataSource createDataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName(driverClass);
            dataSource.setUrl(url);
            dataSource.setUsername(user);
            dataSource.setPassword(password);
            dataSource.setValidationQuery("select 1");
            dataSource.setTestOnBorrow(true);
            return dataSource;
        }
    
        /**
         * 事务管理
         * @param dataSource
         * @return
         */
        @Bean(name = "mybatisTransactionManager")
        public DataSourceTransactionManager mybatisTransactionManager(@Qualifier("mybatisDataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
        /**
         * 初始化 mybatisSqlSessionFactory
         * @param mybatisDataSource
         * @return
         * @throws Exception
         */
        @Bean(name = "mybatisSqlSessionFactory")
        public SqlSessionFactory masterSqlSessionFactory(@Qualifier("mybatisDataSource") DataSource mybatisDataSource)
                throws Exception {
            final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            sessionFactory.setDataSource(mybatisDataSource);
            sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources(DataSourceConfig.MAPPER_LOCATION));
            return sessionFactory.getObject();
        }
    }
    

    这里就已经完成了配置,因为这里我使用的是自定义配置,如果使用的是默认的配置会更简单。SpringBoot+Mybatis就配置好了,非常简单,这里的Mapper接口、**.xml和**PO文件都可以使用 mybatis-generator来生成,如果使用的是IDEA,会有一个插件better-mybatis-generator,安装之后直接可以连接数据库生成上述文件。

    3.Mybatis初始化流程

    看了上面简单的配置,估计会一脸懵,太简单导致你都看不懂为啥它就可以使用了,下面结合项目说下Mybatis是怎么初始化的。

    3-1.启动SpringBoot

    一般一个SpringBoot工程的启动类DemoApplication都是在package路径的根目录下,这样你的@SpringBootApplication注解就可以不用指定scanBasePackages了,因为@SpringBootApplication注解里的@ComponentScan默认扫描的就是DemoApplication所在的路径及其子路径下的所有class。
    3-2.@MapperScan注解
    当SpringBoot启动的时候,扫描到这个@MapperScan 类的时候,就开始了Mybatis的加载。首先看下@MapperScan的源码:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(MapperScannerRegistrar.class)
    public @interface MapperScan {
      .../* 省略 */...
          
      /** 
       * dao接口路径
       */
      String[] basePackages() default {};
        
      /**
       * SqlSessionFactory的bean名称
       */
      String sqlSessionFactoryRef() default "";
    
      /**
       * Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
       *
       */
      Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
    }
    其中最主要的就是 @Import(MapperScannerRegistrar.class) 这句,导入MapperScannerRegistrar.class这个类并把它注册到容器中,看下这个类的关系图

     

    实现了2个接口ImportBeanDefinitionRegistrar和ResourceLoaderAware,其中ImportBeanDefinitionRegistrar这个接口就是Spring留给开发者的扩展接口,通常可以通过实现这个接口来初始化配置项,在这里Mybatis就是使用这个方式来初始化的。当MapperScannerRegistrar这个bean被注册之后,Spring就会调用ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars)方法来对各个register进行BeanDefinitions的注册,然后就开始调用MapperScannerRegistrar这个实现类的方法进行各项属性的注册,源码如下

    @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    
        // this check is needed in Spring 3.1
        if (resourceLoader != null) {
          scanner.setResourceLoader(resourceLoader);
        }
    
        .../* 省略 */...
    
        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
          scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
        }
    
        scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
        scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
    
        .../* 省略 */...
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
          if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
          }
        }
        for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
          basePackages.add(ClassUtils.getPackageName(clazz));
        }
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(basePackages));
      }


    以上源码主要就是设置 basePackages 和 SqlSessionFactoryBeanName,并且扫描basePackages下的所有class注册为BeanDefinition,注册的是交给Spring处理的,注册完成之后Mybatis会进行一些操作来修改这个BeanDefinition的属性,如下:

     private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
    
          if (logger.isDebugEnabled()) {
            logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
          }
    
          // the mapper interface is the original class of the bean
          // but, the actual class of the bean is MapperFactoryBean
          definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
          //这里将这些bean的class全部设置为 mapperFactoryBean.class,
          definition.setBeanClass(this.mapperFactoryBean.getClass());
    
          definition.getPropertyValues().add("addToConfig", this.addToConfig);
    
          boolean explicitFactoryUsed = false;
          if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionFactory != null) {
            definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
            explicitFactoryUsed = true;
          }
    
          if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
            if (explicitFactoryUsed) {
              logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionTemplate != null) {
            if (explicitFactoryUsed) {
              logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
          }
    
          if (!explicitFactoryUsed) {
            if (logger.isDebugEnabled()) {
              logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            }
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
          }
        }
      }
     

    这里对bean定义做了修改,主要就是将beanClass设置为MapperFactoryBean.class,并且将原className作为MapperFactoryBean的构造函数入参,这两步就为后面的初始化做好了准备。OK到此为止完成了所有mapper接口bean的注册。

    3-3.Spring实例化bean

    完成BeanDefinition的注册,IOC的过程就完成一半了,接下来根据BeanDefinition的信息来对bean进行实例化。这里由于这里将所有mapper接口bean的beanClass设置为MapperFactoryBean.class,所以Spring对于实现了FactoryBean接口的类实例化的时候会调用该类的getObject方法,源码如下:

    public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    
      private Class<T> mapperInterface;
    
      private boolean addToConfig = true;
    
      public MapperFactoryBean() {
        //intentionally empty 
      }
      
      public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
     .../* 省略 */...
    
      /**
       * {@inheritDoc}
       */
      @Override
      public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
      }
      .../* 省略 */...
    }


    继续追踪代码,最后到MapperProxyFactory这个类里取初始化Mapper接口的实例。

    public class MapperProxyFactory<T> {
    
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
    
      public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
      }
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }


    这里使用了jdk的动态代理生成了 Mapper接口的真正bean实例,这里的生成细节就不说了。到这里,整个Mapper接口实例化完成。

    3-4.Mapper接口实例化时序图

     

     

    3-5.Mybatis的核心配置的初始化

    接下来就是比较明显的Mybatis的核心配置的初始化,在自定义的数据源配置类中,方法如下:

    /**
         * 初始化 mybatisSqlSessionFactory
         * @param mybatisDataSource
         * @return
         * @throws Exception
         */
        @Bean(name = "mybatisSqlSessionFactory")
        public SqlSessionFactory masterSqlSessionFactory(@Qualifier("mybatisDataSource") DataSource mybatisDataSource)
                throws Exception {
            final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            //设置数据源配置
            sessionFactory.setDataSource(mybatisDataSource);
            //设置Mybatis的mapper.xml的路径,由于初始化sql语句
            sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources(DataSourceConfig.MAPPER_LOCATION));
            //factoryBean 通过getObject获取实例
            return sessionFactory.getObject();
        }

     

    调用的时序大体如下:

     

    这里其实大多数工作都是在解析xml的sql语句到内存,还有就是Configuration的初始化了,这个类是Mybatis里非常核心的一个类,也非常的重,里面包含了几乎所有Mybatis运行时需要的组件。经过上面的初始化之后,Mybatis的主要组件SqlSessionFactory就被Spring注册为Bean放到Spring的容器里,用来初始化前面的Mapper接口的代理类实例。

    4.Mybatis的架构及各个组件作用

    Mybatis分为三层架构,大体分为接口层,数据处理层和基础支撑层,每层负责的职责不一样,模块划分清晰

     

    4-1.接口层

    这里就是我们能够看到的框架最上层的地方,它定义了对于数据库的基本操作,比较简单,要注意Mapper接口和Mapper.xml对应(或者基于注解,更简单,0配置),实例如下

     

    4-2.数据处理层

    这一层是Mybatis的核心层,包括了我们上面分析的加载和初始化都属于这个层,它包含了配置解析、参数解析、SQL解析、SQL执行、结果处理、插件等模块,其功能如下:

    • 配置解析:在Mybatis初始化过程中,会加载mybatis-config.xml配置文件(我们使用的是注解)、映射配置文件以及Mapper接口中的注解信息,解析后的配置信息会形成相应的对象并保存到Configration对象中。之后,根据该对象创建SqlSessionFactory对象。待Mybatis初始化完成后,可以通过SqlSessionFactory创建SqlSession对象并开始数据库操作。这一过程上面已经分析过了。
    • 参数解析:参数映射主要是将java类型和JDBC类型对应起来,Mybatis已经有默认的映射了,初始化的时候就已经默认赋值了,具体可以看看TypeHandlerRegistry类。对应之后就会根据映射将入参由java类型转换为JDBC类型的参数。
    • SQL解析:Mybatis实现的动态SQL语句,几乎可以编写出所有满足需要的SQL,Mybatis中scripting模块会根据用户传入的参数,解析映射文件中定义的动态SQL节点,形成数据库能执行的sql语句。
    • SQL执行:Executor主要维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给StatementHandler完成,这里一般情况下默认使用SimpleExecutor来执行sql。
    • 结果处理:这里和上面参数解析的过程反过来,由于JDBC返回的结果集是JDBC类型的参数,所以也需要对JDBC类型的参数转换为java类型,再转换为对应的ResultType,返回给调用者。
    • 插件:这里没使用到插件,但是一般分页都会使用到插件,参数解析的时候对ParameterHandler进行了增强。

     

    4-3.基础支撑层

    这一层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。其中比较重要有几个模块:

    • 反射模块:对java反射进行了很好的封装,提供给上层使用,并且对反射操作进行了一系列的优化,比如,缓存了类的元数据(MetaClass)和对象的元数据(MetaObject),提高了反射操作的性能。
    • 缓存模块:Mybatis对数据查询进行优化,所以引入了缓存机制,分为一级缓存和二级缓存,两种缓存的区别就是作用域和机制,一级缓存默认开启,作用域是同一SqlSession会话,即当会话被销毁则一级缓存也就没了,但是当SqlSession执行update操作(update()、delete()、insert())的时候,一级缓存同样被删掉。SpringBoot中同样默认帮我们全局开启了二级缓存,但是是需要在相应的mapper接口中加@CacheNamespace注解才有用(或者xml里配置 <cache/>,但是无论如何返回的java对象一定要可以序列化),而且他的作用域是mapper的namespace,多个SqlSession可以使用改缓存,但任何一个SqlSession执行了该Mapper里的update操作(update()、delete()、insert())的时候都会刷新二级缓存。这里就简单说下缓存模块,Mybatis的二级缓存使用不多。
    • 解析器模块:该模块有两个主要功能:一个是封装了XPath,为Mybatis初始化时解析mybatis-config.xml配置文件以及映射配置文件提供支持;另一个为处理动态SQL语句中的占位符提供支持。
    • 数据源和连接池模块:在数据源模块中,Mybatis自身提供了相应的数据源实现,也提供了与第三方数据源集成的接口。
    • 类型转换模块:类型转换模块的另一个功能是实现JDBC类型与Java类型间的转换。
    • 事务管理模块:Mybatis的事务管理分为实现JdbcTransaction 和 ManagedTransaction两种,一个自己管理,一个交给外部容器管理,感兴趣的话可以看看具体的原理。

    5.Mybatis如何完成一条SQL的执行

    5-1.首先看执行的时序图

     

    这是一个比较漫长的流程,涉及到的类主要就是有以下几个:

    • SqlSession接口:Mybatis默认使用的是DefaultSession实现,这里主要就是Mybatis的操作的会话接口,里面定义了一系列的数据库操作方法,例如增删改查。持有2个重要的属性,配置类Configuration和Executor,前者可以获取各种加载的配置如Mapper.xml里的内容;后者则是sql执行器。
    • Executor类:执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。关系图如下,Executor总体来说大同小异,只是以不同场景需要有了4个实现类。

     

    • Configuration类:Mybatis整个架构的大总管,内部持有几乎所有Mybatis的配置。
    • MappedStatement类:这个类主要是用来存储Mapper.xml的节点信息的,如select、update等,是在Mybatis初始化Configuration的时候解析xml或者注解存到这个对象中的,这个对象是被Configuration对象持有的。
    • SqlSource类:这个接口第一个作用是在Mybatis初始化的时候存放解析出来的原始SQL语句的,根据不同类型的SQL初始化为DynamicSqlSource或StaticSqlSource实例,原始SQL语句存放于M,负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。在这里动态SQL会被分为很多节点,大体上是根据<if>、<set>这些节点来分的sql,并且通过不同的SqlNode接口实现循环解析sql,直到全部解析为StaticTextSqlNode为止,然append到sql结尾,组成jdbc可识别的sql语句。
    • SqlNode接口:在动态SQL的解析中至关重要。
    • BoundSql类:存放解析出来的jdbc识别的SQL语句和其参数和值。
    • TypeHandler类:jdbc类型和java类型的映射表和其处理类。

     

     

    • StatementHandler类:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
    • ParameterHandler类:负责对用户传递的参数转换成JDBC Statement 所需要的参数。
    • ResultSetHandler类:对jdbc返回的结果集进行类型转换,使其变为java类型的对象。

    5-2.Mybatis执行流程分析

     

    上面的较为详细的描述了整个sql执行的流程,其中比较重要的步骤就是第三步SQL的构成BoundSql对象,这里涉及到了对动态sql的处理,而且这块设计也比较巧妙,这里对于动态SQL的组装是用递归解析的,我们分析下复杂的SQL的解析,首先会调用MappedStatement.getBoundSql(Object parameterObject)方法,由于本次执行的SQL是动态SQL,所以它最终调用的是DynamicSqlSource类的getBoundSql方法,源码如下

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        // 初始化上下文 主要是sqlBuilder和bindings模块初始化
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        // 这里就是递归调用,根据node的不同类型来进行apply解析,node的种类上面的类图已经画出,这里使用的是MixdSqlNode
        rootSqlNode.apply(context);
        // 从配置类中拿出 typeAliasRegister和TypeHandlerRegister
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        // 解析完整的SQL语句,找到#{},替换为? 输出预编译sql
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
          boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
        }
        return boundSql;
    }

    这里首先初始化一个上下文,主要作用是初始化2个属性 sqlBuilder和bindings,前者为存放完整SQL的,后者则是存放参数的。然后进入MixedSqlNode的apply方法,源码如下:

    public class MixedSqlNode implements SqlNode {
      private final List<SqlNode> contents;
    
      public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
      }
    
      @Override
      public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : contents) {
          sqlNode.apply(context);
        }
        return true;
      }
    }

    可以看到这里循环调用不同类型的SqlNode的qpply方法,最终都是解析为StaticTextSqlNode,并将SQL语句拼接到context的SqlBuilder中。这里构建之后就得到的完整的sql了,但是还不是预编译的sql,后面会在SqlSourceParser类的parse()方法中实例化真正的解析器GenericTokenParser对sql语句进行解析,相关代码为:

    qlSourceBuilder类
    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }
    
    GenericTokenParser类  对#{}的部分进行替换为 ?
    public String parse(String text) {
        if (text == null || text.isEmpty()) {
          return "";
        }
        // search open token
        int start = text.indexOf(openToken, 0);
        if (start == -1) {
          return text;
        }
        char[] src = text.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        while (start > -1) {
          if (start > 0 && src[start - 1] == '\\') {
            // this open token is escaped. remove the backslash and continue.
            builder.append(src, offset, start - offset - 1).append(openToken);
            offset = start + openToken.length();
          } else {
            // found open token. let's search close token.
            if (expression == null) {
              expression = new StringBuilder();
            } else {
              expression.setLength(0);
            }
            builder.append(src, offset, start - offset);
            offset = start + openToken.length();
            int end = text.indexOf(closeToken, offset);
            while (end > -1) {
              if (end > offset && src[end - 1] == '\\') {
                // this close token is escaped. remove the backslash and continue.
                expression.append(src, offset, end - offset - 1).append(closeToken);
                offset = end + closeToken.length();
                end = text.indexOf(closeToken, offset);
              } else {
                expression.append(src, offset, end - offset);
                offset = end + closeToken.length();
                break;
              }
            }
            if (end == -1) {
              // close token was not found.
              builder.append(src, start, src.length - start);
              offset = src.length;
            } else {
              builder.append(handler.handleToken(expression.toString()));
              offset = end + closeToken.length();
            }
          }
          start = text.indexOf(openToken, offset);
        }
        if (offset < src.length) {
          builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
      }
    }

    从代码可以清楚的看到sql中的#{}被找到 然后替换为"?"的,完成了这个替换之后,sql这块基本就准备完成了,后面就是对参数的处理了,这里就不多说了。OK到此为止,Mybatis的运行流程和设计基本上介绍完成。

    5-3.Mybatis使用注意事项

    了解了Mybatis的大体运行原理,在实战过程中难免会遇到一些问题,我这里列举下注意事项:

    • 慎用 ${},多用#{},其中原因是Mybatis在解析#{}语句的时候,会将它解析为预编译语言 如 "update table set a=? where b=?",后续处理在对"?"进行替换填充,这种语句是会被缓存的,以后同样的语句就可以不用再解析了,但是${}不会,而且是直接替换值,可能会发生SQL注入攻击。
    • 同一个NameSpace下,节点的id不能相同
    • XML转义字符,如果直接写就会报错,所以需要换一种xml能识别的写法:
      <!--原符号 < <= > >= & ' "
       替换符号 &lt; &lt;= &gt; &gt;= &amp; &apos; &quot;-->
    展开全文
  • mybatis 原理

    2019-10-04 12:36:47
    什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于...
  • springboot集成mybatis原理剖析

    千次阅读 2019-06-24 00:42:12
    前言:本文主要基于springboot项目,从springboot的拓展角度来分析mybatis是如何与springboot集成的。 1、首先搭建springboot集成mybatis环境 1.1、maven pom文件依赖 <?xml version="1.0" encoding="UTF-8...
  • MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例,深入代码,来探究MyBatis的实现...
  • 主要介绍了在今天这篇博文中,我将要介绍一下mybatis的框架原理,以及mybatis的入门程序,实现用户的增删改查,她有什么优缺点以及mybatis和hibernate之间存在着怎么样的关系,大家这些问题一起通过本文学习吧
  • mybatis原理

    千次阅读 2018-02-26 21:30:15
    MyBatis框架整体设计更多干货Mybatis知识点Spring boot Mybatis 整合 消息中心1Mybatis分页插件PageHelpermybatis原理二、接口层-和数据库交互的方式MyBatis和数据库的交互有两种方式使用传统的MyBatis提供的API使用...
  • MyBatis是由工厂构造器读取配置文件信息,然后生成SQL会话工厂,再由SQL会话工厂生成SQL会话实例,SQL会话实例获取映射器,映射器就会通过命名空间和方法名找到对应的SQL,将SQL发送到数据库执行,数据库执行后返回查询的...
  • spring源码学习之整合Mybatis原理分析

    万次阅读 多人点赞 2018-11-11 01:37:03
    本文主要解析spring是如何与mybatis进行整合,整合的过程中需要哪些组件的支持。以前面提到过的配置例子《spring源码学习之aop事物标签解析》 整合的过程中需要使用以下这个依赖包: &lt;!-- mybatis-spring ...
  • Mybatis原理理解

    千次阅读 多人点赞 2021-02-06 14:11:46
    MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上。MyBatis提供了一级缓存、二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能。本文主要讲述MyBatis...
  • sharding-jdbc+mybatis 原理

    千次阅读 2019-09-24 19:50:30
    项目中使用 1). 项目中使用mybatis 2). 项目中使用sharding-jdbc mybatis+sharding-jdbc结合的入口
  • 深入剖析 mybatis 原理(一)

    万次阅读 多人点赞 2017-12-17 13:13:53
    # 前言在java程序员的世界里,最熟悉的开源软件除了 Spring,Tomcat,还有谁呢?当然是 Mybatis 了,今天楼主是来和大家一起分析他的原理的。
  • MyBatis原理总结(手写实现类)

    千次阅读 2019-07-15 00:12:00
    但是MyBatis是支持写Dao实现类的 注意sqlSession是这里面的一个灵魂,有很多执行api 目录结构: 方法: /** * 用户的持久层接口 */ public interface IUserDao { List<User&g...
  • 《深入理解mybatis原理》 MyBatis事务管理机制

    万次阅读 多人点赞 2014-07-20 22:09:57
    MyBatis作为Java语言的数据库框架,对数据库的事务管理是其非常重要的一个方面。本文将讲述MyBatis的事务管理的实现机制。首先介绍MyBatis的事务Transaction的接口设计以及其不同实现 JdbcTransaction 和 ...
  • MyBatis原理分析

    2020-11-22 11:27:00
    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis可以使用简单的 XML 或注解来配置和映射原生类型、接口和 ...
  • mybatis原理(含图)

    2021-03-10 10:12:23
    上面中流程就是MyBatis内部核心流程,每一步流程的详细说明如下文所述: (1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。 (2)加载映射文件。映射文件即SQL映射...
  • 深入浅出MyBatis技术原理与实战.pdf 和 SpringBoot实战第4版
  • 本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存机制中的方方面面展开讨论。
  • spring 整合 mybatis原理

    千次阅读 2018-12-15 09:46:07
    那么让我在回到之前提到的那个问题,如果有成百上千个dao接口呢,那我们岂不是要配置添加成百上千个bean,当然不是这样,spring还为MyBatis添加了拓展的功能,可以通过扫描包目录的方式,添加dao,让我看看具体使用...
  • SSM第四讲 Mybatis原理及开发流程

    万次阅读 2020-08-17 21:21:05
    Mybatis原理及开发流程 Mybatis介绍 原理及开发流程 log4j日志操作 三种操作方式CRUD 总配置文件说明 mapping映射文件说明 一、MyBatis介绍 1.什么是MyBatis MyBatis 本是apache的一个开源项目iBatis, 2010年 ...
  • MyBatis原理与执行流程

    万次阅读 2018-09-07 11:38:38
    Mybatis流程分四步: 1. 返回创建SqlSessionFactory对象 2. 返回SqlSession的实现类DefaultSqlSession对象 3. 返回一个MapperProxy的代理对象 4. 执行询流程。 第一步返回创建SqlSessionFactory对象 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 80,368
精华内容 32,147
关键字:

mybatis原理