orm的基本概念与mybatis入门

orm:简单来说,orm就像是一个单调函数,实现编程语言的类型与关系型数据库类型之间的相互转换。

接下来简单介绍一下java中的orm框架——mybatis。

mybatis具有四大核心组件

1、SqlSessionFactoryBuilder:会根据xml配置或是java配置来生成SqlSessionFactory,采用建造者模式(简单来说就是分步构建一个大的对象,例如建造一个大房子,采用购买砖头、砌砖、粉刷墙面的步骤建造,其中的大房子就是大对象,一系列的建造步骤就是分步构建)。


2、SqlSessionFactory:用于生成SqlSession,使用工厂模式(简单来说就是我们获取对象是通过一个类,由这个类去创建我们所需的实例并返回,而不是我们自己通过new去创建)


3、SqlSession:相当于connection对象,可用于发送SQL语句并返回结果,也可以获取Mapper。


4、Mapper:由XML文件和Java接口组成,根据XML中配置的映射信息执行对应的SQL语句并返回执行结果。


使用mybatis的步骤

1、创建SqlSessionFactoryBuilder对象,由于该对象主要用于创建SqlSessionFactory,所以在创建SqlSessionFactory后,该对象就没有存在的必要


2、通过SqlSessionFactoryBuilder创建SqlSessionFactory,SqlSessionFactory相当于数据库连接池,所以SqlSessionFactory的生命周期要与mybatis的使用周期相同,由于SqlSessionFactory的作用单一,且创建多个数据库连接池会消耗很多资源,所以一般情况下将其设计为单例


3、通过SqlSessionFactory获取SqlSession对象,该对象相当于一个connection对象,使用完毕后要关闭连接(其实是将其归还给数据库连接池),在代码中我们更倾向于使用Mapper执行SQL语句,一般情况下会让SqlSession在代码中“消失”


4、通过SqlSession可以获取Mapper,专门执行SQL语句并返回执行结果,类似于statement(preparestatement),使用完毕后要关闭,Mapper是一个接口,只定义方法名即可,对应的SQL语句可以在配置文件中给出,也可以在代码中用注释给出,JVM根据配置文件或是注释替我们实现了这个接口


可以看到,在许多应用程序中都会有第一、二、三步的代码,且差异不大,既然如此,为什么不把这一部分交给框架自行处理呢?springboot替我们完成了这部分工作,使得我们可以直接使用Mapper工作。


实战:springboot+mybatis+MySQL整合开发


在创建springboot项目时,需要勾选MySQL、web、mybatis,项目结构如下:




按照定义顺序,代码如下:


首先要定义一个POJO——Role,代码如下:

package com.example.demo;

/*
 * mybatis将SQL查询结果映射到此POJO
 * 
 */
public class Role {
    private String username;
    private String password;
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}


定义一个Mapper,文件名RoleMapper.java,代码如下

package com.example.demo;

import org.apache.ibatis.annotations.Select;

public interface RoleMapper {
   //用于获取用户信息,包括username和password
   public Role getRole(String username);
   //用于根据用户名删除用户信息
   public void deleteRole(String username);
   //用于插入一条记录
   public void insertRole(Role user);
   //用于更新一条记录
   public void updateRole(Role user);
}


定义一个调用Mapper的组建,文件名dealrole.java,代码如下:

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class dealrole {

	@Autowired
	private RoleMapper deal;
	
	public void dealdelete(String username) {
		deal.deleteRole(username);
	}
	
	public void dealinsert(Role role) {
		deal.insertRole(role);
	}
	
	public Role dealget(String username) {
		return deal.getRole(username);
	}
	
	public void dealupdate(Role user) {
		deal.updateRole(user);
	}
	
}

@Service @Controller @Repository @Component ,这四个其实是一样的功能,没有区别,都是让spring自动扫描管理组件,只是在MVC模式上表示的层不一样,service一般标注在service层的bean上,controller标注在控制层,@Repository标注在view层,component通用。


定义一个控制器,文件名为rolemvc.java,代码如下:

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
//使用RestController,因为返回的不是逻辑视图名
@RestController
@RequestMapping("/user")
public class rolemvc {
	
	@Autowired
	private dealrole deal;
	
	@RequestMapping(value="finduser/{username}",method=RequestMethod.GET)
	public String showrole(@PathVariable("username") String username) {
		  Role user=deal.dealget(username);
		  return "username:"+user.getUsername()+"  "+"password: "+user.getPassword();
	}
	
	@RequestMapping(value="/delete/{username}",method=RequestMethod.GET)
	public String deleterole(@PathVariable("username") String username) {
		deal.dealdelete(username);
		return "删除成功";
	}
    
	@RequestMapping(value="/insert",method=RequestMethod.GET)
	public String insertrole(@RequestParam("username")String username,@RequestParam("password") String password) {
		Role role=new Role();
		role.setPassword(password);
		role.setUsername(username);
		deal.dealinsert(role);
		return "插入成功";
	}
	
	@RequestMapping(value="/update",method=RequestMethod.GET)
	public String updaterole(@RequestParam("username")String username,@RequestParam("password")String password) {
		Role role=new Role();
		role.setPassword(password);
		role.setUsername(username);
		deal.dealupdate(role);
		return "修改成功";
	}
}

启动类代码如下:

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@MapperScan("com.example.demo")
public class SbandmybatisApplication {

	public static void main(String[] args) {
		SpringApplication.run(SbandmybatisApplication.class, args);
	}
}

注意标注MapperScan,其值为Mapper所在的包,否则springboot找不到Mapper。


配置文件maybatis-config.xml,用于定义mybatis的基本配置,这里只是配置了一个别名

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
      <typeAliases>
        <typeAlias alias="role" type="com.example.demo.Role"/>
      </typeAliases>
    </configuration>


配置文件mybatismapper.xml,用于定义Mapper执行的SQL语句

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.example.demo.RoleMapper">
      <select id="getRole" parameterType="String" resultType="role">//由于在mybatis的配置文件中指定了别名,所以直接使用别名
          select username,password from user where username=#{username}
      </select>
      <delete id="deleteRole" parameterType="String">
     	  delete from user where username=#{username}
      </delete>
      <insert id="insertRole" parameterType="role">
          insert into user(username,password) values(#{username},#{password})
      </insert>
      <update id="updateRole" parameterType="role">
          update user set username=#{username},password=#{password} where username=#{username}
      </update>
    </mapper>


配置文件application.properties,需要配置mybatis配置文件与Mapper配置文件的路径:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/userinfo
spring.datasource.username=root
spring.datasource.password=1234
mybatis.mapper-locations=classpath:mybatis/mybatismapper.xml
mybatis.config-location=classpath:mybatis/maybatis-config.xml


在本机创建数据库userinfo,创建数据表user,记得创建主键,接下来说说遇到的异常:


异常

1、Field deal in com.example.demo.dealrole required a bean of type 'com.example.demo.RoleMapper' that could not be found.


springboot无法找到Mapper,需要显示指定Mapper的路径,@MapperScan("com.example.demo")


2、Error creating bean with name 'sqlSessionFactory' defined in class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/classpath*:src/main/resources/maybatis-config.xml]


检查mybatis配置文件的路径,检查mybatis配置文件中有无单词拼写错误


3、Fri Apr 27 19:17:15 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.


这是警告不是错误,以后使用是不影响的。大概的意思就是说建立ssl连接,但是服务器没有身份认证,这种方式不推荐使用。


为什么只需要定义一个接口,mybatis就可以实现对数据库的操作?

mybatis提供了Mapper的动态代理对象,当我们调用接口中的函数时,动态代理对象调用invoke方法,但其中并没有调用接口的方法,而是自己根据接口的全限定名和函数名到配置文件中去寻找对应的SQL语句,接着执行这条SQL语句,这就是为什么我们在配置文件中要指明namespace与id值的原因,mybatis创建一个Mapper代理对象的过程如下:

在创建SqlSessionFactory时,mybatis会创建接口的动态代理对象,首先是执行XMLConfigBuilder 的 mapperElement函数,


mapperElement函数代码
private void mapperElement(XNode parent) throws Exception {

-- 省略部分代码

if (resource == null && url == null && mapperClass != null) {

// mapperClass为接口的全限定名,根据全限定名加载类文件
Class<?> mapperInterface = Resources.classForName(mapperClass);
//内部调用MapperRegistry的addMapper方法,传递的参数为mapperInterface
configuration.addMapper(mapperInterface);

} else {

throw new BuilderException("A mapper element may only specify a url,

resource or class, but not more than one.");

}

-- 省略部分代码

}

MapperRegistry 
package org.apache.ibatis.binding;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapperRegistry {
  //全局配置文件对象
  private Configuration config;
  //一个HashMap Key是Mapper的类对象, Value是MapperProxyFactory对象
  //MapperProxyFactory是创建Mapper代理对象的工厂
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  //获取生成的代理对象,在生成代理对象之后在调用
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //通过Mapper的接口类型 去HasMap当中查找 如果为空就抛异常
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      //否则创建一个当前接口的代理对象 并且传入sqlSession sqlSession对象用于获取配置文件的信息,接下来会讲
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }
  //注册Mapper接口 mapperElement中的addMapper内部调用该方法 
  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 {//将Mapper的类对象——代理对象工厂以键值对的形式存储到HashMap 接下来查看MapperProxyFactory的方法
        knownMappers.put(type, new MapperProxyFactory<T>(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);
        }
      }
    }
  }
  public Collection<Class<?>> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }
  //通过包名扫描下面所有接口
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }

}


MapperProxyFactory包含两个属性

mapperInterface:接口的类对象

methodCache:建立接口方法与MapperMethod(接下来会讲)方法的映射关系


MapperProxyFactory
package org.apache.ibatis.binding;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.session.SqlSession;
//这个类负责创建具体Mapper接口代理对象的工厂类
public class MapperProxyFactory<T> {
  //具体Mapper接口的Class对象
  private final Class<T> mapperInterface;
  //该接口下面方法的缓存 key是方法对象 value是对接口中方法对象的封装
  private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  //注意到构造函数没有往methodCache中添加任何东西,此时methodCache是空的
  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) {
    //创建了一个代理类并返回
    //关于Proxy的API 可以查看java官方的API
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  //在这里传入sqlSession 创建一个Mapper接口的代理类
  public T newInstance(SqlSession sqlSession) {
    //在这里创建了MapperProxy对象 这个类实现了JDK的动态代理接口 InvocationHandler
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //调用上面的方法 返回一个接口的代理类
    return newInstance(mapperProxy);
  }
}


我们来理一下上述过程

在Mybatis初始化时,会调用mapperElement方法,在其内部调用addMapper方法,该方法内部调用MapperRegistry的addMapper方法,建立接口类——MapperProxyFactory的映射关系


接下来我们看看当我们调用SqlSession的getMapper时,发生了什么

当我们调用SqlSession的getMapper方法时,内部调用MapperRegistry的getMapper方法,查看上述代码,会有以下代码片段

return mapperProxyFactory.newInstance(sqlSession);

查看mapperProxyFactory的newInstance(SqlSession sqlSession)方法

  public T newInstance(SqlSession sqlSession) {
    //在这里创建了MapperProxy对象 这个类实现了JDK的动态代理接口 InvocationHandler
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //调用上面的方法 返回一个接口的代理类
    return newInstance(mapperProxy);
  }

MapperProxy有三个属性:

sqlSession:用于获取配置信息

mapperInterface:Mapper的类对象

methodcache:MapperProxyFactory中的methodcache

MapperProxy
package org.apache.ibatis.binding;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
//实现了JDK动态代理的接口 InvocationHandler
//在invoke方法中实现了代理方法调用的细节
public class MapperProxy<T> implements InvocationHandler, Serializable {
  private static final long serialVersionUID = -6424540398559729838L;
  //SqlSession
  private final SqlSession sqlSession;
  //接口的类型对象
  private final Class<T> mapperInterface;
  //接口中方法的缓存 有MapperProxyFactory传递过来的。
  private final Map<Method, MapperMethod> methodCache;
  //构造参数
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  //接口代理对象所有的方法调用 都会调用该方法
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //判断是不是基础方法 比如toString() hashCode()等,这些方法直接调用不需要处理
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    //这里进行缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //调用mapperMethod.execute 核心的地方就在这个方法里,这个方法对才是真正对SqlSession进行的包装调用
    return mapperMethod.execute(sqlSession, args);
  }
  //缓存处理
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

注意到MapperProxy实现了InvocationHandler接口,创建完MapperProxy对象后,调用MapperRegistry另外一个newInsance方法,代码如下:

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //创建了一个代理类并返回
    //关于Proxy的API 可以查看java官方的API
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

至此,生成了Mapper的代理对象

我们知道,当我们使用JDK动态代理时,调用接口方法时,内部执行的InvocationHandler的invoke方法,在mybatis中,调用的是MapperProxy的invoke方法,假设我们没有实现接口的方法,则会执行以下两行代码:

    //这里进行缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //调用mapperMethod.execute 核心的地方就在这个方法里,这个方法对才是真正对SqlSession进行的包装调用
    return mapperMethod.execute(sqlSession, args);

查看cachedMapperMethod方法,此时sqlsession的作用便体现出来了

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

此时会往MapperRegistry中的methodCache中添加键值对,接着查看MapperMethod,代理对象通过MapperMethod中的execute执行对应的SQl语句

MapperMethod
package org.apache.ibatis.binding;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;
//这个类是整个代理机制的核心类,对Sqlsession当中的操作进行了封装
public class MapperMethod {
  //一个内部封 封装了SQL标签的类型 insert update delete select
  private final SqlCommand command;
  //一个内部类 封装了方法的参数信息 返回类型信息等
  private final MethodSignature method;
  //构造参数
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }
  //这个方法是对SqlSession的包装调用
  public Object execute(SqlSession sqlSession, Object[] args) {
    //定义返回结果
    Object result;
    //如果是INSERT操作
    if (SqlCommandType.INSERT == command.getType()) {
      //处理参数
      Object param = method.convertArgsToSqlCommandParam(args);
      //调用sqlSession的insert方法
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      //如果是UPDATE操作 同上
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      //如果是DELETE操作 同上
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      //如果是SELECT操作 那么情况会多一些 但是也都和sqlSession的查询方法一一对应
    } else if (SqlCommandType.SELECT == command.getType()) {
      //如果返回void 并且参数有resultHandler
      //则调用 void select(String statement, Object parameter, ResultHandler handler);方法
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      //如果返回多行结果这调用 <E> List<E> selectList(String statement, Object parameter);
      //executeForMany这个方法调用的
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      //如果返回类型是MAP 则调用executeForMap方法
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        //否则就是查询单个对象
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      //如果全都不匹配 说明mapper中定义的方法不对
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    //如果返回值为空 并且方法返回值类型是基础类型 并且不是VOID 则抛出异常
    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;
  }
  private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
      result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = (long) rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = (rowCount > 0);
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }
  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName() 
          + " needs either a @ResultMap annotation, a @ResultType annotation," 
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }
  //返回多行结果 调用sqlSession.selectList方法
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    //如果参数含有rowBounds则调用分页的查询
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      //没有分页则调用普通查询
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }
  private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
    Object collection = config.getObjectFactory().create(method.getReturnType());
    MetaObject metaObject = config.newMetaObject(collection);
    metaObject.addAll(list);
    return collection;
  }
  @SuppressWarnings("unchecked")
  private <E> E[] convertToArray(List<E> list) {
    E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
    array = list.toArray(array);
    return array;
  }
  private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
    Map<K, V> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
  }
  public static class ParamMap<V> extends HashMap<String, V> {
    private static final long serialVersionUID = -2212268410512043556L;
    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
      }
      return super.get(key);
    }
  }

command中有配置信息,有接口名称,有调用方法的名字,可以唯一确定配置文件中的sql语句(这就是在配置文件中配置接口名和调用方法名字的原因),接着调用execute方法执行sql语句


当我们刚创建MapperProxyFactory时,其中的MapperCache对象的键值对为空,当我们第一次调用Mapper中的某方法时,会在MapperCache中建立键值对,当我们下次使用时,可以立刻得到MapperMethod对象:

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);//此处可以直接获取
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }


至此,mybatis接口的动态代理原理解析完毕,如有错误,欢迎指出


猜你喜欢

转载自blog.csdn.net/dhaiuda/article/details/80094226