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連接池的使用:
package test.jdbc; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class Jdbc { public static void main(String[] args) throws ClassNotFoundException, SQLException, PropertyVetoException { String ddl = "create table user(id integer not null primary key,username varchar(64))"; String dml = "insert into user (id,username) values(1,'tom')"; String query = "select id,username from user where username=?"; String className = "org.h2.Driver"; String url = "jdbc:h2:mem:test"; // Connection conn = DriverManager.getConnection(url); Connection conn = get_dbcp2_dataSource(className, url).getConnection(); // Connection conn = get_c3p0_dataSource(className,// // url).getConnection(); conn.setAutoCommit(false); conn.prepareStatement(ddl).execute(); conn.createStatement().execute(dml); conn.commit(); conn.setAutoCommit(true); PreparedStatement statement = conn.prepareStatement(query); statement.setString(1, "tom"); ResultSet rs = statement.executeQuery(); User user = new User(); 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())); } public static Connection getConnection(String driverClassName, String url) { try { Class.forName(driverClassName); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } Connection conn = null; try { conn = DriverManager.getConnection(url); } catch (SQLException e) { e.printStackTrace(); } return conn; } public static DataSource get_dbcp2_dataSource(String clssName, String url) { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(clssName); dataSource.setUrl(url); return dataSource; } public static DataSource get_c3p0_dataSource(String clssName, String url) throws PropertyVetoException { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass(clssName); dataSource.setJdbcUrl(url); return dataSource; } }
其中2dbcp、c3p0和h2database的Maven依賴如下:
<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.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方法。
public interface PreparedStatementCreator { PreparedStatement createPreparedStatement(Connection con) throws SQLException; }
Func<PreparedStatement,T>:PreparedStatementCallback泛型接口的doInPreparedStatement方法。
public interface PreparedStatementCallback<T> { T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException; }
Func<TransactionStatus,T>:TransactionCallback泛型接口的T doInTransaction(TransactionStatus status)方法。
public interface TransactionCallback<T> { T doInTransaction(TransactionStatus status); }
Fcun<ResultSet,T>:ResultSetExtractor泛型方法的T extractData(ResultSet rs)方法。
public interface ResultSetExtractor<T> { T extractData(ResultSet rs) throws SQLException, DataAccessException; }
(1)JdbcTemplate
JdbcTemplate需要配合DataSource一起使用。JdbcTemplate的代碼中很多都是通過IDE自動生成匿名類導致的,真正手寫的代碼量并不多。
public static void main(String[] args) throws ClassNotFoundException, SQLException, PropertyVetoException { String ddl = "create table user(id integer not null primary key,username varchar(64))"; String dml = "insert into user (id,username) values(1,'tom')"; String query = "select id,username from user where username=?"; String className = "org.h2.Driver"; String url = "jdbc:h2:mem:test"; DataSource dataSource = get_dbcp2_dataSource(className, url); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); PlatformTransactionManager tm = new DataSourceTransactionManager(dataSource); TransactionTemplate tt = new TransactionTemplate(tm); tt.execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus status) { jdbcTemplate.execute(ddl); jdbcTemplate.execute(dml); return status; } }); User user = jdbcTemplate.query(query, new Object[] { "tom" }, new ResultSetExtractor<User>() { @Override public User extractData(ResultSet rs) throws SQLException, DataAccessException { User user = new User(); while (rs.next()) { user.setId(rs.getInt(1)); user.setUsername(rs.getString("username")); break; } return user; } }); System.out.println(String.format("id:%d,username:%s", user.getId(), user.getUsername())); }
(3)TransactionTemplate
在上面的代碼中出現了TransactionTemplate用于事務處理,TransactionTemplate依賴PlatformTransactionManager,Spring Jdbc中定義的DataSourceTransactionManager實現類用于處理數據庫事務。在Spring的orm、jsm、tx等模塊中還有其他的實現。
(4)JdbcDaoSupport
JdbcDaoSupport是一個在內部使用JdbcTemplate字段并且實現了DaoSupport接口的抽象類,可供我們自定義DAO類時參考或用作基類。
class MyDao extends JdbcDaoSupport { public MyDao(DataSource dataSource) { this.setJdbcTemplate(new JdbcTemplate(dataSource)); } @SuppressWarnings("unchecked") public void init(String ddl, String dml) { PlatformTransactionManager tm = new DataSourceTransactionManager(this.getDataSource()); TransactionTemplate tt = new TransactionTemplate(tm); tt.execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus status) { getJdbcTemplate().execute(ddl); getJdbcTemplate().execute(dml); return status; } }); } public User getUser(String query, String username) { return this.getJdbcTemplate().query(query, new Object[] { username }, new ResultSetExtractor<User>() { @Override public User extractData(ResultSet rs) throws SQLException, DataAccessException { User user = new User(); while (rs.next()) { user.setId(rs.getInt(1)); user.setUsername(rs.getString("username")); break; } return user; } }); } }
參考
(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