Monthly Archives: 十二月 2008

Java数据库使用方法(以及对象池的使用)

1.连接方式:
众所周知,目前利用JDBC连接数据库的方式有4种:①. JDBC-ODBC桥;②. 部分本地API和部分Java驱动程序;③. JDBC网络纯Java驱动程序;④. 本地协议纯Java驱动程序。如下面所示:
① Java Apps <-> JDBC API <-> JDBC-ODBC Bridge <-> ODBC API <-> ODBC Level <-> Database
这种方案效率比较低,但是在只有ODBC驱动的情况下,只能使用这个方法。例如Microsoft Access。
sun.jdbc.odbc.JdbcOdbcDriver实现了JDBC-ODBC桥。
② Java Apps <-> JDBC API <-> JDBC Driver <-> Local Driver from Manufacturer <-> Database
③ Java Apps <-> JDBC API <-> JDBC Driver <-> Application Server <-> Local Driver <-> Database
Bea的WebLogic和IBM的Websphere使用了这种方法。
④ Java Apps <-> JDBC API <-> JDBC Driver <-> Database
这是所有里面效率最高的,各个大的数据库厂商都为其提供了驱动。我们现在也主要使用这种方式来连接数据库。

2.连接数据库的基本方式:
访问数据库时,有统一的步骤:

Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); 
Connection conn = DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433;databasename=pubs","user","password"); 
Statement stmt = conn.createStatement(); 
ResultSet rs = stmt.executeQuery("select * from jobs"); 

下面我们来详细了解一下:

java.sql.Driver接口是所有驱动程序需要实现的接口。这个接口是数据库厂商使用的。

下面是主要数据库的JDBC驱动的类名:

com.microsoft.jdbc.sqlserver.SQLServerDriver 
oracle.jdbc.driver.OracleDriver 
com.mysql.jdbc.Driver 

接口种有一个Connection connect(String url,Properties info) throws SQLException 方法,来建立到数据库的连接。 在程序中,我们不需要直接访问这些类,而是由驱动程序管理器来调用这些驱动。我们通过JDBC驱动管理器注册每个驱动程序,使用驱动管理器类的方法来建立数据库的连接。

加载JDBC驱动是调用Class类的静态方法forName(),传递要加载的JDBC驱动的类名,运行时,类加载器从CLASSPATH环境变量中定位和加载JDBC驱动类。

我们使用DriverManager的public static void registerDriver(Driver driver) throws SQLException 来注册驱动程序类的实例。

但是一般在实现了Driver接口的驱动程序类里面都包含了静态代码块,在这个静态代码块中,会注册驱动程序自身的一个实例。

我们使用DriverManager的getConnection方法来建立到数据库的连接。

 
public static Connection getConnection(String url) throws SQLException 
public static Connection getConnection(String url,String user,String password) throws SQLException 
public static Connection getConnection(String url,Properties info) throws SQLException 

Properties使用键值对来指定连接参数,通常需要指定user和password属性。

JDBC url的语法如下:

jdbc:subprotocal:subname

例如SQL Server 2000的:

jdbc:microsoft:sqlserver://localhost:1433;databasename=pubs

Oracle:

jdbc:oracle:thin:@localhost:1521:ORCL

MySQL:

jdbc:mysql://localhost:3306/databasename

另外也可以通过JDBC-ODBC桥的方式访问数据库,这时候驱动类型是:

sun.jdbc.odbc.JdbcOdbcDriver

JDBC url是:jdbc:odbc:datasource_name

3.数据库连接池:

在JDBC中,可使用DataSource接口,它由驱动程序供应商来实现。可以通过JNDI服务查询来获得DataSource对象,例如:

 
javax.naming.Context ctx = new javax.naming.InitialContext(); 
javax.sql.DataSource ds = (javax.sql.DataSource)ctx.lookup("java:comp/env/jdbc/bookstore"); 
java.sql.Connection conn = ds.getConnection(); 

其中

javax.naming.Context表示一个命名上下文的接口,在这个接口中,定义了将对象和名字绑定,以及通过名字查询对象的的方法。查询一个命名的对象,使通过调用Context接口的lookup方法。

javax.naming.InitialContext是Context接口的实现。

javax.sql.DataSource接口有三种类型的实现:

① 基本的实现-产生一个标准的连接对象。

② 连接池实现 - 产生一个自动参与到连接池中的连接对象。

③ 分布式事务实现-产生一个用于分布事务的连接对象,这种连接对象几乎总是参与到连接池中。

3.1 连接池简介:

这个其实是很浅显的一个道理,将一群对象放在一个Pool中,需要的时候拿出来,不需要了放回去,这样能显著减少建立对象和销毁对象的时间。特别是数据库中,建立连接是很费时间的,而且能够同时建立的连接数也是有限的,所以采用连接池技术能极大的提高性能。

Pool技术在Apache Commons的Pool项目中。这个是需要用的一个项目。

3.2 Apache Commons DBCP

提供了一种数据库连接池的实现,这个实现用在了Tomcat中。

简单的说,DBCP提供了一个连接池,具体它的运作机制,我还正在研究中,现在仅仅是能够使用其进行基本的工作。

它有两种得到连接Connection的方式,一种是通过实现的各种的DataSource,另一种是通过DriverManager。而得到DataSource的方式也有两种,一种是通过JNDI,另一种是直接建立。

①那么我们就先来看看通过DriverManager得到Connection的方式:

 
GenericObjectPool connectionPool = new GenericObjectPool(null); 
ConnectionFactory connectionFactory = new DriverManagerConnectionFactory("jdbc:some:connect:string", "username", "password"); 
PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,connectionPool,null,null,false,true); 
PoolingDriver driver = new PoolingDriver(); 
driver.registerPool("example",connectionPool); 
Connection conn = DriverManager.getConnection("jdbc:apache:commons:dbcp:example"); 

其中PoolableConnectionFactory()的构造方法使用的是PoolableConnectionFactory(ConnectionFactory connFactory, ObjectPool pool, KeyedObjectPoolFactory stmtPoolFactory, String validationQuery, boolean defaultReadOnly, boolean defaultAutoCommit)

其实就是有一个通用对象池,然后有一个基于DriverManager的产生连接的Factory,还有一个产生PoolableConnection的Factory,产生PoolableConnection的Factory使用前两者做参数,从DriverManagerConnectionFactory处获得连接,然后产生PoolableConnection,放入通用对象池中。然后通过一个Pooling的Driver,这个Driver覆盖了本身的Driver,例如mysql的Driver,DriverManager最后实际上是从这个PoolingDriver这里得到连接的。

那么接下来是通过使用DataSource得到连接的方式,只要得到DataSource,那么就可以使用其getConnection()方法得到连接了。

②下面是一种得到PoolingDataSource的方法:

 
GenericObjectPool connectionPool = new GenericObjectPool(null); 
ConnectionFactory connectionFactory = new DriverManagerConnectionFactory("jdbc:some:connect:string", "username", "password"); 
PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,connectionPool,null,null,false,true); 
PoolingDataSource dataSource = new PoolingDataSource(connectionPool); 

注意,使用①和②的方法的时候,要先注册驱动类,可以简单的采用Class.forName()方法。

下面则是得到BasicDataSource的方法:

 
BasicDataSource ds = new BasicDataSource(); 
ds.setDriverClassName("org.apache.commons.dbcp.TesterDriver"); 
ds.setUrl("jdbc:apache:commons:testdriver"); 
ds.setMaxActive(getMaxActive()); 
ds.setMaxWait(getMaxWait()); 
ds.setDefaultAutoCommit(true); 
ds.setDefaultReadOnly(false); 
ds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); 
ds.setDefaultCatalog("test catalog"); 
ds.setUsername("username"); 
ds.setPassword("password"); 
ds.setValidationQuery("SELECT DUMMY FROM DUAL"); 

下面则是得到BasicDataSource的另外一种方法

 
Properties properties = new Properties(); 
properties.setProperty("driverClassName", "org.apache.commons.dbcp.TesterDriver"); 
properties.setProperty("url", "jdbc:apache:commons:testdriver"); 
properties.setProperty("maxActive", "10"); 
properties.setProperty("maxWait", "500"); 
properties.setProperty("defaultAutoCommit", "true"); 
properties.setProperty("defaultReadOnly", "false"); 
properties.setProperty("defaultTransactionIsolation", "READ_COMMITTED"); 
properties.setProperty("defaultCatalog", "test"); 
properties.setProperty("username", "username"); 
properties.setProperty("password", "password"); 
properties.setProperty("validationQuery", "SELECT DUMMY FROM DUAL"); 
BasicDataSource ds = (BasicDataSource) BasicDataSourceFactory.createDataSource(properties);

下面是获得PerUserPoolDataSource的一种方法:

DriverAdapterCPDS pcds = new DriverAdapterCPDS(); 
pcds.setDriver("org.apache.commons.dbcp.TesterDriver"); 
pcds.setUrl("jdbc:apache:commons:testdriver"); 
pcds.setUser("foo"); 
pcds.setPassword("bar"); 
PerUserPoolDataSource tds = new PerUserPoolDataSource(); 
tds.setConnectionPoolDataSource(pcds); 
tds.setDefaultMaxActive(getMaxActive()); 
tds.setDefaultMaxWait((int)(getMaxWait())); 
tds.setPerUserMaxActive("foo",new Integer(getMaxActive())); 
tds.setPerUserMaxWait("foo",new Integer((int)(getMaxWait()))); 
tds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); 
DataSource ds = tds; 

下面是获得SharedPoolDataSource的一种方法:

 
DriverAdapterCPDS pcds = new DriverAdapterCPDS(); 
pcds.setDriver("org.apache.commons.dbcp.TesterDriver"); 
pcds.setUrl("jdbc:apache:commons:testdriver"); 
pcds.setUser("foo"); 
pcds.setPassword("bar"); 
pcds.setPoolPreparedStatements(false); 
SharedPoolDataSource tds = new SharedPoolDataSource(); 
tds.setConnectionPoolDataSource(pcds); 
tds.setMaxActive(getMaxActive()); 
tds.setMaxWait((int)(getMaxWait())); 
tds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); 
DataSource ds = tds; 

下面是得到PreparedStatement的Pool的例子:

 
ConnectionFactory connFactory = new DriverManagerConnectionFactory("jdbc:apache:commons:testdriver","u1","p1"); 
ObjectPool connPool = new GenericObjectPool(); 
KeyedObjectPoolFactory stmtPoolFactory = new GenericKeyedObjectPoolFactory(null); 
PoolableConnectionFactory x = new PoolableConnectionFactory(connFactory, connPool, stmtPoolFactory, null, false, true); 
DataSource ds = new PoolingDataSource(connPool); 
Connection conn = ds.getConnection(); 
Statement stmt1 = conn.prepareStatement("select 1 from dual"); 

下面是另外一个使用PreparedStatement的Pool的例子:

ds是一个BasicDataSource

ds.setPoolPreparedStatements(true); 
ds.setMaxOpenPreparedStatements(2); 

然后用ds得到的连接conn创建PreparedStatement就可以看出来了。

那么我们现在再来一下利用JNDI得到DataSource的方法。

对于BasicDataSource

 
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); 
System.setProperty(Context.PROVIDER_URL, " file:///tmp"); 
InitialContext ic = new InitialContext(); 
// Construct BasicDataSource reference 
Reference ref = new Reference("javax.sql.DataSource", "org.apache.commons.dbcp.BasicDataSourceFactory", null); 
ref.add(new StringRefAddr("driverClassName", "org.apache.commons.dbcp.TesterDriver")); 
ref.add(new StringRefAddr("url", "jdbc:apache:commons:testdriver")); 
ref.add(new StringRefAddr("username", "username")); 
ref.add(new StringRefAddr("password", "password")); 
ic.rebind("jdbc/basic", ref); 
// Use 
InitialContext ic2 = new InitialContext(); 
DataSource ds = (DataSource) ic2.lookup("jdbc/basic"); 
assertNotNull(ds); 
Connection conn = ds.getConnection(); 
assertNotNull(conn); 
conn.close(); 

对于PerUserPoolDataSource

System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); 
System.setProperty(Context.PROVIDER_URL, " file:///tmp"); 
InitialContext ic = new InitialContext(); 
// Construct DriverAdapterCPDS reference 
Reference cpdsRef = new Reference("org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS", "org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS", null); 
cpdsRef.add(new StringRefAddr("driver", "org.apache.commons.dbcp.TesterDriver")); 
cpdsRef.add(new StringRefAddr("url", "jdbc:apache:commons:testdriver")); 
cpdsRef.add(new StringRefAddr("user", "foo")); 
cpdsRef.add(new StringRefAddr("password", "bar")); 
ic.rebind("jdbc/cpds", cpdsRef); 
// Construct PerUserPoolDataSource reference 
Reference ref = new Reference("org.apache.commons.dbcp.datasources.PerUserPoolDataSource", "org.apache.commons.dbcp.datasources.PerUserPoolDataSourceFactory", null); 
ref.add(new StringRefAddr("dataSourceName", "jdbc/cpds")); 
ref.add(new StringRefAddr("defaultMaxActive", "100")); 
ref.add(new StringRefAddr("defaultMaxIdle", "30")); 
ref.add(new StringRefAddr("defaultMaxWait", "10000")); 
ic.rebind("jdbc/peruser", ref); 
// Use 
InitialContext ic2 = new InitialContext(); 
DataSource ds = (DataSource) ic2.lookup("jdbc/peruser"); 
assertNotNull(ds); 
Connection conn = ds.getConnection("foo","bar"); 
assertNotNull(conn); 
conn.close(); 

还有一种方式是这样的:

首先新建一个DataSource,无论是什么DataSource都行,这里,例如PerUserPoolDataSource。

 
PerUserPoolDataSource dataSource = new PerUserPoolDataSource(); 
Hashtable environment = new Hashtable(); 
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); 
Context context = new InitialContext(environment); 
context.createSubcontext("jdbc"); 
context.bind("jdbc/test",dataSource); 
Context ctx = new InitialContext(environment); 
DataSource ds = (DataSource)context.lookup("jdbc/test"); 

可以看出,这样的方式并没有对DataSource进行必要的配置。

其实结合JNDI和普通创建的方式来看,一个是利用DriverAdapterCPDS和相应类直接创建,另一个是利用DriverAdapterCPDS和相应类的工厂创建。

相比起利用DriverManager的方式,利用JNDI的方式只不过是使用DriverAdapterCPDS这个类从驱动中实现了一个ConectionPoolDataSource,然后再用相应的其他需要的DataSource进行包装即可。看过BasicDataSource和PoolingDataSource的源代码之后,发现其实这个很简单,BasicDataSource是利用DriverConnectionFactory产生的连接,然后得到一个对象池,将其传递给PoolingDataSource,在PoolingDataSource里,对象池还是对象池,只是生成连接的时候使用了一个扩展自DelegatingConnection类的类:PoolGuardConnectionWrapper将Connection进行包装才传递给外面的。这两个相当于利用的方式方法都比较简单。

而InstanceKeyDataSource则更加复杂,里面用到了Java里面的javax.sql.ConnectionPoolDataSource接口,如果有些厂家实现了ConnectionPoolDataSource,那么就可以直接拿来用,如果没有,那么使用DriverAdapterCPDS从驱动中得到一个。

其实有三个数据源接口:

javax.sql.DataSource(普通情况下面用);

javax.sql.XADataSource(多个数据库联合做 transaction);

javax.sql.ConnectionPoolDataSource(连接池 datasource)。

从这里看出,数据源和DriverManager是很不同的,具体使用哪中方式大家自己决定。这里我们引出一个比较重要的话题,那么就是如果选择连接数据库的方式。

从上面可以看出,DBCP是使用的自己的Pool来实现的连接池(BasicDataSource和PoolingDataSource)。而使用Java EE标准的ConnectionPoolDataSource接口的则是使用DriverAdapterCPDS的,有人说Tomcat使用的是BasicDataSource,我还没来得及研究。一般来说,做Java EE开发,Server一般都有自己的连接池,到时候使用就可以了,普通的就用DBCP的BasicDataSource就可以了,这个可以用在非Java EE的应用上,是一个不错的好处。其实连接池这种东西,原理很简单,关键是要高效稳定,ConnectionPoolDataSource是Sun定义的接口,并不是因为它好或者怎么样。像Hibernate和Spring都只用了DataSource。

Oracle和MySQL在其驱动里都实现了ConnectionPoolDataSource,例如com.mysql.jdbc.jdbc2.optional.MySQLConnectionPoolDataSource,其实只要用这个就行了,而比如说你有一些特殊应用,例如就想PerUserPoolDataSource的设计目的一样,不同用户配置不同,这些的时候,你可以用一下DBCP里的相关的,但是其实都有了ConnectionPoolDataSource,其他的就很简单了。而DriverAdapterCPDS是使用驱动(没有ConnectionPoolDataSource)实现一个ConnectionPoolDataSource,所以一般还是不要用的好。

另外,在研究DBCP时,发现JNDI自己学的不好,所以找机会要好好的补一下了。

貌似让人有点犯迷糊了,那么我们接下来结合DBCP的包和类的组成,来看看吧。

DBCP有4个包:

org.apache.commons.dbcp 数据库连接池API

org.apache.commons.dbcp.cpdsadapter 这个包包含了一个公共类,实现了ConnectionPoolDataSource (CPDS),这个类能够使用早期的基于驱动的实现。其实是所有驱动相关的都和这个有关。

org.apache.commons.dbcp.datasources 这个包包含两个DataSources:PerUserPoolDataSource 和 SharedPoolDataSource,这两个类提供数据库连接池。

org.apache.commons.jocl Java Object Configuration Language,一个XML程序,能够描述需要实例化的Java对象。

接下来一个包一个包介绍:

org.apache.commons.dbcp 这个包是最重要的包了,里面就是数据库连接池的实现。

它包含很多类,一个接口ConnectionFactory,这个是用来创建连接的接口。

和ConnectionFactory相关的类有:

DataSourceConnectionFactory,基于DataSource的实现。

DriverConnectionFactory,基于Driver的实现。

DriverManagerConnectionFactory,基于DriverManager的实现。

PoolableConectionFactory,能够实现PoolableConnection的ConnectionFactroy。

PoolableConnection,是一种代理的Connection,能够放进一个ObjectPool。

PoolablePreparedStatement,这个同样也是基于代理,和PoolingConnection结合起来实现一个存放PreparedStatement的Pool。

和DataSource相关的类:

PoolingDataSource,一个简单的DataSource实现,能够从ObjectPool中得到Connection。

BasicDataSource,一个基本的javax.sql.DataSource的实现,其实是一个JavaBean,为基本的应用提供了一站式解决方案。

同样,我们有BasicDataSource的能够利用JNDI生成的BasicDataSourceFactory类,这里需要注意,BasicDataSourceFactory类有一个createDataSource(Properties)的方法,可以生成BasicDataSource。

Pooling相关:

PoolingDriver,一个Driver的实现类,能够从注册了的ObjectPool获得Connection。

PoolingConnection,一个能够实现存放PreparedStatement的Pool的基于代理的Connection。

代理类,都是相应类的利用代理的设计模式的实现。

DelegatingCallableStatement

DelegatingConnection

DelegatingPreparedStatement

DelegatingResultSet

DelegatingStatement

org.apache.commons.dbcp.cpdsadapter 这个包前面说过了,就包括一个DriverAdapterCPDS类,实现了ConnectionPoolDataSource (CPDS),实现和驱动相关的东西。

org.apache.commons.dbcp.datasources 这个包主要包括PerUserPoolDataSource 和 SharedPoolDataSource,以及它们使用JNDI时的工厂类,还有一个它们的基类。PerUserPoolDataSource对于不同用户使用不同的连接池,而且可以调整设置每个用户的参数,SharedPoolDataSource则所有使用相同的设置,分享最多的连接。

org.apache.commons.jocl 这个包里有两个类,ConstructorUtil 和 JOCLContentHandler。前面说过了,这是使用配置文件生成连接池的方式,但是这个有点复杂,对于我来说没什么大的用处,我就没有研究,但是这不影响对DBCP的大部分使用。

那么我们需要再说明一下DBCP的设置参数:

username 用户名

password 密码

url 连接的url

driverClassName 驱动器类名

connectionProperties 生成新的连接时,JDBC驱动使用的参数,格式是 [propertyName=property;]*

其他的参数如下,来自于DBCP的文档,这里就不详细的说了,在这里可以看一下。

我在这里写了一个XML配置文件,用来配置DBCP,文件名为database-config.xml,可以在这里找到。

可以同时设置多个数据库,利用default来设置默认的数据库,也可以在相应的数据库类初始化的时候设置采用的数据库。

4.访问数据库:

数据库中有三个接口,分别是Statement,PreparedStatement和CallableStatement,它们定义了访问数据库的不同方式。

下面是一些语句和注意点:

Statement

Connection接口创建Statement:

Statement createStatement() throws SQLException 
ResultSet executeQuery(String sql) throws SQLException 

查询,返回ResultSet对象。

int executeUpdate(String sql) throws SQLException

执行INSERT,UPDATE或者DELETE,也可执行SQL DDL语句,如CREATE TABLE。

boolean execute(String sql) throws SQLException

有多个结果集返回的语句。返回true表示返回的第一个结果是ResultSet,返回false表示返回的是一个更新的行数或者没有结果。可以使用getResultSet()或者getUpdateCount()来获取结果,并且调用getMoreResults()方法返回下一个结果集。

int[] executeBatch() throws SQLException

提交一批命令,使用addBatch()方法来将SQL命令加入到列表中。

一个Statement对象在同一时刻只能有一个打开的ResultSet对象,在Statement接口中定义的所有executeXXX()方法都隐含的关闭Statement当前的ResultSet对象。

PreparedStatement

从Connection的preparedStatement方法得到PreparedStatement对象。

PreparedStatement pstmt = conn.preparedStatement("insert into employee values(?,?,?)"); 

然后调用set方法:

 
pstmt.setDate(3,java.sql.Date.valueOf("2004-5-8"); 
pstmt.executeUpdate(); 

CallableStatement

用于执行SQL存储过程。

CallableSatement cstmt = conn.preparedCall("call p_changesal(?,?)"); 

在执行存储过程前,凡是存储过程中类型为OUT的参数必须被注册,对于类型为IN的参数,可以通过set方法来设置。

cstmt.registerOutParameter(2,java.sql.Types.INTEGER); 
cstmt.setInt(1,7358); 
cstmt.execute(); 
int sal = cstmt.getInt(2); 

ResultSet

有很多get方法,例如getString()等,一般都提供两种参数,一种以列的索引为参数(从1开始),一种以列的名字为参数。

ResultSetMetaData

使用ResultSet的getMetaData()得到。

有如下方法:

 
int getColumnCount() throws SQLException 
int getColumnDisplaySize(int column) throws SQLException 
String getColumnName(int column) throws SQLException 
int getColumnType(int column) throws SQLException 

得到的是JDBC类型,在java.sql.Types类中定义。

String getColumnTypeName(int column) throws SQLException 

返回数据库特定的类名。

String getTableName(int column) throws SQLException 

DatabaseMetaData

获取数据库的信息,使用Connection的getMetaData()得到。

ParameterMetaData

获取PreparedStatement对象中的参数和属性信息。

使用PaeparedStatement对象的getParameterMetaData()方法得到。

事务处理。

使用Connection的setAutoCommit()方法,传递false,取消自动提交。

使用Connection的rollback()进行回滚,使用commit()提交事务。

可滚动的结果集。

创建Statement的时候,可以使用重载的方法:

Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException 

创建PreparedStatement的时候,也有重载的方法:

PreparedStatement preparedStatment(int resultSetType, int resultSetConcurrency) throws SQLException 

resultSetType:

ResultSet.TYPE_FORWARD_ONLY 只能向前滚动。

ResultSet.TYPE_SCROLL_INSENSITIVE 可以滚动,但是对数据变化不敏感。

ResultSet.TYPE_SCROLL_SENSITIVE 可以滚动,对数据变化敏感。

resultSetConcurrency:

ResultSet.CONCUR_READ_ONLU 不能更新数据库

ResultSet.CONCUR_UPDATABLE 可以更新数据库

通过DatabaseMetaData的两个方法来判断数据库是否支持可以滚动和可更新的结果集。

boolean supportsResultSetType(int type) throws SQLException 
boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException 

ResultSet提供了下面的方法来支持结果集的滚动。

boolean isBeforeFist() throws SQLException 
boolean isAfterLast() throws SQLException 
boolean isFirst() throws SQLException 
boolean isLast() throws SQLException 
void beforeFirst() throws SQLException 
void afterLast() throws SQLException 
boolean first() throws SQLException 
boolean last() throws SQLException 
boolean absolute(int row) throws SQLException 
boolean previous() throws SQLException 
boolean relative(int row) throws SQLException 

ResultSet提供了下面的方法用于修改结果集。

更新一行

void updateXXX(int columnIndex, XXX x) throws SQLException 
void updateXXX(String columnName, XXX x) throws SQLException 

调用上面的方法之后,还要调用updateRow()方法才能有效。

如果不想更改,那么在updateRow()方法之前调用cancelRowUpdates()方法。

可以调用rowUpdated()方法来判断当前行是否被更新。

插入一行

先调用moveToInsertRow(),插入行是一个与可更新结果集相联系的特殊的缓存行。

然后调用updateXXX()方法,然后调用insertRow()方法。

当游标在插入行的时候,只能调用updateXXX(),getXXX(),和insertRow()方法。

在调用getXXX()之前必须先调用updateXXX()。

可以用rowInserted()来判断当前行是否是插入行。

删除一行

使用deleteRow()来删除一行,当游标指向插入行的时候,不能调用这个方法。

一个被删除的行可能在结果集中留下一个空的位置。可以用rowDeleted()方法来判断一行是否被删除。

可以更新结果集必须满足:

只能针对数据库中单张表操作。

查询语句不能包含任何的join操作。

查询操作的表中必须有主键,而且在查询的结果集中必须包含作为主键的字段。

如果还想执行插入,还必须满足:

查询操作必须选择数据库表中所有不能为空的列。

查询操作必须选择所有没有默认值的列。

SQL数据类型与Java数据类型的关系:

INTEGER,INT int 
TINYINT,SMALLINT short 
BIGINT long 
DECIMAL,NUMERIC java.math.BigDecimal 
FLOAT float 
DOUBLE double 
CHAR,VARCHAR String 
BOOLEAN,BIT boolean 
DATE java.sql.Date 
TIME java.sql.Time 
TIMESTAMP java.sql.Timestamp 
BLOB java.sql.Blob 
CLOB java.sql.CLOB 
ARRAY java.sql.Array 

5.附加内容-对象池的使用

在DBCP里,使用了apache commons的另一个项目Pool的结果。

即org.apache.commons.pool

这里简单介绍一下它的使用方法。

相比起DBCP来,Pool简单多了。

它有两个包:

org.apache.commons.pool 对象池API。

org.apache.commons.pool.impl 对象池API的实现。

在org.apache.commons.pool包里:

ObjectPool是一个接口:

public interface ObjectPool { 
	Object borrowObject(); 
	void returnObject(Object borrowed); 
}

BaseObjectPool是其抽象实现,建议都继承它来实际使用对象池。

KeyedObjectPool是另外一个接口,能够使用key得到对象:

public interface KeyedObjectPool { 
	Object borrowObject(Object key); 
	void returnObject(Object key, Object borrowed); 
}

BaseKeyedObjectPool是其抽象实现。

PoolableObjectFactory是一个接口,对于想放进池里的对象,需要实现这个接口:

public interface PoolableObjectFactory { 
	Object makeObject(); 
	void activateObject(Object obj); 
	void passivateObject(Object obj); 
	boolean validateObject(Object obj); 
	void destroyObject(Object obj); 
}

BasePoolableObjectFactory是其抽象实现。

KeyedPoolableObjectFactory是另一个接口,定义了要放进KeyedObjectPools的类需要实现的接口:

public interface KeyedPoolableObjectFactory { 
	Object makeObject(Object key); 
	void activateObject(Object key, Object obj); 
	void passivateObject(Object key, Object obj); 
	boolean validateObject(Object key, Object obj); 
	void destroyObject(Object key, Object obj); 
}

BaseKeyedPoolableObjectFactory是其抽象实现。

在这个包里,除了上面说的外,还包括KeyedObjectPool和ObjectPool的工厂接口。

同时还包括PoolUtils类。

在org.apache.commons.pool.impl里,有

StackObjectPool 提供一个有限对象的对象池,是LIFO的。

StackKeyedObjectPool 提供Keyed对象池,是LIFO的。

GenericObjectPool 通用的对象池,提供了广泛的设置参数,可以设置成LIFO或者FIFO的。

GenericKeyedObjectPool 通用Keyed对象池,是FIFO的。

SoftReferenceObjectPool 提供一个无限的对象池,允许垃圾回收器回收,是LIFO的。

包里其他还有相应Pool的工厂类,还提供了两个个Generic Pool的Config类。

举一下例子:

比如说我们想要得到一个StringBuilder的对象池。

那么,我们建立一个StringBuilderFactory类,然后让其实现PoolableObjectFactory接口。

我们可以直接继承BasePoolableObjectFactory,这也是推荐的做法。

import org.apache.commons.pool.BasePoolableObjectFactory; 
public class StringBuilderFactory extends BasePoolableObjectFactory { 
	// for makeObject we'll simply return a new buffer 
	public Object makeObject() { 
		return new StringBuilder(); 
	} 
	// when an object is returned to the pool, 
	// we'll clear it out 
	public void passivateObject(Object obj) { 
		StringBuilder buf = (StringBuilder)obj; 
		buf.setLength(0); 
	} 
	// for all other methods, the no-op 
	// implementation in BasePoolableObjectFactory 
	// will suffice 
}

然后把这个类传入想要的Pool的构造函数里就可以了:

ObjectPool pool = new StackObjectPool(new StringBufferFactory()); 

OK,就到这里吧,讲了不少,只要能有讲了之后的效果就行。