1. 程式人生 > >orm的基本概念與mybatis入門

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介面的動態代理原理解析完畢,如有錯誤,歡迎指出

相關推薦

orm基本概念mybatis入門

orm:簡單來說,orm就像是一個單調函式,實現程式語言的型別與關係型資料庫型別之間的相互轉換。接下來簡單介紹一下java中的orm框架——mybatis。mybatis具有四大核心元件1、SqlSessionFactoryBuilder:會根據xml配置或是java配置來生

入門】(二)SFM的基本概念opencv實現

目錄 基本概念 關鍵步驟 基本概念 SFM(Structure from motion)由一系列包含著視覺運動資訊(motion signals)的多幅二維影象序列(2D image sequences)估計三維結構(3D model)的技術。它屬於計算

MyBatis基本概念程式例項

MyBatis的基本概念與程式例項 MyBatis優勢 1.和JDBC一樣不遮蔽sql語句,可以直接寫sql語句,比較靈活 2.有對映機制,再組裝各種sql時可以不改變大框架,直接新增對映就可以寫入其他sql 3.對於對映器來說,Mapper提供介面程式設計,只要一個介面和一個x

Android入門——Fragment詳解之基本概念用法(一)

引言 Android在3.0中引入了Fragments的概念,其目的是用在大螢幕裝置上–例如平板電腦上,支援更加動態和靈活的UI設計。平板電腦的螢幕要比手機的大得多,有更多的空間來放更多的UI元件,並且這些元件之間會產生更多的互動。Fragment允許這樣的一

TensorFlow深度學習入門筆記(二)基本概念代碼1

.get ali ant scope 基本 有一個 關註 執行 rbo 關註公眾號“從機器學習到深度學習那些事”獲取更多最新資料 寫在前面 學習建議:以下學習過程中有不理解可以簡單查找下資料,但不必糾結(比如非得深究某一個函數等),盡量快速的學一遍,不求甚解無妨。因為有些知

【Python】 Web開發框架的基本概念開發的準備工作

世紀 依賴包 ade 並不是 模板 界面 inux tar cal Web框架基本概念 現在再來寫這篇文章顯然有些馬後炮的意思。不過正是因為已經學習了Flask框架, 並且未來計劃學習更加體系化的Django框架,在學習過程中碰到的很多術語等等,非常有必要通過這樣一篇

計算機網路基礎 - 一些基本概念網絡結構

logs 網路 兩個 wdm comment quest 利用 gin 規範 1. 基本概念 計算機網絡 = 通信技術+計算機技術,是兩項技術緊密結合的產物。 通信系統的基礎模型: 計算機網絡,是指將地理位置不同、具有獨立功能的多臺計算機及其外部設備,通過通信線路連接,在

Oracle sequence的基本概念理解

side frame create varchar2 inf sel *** 順序 -s 1.如何查看sequence的定義 2.dba_sequences相關字的定義 3.如何修改sequence *************************************

Structured Streaming教程(1) —— 基本概念使用

repr 開放 let 結果 可靠 技術分享 lines ole sock 近年來,大數據的計算引擎越來越受到關註,spark作為最受歡迎的大數據計算框架,也在不斷的學習和完善中。在Spark2.x中,新開放了一個基於DataFrame的無下限的流式處理組件——Struc

[Flink基本概念部署]--Flink 程式設計模型【一】

一、Flink基本程式設計模型 圖1   1、Stateful Stream Processing 它位於最底層, 是core API 的底層實現;推薦學習時使用,生產中以穩定為主,不建議使用。 processFunction:開

Maven基本概念核心配置

Maven的安裝與核心配置 1、安裝Maven   1)、官網下載 Maven (http://maven.apache.org/download.cgi);   2)、解壓指定目錄;   3)、配置環境變數;   4)、使用mvn -version檢視Maven是否安裝成功

圖的基本概念相關術語

1、圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。 2、對於圖的定義,我們需要明確幾個注意的地方: (1)線性表中我們把資料元素叫元素,樹中叫結點,在圖中資料元素我們則稱之為頂點(V

freertos- 任務基本概念任務掛起和恢復解析

1、任務狀態        任務實體 2、任務的優先順序 3、任務掛起和恢復的情形 4、任務掛起和恢復實現           掛起任務列表 5、任務掛起和阻塞,認識恢復和

elasticsearch基本概念查詢語法

序言 後面有大量類似於mysql的sum, group by查詢elk=== elk總體架構 https://www.elastic.co/cn/products Beat 基於go語言寫的輕量型資料採集器,讀取資料,迅速傳送到Logstash進行解析,亦

資料結構演算法——基本概念術語

概述  資料、資料元素、資料物件 資料(data)是對客觀事物的符號表示,在電腦科學中是指所有能輸入到計算機中並被計算機程式處理的程式的總稱。 資料元素(data element)是資料的基本單位,在計算機程式中通常作為一個整體進行考慮和處理。一個數據元素可由若干個資料項(data ite

spark基本概念執行架構

Apache Spark是一個分散式計算框架,旨在簡化運行於計算機叢集上的並行程式的編寫。   RDD:彈性分散式資料集(Resilient Distributed Dataset)是分散式記憶體的一個抽象概念,提供了一個高度受限的共享記憶體模型。一個RDD包含多個分割槽(Partition)。

HDFS基本概念重新梳理(hdfs檔案資訊介紹)

HDFS(Hadoop Distributed File System )Hadoop分散式檔案系統。是根據google發表的論文翻版的。論文為GFS(Google File System)Google 檔案系統(中文,英文)。 HDFS有很多特點: 1.儲存多個副本,且提供容錯機制,副

Git的基本概念操作

=============================基本操作================================= 一、linux安裝git     yum install git 二、拉取專案     git c

圖的基本概念儲存

<一>圖的基本概念 (1);圖由頂點(vertex), 邊(edge)組成,記號G(V,E)表示圖G的頂點集為V,邊集為E。 V={v1,v2,…… } 是有限非空集合,為頂點集,其元素稱為頂點或結點 E={e1,e2,…… } 是有限集合,稱為邊集

Java NIO的基本概念使用

物理 類型 數據 long 緩沖區 需要 方法 ble pos public class TestBuffer { /** * 一. 緩沖區 (Buffer):Java Nio中負責數據的 存取+緩沖就是數組.用於存儲不