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定义了数据源接口,用于连接管理、连接池和分布式事务等高级功能,连接池的重要性不用强调了。我们可以使用Apache Commons DBCP 2或C3P0(Hibernate)等第三方DataSource实现,Spring框架也有内置的一些简单的DataSource实现,如SimpleDriverDataSource、SingleConnectionDataSource、DriverManagerDataSource等。
我们分别使用DriverManager、commons-dbcp2(参考1)、c3p0(参考2)和h2database(参考3)演示无连接池、dbcp2连接池、c3p0连接池的使用:
1 package test.jdbc; 2 3 import java.beans.PropertyVetoException; 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.PreparedStatement; 7 import java.sql.ResultSet; 8 import java.sql.SQLException; 9 import javax.sql.DataSource; 10 11 import org.apache.commons.dbcp2.BasicDataSource; 12 import com.mchange.v2.c3p0.ComboPooledDataSource; 13 14 public class Jdbc { 15 public static void main(String[] args) throws ClassNotFoundException, SQLException, PropertyVetoException { 16 String ddl = "create table user(id integer not null primary key,username varchar(64))"; 17 String dml = "insert into user (id,username) values(1,'tom')"; 18 String query = "select id,username from user where username=?"; 19 String className = "org.h2.Driver"; 20 String url = "jdbc:h2:mem:test"; 21 // Connection conn = DriverManager.getConnection(url); 22 Connection conn = get_dbcp2_dataSource(className, url).getConnection(); 23 // Connection conn = get_c3p0_dataSource(className,// 24 // url).getConnection(); 25 conn.setAutoCommit(false); 26 conn.prepareStatement(ddl).execute(); 27 conn.createStatement().execute(dml); 28 conn.commit(); 29 conn.setAutoCommit(true); 30 PreparedStatement statement = conn.prepareStatement(query); 31 statement.setString(1, "tom"); 32 ResultSet rs = statement.executeQuery(); 33 User user = new User(); 34 while (rs.next()) { 35 user.setId(rs.getInt(1)); 36 user.setUsername("username"); 37 break; 38 } 39 conn.close(); 40 System.out.println(String.format("id:%d,username:%s", user.getId(), user.getUsername())); 41 } 42 43 public static Connection getConnection(String driverClassName, String url) { 44 try { 45 Class.forName(driverClassName); 46 } catch (ClassNotFoundException e1) { 47 e1.printStackTrace(); 48 } 49 Connection conn = null; 50 try { 51 conn = DriverManager.getConnection(url); 52 } catch (SQLException e) { 53 e.printStackTrace(); 54 } 55 return conn; 56 } 57 58 public static DataSource get_dbcp2_dataSource(String clssName, String url) { 59 60 BasicDataSource dataSource = new BasicDataSource(); 61 dataSource.setDriverClassName(clssName); 62 dataSource.setUrl(url); 63 return dataSource; 64 } 65 66 public static DataSource get_c3p0_dataSource(String clssName, String url) throws PropertyVetoException { 67 68 ComboPooledDataSource dataSource = new ComboPooledDataSource(); 69 dataSource.setDriverClass(clssName); 70 dataSource.setJdbcUrl(url); 71 return dataSource; 72 } 73 }
其中2dbcp、c3p0和h2database的Maven依赖如下:
1 <dependency> 2 <groupId>com.h2database</groupId> 3 <artifactId>h2</artifactId> 4 <version>1.4.190</version> 5 </dependency> 6 <dependency> 7 <groupId>org.apache.commons</groupId> 8 <artifactId>commons-dbcp2</artifactId> 9 <version>2.1.1</version> 10 </dependency> 11 <dependency> 12 <groupId>com.mchange</groupId> 13 <artifactId>c3p0</artifactId> 14 <version>0.9.5.2</version> 15 </dependency>
2.Spring Jdbc
JDBC和ADO.NET的API类似,都不适合我们直接使用,如果不适用ORM框架,在.NET中我们使用DataAccessApplicationBlock,在Java中我们可以使用Spring JDBC。Spring JDBC的核心类是JdbcTemplate。
(1)Sprnig的委托实现
要使用Spring Jdbc的功能,首先要理解Spring中委托的使用方式,Spring定义一个包含该委托签名的接口(这些接口通常都是没有内置实现的),当需要委托参数时,使用该接口作为参数类型,利用Java中的匿名类功能,可以在内联代码中实现接口定义的委托,下面列举几个常见的委托接口:
Func<Connection,PreparedStatement>:PreparedStatement接口的createPreparedStatement方法。
1 public interface PreparedStatementCreator { 2 3 PreparedStatement createPreparedStatement(Connection con) throws SQLException; 4 5 }
Func<PreparedStatement,T>:PreparedStatementCallback泛型接口的doInPreparedStatement方法。
1 public interface PreparedStatementCallback<T> { 2 3 T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException; 4 5 }
Func<TransactionStatus,T>:TransactionCallback泛型接口的T doInTransaction(TransactionStatus status)方法。
1 public interface TransactionCallback<T> { 2 3 T doInTransaction(TransactionStatus status); 4 5 }
Fcun<ResultSet,T>:ResultSetExtractor泛型方法的T extractData(ResultSet rs)方法。
1 public interface ResultSetExtractor<T> { 2 3 T extractData(ResultSet rs) throws SQLException, DataAccessException; 4 5 }
(1)JdbcTemplate
JdbcTemplate需要配合DataSource一起使用。JdbcTemplate的代码中很多都是通过IDE自动生成匿名类导致的,真正手写的代码量并不多。
1 public static void main(String[] args) throws ClassNotFoundException, SQLException, PropertyVetoException { 2 String ddl = "create table user(id integer not null primary key,username varchar(64))"; 3 String dml = "insert into user (id,username) values(1,'tom')"; 4 String query = "select id,username from user where username=?"; 5 String className = "org.h2.Driver"; 6 String url = "jdbc:h2:mem:test"; 7 8 DataSource dataSource = get_dbcp2_dataSource(className, url); 9 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); 10 PlatformTransactionManager tm = new DataSourceTransactionManager(dataSource); 11 TransactionTemplate tt = new TransactionTemplate(tm); 12 tt.execute(new TransactionCallback() { 13 14 @Override 15 public Object doInTransaction(TransactionStatus status) { 16 jdbcTemplate.execute(ddl); 17 jdbcTemplate.execute(dml); 18 return status; 19 } 20 }); 21 22 User user = jdbcTemplate.query(query, new Object[] { "tom" }, new ResultSetExtractor<User>() { 23 @Override 24 public User extractData(ResultSet rs) throws SQLException, DataAccessException { 25 User user = new User(); 26 while (rs.next()) { 27 user.setId(rs.getInt(1)); 28 user.setUsername(rs.getString("username")); 29 break; 30 } 31 return user; 32 } 33 }); 34 System.out.println(String.format("id:%d,username:%s", user.getId(), user.getUsername())); 35 }
(3)TransactionTemplate
在上面的代码中出现了TransactionTemplate用于事务处理,TransactionTemplate依赖PlatformTransactionManager,Spring Jdbc中定义的DataSourceTransactionManager实现类用于处理数据库事务。在Spring的orm、jsm、tx等模块中还有其他的实现。
(4)JdbcDaoSupport
JdbcDaoSupport是一个在内部使用JdbcTemplate字段并且实现了DaoSupport接口的抽象类,可供我们自定义DAO类时参考或用作基类。
1 class MyDao extends JdbcDaoSupport { 2 public MyDao(DataSource dataSource) { 3 this.setJdbcTemplate(new JdbcTemplate(dataSource)); 4 } 5 6 @SuppressWarnings("unchecked") 7 public void init(String ddl, String dml) { 8 PlatformTransactionManager tm = new DataSourceTransactionManager(this.getDataSource()); 9 TransactionTemplate tt = new TransactionTemplate(tm); 10 11 tt.execute(new TransactionCallback() { 12 13 @Override 14 public Object doInTransaction(TransactionStatus status) { 15 getJdbcTemplate().execute(ddl); 16 getJdbcTemplate().execute(dml); 17 return status; 18 } 19 }); 20 } 21 22 public User getUser(String query, String username) { 23 return this.getJdbcTemplate().query(query, new Object[] { username }, new ResultSetExtractor<User>() { 24 @Override 25 public User extractData(ResultSet rs) throws SQLException, DataAccessException { 26 User user = new User(); 27 while (rs.next()) { 28 user.setId(rs.getInt(1)); 29 user.setUsername(rs.getString("username")); 30 break; 31 } 32 return user; 33 } 34 }); 35 } 36 }
参考
(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