天天看點

MySQL——JDBC筆記

JDBC

​ ​ ​ ​ ​ 為簡化開發人員對資料庫的操作,提供的一種 Java 資料庫連接配接規範,稱為JDBC。

​ ​ ​ ​ ​ 對開發人員隻需了解接口操作即可。

​ ​ ​ ​ ​ 需要 java.sql,javax.sql 以及一個資料庫驅動包。

​ ​ ​ ​ ​

JDBC項目的建立

​ ​ ​ ​ ​ 1. 建立一個新的項目

​ ​ ​ ​ ​ 2. 将 jdbc 驅動 jar 包添加至一個新的 lib 目錄中,右鍵點選

Add as Library

添加至項目的庫中

​ ​ ​ ​ ​ 3. 編寫測試代碼

//mysql 8.0.23 jdbc 8.0.23
public class JdbcFirstDemo {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.加載驅動
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2.使用者資訊和url
        String url = "jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true";
        String username = "root";
        String password = "123456";

        //3.連接配接成功,資料庫對象  Connection 代表資料庫
        Connection connection = DriverManager.getConnection(url,username,password);

        //4.執行SQL的對象,Statement 執行SQL的對象
        Statement statement = connection.createStatement();

        //5.執行SQL的對象去執行SQL語句,并産生結果
        String sql = "SELECT * FROM users";

        ResultSet resultSet = statement.executeQuery(sql); // 傳回的結果集,封裝了查詢出的全部結果

        while(resultSet.next()){
            System.out.println("id=" + resultSet.getObject("id"));
            System.out.println("name=" + resultSet.getObject("NAME"));
            System.out.println("password=" + resultSet.getObject("PASSWORD"));
            System.out.println("email=" + resultSet.getObject("email"));
            System.out.println("birthday=" + resultSet.getObject("birthday"));
        }

        //6.釋放連接配接
        resultSet.close(); //最後使用的先釋放
        statement.close();
        connection.close();
    }
}
           

測試代碼詳解

​ ​ ​ ​ ​ 1. 加載驅動

Class.forName("com.mysql.cj.jdbc.Driver");  // 固定寫法,為MySQL8.0以上使用,8.0以下删除cj
           

​ ​ ​ ​ ​ 2. url

String url = "jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true";

// mysql 預設端口号3306
// jdbc:mysql://主機位址:端口号/資料庫名?參數1&參數2&參數3

// oracle 預設端口号1521
// jdbc:oracle:thin:@主機位址:端口号:sid
           

​ ​ ​ ​ ​ 3. 連接配接對象

Connection connection = DriverManager.getConnection(url,username,password);
// connection 代表資料庫,資料庫能做的 connection 都能做

connection.rollback() // 事務復原
connection.commit() // 事務送出
connection.setAutoCommit() // 資料庫設定自動送出
           

​ ​ ​ ​ ​ 4. 執行對象

Statement statement = connection.createStatement();
// statement 執行SQL語句

statement.execute() // 可以執行任何SQL語句,相應效率較低
statement.executeQuery() // 執行查詢操作,傳回一個結果集 ResultSet
statement.executeUpdate() // 執行更新、插入、删除操作,傳回一個受影響的行數
statement.executeBatch() // 批處理,執行多個SQL
           

​ ​ ​ ​ ​ 5. ResultSet查詢的結果集

ResultSet resultSet = statement.execute(sql); 
// 結果集中包含所有的查詢結果

// 獲得指定的資料類型
resultSet.getObject() // 在不知道列類型時使用
resultSet.getInt()
resultSet.getString()
resultSet.getFloat()
resultSet.getDate()
...
    
// 可以對結果集 resultSet 進行一系列操作
resultSet.next() // 移動到下一個資料
resultSet.beforeFirst() // 移動到最前面
resultSet.afterLast() // 移動到最後面
resultSet.previous() // 移動到前一行
resultSet.absolute(row) // 移動到指定行
           

​ ​ ​ ​ ​ 6. 釋放資源

// 使用完畢後一定要釋放連接配接,特别是connection十分占用
resultSet.close();
statement.close();
connection.close();
           

提取工具類

  • 将使用者資訊和 url 提取至建立配置檔案 db.properties 中
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
           
  • 編寫方法
public class JdbcUtils {

    private static String driver = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;

    static {

        try{
            //通過獲得Jdbc類加載器,擷取資源:db.properties并産生輸入流,在src目錄下直接調用,否則需要寫路徑
            InputStream input = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");

            //建立Properties對象并加載輸入流,便于使用Properties的方法
            Properties properties = new Properties();
            properties.load(input);

            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");

            // 驅動隻用加載一次
            Class.forName(driver);  //已提取了driver資訊,不需要再寫包名
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //擷取連接配接方法 getConnection
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }

    //釋放連接配接方法 release
    public static void release(Connection conn, Statement st, ResultSet rs){
        
        //按順序釋放
        if (rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st!=null){
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
           

使用提取的工具類

​ ​ ​ ​ ​ 插入語句,增删改同理

public class TestInsert {
    public static void main(String[] args) {

        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            //擷取連接配接
            conn = JdbcUtils.getConnection();//在try中的變量,在finally中無法釋放,需要在外部建立

            //建立statement對象
            st = conn.createStatement();

            //通過st執行sql
            String sql = "INSERT INTO ...";
            int i = st.executeUpdate(sql); //傳回受影響行數
            if(i > 0){
                System.out.println("插入成功");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}
           

​ ​ ​ ​ ​ 查詢語句

public class TestSelect {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();

            String sql = "SELECT * FROM ...";
            rs = st.executeQuery(sql); 
            
            while(rs.next()){
                System.out.println(rs.getString("name"));
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}
           

SQL 的注入問題

​ ​ ​ ​ ​ SQL 注入的本質是 SQL 語句會被拼接,如需要判斷使用者名及密碼是否和資料庫中相同時,隻需要輸入特定使用者名就可以欺騙資料庫。

注入舉例

​ ​ ​ ​ ​ 查詢登入賬号密碼的語句如下

String sql = "SELECT * FROM users WHERE `name` = '" + username +   "' AND `password` = '" password + "'";
           

​ ​ ​ ​ ​ 對應SQL語句為

SELECT * FROM users WHERE `name` = 'username' AND `password` = 'password'
           

​ ​ ​ ​ ​ 當輸入的 username 和 password為

' or 1=1 '

時,相應的 SQL 語句為

SELECT * FROM users WHERE `name` = '' or 1=1 '' AND `password` = '' or 1=1 ''
           

​ ​ ​ ​ ​ 由于 1=1 恒成立,那麼查詢出來的結果即為所有的賬戶和對應的密碼。

PreparedStatement 對象

​ ​ ​ ​ ​ PreparedStatement 對象可以防止 SQL 注入,且效率更高。

​ ​ ​ ​ ​ PreparedStatement 的本質,把傳入的參數當做字元處理。

//使用PreparedStatement插入
public class TestPreparedStatement {
    public static void main(String[] args) {

        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();

            //差別
            //使用 ? 占位符代替參數
            String sql = "INSERT INTO users(id, `name`, `password`, `email`, `birthday`) values(?,?,?,?,?)";
            pst = conn.prepareStatement(sql); //預編譯 sql,先寫 sql 不執行

            //手動給參數指派
            pst.setInt(1,1002); //給第一個占位符賦 int 類型的值,插入 id 為1002
            pst.setString(2,"小明"); //給第二個占位符賦 String 類型的值,插入 name 為小明
            pst.setString(3,"123456");
            pst.setString(4,"[email protected]");

            //sql.Date  資料庫 java.sql.Date
            //util.Date Java  Date().getTime() 獲得時間戳
            pst.setDate(5,new java.sql.Date(new Date().getTime()));

            //執行 sql
            int i = pst.executeUpdate(); //不需要傳參

            if(i > 0){
                System.out.println("插入成功");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            JdbcUtils.release(conn,pst,rs);
        }
    }
}
           

資料庫連接配接池

​ ​ ​ ​ ​ 資料庫連接配接——執行——釋放一個流程中,連接配接和釋放非常浪費系統資源,是以出現池化技術,預先準備好一些資源,需要調用這些準備好的資源時可以避免連接配接釋放,直接使用。

​ ​ ​ ​ ​ 連接配接池實作接口 DataSource,對此較為常用的兩個實作類:DBCP,C3P0,Druid

​ ​ ​ ​ ​ 使用該資料庫連接配接池後,在項目開發中不需要編寫連接配接資料庫代碼。

DBCP

​ ​ ​ ​ ​ 需要 commons-dbcp、commons-pool 兩種jar包

配置檔案

dbcpconfig.properties

#連接配接設定
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456

#初始化連接配接
initialSize=10

#最大連接配接數量
maxActive=50

#最大空閑連接配接
maxIdle=20

#最小空閑連接配接
minIdle=5

#逾時等待時間以毫秒為機關 6000毫秒/1000等于60秒
maxWait=60000
#JDBC驅動建立連接配接時附帶的連接配接屬性屬性的格式必須為這樣:【屬性名=property;】
#注意:user 與 password 兩個屬性會被明确地傳遞,是以這裡不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=UTF8

#指定由連接配接池所建立的連接配接的自動送出(auto-commit)狀态。
defaultAutoCommit=true

#driver default 指定由連接配接池所建立的連接配接的隻讀(read-only)狀态。
#如果沒有設定該值,則“setReadOnly”方法将不被調用。(某些驅動并不支援隻讀模式,如:Informix)
defaultReadOnly=

#driver default 指定由連接配接池所建立的連接配接的事務級别(TransactionIsolation)。
#可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
           

方法實作

public class JdbcUtils_DBCP {

    private static DataSource dataSource = null;

    static {

        try{
            InputStream input = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");

            //建立Properties對象并加載輸入流,便于使用Properties的方法
            Properties properties = new Properties();
            properties.load(input);

            //建立資料源 工廠模式建立資料源
            dataSource = BasicDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //擷取連接配接方法,getConnection()
    public static Connection getConnection() throws SQLException {
            return dataSource.getConnection();  //從資料源擷取連接配接
    }


    //釋放連接配接,與自寫工具類相同
    public static void release(Connection conn, Statement st, ResultSet rs){
        if (rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st != null){
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
           

方法調用

public class TestDBCP {
    public static void main(String[] args) {

        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            //擷取連接配接
            conn = JdbcUtils_DBCP.getConnection();//在try中的變量,在finally中無法釋放,需要在外部建立

            //建立statement對象
            st = conn.createStatement();

            //通過st執行sql
            String sql = "INSERT INTO users(id, `name`, `password`, `email`, `birthday`) values(4,'趙六','123456','[email protected]','1988-12-08')";
            int i = st.executeUpdate(sql); //傳回受影響行數
            if(i > 0){
                System.out.println("插入成功");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_DBCP.release(conn,st,rs);
        }
    }
}
           

C3P0

​ ​ ​ ​ ​ C3P0支援多套資料源,需要 c3p0,mchange-commons-java 兩種 jar 包

c3p9-config.xml

<?xml version="1.0" encoding="UTF-8" ?>

<c3p0-config>
    <!--
    如果在代碼中使用如下寫法
    ComboPooledDataSource ds = new ComboPooledDataSource();
    則使用預設的配置讀取連接配接池對象 -->
    <default-config>
        <!--  連接配接參數 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true</property>
        <property name="user">root</property>
        <property name="password">root123456</property>

        <!-- 連接配接池參數 -->
        <!--初始化的申請的連接配接數量-->
        <property name="initialPoolSize">5</property>
        <!--最大的連接配接數量-->
        <property name="maxPoolSize">10</property>
        <!--連接配接逾時時間-->
        <property name="checkoutTimeout">3000</property>
    </default-config>

    <!--
    如果在代碼中使用如下寫法
    ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");
    則使用如下配置讀取連接配接池對象 -->
    <named-config name="MySQL">
        <!--  連接配接參數 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true</property>
        <property name="user">root</property>
        <property name="password">root123456</property>

        <!-- 連接配接池參數 -->
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">1000</property>
    </named-config>
</c3p0-config>
           
public class JdbcUtils_C3P0 {

    private static DataSource dataSource = null;

    static {

        try{
            //xml 不需要讀取,自動比對
            //建立資料源
            dataSource = new ComboPooledDataSource();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //擷取連接配接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();  //從資料源擷取連接配接
    }


    //釋放連接配接
    public static void release(Connection conn, Statement st, ResultSet rs){
        if (rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st != null){
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
           
public class TestC3P0 {
    public static void main(String[] args) {

        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            //擷取連接配接
            conn = JdbcUtils_C3P0.getConnection();//在try中的變量,在finally中無法釋放,需要在外部建立

            //建立statement對象
            st = conn.createStatement();

            //通過st執行sql
            String sql = "INSERT INTO users(id, `name`, `password`, `email`, `birthday`) values(4,'趙六','123456','[email protected]','1988-12-08')";
            int i = st.executeUpdate(sql); //傳回受影響行數
            if(i > 0){
                System.out.println("插入成功");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_C3P0.release(conn,st,rs);
        }
    }
}