回顧總結21
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結果集
- 表示資料庫結果集的資料表,通常通過執行查詢資料庫的語句生成
- ResultSet物件保持一個游標指向其當前的資料行,最初,游標位於表頭
- 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
- Statement物件,用於執行SQL語句並返回其生成結果
- 連線建立後,需要對資料庫進行訪問,執行命令或是SQL語句,可以通過
- Statement【存在SQL注入】
- PreparedStatement【預處理】
- CallableStatement【儲存過程】
- Statement物件執行SQL語句,存在SQL注入風險
- SQL注入是利用某些系統沒有對使用者輸入的資料進行充分的檢查,而在使用者輸入資料注入非法的SQL語句段或命令,惡意攻擊資料庫
- 防範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優點
- 不再使用 + 拼接sql語句,減少語法錯誤
- 有效解決了sql注入問題
- 大大減少了編譯次數,效率較高
//獲得連線資料庫的引數,賬號、密碼、資料庫、驅動
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,停止事務的自動提交相當於開啟你自己的事務。
批處理
-
當需要成批插入或者更新記錄時,可以採用Java的批量更新機制,這一機制允許多條語句一次性提交給資料庫批量處理,通常情況下比單獨提交處理更有效率
-
JDBC的批處理語句包括下面方法
addBatch:新增需要批量處理的SQL語句或引數
executeBatch:執行批量處理語句
clearBatch:清空批處理包的語句
-
批處理底層是ArrayList,通過將sql語句放入arrayList,滿足條件後,將arrayList裡的sql語句一次性傳輸給mysql執行。
details
-
如果要使用批處理,需要在配置檔案的url新增?rewriteBatchedStatements=true
-
sql語句要寫的規範。
例如insert into admin values(null,?,?),批處理不生效。
在values和插入資料間加個空格就好了。
insert into admin values (null,?,?)
資料庫連線池
傳統連線Connection問題分析
- 傳統的JDBC資料庫連線使用DriverManager獲取,每次向資料庫建立連線的時候都要將Connection載入到記憶體中,再驗證IP地址,使用者名稱和密碼(0.05s~1s時間)。需要資料庫連線的時候,就向資料庫要求一個,頻繁的進行資料庫連線操作將佔用很多的系統資源,容易造成伺服器崩潰
- 每一次資料庫連線,使用完後都得斷開,如果程式出現異常而未能關閉,將導致資料庫記憶體洩露,最終將導致重啟資料庫
- 傳統獲取連線的方式,不能控制建立的連線數量,如連線過多,也可能導致記憶體洩露,Mysql崩潰
- 解決傳統開發中的資料庫連線問題,可以採用資料庫連線池技術。(connection pool)
資料庫連線池
- 預先在緩衝池中放入一定數量的連線,當需要建立資料庫連線時,只需從"緩衝池"中取出一個,使用完畢之後再放回去
- 資料庫連線池負責分配,管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是重新建立一個。
- 當應用程式向連線池請求的連線數超過最大連線數量時,這些請求將被加入到等待佇列中
資料庫連線池種類
JDBC的資料庫連線池使用javax.sql.DataSource來表示,DataSource只是一個介面,該介面通常由第三方提供實現
- C3P0資料庫連線池:速度相對較慢,穩定性不錯(應用在hibernate,spring)
- DBCP資料庫連線池:速度相對C3P0較快,但是不穩定
- Proxool資料庫連線池:有監控連線池狀態的功能,穩定性較C3P0差一點
- BoneCP資料庫連線池:速度快
- 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的許多問題
- 結果集合connection是關聯的,即如果關閉連線,就不能使用結果集
- 結果集不利於資料管理,只能使用一次
- 使用返回資訊也不方便
之前我們是連線後通過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介紹
- commons-dbutils是Apache組織提供的一個開源JDBC工具類庫,它是對JDBC的封裝,使用dbutils能極大簡化jdbc編碼的工作量
DbUtils類
- QueryRunner類:該類封裝了SQL的執行,是執行緒安全的,開源實現增、刪、改、查、批處理
- 使用QueryRunner類實現查詢
- ResultSetHandler介面:該介面用於處理java.sql.ResultSet,將資料按要求轉換為另一種形式
常用方法
- ArrayHandler:把結果集中的第一行資料轉成物件陣列
- ArrayListHandler:每一行都轉成一個數組,再存放到List中
- BeanHandler:將結果集中的第一行資料封裝到一個對應的JavaBean例項中
- BeanListHandler:同上
- ColumnListHandler:將結果集中某一列的資料存放到List中
- KeyedHandler(name):將結果集中每行資料都封裝到Map裡,再把這些Mao存到一個Map裡,其key為指定的key
- MapHandler:將結果集中的第一行資料封裝到一個Map裡,key是列名,value是對應的值
- MapListHandler:同上
總結
以上相當於完成了在Java程式裡對資料庫進行操作,但是要操作所有的表,增刪改查,沒有區分,sql語句是寫死的,每操作一次都需要一個新的sql語句。極不方便。
通過下面的方法,我們歸類sql語句,將每個表的sql語句單獨區分出來,劃到各個表的DAO裡進行操作增刪改查。
DAO(data access object)
以上方法還是存在不足
- SQL語句固定,不能通過引數傳入,通用性不好,需要進行改進,更方便執行增刪改查
- 對於select操作,如果有返回值,返回值型別不能固定,需要使用泛型
- 將來的表很多,業務需求複雜,不可能只靠一個Java類完成
各司其職,每個DAO,操作對應的表
DAO
資料訪問物件