JDBC學習1:詳解JDBC使用

五月的倉頡發表於2015-10-02

什麼是JDBC

JDBC(Java Database Connectivity),即Java資料庫連線,是一種用於執行SQL語句的Java API,可以為多種關聯式資料庫提供同一訪問,它由一組用Java語言編寫的類和介面組成。JDBC提供了一種基準,根據這種基準可以構建更高階的工具和介面,使資料庫開發人員能夠編寫資料庫應用程式。總而言之,JDBC做了三件事:

1、與資料庫建立連線

2、傳送運算元據庫的語句

3、處理結果

 

JDBC簡單示例

下面的程式碼演示瞭如何利用JDBC從資料庫中查詢若干條符合要求的資料出來,使用的資料庫是MySql。

1、建立一個資料庫和一張表,我的習慣是在CLASSPATH底下建立一個.sql的檔案用於存放sql語句

create database school;

use school;

create table student
(
    studentId            int                 primary key    auto_increment    not null,
    studentName        varchar(10)                                                            not null,
    studentAge        int,
    studentPhone    varchar(15)
)

insert into student values(null,'Betty', '20', '00000000');
insert into student values(null,'Jerry', '18', '11111111');
insert into student values(null,'Betty', '21', '22222222');
insert into student values(null,'Steve', '27', '33333333');
insert into student values(null,'James', '22', '44444444');
commit;

2、建立一個.properties檔案用於儲存MySql連線的幾個屬性。為什麼要建立.properties而不在程式碼裡面寫死,由於這個並不是Java設計模式的分類,就不細講了,只需要記住:從設計的角度看,把內容寫在配置檔案中永遠好過把內容寫死在程式碼中

mysqlpackage=com.mysql.jdbc.Driver
mysqlurl=jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=utf-8
mysqlname=root
mysqlpassword=root

3、根據表欄位建立實體類

public class Student
{
    private int        studentId;
    private String    studentName;
    private int        studentAge;
    private String    studentPhone;
    
    public Student(int studentId, String studentName, int studentAge,
            String studentPhone)
    {
        this.studentId = studentId;
        this.studentName = studentName;
        this.studentAge = studentAge;
        this.studentPhone = studentPhone;
    }
    
    public int getStudentId()
    {
        return studentId;
    }

    public String getStudentName()
    {
        return studentName;
    }

    public int getStudentAge()
    {
        return studentAge;
    }

    public String getStudentPhone()
    {
        return studentPhone;
    }

    public String toString()
    {
        return "studentId = " + studentId + ", studentName = " + studentName + ", studentAge = " +
                studentAge + ", studentPhone = " + studentPhone;
    }
}

4、寫一個DBConnection類專門用於向外提供資料庫連線。我這裡用了MySql,所以只有一個mysqlConnection,如果還用到了Oracle,當然還可以向外提供一個oracleConnection。把這些連線設為全域性的可能有人會想是否會有執行緒安全問題,這是一個很好的問題。那因為我們只從Connection裡面讀取一個PreparedStatement出來,而不會去寫它,只讀不修改,是不會引發執行緒安全問題的。另外把Connection設定為static的保證了Connection在記憶體中只有一份,不會佔多大資源,每次使用完不呼叫close()方法去關閉它也沒事。至於把.properties檔案讀到記憶體中,可以參看http://www.cnblogs.com/xrq730/p/4847337.html我之前寫的文章的最後

public class DBConnection
{    
    private static Properties properties = new Properties();
    
    static
    {
        /** 要從CLASSPATH下取.properties檔案,因此要加"/" */
        InputStream is = DBConnection.class.getResourceAsStream("/db.properties");
        try
        {
            properties.load(is);
        } 
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    
    /** 這個mysqlConnection只是為了用來從裡面讀一個PreparedStatement,不會往裡面寫資料,因此沒有執行緒安全問題,可以作為一個全域性變數 */
    public static Connection mysqlConnection = getConnection();
    
    public static Connection getConnection()
    {
        Connection con = null;
        try
        {
            Class.forName((String)properties.getProperty("mysqlpackage"));
            con = DriverManager.getConnection((String)properties.getProperty("mysqlurl"), 
                    (String)properties.getProperty("mysqlname"), 
                    (String)properties.getProperty("mysqlpassword"));
        } 
        catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        } 
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        return con;
    }
}    

5、建立一個工具類,用來寫各種方法,專門和資料庫進行互動。這種工具類最好搞成單例的,這樣就不用每次去new出來了(實際上new出來也沒看出來會有什麼好處),節省資源

package com.xrq.test11;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class StudentManager
{
    private static StudentManager instance = new StudentManager();
    
    private StudentManager()
    {
        
    }
    
    public static StudentManager getInstance()
    {
        return instance;
    }
    
    public List<Student> querySomeStudents(String studentName) throws Exception
    {
        List<Student> studentList = new ArrayList<Student>();
        Connection connection = DBConnection.mysqlConnection;
        PreparedStatement ps = connection.prepareStatement("select * from student where studentName = ?");
        ps.setString(1, studentName);
        ResultSet rs = ps.executeQuery();
        
        Student student = null;
        while (rs.next())
        {
            student = new Student(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getString(4));
            studentList.add(student);
        }
        
        ps.close();
        rs.close();
        return studentList;
    }
}

6、寫個main函式去呼叫一下

List<Student> studentList = StudentManager.getInstance().querySomeStudents("Betty");
for (Student student : studentList) {
    System.out.println(student);
}

7、看一下執行結果,和資料庫裡面的一樣,成功

studentId = 1, studentName = Betty, studentAge = 20, studentPhone = 00000000
studentId = 3, studentName = Betty, studentAge = 21, studentPhone = 22222222

 

為什麼要使用佔位符"?"

看一下第5點,大家一定注意到了,寫sql語句的時候用了"?"佔位符,當然有美化程式碼的因素,不用佔位符就要在括號裡寫"+"來拼接引數,如果要拼接的引數一多,程式碼肯定不好看,可讀性不強。但是除了這個原因,還有另外一個重要的原因,就是避免一個安全問題。假設我們不用佔位符寫sql語句,那"querySomeStudents(String name) throws Exception"方法就要這麼寫:

public List<Student> querySomeStudents(String studentName) throws Exception
{
    List<Student> studentList = new ArrayList<Student>();
    Connection connection = DBConnection.mysqlConnection;
    PreparedStatement ps = connection.prepareStatement("select * from student where studentName = '" + studentName + "'");
    ResultSet rs = ps.executeQuery();
        
    Student student = null;
    while (rs.next())
    {
        student = new Student(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getString(4));
        studentList.add(student);
    }
        
    ps.close();
    rs.close();
    return studentList;
}

上面的main函式一樣可以獲取到兩條資料,但是問題來了,如果我這麼呼叫呢:

public static void main(String[] args) throws Exception
    {
        List<Student> studentList = new ArrayList<Student>();
        studentList = StudentManager.getInstance().querySomeStudents("' or '1' = '1");
        for (Student student : studentList)
            System.out.println(student);
    }

看下執行結果:

studentId = 1, studentName = Betty, studentAge = 20, studentPhone = 00000000
studentId = 2, studentName = Jerry, studentAge = 18, studentPhone = 11111111
studentId = 3, studentName = Betty, studentAge = 21, studentPhone = 22222222
studentId = 4, studentName = Steve, studentAge = 27, studentPhone = 33333333
studentId = 5, studentName = James, studentAge = 22, studentPhone = 44444444

為什麼?看下拼接之後的sql語句就知道了:

select * from student where studentName = '' or '1' = '1'

'1'='1'永遠成立,所以前面的查詢條件是什麼都沒用。這種問題是有應用場景的,不是隨便寫一下。Java越來越多的用在Web上,既然是Web,那麼查詢的時候有一種情況就是使用者輸入一個條件,後臺獲取到查詢條件,拼接sql語句查資料庫,有經驗的使用者完全可以輸入一個"‘'' or '1' = '1",這樣就拿到了庫裡面的所有資料了。

 

JDBC事物

談資料庫必然離不開事物,事物簡單說就是"要麼一起成功,要麼一起失敗"。那簡單往前面的StudentManager裡面寫一個插入學生資訊的方法:

public void addStudent(String studentName, int studentAge, String studentPhone) throws Exception
{
    Connection connection = DBConnection.mysqlConnection;
    PreparedStatement ps = connection.prepareStatement("insert into student values(null,?,?,?)");
    ps.setString(1, studentName);
    ps.setInt(2, studentAge);
    ps.setString(3, studentPhone);
    if (ps.executeUpdate() > 0)
        System.out.println("新增學生資訊成功");
    else
        System.out.println("新增學生資訊失敗");    
}
public static void main(String[] args) throws Exception
{
    StudentManager.getInstance().addStudent("Betty", 17, "55555555");
}

執行就不執行了,反正最後結果是"新增學生資訊成功",資料庫裡面多了一條資料。注意一下:

1、增刪改用的是executeUpdate()方法,因為增刪改認為都是對資料庫的更新

2、查詢用的是executeQuery()方法,看名字就知道了"Query",查詢嘛

可能有人注意到一個問題,就是Java程式碼在insert後並沒有對事物進行commit,資料就新增進資料庫了,也能查出來,這是為什麼呢?因為JDK的Connection設定了事物的自動提交。如果在addStudent(...)方法裡面這麼寫:

Connection connection = DBConnection.mysqlConnection;
connection.setAutoCommit(false);

autoCommit這個屬性原來是true,JDK自然會幫助開發者自動提交事物了。OK,如果要改成手動提交事物的程式碼,那麼應該這麼寫addStudent(...)方法:

public void addStudent(String studentName, int studentAge, String studentPhone) throws Exception
{
    Connection connection = DBConnection.mysqlConnection;
    connection.setAutoCommit(false);
    PreparedStatement ps = connection.prepareStatement("insert into student values(null,?,?,?)");
    ps.setString(1, studentName);
    ps.setInt(2, studentAge);
    ps.setString(3, studentPhone);
    try
    {
        ps.executeUpdate();
        connection.commit();
    } 
    catch (Exception e)
    {
        e.printStackTrace();
        connection.rollback();
    }
}

要記得拋異常的時候利用rollback()方法回滾掉事物。

相關文章