mybatis源码学习之org.apache.ibatis.datasource.jndi包

本文介绍了MyBatis中org.apache.ibatis.datasource.jndi包的JndiDataSourceFactory类及其在Spring中的使用步骤。内容涵盖如何在Tomcat的server.xml或context.xml中配置数据源,Spring的XML和Java配置方式,以及测试获取数据源的方法。同时提供了context.xml和mybatis-config.xml的配置示例。文章旨在帮助读者理解MyBatis JNDI数据源的配置和使用。

文章内容和参考博文来源:

1 mybatis源码jar包–package org.apache.ibatis.datasource.jndi;
2 http://blog.csdn.net/reliveit/article/details/45136297
3 http://www.cnblogs.com/V1haoge/p/6675700.html
4 Spring 实战(第四版)第十章 通过spring和jdbc征服数据库
5 http://kingxss.iteye.com/blog/1481872

前言:

jndi方式配置数据源的方式还是很少见的,因为需要在项目之外的容器或服务上修改相应的配置文件,后期可能不便于部署项目,但是这个问题也有对应的解决办法,为了提高项目的可移植性,可以讲服务器上修改的配置文件内容放入到工程的META-INF目录的context.xml中(这个文件需要自行建立).总归而言,jndi方式配置数据源方式还是针对特定的需求来做的,比如说服务器性能很好.

在本文只是简单的介绍org.apache.ibatis.datasource.jndi包下JndiDataSourceFactory 工厂类的实现过程,以及具体运用和使用过程,至于涉及的一些底层原理以及一些常见模式,在本文先不做讨论,后续会针对org.apache.ibatis.datasource整个包做一些简要的分析.package org.apache.ibatis.datasource.jndi包下的源码本身不难,但是个人感觉对于一些知识点,提前了解一下,会很快就能了解org.apache.ibatis.datasource.jndi包下JndiDataSourceFactory类的实现过程.当前介绍的mybatis版本为:mybatis-3-mybatis-3.4.4.

前置知识点:

(1)jndi的概念: http://baike.baidu.com/item/JNDI?fr=aladdin

(2)Properties类和InitialContext类和Context类或接口中的方法功能以及大概实现过程.

(3)最好能找一下mybatis的Jndi+Tomcat配置数据源的大概过程,去和mybatis的pooled池型数据源以及unpooled非池型数据源的使用过程对比以下.

1、源码结构图:

这里写图片描述

2、mybatis的jndi形式的数据源使用步骤

在介绍具体源码的细节之前,先来说一下mybatis的jndi形式的数据源的使用步骤和一些细节问题,这样如果只是想了解如何使用mybatis的jndi方式去配置数据源,就不用继续看下面的源码分析了.在spring中使用jndi方式配置数据源大概分为下面几个步骤:

第一步:添加数据源信息到tomcat的conf目录下的server.xml或context.xml文件中,两者选择一个.

第二步:在Spring的配置文件,如applicationContext.xml配置文件中,引入jndi配置方式.

第三步:测试从spring中拿到datasource.

第一步:添加数据源信息到tomcat的conf目录下的server.xml或context.xml文件中,两者选择一个.

首先说明一下使用情形,是在spring框架的去使用JNDI数据源.因为spring项目经常部署在Java EE应用服务器中,如WebSphere,JBoss或甚至像Tomcat这样的Web容器中.这些服务器允许你配置通过JNDI获取数据源.这种配置的好处在于数据源完全可以在应用程序之外进行管理,这样应用程序只需在访问数据库的时候查找数据源就可以了.另外,在应用服务器中管理的数据源通常以池的方式组织,从而具备更好的性能,并且还支持系统管理员对其进行热切换.
第一步分为三种方式实现:

第一种方式:

在tomcat中的server.xml添加的代码如下形式:
<Context>
    <Resource name="hello" auth="Container" type="javax.sql.DataSource"
        maxActive="100" maxIdle="30" maxWait="10000" 
        username="postgres"
        password="123456" 
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://127.0.0.1:5432/center_db" />
  </Context>

第二种方式:

在context.xml添加的代码如下形式:
<Resource name="hello" auth="Container" type="javax.sql.DataSource"
        maxActive="100" maxIdle="30" maxWait="10000" 
        username="postgres"
        password="123456" 
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://127.0.0.1:5432/center_db" />  

第三种方式:

在META-INF目录下新建的context.xml中(这个文件需要自行建立)
<?xml version="1.0" encoding="UTF-8"?>  
<Context>  
      <Resource name="hello"    
        auth="Container"    
        type="javax.sql.DataSource"     
        driverClass="org.postgresql.Driver"      
        jdbcUrl="jdbc:postgresql://127.0.0.1:5432/center_db"   
        user="postgres"    
        password="123456"    
        maxPoolSize="10"    
        minPoolSize="2"    
        acquireIncrement="2" />  
</Context>  

第二步:在Spring的配置文件,如applicationContext.xml配置文件中,引入jndi配置方式.

第二步分为下面几种方式实现:

第一种方式:

利用spring,我们可以使用spring bean那样配置jndi中数据源的引用并将其装配到需要的类中.位于jee命名空间下的

<jee:jndi-lookup>

元素可以用于检索JNDI中的任何对象(包括数据源),并将其作为spring中的bean.例如,如果应用程序的数据源配置在jndi中,我们可以使用

<jee:jndi-lookup>

元素将其装配到spring中,如下所示:

<jee:jndi-lookup id="dataSource" jndi-name="hello" resource-ref="true" />

其中jndi-name属性用于指定JNDI中资源的名称.如果只设置了jndi-name属性,那么就会根据指定的名称去查找数据源.但是,如果应用程序运行在java应用服务器中,你需要将resource-ref属性设置为true,这样给定的jndi-name将会自动添加”java:comp/env”前缀.

第二种方式:

当然,spring配置除了xml方式配置以外,还有另外一种java配置方式,如果想使用java方式配置的话,那我们可以借助JndiObjectFactoryBean从JNDI中查找DataSource:

@Bean
public JndiObjectFactoryBean dataSource(){
    JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
    jndiObjectFB.setJndiName("hello");
    jndiObjectFB.setResourceRef(true);
    jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
    return jndiObjectFB;
}

第三种方式:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">  
        <property name="jndiName">  
            <value>java:comp/env/hello</value>  
        </property>  
    </bean>  

第三步:测试从spring中拿到datasource.

利用springmvc在controller控制器的方法体中拿datasource,并获得connection,观察有jndi方式得到的数据源的信息,(下面这个测试代码来自于本人博客中另外一个ssm项目搭建的demo的代码,地址:http://blog.csdn.net/u013412772/article/details/73252921).
下面测试类中,主要看loginMainJSP()方法,具体想了解jsp界面和service以及dao编写,可以参照(http://blog.csdn.net/u013412772/article/details/73252921)

//测试代码如下:

package com.cn.controller;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.validation.Valid;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jndi.JndiTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.cn.config.validator.UserValidator;
import com.cn.entity.User;
import com.cn.service.IUserService;

@Controller
@RequestMapping(value="/user")
public class UserController {

    private Logger logger = Logger.getLogger(UserController.class);

    @Autowired
    private IUserService userServiceImpl;

    /**
     * 进入登录界面
     * @return
     */
    @RequestMapping(value = "/getLoginJSP", method = RequestMethod.GET)
    public String getLoginJSP(Model model){
        User user = new User();
        model.addAttribute("user", user);
        return "login";
    }

    /**
     * 检查用户登录信息
     * @param user
     * @param errors
     * @param model
     * @return
     * @throws NamingException 
     * @throws SQLException 
     */
    @RequestMapping(value = "/loginMainJSP", method = RequestMethod.POST)
    public String loginMainJSP(@Valid User user, BindingResult result, Model model) throws NamingException, SQLException{
        logger.info("get datasource in UserController --> loginMainJSP");
        InitialContext initCtx = new InitialContext();
        DataSource datasource=(DataSource)initCtx.lookup("java:comp/env/hello");
        logger.info("jndi方式获取得到的数据源为: " +datasource);
        Connection connection = datasource.getConnection();
        logger.info("connection信息为: " + connection);

        if(result.hasErrors()){
            return "login";
        }
        boolean flag = userServiceImpl.checkUserLoginInformation(user);
        if(flag){
            model.addAttribute("user", user);
            return "main";
        }else{
            model.addAttribute("errorMessage", "登录名或者密码或者颜值出错");
            return "login";
        }
    }
}

测试结果:观察图片的最下面部分,可以看出,拿到datasource和connection了.
这里写图片描述

3、源码学习

下面的代码来自上述包中,下面的代码理解起来不难,最主要的是,怎么这段代码的用途或者使用方式是很有必要了解一下的.

/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.datasource.jndi;

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;

/**
 * @author Clinton Begin
 */
public class JndiDataSourceFactory implements DataSourceFactory {

  //服务器的JNDI目录,不同的服务器该值不同,因此需要在mybatis-config的配置文件中传入该值
  //下面会有mybatis-config.xml配置文件与之对应
  public static final String INITIAL_CONTEXT = "initial_context";

  //data_source:对应着META-INF/context.xml中注册的服务名称(name属性值),即键值对中的键值
  //下面会有context.xml文件与之对应
  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的值
   */
  @Override
  public void setProperties(Properties properties) {
    try {
      // 第一步:InitialContext此类是执行命名操作的初始上下文,即声明JAVA命名和目录接口的上下文类
      InitialContext initCtx;

      // properties在SqlSessionFactoryBuilder创建SqlSessionFactory的过程中收集mybatis-config.xml配置文件中的<dataSource>标签下属性建立
      // env不为null的条件是在mybatis-config.xml中配置的JNDI属性以"env."开头
      Properties env = getEnvProperties(properties);// 获取配置文件中key以 env. 开头的配置信息
      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
   */
  @Override
  public DataSource getDataSource() {
    return dataSource;
  }

  /**
   * 函数功能描述:获取配置文件当中以 env. 开头的配置信息
   * @param allProps
   * @return
   */
  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();
      //首先判断每次循环得到的key是够以 env. 为开头,如果是,则将此信息放在contextProperties中.
      if (key.startsWith(PREFIX)) {
        if (contextProperties == null) {
          contextProperties = new Properties();
        }
        contextProperties.put(key.substring(PREFIX.length()), value);
      }
    }
    return contextProperties;
  }

}

下面给出一些在META-INF文件夹中配置context.xml的样例以及没有将mybatis-config.xml的配置文件整合到spring配置文件之前的样例.下面这种方式,在真正的项目中,很少采用这种方式,因为都会采用spring+mybatis整合之后的配置文件即spring-mybatis.xml这一个配置文件.即如果采用mybatis的jndi形式的数据源使用步骤方式去配置的话,下面的配置文件就用不到了.也就不用看了

context.xml配置文件样例

<?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="hello" auth="Container" type="javax.sql.DataSource"
        maxActive="100" maxIdle="30" maxWait="10000" 
        username="postgres"
        password="123456" 
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://127.0.0.1:5432/center_db" />
</Context>

mybatis-config.xml配置文件样例(在实际项目中,一般适合上述context.xml是配套的)

<?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="hello"/>
                <property name="initial_context" value="java:/comp/env"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

4、知识点补充

(1):Properties类是继承自java.util.Hashtable类的,所以Properties类的变量可以直接调用HashTable的entrySet和put以及其他的继承方法,去遍历其中的内容.

(2):Connection与DataSource的关系,Connection(连接)是包含在DataSource(数据源)之内的,我们可以通过DataSource来得到其中的Connection,反之不可能,所以我们需要先创建DataSource实例,以此来获取Connection数据库连接,所有DataSource是基础,Connection是目的。

(3):JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。JNDI可访问的现有的目录及服务有: DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。具体介绍可以参照(http://baike.baidu.com/item/JNDI?fr=aladdin)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值