• mybatis原理分析


    1:mybatis首先构建SqlSessionFactory,这个工厂主要就是初始化与数据库操作有关的上下文信息。会收集xml中的配置,如environment,datasource,transactionManager,setting配置等等,而默认构建出来的是DefaultSqlSessionFactory。

    在mybatis最重要的配置节点就是和sql执行相关的mappers配置节点:

    mybatis的mappers节点下可配置mapper和package:
    (1)如果配置package首先会扫描接口,将接口中的每一个方法扫描到,并获取注解,“接口方法的全名称”和对应的“mappedStatement”对象(mappedStatement实际就是sql配置节点各个属性封装成的java对象)放入strictMap中。
    (2)如果配置了mapper则,resources属性,url属性,class属性只能选择一个,因为源码只允许写一个,否则会抛出异常。

    mybatis实际生产sql的时候是有两种方式,一个是动态sql一个是静态sql:
    (1)动态sql中mybatis解析器会有一个标识,如果开头是“${”,结尾是“}”,则认为是动态sql。
    (2)如果是静态sql那么加入,mappedStatement中的sqlResource就是将“#{}”替换成了“?”占位符的sql样子。

    2:当SqlSessionFactory创建完成以后就会通过openSession方法获得一个session,默认情况下也是获取了一个DefaultSqlSession,er该sqlsession并没有数据库打开链接之类的行为,而是存储了一些信息,比如“sql执行器”,“配置信息configuration”,以及事务是否自动提交。

    tx =transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);

    3:核心方法通过getMapper获取实现了接口的代理对象,通过jdk动态代理方式。

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        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);
        }
      }
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }

     4:MapperProxy是动态代理的核心方法它继承自InvocationHandler,而获取对应的Mapper类以后实际执行的方法就是MapperProxy的invok方法

    @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          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);
        return mapperMethod.execute(sqlSession, args);
      }
    
    @UsesJava7
      private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
          throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
            .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
            .newInstance(declaringClass,
                MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                    | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
            .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
      }

     5 mybatis中除了代理对象通过invoke方法执行以外实际方法名对应的sqlResource以外,还有比较重要的就是参数解析,抛开实体对象参数和Map参数,因为这两种参数实际的值都有对应的key。

    我们下面来说下普通多个参数的问题,如果配置sql如下select * from table where id=#{id} and name=#{name}。
    如果在java1.7中,你的方法是这样的:select(String name,int age),那么在mybatis源码中参数只能按照顺序获取并获取到arg0,arg1这种样子,因为1.7中的反射并没有提供实际的参数名称,也就是在sql的#占位符中只能按照参数的顺序替换值,在1.8中反射可以拿到参数名,如果在1.7中使用@params("id")注解也是可以解决这个问题。
    而在myBatis中提供一种额外的方式来获取参数内容,因为在基础反射条件下参数名称多变的缘故,会按照参数顺序在paramsMap中提供了param1,param2这样的参数信息并始终存在。

    {
      "arg0":"id1",
      "arg1":"name1",
      "param1":"id1",
      "param2":"name1",
    }

     6 在myBatis中where条件后尽量不要使用${}这种动态的sql语句,而${}这种动态语句的实际作用是作用在sql 语句的其它位置,比如select结果集字段有哪些。

            因为${}相当于动态替换会带来sql注入风险;如果使用#{}则可以变为参数化查询,底层源码实际调用了PreparedStatement去进行参数化处理,而sqlResource中的参数占位符

    变成了“?”,如:select * from table where id=? and name=?

  • 相关阅读:
    nginx
    DNS
    lrzsz上传下载命令
    linux命令大全20180614
    解决Nginx的connect() to 127.0.0.1:8080 failed (13: Permission denied) while connect
    安装nginx
    scp复制
    Vue3中报错“export ‘createWebHistory, createRouter‘ was not found in ‘vue-router‘
    点击div,span, p这些文本标签时隐藏光标的方法
    el-upload使用beforeUpload不生效
  • 原文地址:https://www.cnblogs.com/zzq-include/p/12013792.html
Copyright © 2020-2023  润新知