数据源(Data Source)及JNDI
数据源是在JDBC 2.0中引入的一个概念。在JDBC 2.0扩展包中定义了javax.sql.DataSource接口来描述这个概念。如果用户希望建立一个数据库连接,通过查询在JNDI服务中的数据源,可以从数据源中获取相应的数据库连接。这样用户就只需要提供一个逻辑名称(Logic Name),而不是数据库登录的具体细节。在这里有必要简单介绍一下JNDI。JNDI的全称是Java Naming and Directory Interface, 可以理解为Java名称和目录服务接口。JNDI向应用程序提供了一个查询和使用远程服务的机制。这些服务可以是任何企业服务。对于JDBC应用程序来说,JNDI提供的是数据库连接服务。当然JNDI也可以向数据库提供其他服务,但是这超出了本文范围,在此不做论述。其实JNDI并不难理解。简单来说,名称服务提供了一个把文件,打印机,服务器等实体映射到一个逻辑名称的机制。例如在操作系统中的名称服务就把打印机映射到一个I/O端口。而目录服务可以理解为名称服务的一个扩展,它允许在服务中的各项拥有自己的属性。又以打印机为例,打印机可以是彩色打印机,支持双面打印,支持网络打印,支持高速打印等。所有这些打印机的属性都可以储存在目录服务中,和相应的打印机联系起来。一些常见的目录服务有NIS,NIS+,LDAP和Novell的NDS等。JNDI使应用程序通过使用逻辑名称获取对象和对象提供的服务,从而使程序员可以避免使用与提供对象的机构有关联的代码。例如在下面的例子中使用了在JNDI中的数据源,程序员就不需要提供 下面详细介绍一下数据源和javax.sql.DataSource接口。在数据源中存储了所有建立数据库连接的信息。就象通过指定文件名你可以在文件系统中找到文件一样,通过提供正确的数据源名称,你可以找到相应的数据库连接。javax.sql.DataSource接口定义了如何实现数据源。在该接口中定义了九个属性。表一列出了对这些属性的描述。由于本文是以8i为例,在8i中没有实现roleName属性,所以在表中没有对此属性做描述。表一:数据源标准属性属性名称
 属性数据类型 描述 
databaseName String 数据库名称,即数据库的SID。 
dataSourceName String 数据源接口实现类的名称。 
description String 对数据源的描述。 
networkProtocol String 和服务器通讯使用的网络协议名。在8i中,该属性只在使用OCI驱动程序时有效,缺省协议是TCP协议。 
password String 用户登录密码。 
portNumber Int 数据库服务器使用的端口,缺省值为1521。 
serverName String 数据库服务器名称。 
user String 用户登录名。 在javax.sql.DataSource接口中定义了以下方法:* public synchronized void setDatabaseName(String dbname)
* public synchronized String getDatabaseName()
* public synchronized void setDataSourceName(String dsname)
* public synchronized String getDataSourceName()
* public synchronized void setDescription(String desc)
* public synchronized String getDescription()
* public synchronized void setNetworkProtocol(String np)
* public synchronized String getNetworkProtocol()
* public synchronized void setPassword(String pwd)
* public synchronized void setPortNumber(int pn)
* public synchronized int getPortNumber()
* public synchronized void setServerName(String sn)
* public synchronized String getServerName()
* public synchronized void setUser(String user)
* public synchronized String getUser()  通过这些方法,程序员可以获得建立连接需要的所有信息。需要注意的是,程序员不可以获取登陆密码,这就在一定程度上保证了安全性。需要注意的另一点是所有的方法都是synchronized方法,这是为了保证应用程序的线程安全(Thread-safe)。如果在调用该方法时,即使数据源实例发生变化不会影响程序的正确运行。除了实现由SUN定义的属性和方法外,8i还提供了自己的数据源属性和方法。这些方法和属性是在oracle.jdbc.pool.DataSource中实现的。 8i扩展数据源属性如表二所示:表二:属性名称 属性数据类型 描述 
driverType String 使用的 JDBC驱动程序的类型,包括oci8, thin和kprb 
url String 数据库连接的URL。 
tnsEntry String TNS条目名称 在oracle.jdbc.pool.DataSource中除了实现javax.sql.DataSource接口中定义的方法外,还实现了以下方法:* public synchronized void setDriverType(String dt)
* public synchronized String getDriverType()
* public synchronized void setURL(String url)
* public synchronized String getURL()
* public synchronized void setTNSEntryName(String tns)
* public synchronized String getTNSEntryName() 同时,DataSource还实现了java.io.Serializable和javax.naming.Referenceable接口。独立使用数据源
实际应用中,你可以把DataSource注册到JNDI,也可以单独使用。下面先给出一个单独使用DataSource的例子:// 初始化数据源实例
DataSource ods = new DataSource();ods.setDriverType("thin");
ods.setServerName("Chicago");
ods.setNetworkProtocol("tcp");
ods.setDatabaseName("chidb");
ods.setPortNumber(1521);
ods.setUser("guest");
ods.setPassword("guest");// 从数据源中获取数据库连接
Connection conn = ods.getConnection();
// 通过数据库连接进行数据操作
   ……………… 使用DataSource时有几点需要注意:如果使用的时服务器端内部驱动程序(server-side internal driver),driverType属性会被设置为kprb,其它所有属性失效。 
如果使用Thin或OCI驱动程序: 
URL中可以包括用户登录名和用户登录密码。例如:
jdbc:oracle:thin:guest/guest@Chicago:1521:chidb; 
如果设定了url属性,tnsEntry, driverType, portNumber, networkProtocol, serverName,和databaseName属性将失效。 
在没有设定url属性的情况下,如果设定了tnsEntry属性,portNumber, networkProtocol, serverName,和databaseName属性将失效。 
如果使用OCI驱动程序,并且networkProtocol属性被设定为ipc,除user和password外的所有其他属性将失效。
通过JNDI使用数据源
在本节首先给出了一个实际程序,然后通过程序来讲解如何通过JNDI查询数据源。import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.DataSource;
import javax.naming.*;
import javax.naming.spi.*;
import java.util.Hashtable;public class DataSourceJNDI
{
public static void main (String args [])
throws SQLException
{
// 初始化名称服务环境
Context ctx = null;
try
{
Hashtable env = new Hashtable (5);
env.put (Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
ctx = new InitialContext(env);
}
catch (NamingException ne)
{
ne.printStackTrace();
}
bind(ctx, "jdbc/chidb");
lookup(ctx, "jdbc/chidb");}static void bind (Context ctx, String ln)
throws NamingException, SQLException
{
// 创建一个DataSource实例
DataSource ods = new DataSource();ods.setDriverType("thin");
ods.setServerName("Chicago");
ods.setNetworkProtocol("tcp");
ods.setDatabaseName("chidb");
ods.setPortNumber(1521);
ods.setUser("guest");
ods.setPassword("guest");
// 把DataSource实例注册到JNDI中
System.out.println ("Doing a bind with the logical name : " + ln);
ctx.bind (ln,ods);
System.out.println ("Successfully bound");
}static void lookup (Context ctx, String ln)
throws NamingException, SQLException
{
// 从JNDI中查询DataSource实例
System.out.println ("Doing a lookup with the logical name : " + ln);
DataSource ods = (DataSource) ctx.lookup (ln);
System.out.println ("Successful lookup");// 从查询到的DataSource实例中获取数据库连接
  Connection conn = ods.getConnection();
  // 进行数据库操作
getUserName(conn);
// 关闭连接
conn.close();
conn = null;
}static void getUserName(Connection conn)
throws SQLException
{
// 生成一个Statement实例
Statement stmt = conn.createStatement ();// 从addressbook表中选中姓名列
ResultSet rset = stmt.executeQuery ("select NAME from addressbook");// 列出addressbook表所有人的姓名
while (rset.next ())
System.out.println ("Name is " + rset.getString (1));// 关闭RseultSet实例
rset.close();
rset = null;// 关闭Statement实例
stmt.close();
stmt = null;
}
} 程序首先生成了一个Context实例。javax.naming.Context接口定义了名称服务环境(Naming Context)及该环境支持的操作。名称服务环境实际上是由名称和对象间的相互映射组成。程序中初始化名称服务环境的环境工厂(Context Factory)是com.sun.jndi.fscontext.RefFSContextFactory(该类在fscontext.jar中可以找到,由于fscontext.jar中包含的不是标准的API,用户需要从www.javasoft.com中的JNDI专区下载一个名为fscontext1_2beta3.zip的压缩文件,在该文件中可以找到fscontext.jar)。环境工厂的作用是生成名称服务环境的实例,javax.naming.spi.InitialContextFactory接口定义了环境工厂应该如何初始化名称服务环境。在初始化名称服务环境时还需要定义环境的URL。程序中使用的是"file:JNDI",也就是把环境保存在本地硬盘的JNDI目录下。

解决方案 »

  1.   

    如下是在其他的论坛给的回复:每一个数据库(数据源)对应一个数据连接池。 
    --------------- 
    你说的那种情况,只能分别建立不同的数据连接起(Connection Pool),但是这样就涉及到,每增加一个客户,你就必须重新配置部署文件,建立新的数据源,更改程序。 
    --------------- 
    而且,这样不是个好方法(因为后台的配置不要经常更改) 如果你是同一个数据库,各个公司对应不同的数据表,到是还好说。 
    (既然是web server是tomcat,可见系统不是太大,所以更没有必要太麻烦了)  
      

  2.   

    使用TOMCAT不是因为系统小,是因为是免费的,因为我们要将该软件做成产品,这样打包到光盘里就只能用免费的. 
    我们的数据库一共有100多个表和100多个存储策略,所以不是一个很简单的系统 
    我是这样想的:我是不是可以每添加一个公司就复制一下那些类文件包,放到不同的目录下,然后修改每个的数据库连接池的配置文件?但是TOMCAT里的WEB.XML是不是只能放在那个固定的目录下? 
      

  3.   

    楼上那篇文章可以在清华大学水木清华bbs的java版面找到
    hoho
    我也收藏了
      

  4.   

    据我所知只能一个DataSource对应一个数据库。但如果你的程序逻辑部分是一样的,那么加数据库应该不用改源代码,只需要制定数据库与客户的对应方法就可以了,这与你的程序怎么与客户交互有关。大概是由程序确定客户,然后打开客户对应的DataSource,然后做处理。