转自:http://www.oseye.net/user/kevin/blog/269
JDBC是什么
JDBC(Java Data Base Connectivity)是Java访问数据库的桥梁,但它只是接口规范,具体实现是各数据库厂商提供的驱动程序(Driver)。
应用程序、JDBC、JDBC驱动、数据库之间的关系如上图:应用程序通过JDBC访问数据库,而JDBC驱动来具体实现JDBC访问数据库的动作。
JDBC版本
JDBC的版本和JDK版本是独立的,他们的对应关系:
JDK版本 | DB版本 |
---|---|
JDK 1.1 | JDBC1 |
JDK 1.2, 1.3 | JDBC2 |
JDK 1.4, 1.5 | JDBC3 |
JDK 1.6 | JDBC4 |
JDK 1.7 | JDBC4.1 |
JDBC4.0新特性
JDBC 4.0中增加的主要特性包括:
JDBC链接步骤
以MySql为例,制作了一个DEMO(也可以了解下我早期入门的一个DEMO)。
pom.xml添加MySql驱动依赖:
完整的App.java代码
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.30</version>
- </dependency>
相关API在此不做详细介绍,请查看JDK API文档。
- package net.oseye.DbDemo;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.ResultSet;
- import java.sql.Statement;
- public class App
- {
- public static void main( String[] args ) throws Exception
- {
- //链接数据库
- Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
- //创建执行声明
- Statement stmt=conn.createStatement();
- //执行Sql语句
- ResultSet rs=stmt.executeQuery("select user_id,user_name from user");
- //处理结果集
- while(rs.next()){
- System.out.println("用户ID"+rs.getInt(1)+"\t用户名:"+rs.getString(2));
- }
- //释放资源
- rs.close();
- stmt.close();
- conn.close();
- }
- }
JDBC预处理PreparedStatement
上面的示例有两个缺点:
PreparedStatement是Statement的子接口,它在执行sql之前首先准备好sql语句,将其中的条件通过?进行占位,还有一个好处就是,同样的sql会被PreparedStatement有效的缓存,也就是说,数据库会减少校验的过程,减少消耗,这就是我们常说的预处理命令方式。
- package net.oseye.DbDemo;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- public class App
- {
- public static void main( String[] args ) throws Exception
- {
- //链接数据库
- Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
- //创建执行声明
- PreparedStatement pstmt=conn.prepareStatement("select user_id,user_name from user where user_id < ?");
- //填充占位符?
- pstmt.setInt(1, 3);
- //执行Sql语句
- ResultSet rs=pstmt.executeQuery();
- //处理结果集
- while(rs.next()){
- System.out.println("用户ID"+rs.getInt(1)+"\t用户名:"+rs.getString(2));
- }
- //释放资源
- rs.close();
- pstmt.close();
- conn.close();
- }
- }
JDBC调用存储过程请使用“CallableStatement”接口。
JDBC事务
数据库的事务是保证数据完整性的一种机制,简而言之,就是怎样确保数据的执行过程要么都成功,要么都失败
示例代码( 也可以了解下我早期的一个DEMO)
- connection.setAutoCommit(false);
这里没有对事务做深入讨论,看到这儿容易让你产生误会,以为事务如此简单,请正式生产前一定找相关专业书籍学习,切记切记。
- package net.oseye.DbDemo;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- public class App
- {
- public static void main( String[] args ) throws Exception
- {
- Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
- //取消默认事务
- conn.setAutoCommit(false);
- PreparedStatement pstmt=conn.prepareStatement("update user set user_name=? where user_id = ?");
- pstmt.setString(1, "kevin");
- pstmt.setInt(2, 1);
- pstmt.execute();
- pstmt=conn.prepareStatement("select user_name from user where user_id=?");
- pstmt.setInt(1,1);
- ResultSet rs=pstmt.executeQuery();
- //提交事务
- conn.commit();
- if(rs.next()){
- System.out.println("用户名:"+rs.getString(1));
- }
- rs.close();
- pstmt.close();
- conn.close();
- }
- }
JDBC的DataSource
前文提到使用DriverManager获取数据库连接的低效,是因为数据库连接是一种关键的有限而昂贵的资源。所以JDK为我们提供了一个DataSource接口。DataSource作为 DriverManager 工具的替代项,DataSource 对象是获取连接的首选方法。DataSource接口:
有很多容器如Tomcat都实现DataSource接口而提供了连接池功能,现在我们来简单实现一个连接池:
- //尝试建立与此 DataSource 对象所表示的数据源的连接。
- Connection getConnection()
- //尝试建立与此 DataSource 对象所表示的数据源的连接。
- Connection getConnection(String username, String password)
使用连接池
- /**
- * 实现DataSource接口的简单连接池
- */
- public class ConnectPool implements DataSource {
- private static final String url = "jdbc:mysql://127.0.0.1:3306/test";
- private static final String user = "root";
- private static final String pswd = "root";
- // 连接池队列
- private static LinkedList<Connection> pool = new LinkedList<Connection>();
- private static ConnectPool instance = new ConnectPool();
- /**
- * 获取数据源单例
- */
- public static ConnectPool instance() {
- if (instance == null)
- instance = new ConnectPool();
- return instance;
- }
- /**
- * 获取一个数据库连接
- */
- public Connection getConnection() throws SQLException {
- synchronized (pool) {
- if (pool.size() > 0) {
- return pool.removeFirst();
- } else {
- return DriverManager.getConnection(url, user, pswd);
- }
- }
- }
- /**
- * 连接归池,这里的实现思想是使用过的线程入池以备下次使用
- */
- public static void freeConnection(Connection conn) {
- pool.addLast(conn);
- }
- public Connection getConnection(String username, String password)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PrintWriter getLogWriter() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public void setLogWriter(PrintWriter out) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setLoginTimeout(int seconds) throws SQLException {
- // TODO Auto-generated method stub
- }
- public int getLoginTimeout() throws SQLException {
- // TODO Auto-generated method stub
- return 0;
- }
- public Logger getParentLogger() throws SQLFeatureNotSupportedException {
- // TODO Auto-generated method stub
- return null;
- }
- public <T> T unwrap(Class<T> iface) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public boolean isWrapperFor(Class<?> iface) throws SQLException {
- // TODO Auto-generated method stub
- return false;
- }
- }
输出
- public class App {
- public static void main(String[] args) throws Exception {
- //没有使用连接池
- long start=System.currentTimeMillis();
- for (int i = 0; i < 100; i++) {
- Connection conn = DriverManager.getConnection(
- "jdbc:mysql://127.0.0.1:3306/test",
- "root", "root");
- PreparedStatement pstmt = conn
- .prepareStatement("insert into user(user_name) values(?)");
- pstmt.setString(1, String.valueOf(System.currentTimeMillis()));
- pstmt.execute();
- pstmt.close();
- conn.close();
- }
- System.out.println("没有使用连接池花费时间:"+(System.currentTimeMillis()-start));
- //使用连接池
- start=System.currentTimeMillis();
- for (int i = 0; i < 100; i++) {
- Connection conn=ConnectPool.instance().getConnection();
- PreparedStatement pstmt = conn
- .prepareStatement("insert into user(user_name) values(?)");
- pstmt.setString(1, String.valueOf(System.currentTimeMillis()));
- pstmt.execute();
- pstmt.close();
- ConnectPool.freeConnection(conn);
- }
- System.out.println("使用连接池花费时间:"+(System.currentTimeMillis()-start));
- }
- }
示例是使用的单线程就能足以展现出差距了,如果使用多线程会更明显,但多线程就要对连接池进行同步操作,实现起来会更复杂。实例中的连接池实现虽然使用了同步(synchronized ),但是向当地弱,此实现只是简单展示连接池的实现以及使用,禁止把如此简单实现投入生产,切记切记。没有使用连接池花费时间:4625
使用连接池花费时间:2782
开源连接池
有很多产品都实现了自己的连接池,如Jboss、Tomcat等容器,业界也有很多著名的开源连接池供大家使用,如:C3P0、BoneCP、DBCP 、Proxool等。我们以BoneCP连接池为例来展示。
pom.xml增加BoneCP的依赖配置(BoneCP使用了SLF4J,这里暂时没做配置)
App.java代码
- <dependency>
- <groupId>com.jolbox</groupId>
- <artifactId>bonecp</artifactId>
- <version>0.8.0.RELEASE</version>
- </dependency>
输出
- package net.oseye.DbDemo;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
- import com.jolbox.bonecp.BoneCP;
- import com.jolbox.bonecp.BoneCPConfig;
- public class App {
- public static void main(String[] args) {
- long start=System.currentTimeMillis();
- BoneCP connectionPool = null;
- Connection connection = null;
- try {
- //连接池配置
- BoneCPConfig config = new BoneCPConfig();
- config.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test");
- config.setUsername("root");
- config.setPassword("root");
- config.setMinConnectionsPerPartition(5);
- config.setMaxConnectionsPerPartition(10);
- config.setPartitionCount(1);
- connectionPool = new BoneCP(config);
- for (int i = 0; i < 100; i++) {
- //获取链接
- connection = connectionPool.getConnection();
- if (connection != null) {
- PreparedStatement pstmt = connection
- .prepareStatement("insert into user(user_name) values(?)");
- pstmt.setString(1,
- String.valueOf(System.currentTimeMillis()));
- pstmt.execute();
- pstmt.close();
- connection.close();
- }
- }
- connectionPool.shutdown();
- System.out.println("使用BoneCP连接池花费时间:"+(System.currentTimeMillis()-start));
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
使用BoneCP连接池花费时间:3531