1. 程式人生 > >【MyBatis】MyBatis Tomcat JNDI原理及原始碼分析

【MyBatis】MyBatis Tomcat JNDI原理及原始碼分析

一、 Tomcat JNDI

JNDI(java nameing and drectory interface),是一組在Java應用中訪問命名和服務的API,所謂命名服務,即將物件和名稱聯絡起來,使得可以通過名稱訪問並獲取物件。

簡單原理介紹:點選訪問

tomcat已經整合該服務(內建並預設使用DBCP連線池),簡單來說就是鍵值對的mapping,而且在tomcat伺服器啟動的首頁configuration中就已經有完成的示例程式碼。要想使用tomcat的JNDI服務,只需要匯入相關的jar包,建立所需的配置檔案,採用JDK的命名服務API根據配置名稱即可獲得相應的服務。每個步驟的詳細解釋以及範例如下文所述。

1. jar包匯入

tomcat內建了DBCP並預設使用該連線池,在tomcat的lib包中已經DBCP的兩個jar包,因此不需要匯入,如果使用其他連線池技術,則需要重新拷貝連線池的jar包。拷貝資料庫驅動包到tomcat lib目錄下,完成jar包的匯入。

細節:將所需jar包直接拷貝到tomcat的lib目錄中,而不是在應用中build path匯入jar包。這是因為JNDI的原理類似於windows的登錄檔,通過配置檔案(context.xml:下節詳細介紹)在tomcat啟動的時候就告訴tomcat在其命名服務目錄下對應著配置檔案中的服務名字建立服務應用。這是tomcat對外提供的一個整體服務,而不是單獨對某一個應用提供的服務。

2. 配置檔案:comtext.xml

在META-INF目錄下建立context.xml配置檔案,在檔案中需要配置資源名字name和資源型別type,建立檔案的目的就是告訴伺服器根據服務名字建立相應的服務應用。

如下示例(DBCP),服務名稱是“jdbc/mybatis-jndi”,對應的服務型別是“javax.sql.DataSource”,即通過Tomcat提供的JNDI服務,根據name=“jdbc/mybatis-jndi”可以獲取到type=“javax.sql.DataSource”的服務,至於type中還需要配置什麼東西,則根據實際的type型別來進行配置即可。

<?xml version="1.0" encoding="UTF-8"?>
<Context> <!-- maxActive: Maximum number of database connections in pool. Make sure you configure your mysqld max_connections large enough to handle all of your db connections. Set to -1 for no limit. --> <!-- maxIdle: Maximum number of idle database connections to retain in pool. Set to -1 for no limit. See also the DBCP documentation on this and the minEvictableIdleTimeMillis configuration parameter. --> <!-- maxWait: Maximum time to wait for a database connection to become available in ms, in this example 10 seconds. An Exception is thrown if this timeout is exceeded. Set to -1 to wait indefinitely. --> <!-- username and password: MySQL username and password for database connections --> <!-- driverClassName: Class name for the old mm.mysql JDBC driver is org.gjt.mm.mysql.Driver - we recommend using Connector/J though. Class name for the official MySQL Connector/J driver is com.mysql.jdbc.Driver. --> <!-- url: The JDBC connection url for connecting to your MySQL database. --> <Resource name="jdbc/mybatis-jndi" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="密碼" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/javadb" /> </Context>

3. API程式碼示例

通過JDK提供的命名服務API,可以通過name獲取type服務,示例程式碼如下,其中tomcat將所有JNDI對應服務註冊在/com/env目錄之下(寫法固定,除非不同版本的tomcat有不同的實現),因此應用想要獲取服務,則需要先知道tomcat的jndi都提供了哪些服務,在根據某一個服務的name來獲取具體的服務,而這個name對應著上一節中context.xml中配置的name。

Context initContext = new InitialContext();
Context envContext = (Context) initContext.lookup("java:/comp/env");
DataSource ds = (DataSource) envContext.lookup("jdbc/mybatis-jndi");
Connection conn = ds.getConnection();

這裡寫圖片描述

總結   使用tomcat的JNDI服務需要以下三個步驟
1. 拷貝jar包到tomcat的lib目錄中
2. 在應用的META-INF目錄中建立context.xml配置檔案,將KV的服務註冊到tomcat的”java:/comp/env”目錄下
3. 通過JDK naming API獲取服務

二、MyBatis JNDI原始碼分析

MyBatis的dataSource型別有三種,其中JNDI的實現和tomcat JNDI一模一樣,只是MyBatis的JNDI工廠(org.apache.ibatis.datasource.jndi.JndiDataSourceFactory)已經幫我們實現了第三步”通過API獲取datasource“,但是還需要我們自己進行第一步匯入jar包和第二步context.xml的配置。

細節
1. 在進行context.xml配置的時候,其中服務名稱是可變的,需要通過配置檔案注入到MyBatis的JNDI工廠中;
2. 因為不同伺服器的JNDI目錄不一樣,因此在context.xml中配置的時候也許要注入到MyBatis的JNDI工廠中;

1. 原始碼剖析

package cn.wxy.analysis;

import java.util.Map.Entry;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
/**
 * Mybatis JNDI原始碼剖析
 * @author Administrator
 */
public class JndiDataSourceFactory implements DataSourceFactory {
    /**
     * initial_context: 伺服器的JNDI目錄,不同的伺服器該值不同,因此需要在mybatis-config的配置檔案中傳入該值
     * data_source:對應著META-INF/context.xml中註冊的服務名稱(name屬性值),即鍵值對中的鍵值
     */
    public static final String INITIAL_CONTEXT = "initial_context";
    public static final String DATA_SOURCE = "data_source";
    public static final String ENV_PREFIX = "env.";
    private DataSource dataSource;
    /**
     * 該方法在初始化Mybatis的時候被呼叫,會將mybatis-config.xml中配置的屬性注入進來
     * 主要注入的是initial_context和data_source的值
     */
    public void setProperties(Properties properties) {
        /**
         * 參照tomcat直接獲取JNDI服務
         * -------------------------
         * 第一步:Context initContext = new InitialContext();
         * 第二步:Context envContext = (Context) initContext.lookup("java:/comp/env");
         * 第三步:DataSource ds = (DataSource) envContext.lookup("jdbc/mybatis-jndi");
         */
        try {
            //第一步:宣告JAVA命名和目錄介面的上下文類
            InitialContext initCtx = null;
            // properties在SqlSessionFactoryBuilder建立SqlSessionFactory的過程中收集<dataSource>標籤下屬性建立
            // env不為null的條件是在mybatis-config.xml中配置的JNDI屬性以"env."開頭
            // 其實不必要以"env."開頭,在getEnvProperties方法中最終也會去掉"env."
            Properties env = getEnvProperties(properties);
            if (env == null) {
                // 進入到這個流程,預設使用SqlSessionFactoryBuilder流程中的properties
                initCtx = new InitialContext();
            } else {
                // 如果配置檔案中配置的JNDI屬性以"env."開頭,則進入這個步驟
                // 實際上有些冗餘,雞肋沒有必要
                initCtx = new InitialContext(env);
            }
            /**
             * mybatis-config.xml中有兩種方式可以進行JNDI資料來源的配置
             * 1. 第一種方式需要配置initial_context和data_source的值,本例中
             *      initial_context="java:/comp/env"
             *      data_source="jdbc/mybatis-jndi"
             * 2. 第二種方式只需要配置data_source的值
             *      data_source="java:/comp/env/jdbc/mybatis-jndi"
             * 
             * 結論:其實是一樣的,請結合context.xml配置檔案內容檢視此處
             */
            if (properties.containsKey(INITIAL_CONTEXT)
                    && properties.containsKey(DATA_SOURCE)) {
                //第一種方式
                Context ctx = (Context) initCtx.lookup(properties
                        .getProperty(INITIAL_CONTEXT));
                dataSource = (DataSource) ctx.lookup(properties
                        .getProperty(DATA_SOURCE));
            } else if (properties.containsKey(DATA_SOURCE)) {
                //第二種方式
                dataSource = (DataSource) initCtx.lookup(properties
                        .getProperty(DATA_SOURCE));
            }

        } catch (NamingException e) {
            throw new DataSourceException(
                    "There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
        }
    }
    /**
     * 直接返回資料來源
     *      因為資料來源交由伺服器託管,因此mybatis不需要再像pooled型別那樣自己實現連線池並通過動態代理增強java.sql.Connection
     */
    public DataSource getDataSource() {
        return dataSource;
    }

    // 如果配置檔案中配置的JNDI屬性以"env."開頭,那麼就新建一個properties
    // 最後再把"evn."去掉放入properties中
    // 現在兩個properties一模一樣了,只是構造InitalContext的時候不同
    // 這個感覺是比較雞肋的方法,也許是javax.naming中有優化吧
    private static Properties getEnvProperties(Properties allProps) {
        final String PREFIX = ENV_PREFIX;
        Properties contextProperties = null;
        for (Entry<Object, Object> entry : allProps.entrySet()) {
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if (key.startsWith(PREFIX)) {
                if (contextProperties == null) {
                    contextProperties = new Properties();
                }
                contextProperties.put(key.substring(PREFIX.length()), value);
            }
        }
        return contextProperties;
    }

}

2. 程式碼範例

MyBatis中使用tomcat的JNDI服務,操作步驟如下:
A. 匯入jar包到tomat lib目錄中,資料庫驅動包需要匯入,如果使用DBCP則無需匯入(tomcat已經集成了),但是使用其他資料來源技術則需要匯入;
B. 配置/META-INF/context.xml檔案,在tomcat中註冊JNDI服務,配置檔案內容和第一節tomcat jndi一模一樣,不需要任何變化,配置項的詳細解釋請看第一節。

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="jdbc/mybatis-jndi" auth="Container" type="javax.sql.DataSource"
        maxActive="100" maxIdle="30" maxWait="10000" username="root"
        password="51NByes!" driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/javadb" />
</Context>

C. 配置mybatis-config.xml檔案,告訴mybatis啟用JNDI型別資料來源,並將註冊服務的名稱以及對應伺服器的JNDI目錄注入mybatis JNDI工廠類中,完成註冊。

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="JNDI">
                <property name="data_source" value="jdbc/mybatis-jndi"/>
                <property name="initial_context" value="java:/comp/env"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

細節
a. 資料來源無需手動獲取,最後會被注入到SqlSessionFactory中,對使用mybatis的程式設計師來說透明,只需要以上三個步驟即可;
這裡寫圖片描述
b. 如果想手動驗證是否配置成功,除了直接使用SqlSessionFactory進行操作驗證之外,本處還提供另一種方式直接獲取資料來源,在mybatis-config.xml中兩種配置方式檢測程式碼以及檢測結果截圖如下:

第一種方式
這裡寫圖片描述
第二種方式
這裡寫圖片描述

附註:
本文如有錯漏,煩請不吝指正,謝謝!

相關推薦

MyBatisMyBatis Tomcat JNDI原理原始碼分析

一、 Tomcat JNDI JNDI(java nameing and drectory interface),是一組在Java應用中訪問命名和服務的API,所謂命名服務,即將物件和名稱聯絡起來,使得可以通過名稱訪問並獲取物件。 簡單

java基礎ConcurrentHashMap實現原理原始碼分析

  ConcurrentHashMap是Java併發包中提供的一個執行緒安全且高效的HashMap實現(若對HashMap的實現原理還不甚瞭解,可參考我的另一篇文章),ConcurrentHashMap在併發程式設計的場景中使用頻率非常之高,本文就來分析下Concurre

JavaJava8 HashMap工作原理實現

1 、概述 從本文你可以學到 什麼時候會使用HashMap?他有什麼特點? 你知道HashMap的工作原理嗎? 你知道get和put的原理嗎?equals()和hashCode()的都有什麼作用? 你知道hash的實現嗎?

NLPCNN文字分類原理python程式碼實現

CNN分類模型架構   python程式碼實現: #!/usr/bin/python # -*- coding: utf-8 -*- import tensorflow as tf class TCNNConfig(object): #class TCNNConfig(

高清顯示屏原理設計方案

接近年底了,又到產品們趕KPI的時間,開發也跟著辛苦,於是連續加班了4個星期,專案總算有點起色,也終於擠出點時間,寫篇文章,just for fun ~ 高清顯示屏原理 ,之前在團隊內做過的一個類似的分享,因為上次有園友問了我 手機端css sprite 的設計原理,不知道手機端的圖片為什麼是用2倍大,背景

HashMap實現原理原始碼分析(轉載)

作者: dreamcatcher-cx 出處: <http://www.cnblogs.com/chengxiao/>        雜湊表(hash table)也叫散列表,是一種非常重要的資料結構,應用場景及其豐富,

併發程式設計(三)—— ReentrantLock實現原理原始碼分析

  ReentrantLock是Java併發包中提供的一個可重入的互斥鎖。ReentrantLock和synchronized在基本用法,行為語義上都是類似的,同樣都具有可重入性。只不過相比原生的Synchronized,ReentrantLock增加了一些高階的擴充套件功能,比如它可以實現公平鎖,同時也可以

HashMap、ConcurrentHashMap實現原理原始碼分析

HashMap:https://www.cnblogs.com/chengxiao/p/6059914.html ConcurrentHashMap:https://blog.csdn.net/dingjianmin/article/details/79776646   遺留問

ConcurrentHashMap JDK1.8中結構原理原始碼分析

注:本文根據網路和部分書籍整理基於JDK1.7書寫,如有雷同敬請諒解  歡迎指正文中的錯誤之處。 資料結構       ConcurrentHashMap 1.8 拋棄了Segment分段鎖機制,採用Node + CAS + Synchronized來保證併發安全進行實現

HashMap實現原理原始碼分析

雜湊表(hash table)也叫散列表,是一種非常重要的資料結構,應用場景及其豐富,許多快取技術(比如memcached)的核心其實就是在記憶體中維護一張大的雜湊表,而HashMap的實現原理也常常出現在各類的面試題中,重要性可見一斑。本文會對java集合框架中的對

ConcurrentHashMap實現原理原始碼分析

ConcurrentHashMap是Java併發包中提供的一個執行緒安全且高效的HashMap實現(若對HashMap的實現原理還不甚瞭解,可參考我的另一篇文章),ConcurrentHashMap在併發程式設計的場景中使用頻率非常之高,本文就來分析下Concurrent

[轉]HashMap實現原理原始碼分析

目錄: 一、什麼是雜湊表 二、HashMap實現原理 三、為何HashMap的陣列長度一定是2的次冪? 四、為什麼重寫equals方法需同時重寫hashCode方法 一、什麼是雜湊表 雜湊表(hash table)也叫散列表,是一

(轉)HashMap實現原理原始碼分析

雜湊表(hash table)也叫散列表,是一種非常重要的資料結構,應用場景及其豐富,許多快取技術(比如memcached)的核心其實就是在記憶體中維護一張大的雜湊表,而HashMap的實現原理也常常出現在各類的面試題中,重要性可見一斑。本文會對java集合框架中的對應實

HashMap, ConcurrentHashMap 最詳細的原理原始碼分析

網上關於 HashMap 和 ConcurrentHashMap 的文章確實不少,不過缺斤少兩的文章比較多,所以才想自己也寫一篇,把細節說清楚說透,尤其像 Java8 中的 ConcurrentHashMap,大部分文章都說不清楚。 終歸是希望能降低大家學習的成本,不希望大家到處找各種不是很

JDK8中的HashMap實現原理原始碼分析

本篇所述原始碼基於JDK1.8.0_121 在寫上一篇線性表的文章的時候,筆者看的是Android原始碼中support24中的Java程式碼,當時發現這個ArrayList和LinkedList的原始碼和Java官方的沒有什麼區別,然而在閱讀HashMap原

springmvc工作原理原始碼分析

一、JavaEE體系結構 二、mvc 設計模式|思想 Model 模型層 (javaBean元件 = 領域模型(javaBean) + 業務層 + 持久層) View 檢視層( html、jsp…) Controller 控制層(委託模型層進行資料處理) 複製程式

技術分享:springmvc工作原理原始碼分析

一、JavaEE體系結構       二、mvc 設計模式|思想       Model 模型層 (javaBean元件 = 領域模型(javaBean) + 業務層 + 持久層) View

ReentrantLock實現原理原始碼分析

       ReentrantLock實現原理及原始碼分析        ReentrantLock是Java併發包中提供的一個可重入的互斥鎖。ReentrantLock和synchronized在基本用法,行為語

Python實現網站備份檔案掃描+原始碼分析

一開始我用的requests庫的get方法 但是這種方法會自動下載檔案,所以不可取 後來發現urllib2的庫相對來說不錯 原始碼如下# coding = utf-8 import urllib2 i

6以太坊Miner模組原始碼分析

Miner模組: 類結構如下: 主要結構體 Miner、Worker、Work、CpuAgent、Result、unconfiredBlocks Miner為挖礦的核心物件(礦工),核心成員包括註冊處理事件(mux)、工人(worker)、coinbase(預設賬戶地址hash