• 【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="51NByes!" driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/javadb" />
    </Context>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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();
    • 1
    • 2
    • 3
    • 4

    这里写图片描述

    总结   使用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;
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    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>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    细节: 
    a. 数据源无需手动获取,最后会被注入到SqlSessionFactory中,对使用mybatis的程序员来说透明,只需要以上三个步骤即可; 
    这里写图片描述 
    b. 如果想手动验证是否配置成功,除了直接使用SqlSessionFactory进行操作验证之外,本处还提供另一种方式直接获取数据源,在mybatis-config.xml中两种配置方式检测代码以及检测结果截图如下:

    第一种方式 
    这里写图片描述 
    第二种方式 
    这里写图片描述

  • 相关阅读:
    查询SystemFeature的方法
    【HTML5游戏开发小技巧】RPG情景对话中,令文本逐字输出
    BFS寻路的AS3实现
    超级坑人的Couchbase数据库问题!!!
    java--函数练习
    CentOS 6.2 二进制安装apache2.4.3出现configure: error: APR-util not found. Please read the documentation的解决方
    2017第27周六努力与积累
    2017第27周五
    丢掉生活中的90%,你会收获更多
    《时间简史》笔记摘录
  • 原文地址:https://www.cnblogs.com/likeju/p/4711838.html
Copyright © 2020-2023  润新知