1. 程式人生 > 其它 >回顧總結21

回顧總結21

JDBC、連線池

JDBC

JDBC(java.sql和javax.sql)是為了訪問不同的資料庫提供了統一的介面(JDBC API),這些介面統一和規範了應用程式與資料庫的連線,執行SQL語句,並得到返回結果等各類操作,為使用者遮蔽了細節問題。

Java程式設計師使用JDBC,可以連線任何提供了JDBC驅動程式的資料庫系統,從而完成對資料庫的各種操作。

Java可以不直接連線資料庫,只提供一個介面,接口裡面有一系列的方法讓資料庫層面去實現,從而獲得實現該介面的類。而我們就可以使用這些類來操作相應的資料庫

JDBC連線資料庫

方式一:

//JDBC編寫步驟
//1.註冊驅動
Driver driver = new Driver();
//2.獲取連線
String url = "jdbc:mysql://localhost:3306/db02";
//將 資料庫的使用者名稱和密碼放入到Properties物件
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","1347");
//獲取連線
Connection connect = driver.connect(url,properties);
//3.執行SQL
String sql = "insert into actor values(null,'劉德華','男','1970-11-11','788787')";
Statement statement = connect.createStatement();
//statement用於執行靜態sql語句並返回生成結果的資訊
int rows = statement.executeUpdate(sql);
System.out.println(rows > 0 ? "成功" : "失敗" );
//4.關閉連線
statement.close();
connect.close();

方式二:

//通過反射載入Driver類,動態載入,更加的靈活,減少依賴性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();

方式三:

//使用DriverManager來管理Driver,有更好的擴充套件性
//1.先獲取一個Driver物件
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
//2.註冊Driver
DriverManager.registerDriver(driver);
//3.得到url和配置檔案
String url = "jdbc:mysql://localhost:3306/db02";
//4.將 資料庫的使用者名稱和密碼放入到Properties物件
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","1347");
//5.獲取連線,使用DriverManager
Connection connection = DriverManager.getConnection(url,properties);

方式四:

最常用

//使用Class.forName會自動完成註冊驅動
//底層進行了優化,會幫我們進行driver的註冊
//在mysql驅動5.1.6以後,Class.forName也可省略。
//因為jdk1.5以後使用了jdbc4,不再需要顯示呼叫class.forName,而是自動呼叫
//在jar包下的META-INF\services\java.sql.Driver文字中的類名去註冊
//但是老師建議寫上這句話,更加明確,可讀性高。
Class.forName("com.mysql.jdbc.Driver");
//得到url和配置資訊
String url = "jdbc:mysql://localhost:3306/db02";
//將 資料庫的使用者名稱和密碼放入到Properties物件
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","1347");
//獲取連線
Connection connection = DriverManager.getConnection(url,prop

方式五:

建議使用

//得到url和配置資訊
//將 資料庫的使用者名稱和密碼放入到Properties物件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
//會自動完成註冊驅動.建議寫上
Class.forName(driver);
//獲取連線
Connection connection = DriverManager.getConnection(url,user,password);

ResultSet結果集

  1. 表示資料庫結果集的資料表,通常通過執行查詢資料庫的語句生成
  2. ResultSet物件保持一個游標指向其當前的資料行,最初,游標位於表頭
  3. next方法將游標移動到下一行,並且由於ResultSet物件中沒有更多行時返回false,因此可以在while迴圈中使用迴圈來遍歷結果集

獲取查詢表的資料,通過exectuQuery返回一個ResultSet集合,集合中儲存了查詢表的資料,我們可以通過方法來進行讀取

每一個ResultSet底層是ArrayList,ArrayList每一行的資料是使用ByteArrayRow儲存的

//獲得連線資料庫的引數,賬號、密碼、資料庫、驅動
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));

String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");

//1.註冊驅動,通過反射方式
Class.forName(driver);
//2.獲取資料庫連線
Connection connection = DriverManager.getConnection(url,user,password);
//3.寫sql語句,獲得操作sql語句的statement
String sql = "select name,pwd from admin";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//獲取結果集,並列印輸出
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
    String name = resultSet.getString(1);
    String pwd = resultSet.getString(2);
    System.out.println(name + "\t" + pwd);
}
//關流
resultSet.close();
preparedStatement.close();
connection.close();

Statement

  1. Statement物件,用於執行SQL語句並返回其生成結果
  2. 連線建立後,需要對資料庫進行訪問,執行命令或是SQL語句,可以通過
    • Statement【存在SQL注入】
    • PreparedStatement【預處理】
    • CallableStatement【儲存過程】
  3. Statement物件執行SQL語句,存在SQL注入風險
  4. SQL注入是利用某些系統沒有對使用者輸入的資料進行充分的檢查,而在使用者輸入資料注入非法的SQL語句段或命令,惡意攻擊資料庫
  5. 防範SQL注入,只要用PreparedStatement取代Statement就可以了

SQL注入

通過傳入的使用者名稱和密碼,改動查詢條件,達到繞過驗證的操作

比如一條查詢語句是 SELECT * FROM WHERE id = '賬號' AND pwd = '密碼'
只有當賬號密碼和資料庫中的資料對應上,才能登入進去
現在我們嘗試SQL注入,改變查詢條件,輸入id = '(1'or)' AND pwd = '(or'1' = '1)'
將查詢條件改為了 SELECT * FROM WHERE (id = '1') OR ('AND pwd =') OR ('1' = '1')

preparedStatement優點

  1. 不再使用 + 拼接sql語句,減少語法錯誤
  2. 有效解決了sql注入問題
  3. 大大減少了編譯次數,效率較高
//獲得連線資料庫的引數,賬號、密碼、資料庫、驅動
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));

String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");

//1.註冊驅動,通過反射方式
Class.forName(driver);
//2.獲取資料庫連線
Connection connection = DriverManager.getConnection(url,user,password);
//增
String sql = "insert into admin values (?,?)";
//改
//String sql = "update admin set name = ? where name = ?";
//刪
//String sql = "delete from admin where name = ?";
//獲得操作sql語句的statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"king");
preparedStatement.setString(2,"tom");
//返回語句執行結果
int i = preparedStatement.executeUpdate();
System.out.println(i > 0 ? "成功" : "失敗" );
//關流
preparedStatement.close();
connection.close();

JDBC API小結

DriverManager驅動管理類

getConnection(url,user,pwd) :獲取到連線

Connection介面

createStatement:建立Statement物件,有sql注入風險
prepareStatement(sql):生成preparedStatement預處理物件,解決了sql注入

Statement介面

executeUpdate(sql):執行dml語句,返回被影響的行數
executeQuery(sql):執行查詢,返回ResultSet物件
execute(sql):執行任意的sql,返回布林值

PreparedStatement介面

executeUpdate(sql):執行dml語句,返回被影響的行數
executeQuery(sql):執行查詢,返回ResultSet物件
execute(sql):執行任意的sql,返回布林值
setXxx(佔位符索引,佔位符值):用來替換前面的佔位符,接收型別為Xxx
setObject(佔位符索引,佔位符的值):當做物件處理,接收型別為Object

ResultSet結果集

next:向下移動一行,如果沒有下一行,就返回false
previous:向上移動一行,如果沒有上一行,返回false
getXxx(列的索引名)或者getXxx(列名):獲取對應列的值,接收型別為Xxx
getObject(列的索引名)或者getXxx(列名):同上,接收型別為Object

事務

JDBC開啟事務,不使用start transaction,使用setautocommit為false,停止事務的自動提交相當於開啟你自己的事務。

批處理

  1. 當需要成批插入或者更新記錄時,可以採用Java的批量更新機制,這一機制允許多條語句一次性提交給資料庫批量處理,通常情況下比單獨提交處理更有效率

  2. JDBC的批處理語句包括下面方法

    addBatch:新增需要批量處理的SQL語句或引數

    executeBatch:執行批量處理語句

    clearBatch:清空批處理包的語句

  3. 批處理底層是ArrayList,通過將sql語句放入arrayList,滿足條件後,將arrayList裡的sql語句一次性傳輸給mysql執行。

details

  1. 如果要使用批處理,需要在配置檔案的url新增?rewriteBatchedStatements=true

  2. sql語句要寫的規範。
    例如insert into admin values(null,?,?),批處理不生效。
    在values和插入資料間加個空格就好了。
    insert into admin values (null,?,?)

資料庫連線池

傳統連線Connection問題分析

  1. 傳統的JDBC資料庫連線使用DriverManager獲取,每次向資料庫建立連線的時候都要將Connection載入到記憶體中,再驗證IP地址,使用者名稱和密碼(0.05s~1s時間)。需要資料庫連線的時候,就向資料庫要求一個,頻繁的進行資料庫連線操作將佔用很多的系統資源,容易造成伺服器崩潰
  2. 每一次資料庫連線,使用完後都得斷開,如果程式出現異常而未能關閉,將導致資料庫記憶體洩露,最終將導致重啟資料庫
  3. 傳統獲取連線的方式,不能控制建立的連線數量,如連線過多,也可能導致記憶體洩露,Mysql崩潰
  4. 解決傳統開發中的資料庫連線問題,可以採用資料庫連線池技術。(connection pool)

資料庫連線池

  1. 預先在緩衝池中放入一定數量的連線,當需要建立資料庫連線時,只需從"緩衝池"中取出一個,使用完畢之後再放回去
  2. 資料庫連線池負責分配,管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是重新建立一個。
  3. 當應用程式向連線池請求的連線數超過最大連線數量時,這些請求將被加入到等待佇列中

資料庫連線池種類

JDBC的資料庫連線池使用javax.sql.DataSource來表示,DataSource只是一個介面,該介面通常由第三方提供實現

  1. C3P0資料庫連線池:速度相對較慢,穩定性不錯(應用在hibernate,spring)
  2. DBCP資料庫連線池:速度相對C3P0較快,但是不穩定
  3. Proxool資料庫連線池:有監控連線池狀態的功能,穩定性較C3P0差一點
  4. BoneCP資料庫連線池:速度快
  5. Druid(德魯伊)是阿里提供的資料庫連線池,集DBCP、C3P0、Proxool優點於一身的資料庫連線池

傳統方式連線資料庫

#mysql.properties配置檔案
url=jdbc:mysql://localhost:3306/db02?rewriteBatchedStatements=true
user=root
password=1347
driver=com.mysql.jdbc.Driver
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));

String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");

Class.forName(driver);
Connection connection = DriverManager.getConnection(url,user,password);

C3P0連線池

首先要引用對應jar包

方式一

Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));

String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
//建立資料來源物件
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//給資料來源設定相關的引數
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//設定初始化連線數和最大連線數
comboPooledDataSource.setInitialPoolSize(10);
comboPooledDataSource.setMaxConnectionAge(50);
//獲取連線,從連線池中拿取連線
Connection connection = comboPooledDataSource.getConnection();

方式二

//使用c3p0的xml配置檔案,將所有資訊寫入xml檔案,獲取連線池的時候直接讀取//xml檔案內容如下,檔名為c3p0-config.xml<c3p0-config>    <!-- 連線池的名稱 -->    <named-config name="comboPool_name">        <!-- 驅動類 -->        <property name="driverClass">com.mysql.jdbc.Driver</property>        <!-- url -->        <property name="jdbcUrl">jdbc:mysql://localhost/db02</property>        <!-- 資料庫使用者名稱 -->        <property name="user">root</property>        <!-- 密碼 -->        <property name="password">1347</property>        <!-- 每次增長的連線數 -->        <property name="acquireIncrement">5</property>        <!-- 初始的連線數 -->        <property name="initialPoolSize">10</property>        <!-- 最小連線數 -->        <property name="minPoolSize">5</property>        <!-- 最大連線數 -->        <property name="maxPoolSize">10</property>        <!-- 可連線的最多的命令物件數 -->        <property name="maxStatements">5</property>        <!-- 每個連線物件可連線的最多的命令物件數 -->        <property name="maxStatementsPerConnection">2</property>    </name-config></c3p0-config>
//有了xml檔案之後使用c3p0連線池//建立資料來源物件(連線池)ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("comboPool_name");//獲取連線,從連線池中拿取連線Connection connection = comboPooledDataSource.getConnection();

Druid連線池

引入druid的jar包

#druid.properties配置檔案#key=valuedriverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/db02?rewriteBatchedStatements=true#url=jdbc:mysql://localhost:3306/db02username=rootpassword=1347#initial connection SizeinitialSize=10#min idle connection sizeminIdle=5#max active connection sizemaxActive=20#max wait time (5000 mil seconds)maxWait=5000
//引入配置檔案資料Properties properties = new Properties();properties.load(new FileInputStream("src\\druid.properties"));//使用Druid建立資料來源(連線池)傳入配置檔案引數DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);Connection connection = dataSource.getConnection();connection.close();

Apache--DBUtils

原始方法完成資料轉換

連線的事情告一段落之後,我們開始對效能和便利方面進行學習

對於resultSet的許多問題

  1. 結果集合connection是關聯的,即如果關閉連線,就不能使用結果集
  2. 結果集不利於資料管理,只能使用一次
  3. 使用返回資訊也不方便

之前我們是連線後通過JDBC把java語句轉化成sql語句,去資料庫中盡心查詢,然後返回資料庫查詢的內容。然後關閉連線。

這會導致很多不方便的操作。我們現在需要的是在java層面,獲取資料,並且進行操作。在資料庫的連線關閉之後,我們也能留住資料。
因此我們可以根據資料庫中的表,去設計對應的一個類。表對應類,欄位對應欄位,獲取資料對應方法。一個例項物件對應一條資料庫記錄,然後存放到ArrayList集合中

//Actor表對應的類public class Actor {    private Integer id;    private String name;    private String sex;    private Date bornDate;    private String phone;    //還有無參構造、有參構造、getset、toString}
//
Connection connection = null;
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
ArrayList<Actor> list = new ArrayList<>();

try {
    connection = JDBCUtilsByDruid.getConnection();
    preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setInt(1,1);
    set = preparedStatement.executeQuery();
    while (set.next()){
        int id = set.getInt("id");
        String name = set.getString("name");
        String sex = set.getString("sex");
        Date bornDate = set.getDate("bornDate");
        String phone = set.getString("phone");
        list.add(new Actor(id,name,sex,bornDate,phone));
    }
    System.out.println(list);
} catch (Exception e) {
    e.printStackTrace();
}finally {
    JDBCUtilsByDruid.close(set,preparedStatement,connection);
}

Apache介紹

  1. commons-dbutils是Apache組織提供的一個開源JDBC工具類庫,它是對JDBC的封裝,使用dbutils能極大簡化jdbc編碼的工作量

DbUtils類

  1. QueryRunner類:該類封裝了SQL的執行,是執行緒安全的,開源實現增、刪、改、查、批處理
  2. 使用QueryRunner類實現查詢
  3. ResultSetHandler介面:該介面用於處理java.sql.ResultSet,將資料按要求轉換為另一種形式

常用方法

  1. ArrayHandler:把結果集中的第一行資料轉成物件陣列
  2. ArrayListHandler:每一行都轉成一個數組,再存放到List中
  3. BeanHandler:將結果集中的第一行資料封裝到一個對應的JavaBean例項中
  4. BeanListHandler:同上
  5. ColumnListHandler:將結果集中某一列的資料存放到List中
  6. KeyedHandler(name):將結果集中每行資料都封裝到Map裡,再把這些Mao存到一個Map裡,其key為指定的key
  7. MapHandler:將結果集中的第一行資料封裝到一個Map裡,key是列名,value是對應的值
  8. MapListHandler:同上

總結

以上相當於完成了在Java程式裡對資料庫進行操作,但是要操作所有的表,增刪改查,沒有區分,sql語句是寫死的,每操作一次都需要一個新的sql語句。極不方便。

通過下面的方法,我們歸類sql語句,將每個表的sql語句單獨區分出來,劃到各個表的DAO裡進行操作增刪改查。

DAO(data access object)

以上方法還是存在不足

  1. SQL語句固定,不能通過引數傳入,通用性不好,需要進行改進,更方便執行增刪改查
  2. 對於select操作,如果有返回值,返回值型別不能固定,需要使用泛型
  3. 將來的表很多,業務需求複雜,不可能只靠一個Java類完成

各司其職,每個DAO,操作對應的表

DAO

資料訪問物件