Java Web系列:JDBC 基础
原文出处:王刚
ADO.NET在Java中的对应技术是JDBC,企业库DataAccessApplicationBlock模块在Java中的对应是spring-jdbc模块,EntityFramework在Java中对应的ORM是Hibernate。关系数据库、SQL、数据库事务、分布式事务的概念都是通用的。
1.JDBC
JDBC代码和ADO.NET代码一样,除了学习时写demo来掌握核心对象外,不适合在项目中直接使用。另外Java中万年不变的学术派抽象接口给我目前看到的大多数容器和框架带来了极大的不便,如Tomcat和Spring中定义的一些抽象类型无论是属性和方法都和.NET中的很相似,但又不得不和Java中的基础接口适配。抽象不能像Java中一样只关注操作接口而不关注基础的数据结构,正确的类型的抽象比方法的抽象更重要,但Java中抽象的做法好像还有一个原则就是与众不同,而不只是关注操作。
(1)核心对象
ADO.NET中的4个核心抽象类是DbConnection、DbCommand、DbParameter和DbTransaction.DataAdapter和DataSet构建在核心对象之上。
JDBC的4个核心接口DataSource、Connection、Statement和ResultSet。PreparedStatement是Statement的子类型。
虽然两者不能完全匹配,对数据库操作的核心操作是一致的,都是打开链接、发送命令和关闭连接的过程。
(2)参数处理
参数的核心是索引和参数值。ADO.NET中提供了DbDataParameter类型用于参数的抽象。JDBC中没有提供参数的抽象类型,而是通过PreparedStatement定义的方法来设置参数,可以通过Connection对象获取该类型对象。
(3)事务处理
事务的核心操作是提交和回滚。ADO.NET中提供了DbTransaction类型用于事务的抽象,可以通过DbConnection的BeginTransaction方法开启显式事务.JDBC中没有提供事务的抽象类型,而是通过Connection定义的setAutoCommit、commit和rollback方法来操作事务。隐性事务的自动提交和显性事务的手动控制方面两者是一致的。
(4)数据源
JDBC中的Connection不具备连接池等高级功能。DataSource定义了数据源接口,用于连接管理、连接池和分布式事务等高级功能,连接池的重要性不用强调了。我们可以使用ApacheCommonsDBCP2或C3P0(Hibernate)等第三方DataSource实现,Spring框架也有内置的一些简单的DataSource实现,如SimpleDriverDataSource、SingleConnectionDataSource、DriverManagerDataSource等。
我们分别使用DriverManager、commons-dbcp2(参考1)、c3p0(参考2)和h2database(参考3)演示无连接池、dbcp2连接池、c3p0连接池的使用:
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
packagetest.jdbc;
importjava.beans.PropertyVetoException;
importjava.sql.Connection;
importjava.sql.DriverManager;
importjava.sql.PreparedStatement;
importjava.sql.ResultSet;
importjava.sql.SQLException;
importjavax.sql.DataSource;
importorg.apache.commons.dbcp2.BasicDataSource;
importcom.mchange.v2.c3p0.ComboPooledDataSource;
publicclassJdbc{
publicstaticvoidmain(String[]args)throwsClassNotFoundException,SQLException,PropertyVetoException{
Stringddl="createtableuser(idintegernotnullprimarykey,usernamevarchar(64))";
Stringdml="insertintouser(id,username)values(1,'tom')";
Stringquery="selectid,usernamefromuserwhereusername=?";
Stringclassname="org.h2.Driver";
Stringurl="jdbc:h2:mem:test";
//Connectionconn=DriverManager.getConnection(url);
Connectionconn=get_dbcp2_dataSource(className,url).getConnection();
//Connectionconn=get_c3p0_dataSource(className,//
//url).getConnection();
conn.setAutoCommit(false);
conn.prepareStatement(ddl).execute();
conn.createStatement().execute(dml);
conn.commit();
conn.setAutoCommit(true);
PreparedStatementstatement=conn.prepareStatement(query);
statement.setString(1,"tom");
ResultSetrs=statement.executeQuery();
Useruser=newUser();
while(rs.next()){
user.setId(rs.getInt(1));
user.setUsername("username");
break;
}
conn.close();
System.out.println(String.format("id:%d,username:%s",user.getId(),user.getUsername()));
}
publicstaticConnectiongetConnection(StringdriverClassName,Stringurl){
try{
Class.forName(driverClassName);
}catch(ClassNotFoundExceptione1){
e1.printStackTrace();
}
Connectionconn=null;
try{
conn=DriverManager.getConnection(url);
}catch(SQLExceptione){
e.printStackTrace();
}
returnconn;
}
publicstaticDataSourceget_dbcp2_dataSource(StringclssName,Stringurl){
BasicDataSourcedataSource=newBasicDataSource();
dataSource.setDriverClassName(clssName);
dataSource.setUrl(url);
returndataSource;
}
publicstaticDataSourceget_c3p0_dataSource(StringclssName,Stringurl)throwsPropertyVetoException{
ComboPooledDataSourcedataSource=newComboPooledDataSource();
dataSource.setDriverClass(clssName);
dataSource.setJdbcUrl(url);
returndataSource;
}
}
其中2dbcp、c3p0和h2database的Maven依赖如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.190</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
2.SpringJdbc
JDBC和ADO.NET的API类似,都不适合我们直接使用,如果不适用ORM框架,在.NET中我们使用DataAccessApplicationBlock,在Java中我们可以使用SpringJDBC。SpringJDBC的核心类是JdbcTemplate。
(1)Sprnig的委托实现
要使用SpringJdbc的功能,首先要理解Spring中委托的使用方式,Spring定义一个包含该委托签名的接口(这些接口通常都是没有内置实现的),当需要委托参数时,使用该接口作为参数类型,利用Java中的匿名类功能,可以在内联代码中实现接口定义的委托,下面列举几个常见的委托接口:
Func<Connection,PreparedStatement>:PreparedStatement接口的createPreparedStatement方法。
1
2
3
4
5
publicinterfacePreparedStatementCreator{
PreparedStatementcreatePreparedStatement(Connectioncon)throwsSQLException;
}
Func<PreparedStatement,T>:PreparedStatementCallback泛型接口的doInPreparedStatement方法。
1
2
3
4
5
publicinterfacePreparedStatementCallback<T>{
TdoInPreparedStatement(PreparedStatementps)throwsSQLException,DataAccessException;
}
Func<TransactionStatus,T>:TransactionCallback泛型接口的TdoInTransaction(TransactionStatusstatus)方法。
1
2
3
4
5
publicinterfaceTransactionCallback<T>{
TdoInTransaction(TransactionStatusstatus);
}
Fcun<ResultSet,T>:ResultSetExtractor泛型方法的TextractData(ResultSetrs)方法。
1
2
3
4
5
publicinterfaceResultSetExtractor<T>{
TextractData(ResultSetrs)throwsSQLException,DataAccessException;
}
(1)JdbcTemplate
JdbcTemplate需要配合DataSource一起使用。JdbcTemplate的代码中很多都是通过IDE自动生成匿名类导致的,真正手写的代码量并不多。
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
publicstaticvoidmain(String[]args)throwsClassNotFoundException,SQLException,PropertyVetoException{
Stringddl="createtableuser(idintegernotnullprimarykey,usernamevarchar(64))";
Stringdml="insertintouser(id,username)values(1,'tom')";
Stringquery="selectid,usernamefromuserwhereusername=?";
Stringclassname="org.h2.Driver";
Stringurl="jdbc:h2:mem:test";
DataSourcedataSource=get_dbcp2_dataSource(className,url);
JdbcTemplatejdbcTemplate=newJdbcTemplate(dataSource);
PlatformTransactionManagertm=newDataSourceTransactionManager(dataSource);
TransactionTemplatett=newTransactionTemplate(tm);
tt.execute(newTransactionCallback(){
@Override
publicObjectdoInTransaction(TransactionStatusstatus){
jdbcTemplate.execute(ddl);
jdbcTemplate.execute(dml);
returnstatus;
}
});
Useruser=jdbcTemplate.query(query,newObject[]{"tom"},newResultSetExtractor<User>(){
@Override
publicUserextractData(ResultSetrs)throwsSQLException,DataAccessException{
Useruser=newUser();
while(rs.next()){
user.setId(rs.getInt(1));
user.setUsername(rs.getString("username"));
break;
}
returnuser;
}
});
System.out.println(String.format("id:%d,username:%s",user.getId(),user.getUsername()));
}
(3)TransactionTemplate
在上面的代码中出现了TransactionTemplate用于事务处理,TransactionTemplate依赖PlatformTransactionManager,SpringJdbc中定义的DataSourceTransactionManager实现类用于处理数据库事务。在Spring的orm、jsm、tx等模块中还有其他的实现。
(4)JdbcDaoSupport
JdbcDaoSupport是一个在内部使用JdbcTemplate字段并且实现了DaoSupport接口的抽象类,可供我们自定义DAO类时参考或用作基类。
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
classMyDaoextendsJdbcDaoSupport{
publicMyDao(DataSourcedataSource){
this.setJdbcTemplate(newJdbcTemplate(dataSource));
}
@SuppressWarnings("unchecked")
publicvoidinit(Stringddl,Stringdml){
PlatformTransactionManagertm=newDataSourceTransactionManager(this.getDataSource());
TransactionTemplatett=newTransactionTemplate(tm);
tt.execute(newTransactionCallback(){
@Override
publicObjectdoInTransaction(TransactionStatusstatus){
getJdbcTemplate().execute(ddl);
getJdbcTemplate().execute(dml);
returnstatus;
}
});
}
publicUsergetUser(Stringquery,Stringusername){
returnthis.getJdbcTemplate().query(query,newObject[]{username},newResultSetExtractor<User>(){
@Override
publicUserextractData(ResultSetrs)throwsSQLException,DataAccessException{
Useruser=newUser();
while(rs.next()){
user.setId(rs.getInt(1));
user.setUsername(rs.getString("username"));
break;
}
returnuser;
}
});
}
}
参考
(1)http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi
(2)http://sourceforge.net/projects/c3p0/
(3)http://www.h2database.com/html/cheatSheet.html
(4)http://commons.apache.org/proper/commons-dbcp/guide/jndi-howto.html