[JDBC] JDBC 从原始到高度封装演进

1. 原始JDBC查询大致分为六个步骤:

(1) 注册数据库驱动(动态加载类)

(2) 创建数据库连接

(3) 执行SQL脚本

(4) 运行数据处理逻辑

(5) 关闭资源

(6) finally关闭句柄,关闭连接

package com.cisco.eStore.test.dao;

import org.apache.log4j.Logger;

import java.sql.*;

public class UserDao {

    static Logger logger = Logger.getLogger(UserDao.class);
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://localhost:3306/estore";

    //  Database credentials
    static final String USER = "clops";
    static final String PASS = "cscocmse";

    public UserDao(){
    }

    public static void main(String [] args){
        UserDao userDao = new UserDao();
        try {
            userDao.getUserList();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public void getUserList() throws SQLException, ClassNotFoundException {
        // 1. Register Driver
        Class.forName(JDBC_DRIVER);

        // 2. Open Connection
        Connection con = DriverManager.getConnection(DB_URL, USER, PASS);
        PreparedStatement pstmt = null;
        Statement stat = null;

        try {
            // 3. Execute Query Sql
            con.setAutoCommit(false);
            String sql = "select name, password, email from user where uuid = ? ";
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, "1");
            ResultSet rs = pstmt.executeQuery();

            // 4. do Data logic
            while(rs.next()){
                logger.error("name: "+ rs.getString("name"));
            }
            logger.error("End Query");

            // 5. close Resource
            rs.close();
            con.commit();
            pstmt.close();

        } catch(Exception e){
            logger.error(e.getStackTrace());
        } finally {
            // 6. Always close connection
            try{
                if(pstmt!=null){
                    pstmt.close();
                }
            }catch(SQLException e){
            }

            try{
                if (con != null){
                    con.close();
                }
            }catch (SQLException e){}
        }
    }
}

思考1: 可配置的用户名密码?

(1) 建立配置文件:db.properties

db.url=jdbc:mysql://localhost:3306/estore
db.username=clops
db.password=cscocmse
db.driver=com.mysql.jdbc.Driver
(2) Properties读取相应字段名称
 static {
        Properties prop = new Properties();     //第一步:新建一个配置文件对象
        try {
            Reader in = new FileReader("C:\\Develop\\IDEA\\eStore\\target\\classes\\db.properties");   //第二步:指定输入流为文件输入流
            prop.load(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
        JDBC_DRIVER = prop.getProperty("db.driver");    // getProperty(“driver”)用于获取配置文件 driver= 后的值
        DB_URL = prop.getProperty("db.url");
        USER = prop.getProperty("db.username");
        PASS = prop.getProperty("db.password");
    }

思考2:为何使用Class.forName("com.mysql.jdbc.driver"), 动态加载类,可以直接注册数据库驱动?

(1) 查看Driver类源码,发现在类静态块中调用了Driver.register()的注册方法

扫描二维码关注公众号,回复: 203615 查看本文章
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

思考3: 如何复用重复的建立连接,建立句柄,释放连接,释放句柄?

(1) 尝试1:提取到一个公用类中, 提取公共代码,将sql,操作类型作为input参数,ResultSet作为输出参数:


思考4: Connection的创建 方式, 除了DriverManager,还有什么?

(1) 跟据oracle官网的描述,还可以使用DataSource Objects来生成相应的Connections, 这也是官网推荐的从数据源更好的获取connection的方式。DataSource提供了Pool(池)和Distribute transactions(分布式事务)的功能实现,这些对于分布式系统有非常重要的作用。

ref: https://docs.oracle.com/javase/tutorial/jdbc/basics/sqldatasources.html


思考5:transaction(事务)的实现方式,几种隔离等级?

(1) conn.setAutoCommit(false)会取消自动提交执行句柄动作,直到conn.commit()才会按照一个事务,执行数据库处理

(2)事务处理失败后,如何回滚?可以在catch(Exception e)中,执行连接内回滚动作

conn.rollback();
 try {
        updateSales = con.prepareStatement(updateString);
        updateTotal = con.prepareStatement(updateStatement);
        con.setAutoCommit(false);
        for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
            updateSales.setInt(1, e.getValue().intValue());
            updateSales.setString(2, e.getKey());
            updateSales.executeUpdate();
            updateTotal.setInt(1, e.getValue().intValue());
            updateTotal.setString(2, e.getKey());
            updateTotal.executeUpdate();
            conn.commit();
        }
    } catch (SQLException e ) {
        JDBCTutorialUtilities.printSQLException(e);
        if (con != null) {
            try {
                System.err.print("Transaction is being rolled back");
         
            } catch(SQLException excep) {
                JDBCTutorialUtilities.printSQLException(excep);
            }
        }
    } finally {
        if (updateSales != null) {
            updateSales.close();
        }
        if (updateTotal != null) {
            updateTotal.close();
        }
        con.setAutoCommit(true);
    }

思考6:如何使用数据库连接池,分配Connection?

基本的实现原理为:使用某种容器(连接池)存储所有打开的Connection,需要的时候分配池中的Connection给调用方,close时返回给数据库连接池。这么做的原因是:打开,关闭数据库连接是耗资源的,所以复用Connection在高并发情况下,有重要意义。


是否使用连接池的性能测试:


(1) 开源connection pool主要使用几种:DBCP和C3P0的数据库连接池 

(2) DBCP是Apache的开源Connection Pool

package com.cisco.eStore.test.util;

import org.apache.commons.dbcp.BasicDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DbcpUtil {

    public static void main(String[] args){
        DbcpUtil dbcpUtil = new DbcpUtil();
        dbcpUtil.testDB();
    }
    private void testDB(){
        BasicDataSource dataSource = new BasicDataSource();

        // 1. Set jdbc configuration
        dataSource.setUsername("clops");
        dataSource.setPassword("cscocmse");
        dataSource.setUrl("jdbc:mysql://localhost:3306/estore");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");

        // 2. Set connection configuration
        dataSource.setInitialSize(10);
        dataSource.setMaxActive(100);

        // 3. Get Connection
        try {
            Connection conn = dataSource.getConnection();
            String sql="select * from user where name=?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, "sky");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()){
                String name = rs.getString("name");
                System.out.println("name: " + name);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

查看内部源码,发现内部存储数据库连接是使用GenericObjectPool存储的, GenericObjectPool到底有那些功能呢?


(3) C3P0

package com.cisco.eStore.test.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class C3P0Util {
    public static void main(String[] args){
        C3P0Util c3P0Util = new C3P0Util();
        c3P0Util.testDB();
    }

    private void testDB() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/estore");
            ds.setUser("clops");
            ds.setPassword("cscocmse");

            ds.setInitialPoolSize(10);
            ds.setMaxPoolSize(20);
            ds.setMinPoolSize(5);

            Connection conn = ds.getConnection();
            String sql="select * from user where name=?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, "sky");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()){
                String name = rs.getString("name");
                System.out.println("name: " + name);
            }

        } catch (PropertyVetoException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }


    }
}

(4)使用connection后如何归还connection给 connection pool呢?

I. DBCP中,实现的connection接口的PooledConnection中,在执行完数据库查询后,执行conn.close后,在内部会将连接归还给Pool。

  _pool.returnObject(this); // XXX should be guarded to happen at most once

II. C3P0中,实现connection pool的是自定义的BasicResourcePool类, 

    /*  keys are all valid, managed resources, value is a PunchCard */ 
    HashMap  managed = new HashMap();

    /* all valid, managed resources currently available for checkout */
    LinkedList unused = new LinkedList();

    /* resources which have been invalidated somehow, but which are */
    /* still checked out and in use.                                */
    HashSet  excluded = new HashSet();

    Map formerResources = new WeakHashMap();

    Set idleCheckResources = new HashSet();

    boolean force_kill_acquires = false;

    boolean broken = false;

//  long total_acquired = 0;

调用connection.close()的时候,会触发:

public synchronized void close() throws SQLException {
        try {
            if (!this.isDetached()) {
                NewPooledConnection npc = this.parentPooledConnection;
                this.detach();
                npc.markClosedProxyConnection(this, this.txn_known_resolved);
                this.inner = null;
            } else if (logger.isLoggable(MLevel.FINE)) {
                logger.log(MLevel.FINE, this + ": close() called more than once.");
            }
        }
}

思考4: 如何使用数据库事务,如何保证不死锁?

思考4: 避免每次重复建立释放连接?

思考3: SQL 连接查询的数量限制?如何测试?

思考4: 如何使用数据库事务,如何保证不死锁?


猜你喜欢

转载自blog.csdn.net/blueskypan/article/details/80183651