1. 程式人生 > >學習ART之生成Runtime OAT檔案

學習ART之生成Runtime OAT檔案

從前一篇學習筆記——ART無縫對接Dalvik虛擬機器中可以瞭解到Dalvik和ART這兩種虛擬機器機制的可執行檔案存在著區別,ART對應的是OAT檔案格式,而Dalvik則是dexy格式的檔案,而且還知道將apk檔案進行優化並轉換為指定可執行檔案的過程都是發生在應用程式安裝的過程中,所不同的只是呼叫了不同的模組來進行轉化而已。這次先來分析一下art虛擬機器啟動過程中生成oat檔案的過程。

1.dex檔案轉化為oat檔案

首先還是看一下講dex轉化成oat檔案的程式碼,前兩個引數就是輸入的apk檔案和輸出的oat檔案的描述符,後兩個引數則是輸入的檔案路徑和輸出的檔案路徑,最後一個就是優化的標誌:

static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name,
    const char* output_file_name, const char* dexopt_flags)
{
    static const char* DEX2OAT_BIN = "/system/bin/dex2oat";
    static const int MAX_INT_LEN = 12;      // '-'+10dig+'\0' -OR- 0x+8dig
    char zip_fd_arg[strlen("--zip-fd="
) + MAX_INT_LEN]; char zip_location_arg[strlen("--zip-location=") + PKG_PATH_MAX]; char oat_fd_arg[strlen("--oat-fd=") + MAX_INT_LEN]; char oat_location_arg[strlen("--oat-name=") + PKG_PATH_MAX]; sprintf(zip_fd_arg, "--zip-fd=%d", zip_fd); sprintf(zip_location_arg, "--zip-location=%s"
, input_file_name); sprintf(oat_fd_arg, "--oat-fd=%d", oat_fd); sprintf(oat_location_arg, "--oat-location=%s", output_file_name); ALOGV("Running %s in=%s out=%s\n", DEX2OAT_BIN, input_file_name, output_file_name); execl(DEX2OAT_BIN, DEX2OAT_BIN, zip_fd_arg, zip_location_arg, oat_fd_arg, oat_location_arg, (char*) NULL); ALOGE("execl(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno)); }

這裡將會執行的時在/system/bin/dex2oat來對apk檔案中的class.dex進行優化。這個程式的原始碼檔案在art/dex2oat/dex2oat.cc檔案中,本人水平所限,裡面轉化的程式碼有點難看懂,暫時就先放在一邊了。

2.JNI_CreateJavaVM

在建立ART虛擬機器的過程中會進行第一次的dex到oat檔案的轉化,這裡就先分析一下這個過程以便對整個過程有個基礎的瞭解。在之前分析過的JniInvocation::Init函式中有如下一段程式碼:

 if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
                 "JNI_GetDefaultJavaVMInitArgs")) { 
   return false; 
 }
 if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
                 "JNI_CreateJavaVM")) {
   return false;
 }      
 if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
                 "JNI_GetCreatedJavaVMs")) {
   return false;
 }
 return true;

這段程式碼就是獲得建立虛擬機器所必須的介面函式指標,而這裡的介面則是根據檔案中定義klibararyFallback字串來獲取的,kitkat中是libdvm.so,而在最新的5.0 lollipop的程式碼中可以看到變成了libart.so,也就是說art模式已經成為預設的虛擬機器了:


再回到kitkat的程式碼中,假設這裡也是執行libart.so中的art虛擬機器建立函式,下面就要來分析一下建立過程中解析oat檔案的過程。在獲得libart.so中的三個函式指標之後會呼叫JNI_CreateJavaVM來建立art虛擬機器,這個函式在art/runtime/jni_internal.cc中:

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
  //判斷JNI的版本,如果版本錯誤則直接返回當前版本
  if (IsBadJniVersion(args->version)) {
    LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;
    return JNI_EVERSION;
  }
  Runtime::Options options;
  //將第三個引數中的option項取出
  for (int i = 0; i < args->nOptions; ++i) {
    JavaVMOption* option = &args->options[i];
    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
  }
  bool ignore_unrecognized = args->ignoreUnrecognized;
  //呼叫Runtime::Create建立一個新的Runtime例項並進行初始化
  if (!Runtime::Create(options, ignore_unrecognized)) {
    return JNI_ERR;
  }
  //返回當前例項的指標
  Runtime* runtime = Runtime::Current();
  bool started = runtime->Start();
  if (!started) {
    delete Thread::Current()->GetJniEnv();
    delete runtime->GetJavaVM();
    LOG(WARNING) << "CreateJavaVM failed";
    return JNI_ERR;
  }
  *p_env = Thread::Current()->GetJniEnv();
  *p_vm = runtime->GetJavaVM();
  return JNI_OK;
}

3.Runtime.Create和Runtime.Init

Runtime.Create這個函式在art/runtime/runtime.cc檔案的Runtime類中,instance_中儲存著Runtime的例項,如果不存在Runtime例項才會進行建立。在new了一個Runtime例項之後呼叫Init函式對其進行初始化:

bool Runtime::Create(const Options& options, bool ignore_unrecognized) {
  // TODO: acquire a static mutex on Runtime to avoid racing.
  if (Runtime::instance_ != NULL) {
    return false;
  }
  InitLogging(NULL);  // Calls Locks::Init() as a side effect.
  instance_ = new Runtime;
  if (!instance_->Init(options, ignore_unrecognized)) {
    delete instance_;
    instance_ = NULL;
    return false;
  }
  return true;
}

接下去再來分析一下Init函式,這個函式還在同一個檔案中,

bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) {
  ... ....
  //根據傳入的options引數建立一個ParsedOptions物件
  UniquePtr<ParsedOptions> options(ParsedOptions::Create(raw_options, ignore_unrecognized));
  ... ...
  //根據新建立的ParsedOptions物件的值初始化Runtime例項中的一些值
  host_prefix_ = options->host_prefix_;
  boot_class_path_string_ = options->boot_class_path_string_;
  class_path_string_ = options->class_path_string_;
  properties_ = options->properties_;
  ... ...
  //同樣時根據ParsedOptions的引數建立一個新的堆
  heap_ = new gc::Heap(options->heap_initial_size_,
                       options->heap_growth_limit_,
                       options->heap_min_free_,
                       options->heap_max_free_,
                       options->heap_target_utilization_,
                       options->heap_maximum_size_,
                       options->image_,
                       options->is_concurrent_gc_enabled_,
                       options->parallel_gc_threads_,
                       options->conc_gc_threads_,
                       options->low_memory_mode_,
                       options->long_pause_log_threshold_,
                       options->long_gc_log_threshold_,
                       options->ignore_max_footprint_);

  //建立一個JavaVMExt物件,這個物件後面可以通過GetJavaVM函式獲得,以便外部程式和虛擬機器進行互動
  java_vm_ = new JavaVMExt(this, options.get());

  //根據當前的執行緒列表和虛擬機器環境建立一個新執行緒並設為主執行緒
  Thread* self = Thread::Attach("main", false, NULL, false);
  CHECK_EQ(self->thin_lock_id_, ThreadList::kMainId);
  CHECK(self != NULL);
  ... ... 
  //GetHeap函式獲得上面新建立的堆指標,GetContinuousSpaces函式獲得堆中的連續空間,判斷這個連續空間是不是Image空間,如果是則呼叫CreateFromImage函式來建立一個ClassLinker物件,否則呼叫CreateFromCompiler來建立這個物件
  if (GetHeap()->GetContinuousSpaces()[0]->IsImageSpace()) {
    class_linker_ = ClassLinker::CreateFromImage(intern_table_);
  } else {
    CHECK(options->boot_class_path_ != NULL);
    CHECK_NE(options->boot_class_path_->size(), 0U);
    class_linker_ = ClassLinker::CreateFromCompiler(*options->boot_class_path_, intern_table_);
  }
  ... ...
  return true;
}

4.ParsedOptions::Create和gc::Heap::Heap

在上面的函式中有兩個還需要深入分析的,首先就是ParsedOptions::Create,這個函式仍然是在runtime.cc檔案中,它通過傳入的option引數建立一個ParsedOptions物件,這個物件中的值在後面的程序中起到了非常重要的作用,部分程式碼如下:

Runtime::ParsedOptions* Runtime::ParsedOptions::Create(const Options& options, bool ignore_unrecognized) {
  //從環境變數中獲取到啟動類的預設路徑
  const char* boot_class_path_string = getenv("BOOTCLASSPATH");
  if (boot_class_path_string != NULL) {
    parsed->boot_class_path_string_ = boot_class_path_string;
  }
  ... ...
  //is_compiler_用於定義當前的art虛擬機器時用來編譯dex檔案到oat檔案的還是第一次啟動虛擬機器時呼叫的,預設為後者
  parsed->is_compiler_ = false;
  ... ...
  //遍歷所有選項,對每個選項進行鍼對性的處理
  for (size_t i = 0; i < options.size(); ++i) {
    const std::string option(options[i].first);
    //啟動路徑的引數
     if (StartsWith(option, "-Xbootclasspath:")) {
      parsed->boot_class_path_string_ = option.substr(strlen("-Xbootclasspath:")).data();
      }
     //這個引數是用來指定建立虛擬機器的image所在的路徑
     else if (StartsWith(option, "-Ximage:")) {
      parsed->image_ = option.substr(strlen("-Ximage:")).data();
      }
     ... ...
     else if (option == "compiler") {
      parsed->is_compiler_ = true;
      }
     ... ...
     //如果既不是用於編譯的虛擬機器也不存在建立虛擬機器所必須的映象直接把映象路徑指定為預先定義的位置,也就是/system/framework/boot.art
     if (!parsed->is_compiler_ && parsed->image_.empty()) {
    parsed->image_ += GetAndroidRoot();
    parsed->image_ += "/framework/boot.art";
    }
  }
}

目前就關注以上這幾個引數:
    1.-Xbootclasspath引數用於指定Runtime需要用到的生成oat檔案的dex檔案路徑。也就是這個路徑下存在著一些dex檔案,用於生成Runtime自己的oat檔案,這個會在後面提到。
    2.compiler引數用於指定當前是否是安裝應用程式時所呼叫的art虛擬機器。在應用程式安裝的時候會fork一個虛擬機器用來將apk檔案中的classes.dex翻譯成oat檔案格式。
    3.-Ximage引數用語指定將要生成的art虛擬機器image映象檔案所在的路徑,如果不存在則採用預設路徑/system/framework/boot.art,當然這個檔案也有可能不存在,如果是這樣就會生成一個image檔案,這個在後面會提到。

下面再來看Heap的建構函式,這個函式也比較大,在art/runtime/gc/heap.cc檔案中,目前就只看關於啟動image的那部分,這裡會根據傳入的image映象的路徑(就是ParsedOptions::Create函式中的獲得的image引數)去獲取這個image檔案,然後根據這個檔案建立第一個連續的堆空間:

Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max_free,
           double target_utilization, size_t capacity, const std::string& original_image_file_name,
           bool concurrent_gc, size_t parallel_gc_threads, size_t conc_gc_threads,
           bool low_memory_mode, size_t long_pause_log_threshold, size_t long_gc_log_threshold,
           bool ignore_max_footprint)
{
 ... ...
 std::string image_file_name(original_image_file_name);
  if (!image_file_name.empty()) {
    space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name);
    ... ...
    AddContinuousSpace(image_space);
    ... ...
  }
 ... ...
}

5.ImageSpace::Create

再來看ImageSpace::Create函式,這個函式在art/runtime/gc/space/image_space.cc檔案中,其根據image映象初始化第一塊連續堆記憶體,它會依次從指定路徑尋找image檔案,全都不存在時便會自己生成一個並進行初始化:

ImageSpace* ImageSpace::Create(const std::string& original_image_file_name) {
  ///system目錄下的映象檔案存在
  if (OS::FileExists(original_image_file_name.c_str())) {
    return space::ImageSpace::Init(original_image_file_name, false);
  }
  ///system檔案不存在則從/data/dalvik-cache查詢名為[email protected]@[email protected](檔名由原路徑的/替換為@並在後面加上classes.dex)檔案,找到則直接初始化
  std::string image_file_name(GetDalvikCacheFilenameOrDie(original_image_file_name));
  if (OS::FileExists(image_file_name.c_str())) {
    space::ImageSpace* image_space = space::ImageSpace::Init(image_file_name, true);
    if (image_space != NULL) {
      return image_space;
    }
  }
  //呼叫GenerateImage函式生成一個映象檔案隨後進行初始化
  CHECK(GenerateImage(image_file_name)) << "Failed to generate image: " << image_file_name;
  return space::ImageSpace::Init(image_file_name, true);
}

6.ImageSpace::Init和GenerateImage

在ImageSpace::Create函式在art/runtime/gc/space/image_space.cc檔案中,部分程式碼如下,首先會開啟image檔案並將檔案內容對映到記憶體中,隨後根據image檔案的頭部獲得Runtime oat檔案的位置,經過對oat檔案的一系列校驗後開啟:

ImageSpace* ImageSpace::Init(const std::string& image_file_name, bool validate_oat_file) {
  ... ...
  //開啟image映象檔案
  UniquePtr<File> file(OS::OpenFileForReading(image_file_name.c_str()));
  ... ...
  ImageHeader image_header;
  //獲取image檔案的頭部
  bool success = file->ReadFully(&image_header, sizeof(image_header));
  ... ...
  //下面的程式碼就是將image檔案以及它的點陣圖檔案對映到記憶體空間了
  UniquePtr<MemMap> map(MemMap::MapFileAtAddress(image_header.GetImageBegin(),
                                                 image_header.GetImageSize(),
                                                 PROT_READ | PROT_WRITE,
                                                 MAP_PRIVATE | MAP_FIXED,
                                                 file->Fd(),
                                                 0,
                                                 false));
  ... ... 
   std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u", image_file_name.c_str(),
                                       bitmap_index));
  UniquePtr<accounting::SpaceBitmap> bitmap(
      accounting::SpaceBitmap::CreateFromMemMap(bitmap_name, image_map.release(),
                                                reinterpret_cast<byte*>(map->Begin()),
                                                map->Size()));
 ... ...
  UniquePtr<ImageSpace> space(new ImageSpace(image_file_name, map.release(), bitmap.release()));
  if (kIsDebugBuild) {
    space->VerifyImageAllocations();
  }
  //根據image的檔案頭獲取到Runtime oat檔案
  space->oat_file_.reset(space->OpenOatFile());
  ... ... 
  return space.release();
}

上一個函式是在指定路徑下存在image檔案直接呼叫Init函式進行初始化,而當image檔案不存在時則會先呼叫GenerateImage是建立一個image檔案,這個函式的程式碼在art/runtime/gc/space/image_space.cc中.通過構造引數,呼叫dex2oat程式並從指定的jar中獲取資訊生成image檔案,同時由boot_class_path_string路徑下的dex檔案生成對應Runtime的oat檔案:

static bool GenerateImage(const std::string& image_file_name) {
  const std::string boot_class_path_string(Runtime::Current()->GetBootClassPathString());
  std::vector<std::string> boot_class_path;
  Split(boot_class_path_string, ':', boot_class_path);
  if (boot_class_path.empty()) {
    LOG(FATAL) << "Failed to generate image because no boot class path specified";
  }

  std::vector<std::string> arg_vector;
  //構造命令列引數,選擇哪一個dex2oat執行生成image檔案
  std::string dex2oat(GetAndroidRoot());
  dex2oat += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat");
  arg_vector.push_back(dex2oat);

  //生成的image檔案[email protected]@[email protected]通過--image引數傳入
  std::string image_option_string("--image=");
  image_option_string += image_file_name;
  arg_vector.push_back(image_option_string);
  ... ...
  //需要加入到oat檔案中的dex檔案加入到引數中,dex2oat將這些dex檔案翻譯成本地指令並整合到同一個oat檔案中
  for (size_t i = 0; i < boot_class_path.size(); i++) {
    arg_vector.push_back(std::string("--dex-file=") + boot_class_path[i]);
  }

  //除了生成image檔案外,還會生成一個以oat為字尾的[email protected]@[email protected]檔案
  std::string oat_file_option_string("--oat-file=");
  oat_file_option_string += image_file_name;
  oat_file_option_string.erase(oat_file_option_string.size() - 3);
  oat_file_option_string += "oat";
  arg_vector.push_back(oat_file_option_string);

  arg_vector.push_back(StringPrintf("--base=0x%x", ART_BASE_ADDRESS));

  //生成image檔案需要預載入的類就在這裡指定
  if (kIsTargetBuild) {
    arg_vector.push_back("--image-classes-zip=/system/framework/framework.jar");
    arg_vector.push_back("--image-classes=preloaded-classes");
  } else {
    arg_vector.push_back("--host");
  }
  ... ...
  std::vector<char*> char_args;
  //vector<string>引數列表轉換為vector<char*>形式的列表
  for (std::vector<std::string>::iterator it = arg_vector.begin(); it != arg_vector.end();
      ++it) {
    char_args.push_back(const_cast<char*>(it->c_str()));
  }
  char_args.push_back(NULL);

  // fork and exec dex2oat
  pid_t pid = fork();
  if (pid == 0) {
    setpgid(0, 0);
    //執行生成image檔案和oat檔案的指令
    execv(dex2oat.c_str(), &char_args[0]);

    PLOG(FATAL) << "execv(" << dex2oat << ") failed";
    return false;
  } else {
    ... ... 
  }
  return true;
}

無論如何,最終art虛擬機器在建立過程中都會進行image檔案的對映以及開啟Runtime oat檔案。

最後再來梳理一下整個過程:
1.在啟動Zygote程序之前需要呼叫libart.so中的JNI_CreateJavaVM建立虛擬機器
2.建立虛擬機器時需要對Runtime執行時進行初始化操作
3.Runtime初始化的過程中會建立虛擬機器所需要的堆空間並使用image映象檔案堆首塊堆連續記憶體進行初始化並獲得Runtime所必須的oat檔案

下一次將會分析art虛擬機器解析oat檔案的過程和oat檔案格式。



相關推薦

學習ART生成Runtime OAT檔案

從前一篇學習筆記——ART無縫對接Dalvik虛擬機器中可以瞭解到Dalvik和ART這兩種虛擬機器機制的可執行檔案存在著區別,ART對應的是OAT檔案格式,而Dalvik則是dexy格式的檔案,而且還知道將apk檔案進行優化並轉換為指定可執行檔案的過程都是發生在應用程式安

Qt學習筆記——生成exe可執行檔案並打包生成安裝軟體

之前用MFC生成過安裝檔案,今天想嘗試採用Qt生成的exe檔案打包並生成安裝軟體。 開始我認為比較簡單,但是嘗試過程中遇到了很多問題。下面一一列出來 首先:我認為,要完成一個軟體,應儘可能的使用Release版本檔案,當然了,可以現在Debug版本下除錯通過再進行測試 打包

Python學習手冊Python異常和檔案

 在上一篇文章中,我們介紹了 Python 的函式和模組,現在我們介紹 Python 中的異常和檔案。  檢視上一篇文章請點選:https://www.cnblogs.com/dustman/p/9963920.html 異常和檔案 異常 異常也叫例外。在之前的幾篇文章中

Django學習總結中介軟體與檔案操作

中介軟體: 面向切面程式設計   底層輕量級外掛 可以介入請求與響應 ​ 咱們以前接觸的程式設計方式: python:面向物件 C語言:面向過程     ​ 喝茶:

python學習筆記讀寫excel檔案

python 處理excel資料的兩種方式: 首選pandas庫裡pandas.read_excel函式,相對比較簡單 其次使用xlrd庫,感覺沒有pandas好用 如何寫excel,後續更新 #coding=utf-8 """ Created on

Python學習筆記遍歷目錄檔案(遞迴和walk())

python中遍歷指定目錄下所有的檔案和資料夾,包含多級目錄,有兩種方法,一種是通過遞迴思想去遍歷,另一種是os模組的walk()函式 要列出目錄結構 一.遞迴方法 #coding:utf-8 import os a

博科SAN交換機學習筆記二:配置檔案備份與韌體升級 作者 LiaoJL | 轉載時請務必以超連結形式標明文章原文連結和作者資訊及本版權宣告。 原文連結:http://www.liaojl.co

配置檔案恢復 當需要備份中恢復交換機配置時,可以通過configdownload命令將博科交換機的配置從遠端伺服器恢復到交換機。博科交換機支援將舊版本的配置檔案匯入新版本韌體的交換機,例如將v6.2.0的配置檔案匯入v6.3.0韌體版本的交換機,或者將v6.4.1 配置檔案匯入 v7.0.0 版本的交換機。

Linux學習1shell中將指令碼檔案呼叫函式的輸出值輸出到檔案

一般a.sh等指令碼檔案可以很容易的將a.sh的echo等資料輸出到文字檔案,如: ./a.sh >1.txt 但是無法將指令碼檔案呼叫函式的輸出值輸出到檔案 可以使用%>: [email

caffe 學習系列生成txt 和lmdb(2)

    在上個筆記中,已經學會了如何使用Caffe利用作者給的指令碼訓練CIFAR-10資料集,得到訓練好的CNN模型。但是在上個筆記中,使用的都是作者提供好的指令碼檔案,完全就是按照教程跑了一下提供的demo。對於自己手裡的一些圖片資料集,如何轉換圖片格式、如何計算圖片資料的均值、如何編寫protot

ClickHouse學習系列三【配置檔案說明】

背景       最近花了些時間看了下ClickHouse文件,發現它在OLAP方面表現很優異,而且相對也比較輕量和簡單,所以準備入門瞭解下該資料庫系統。在介紹了安裝和使用者許可權管理之後,本文對其配置檔案做下相關的介紹說明。 說明       Cl

Golang學習標準庫io/ioutil,讀取檔案生成臨時目錄/檔案

1.讀取目錄 list, err := ioutil.ReadDir("DIR")//要讀取的目錄地址DIR,得到列表 if err != nil { fmt.Println("read dir error") return } for _,

Python學習筆記--實驗室燃燒分析儀vie資料提取---生成excel檔案

# -*- coding: utf-8 -*- import os import xlwt input("Please click enter to start !") print("Please wait ...") path=os.getcwd()+'\\' Excel

python學習-- 生成唯一ID

uuid md5 hex true encoding odin uid 方法 ret 以下以2種方法生成唯一ID def uuid_method(): """第一種方法""" import uuid return str(uuid.uuid1())

從零開始學習比特幣開發(七)-P2P網路建立流程生成地址對並連線到指定地址

本節繼續講解比特幣P2P網路建立流程,這節講解的執行緒為’ThreadOpenAddedConnections’,它的作用是生成地址對並連線到指定地址。 本文可以結合比特幣系統啟動的的第12步的講解來看,可以更加系統的瞭解比特幣系統啟動的過程。 P2P 網路的建立是在比特幣系統啟動的第

c++學習筆記檔案操作

每天進步一點點,努力奮鬥的小菜鳥。 曾經搞了好多次的C語言多檔案操作,都沒搞成功,昨天晚上終於搞成功了,雖然是簡單到爆的操作,但我還是挺高興的,哈哈哈。貼出來一方面怕自己忘,一方面若有初學者看到希望能對他們有點小小的幫助。現代數字訊號處理快要考試了,現在慌得一批,估計兩週以內會減少程式設計的時間

Linux學習筆記_shell程式設計環境變數配置檔案

shell程式設計之環境變數配置檔案 https://www.imooc.com/learn/361 簡介:本課程是《Tony老師聊shell》系列課程的第三篇,為你帶來常用的Linux環境變數配置檔案的使用。對環境變數配置檔案的功能進行了詳解, 然後又介紹了其他環境變數配置檔案,包括登

mybatis學習筆記——mybatis的XML配置檔案(全域性配置檔案

MyBatis的配置檔案包含了會深深影響MyBatis行為的設定(settings)和屬性(properties)資訊。我們詳細瞭解一下MyBatis的各種標籤的作用以及使用方法。 properties properties:配置,mybatis可以使用properties標籤來引入外部pr

mybatis學習筆記——mybatis的Mapper XML檔案中select元素

select元素: Select元素用來定義查詢操作,常用屬性如下。 id:唯一識別符號。用來引用這條語句,需要和介面的方法名一致。 parameterType:將會傳入這條語句的引數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具

mybatis學習筆記——mybatis的Mapper XML對映檔案配置資訊

sql對映檔案對應的增刪改查都有自己的標籤: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

mybatis學習筆記——mybatis的Mapper XML檔案中resultMap屬性

resultMap resultMap:自定義結果集對映規則,自定義某個JavaBean的封裝規則。 id:唯一id,方便引用。 type:自定義規則的Java類。 具體其他屬性詳細資訊和配置程式碼如下: <resultMap id="MyEmp" type="com.te