JDBC連線操作資料庫
1.1 Driver:
Java.sql.Driver 介面是所有 JDBC 驅動程式需要實現的介面。這個介面是提供給資料庫廠商使用的,不同資料庫廠商提供不同的實現
在程式中不需要直接去訪問實現了 Driver 介面的類,而是由驅動程式管理器類(java.sql.DriverManager)去呼叫這些Driver實現
操作步驟:
1.2 四種連結方式
方式一:以下生成的Driver是固定mysql的,方式二:可以使用反射根據傳入的動態生成各種資料庫驅動
@Test public void test1() throws SQLException { // JDBC的連線測試 // 1. driver // 每個資料庫驅動程式需要對Driver介面進行實現 Driver driver = new com.mysql.jdbc.Driver(); // 2. url: jdbc:mysql://127.0.0.1:3306/mybatis String url = "jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true"; // 3. 封裝屬性 Properties pro = new Properties(); pro.setProperty("user","root"); pro.setProperty("password","password"); // driver獲取資料庫的連線 Connection connect = driver.connect(url, pro); System.out.println(connect); connection.close(); }
DriverManager 靜態
方式三:利用DriverManager替換Driver
@Test public void test2()throws Exception{ // 使用DriverManager替換driver Class<?> cla = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) cla.newInstance(); String url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true"; String user = "root"; String password = "password"; // 註冊Driver DriverManager.registerDriver(driver); Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); connection.close(); }
方式四: // 在方式三的基礎上,多部分程式碼預設已經完成了
@Test public void test2()throws Exception{ // 使用DriverManager替換driver /*Class<?> cla = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) cla.newInstance();*/ String url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true"; String user = "root"; String password = "password"; // 註冊Driver //DriverManager.registerDriver(driver); Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); connection.close(); }
1. 連線資料庫最終版
方式五: 配置資訊放在配置檔案中,程式碼讀取
@Test
public void test3()throws Exception{
// 讀取配置檔案,載入配置資訊
Properties pro = new Properties();
// 獲取配置檔案流,FileInputStream預設在當前專案下, 類載入器預設在src下或者resource資源目錄下
//FileInputStream fis = new FileInputStream("src\\properties.properties"); 不行,找不到resource目錄下的資源
// 類載入器載入
ClassLoader loader = JdbcTest.class.getClassLoader();
InputStream fis = loader.getResourceAsStream("properties.properties");
pro.load(fis);
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
String driv = pro.getProperty("driver");
Class<?> cla = Class.forName(driv);
Driver driver = (Driver) cla.newInstance();
// 註冊Driver
DriverManager.registerDriver(driver);
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
connection.close();
fis.close();
}
1.4 Statement
弊端:SQL注入問題:由於Statement的sql語句是拼接在,在輸入資料時,輸入資料有可能拼接成sql的關鍵字,導致出現sql語句執行出現異常
如何避免SQL注入問題:使用PreparedStatement替換Statement
@Test
public void test4() throws Exception {
// statement 執行sql語句
// 自定義的JDBC工具類獲取資料庫連線
Connection connection = JDBCUtils.getConnection();
System.out.println(connection);
// 4.建立sql語句的執行物件Statement
Statement statement = connection.createStatement();
// 5.用statement物件執行sql語句,可能有返回的結果集(連結串列的形式)
// 執行查詢語句用executeQuery(),執行插入、刪除、修改用executeUpdate()
// execute(),增刪改查的sql都能執行,有判斷效率會低點
ResultSet resultSet = statement.executeQuery("select *from mybatis.books");
while (resultSet.next()){
System.out.print(resultSet.getInt("bookID")+"\t"); //在不知道型別的情況下可以用getObject()獲取
System.out.print(resultSet.getObject("bookName")+"\t");// 裡面的引數要和資料庫一一對應
System.out.println(resultSet.getObject("detail")+"\t");
}
// 6.釋放連線,關閉資源
resultSet.close();
statement.close();
connection.close();
}
1.5 PreparedStatement
因為sql語句是預編譯的,而且語句中使用了佔位符,規定了sql語句的結構。使用者可以設定"?"的值,但是不能改變sql語句的結構,因此想在sql語句後面加上如“or 1=1”實現sql注入是行不通的。
connection.prepareStatement(sql); // 預編譯,得到後結構就固定了
優點:
-
可以實更高效的批量操作
-
sql語句在預編譯後就會快取下來,繼續執行同樣的sql語句時,就不會有sql語句的校驗
for(int i=0;i < 100;i++){ pre.setObject(""); pre.execute(); }
-
以上優化:sql只有值在變化,在多儲存一些值,後在提交執行插入操作
- addBatch( )、executeBatch( )、clearBatch( )
- mysq1伺服器預設是關閉批處理的,我們需要通過一個引數, 讓mysq1開啟批處理的支援。? rewriteBatchedstatements=true寫在配置檔案的ur1後面
- 3.使用更新的mysql 驅動: mysql-connector-java-5.1.37-bin.jar
for(int i=0;i < 100;i++){ pre.setObject(""); // 1.攢sql pre.addBatch(); if(i <= 10){ // 執行bathch pre.executeBatch(); //清空batch pre.clearBatch(); } }
-
插入:
@Test
public void test5() throws SQLException {
// 使用PreparedStatement避免Statement的sql注入問題
// 使用了佔位符,解決sql注入問題
// 自定義的JDBC工具類獲取資料庫連線
Connection connection = JDBCUtils.getConnection();
String sql = "INSERT into mybatis.books values(?,?,?,?)";
PreparedStatement pre = connection.prepareStatement(sql);
pre.setInt(1,31);
pre.setString(2,"java神書");
pre.setInt(3,30);
pre.setString(4,"從入門到入獄");
pre.execute();
pre.close();
connection.close();
}
修改:
@Test
public void test6()throws Exception{
// 1. 獲取資料庫連線,此處是自定義的工具類
Connection connection = JDBCUtils.getConnection();
// 2. 預編譯sql語句,返回PrepareStatement
String sql = "update books set bookID=? where bookID=?";
PreparedStatement pre = connection.prepareStatement(sql);
// 3. 填充佔位符
pre.setInt(1,34);
pre.setInt(2,31);
// 3. 執行
pre.execute();
// 4. 資源的關閉
pre.close();
connection.close();
}
通用的執行sql的方法:利用可變形參,實現佔位符的填充
public void test7(String sql,Object ...data)throws Exception{
// 1. 獲取資料庫連線
Connection connection = JDBCUtils.getConnection();
// 2. 預編譯sql語句,返回PrepareStatement
PreparedStatement pre = connection.prepareStatement(sql);
// 3. 填充佔位符
for (int i = 0; i < data.length; i++) {
pre.setObject(i+1,data[i]);
}
// 3. 執行
pre.execute();
// 4. 資源的關閉
pre.close();
connection.close();
}
查詢: 遍歷返回的結果集
getObject(int ) : 取出第幾個返回值的結果 (從1開始)
getObject(String ) :返回變數名是引數值得 結果
@Test
public void test9()throws Exception{
// 查詢,返回結果集
// 1. 獲取資料庫連線
Connection connection = JDBCUtils.getConnection();
// 2. 預編譯sql語句,返回PrepareStatement
PreparedStatement pre = connection.prepareStatement("select * from mybatis.books");
// 3. 執行
ResultSet resultSet = pre.executeQuery();
// 4. 遍歷返回的結果
//方式一:
while (resultSet.next()){ // 結果集中的next: 判斷指標的下一個位置是否有元素,返回boolean型資料,為true指標下移
System.out.print(resultSet.getInt("bookID")+"\t\t"); //在不知道型別的情況下可以用getObject()獲取
System.out.print(resultSet.getObject("bookName")+"\t\t");// 裡面的引數要和資料庫一一對應
System.out.println(resultSet.getObject("detail")+"\t");
}
// 5. 資源的關閉
pre.close();
connection.close();
}
獲取返回的結果集中資料的列數,可以寫一個通用的查詢:
// 獲取返回資料,有多少列
// 獲取返回值的元資料
ResultSetMetaData metaData = resultSet.getMetaData();
// 獲取有多少列
int columnCount = metaData.getColumnCount();
// 獲取第一列的列名
String name = metaData.getColumnName(1);
// 獲取列的別名 , 沒有別名就返回列名
String columnLabel = metaData.getColumnLabel(1);
1.6 別名解決欄位問題
針對資料庫欄位名,和java實體類的屬性名不一致的問題:
- sql取別名: as
查詢: 結果對映,利用反射,通用查詢
public <T> List<T> test1(Class<T> clazz,String sql,Object ...args)throws Exception{
/** 查詢:
* 利用返回的結果集的元資料
* 獲取返回的列數,列的別名,列名
* 編寫一個通用(想查哪幾列資料就只顯示哪幾列資料)的查詢
* 解決:資料庫欄位和java實體類屬性不一致問題
* */
// 1. 獲取資料庫連線
Connection connection = JDBCUtils.getConnection();
// 2. 獲取PrepareStatement 防止Statement的sql注入問題
PreparedStatement pre = connection.prepareStatement(sql);
// 3. 填充佔位符
for (int i = 0; i < args.length; i++) {
pre.setObject(i+1,args[i]);
}
// 4. 執行查詢,返回結果集
ResultSet resultSet = pre.executeQuery();
// 5. 獲取元資料 , 獲取列數
ResultSetMetaData metaData = resultSet.getMetaData();// 元資料
int columnCount = metaData.getColumnCount();
// 遍歷結果集
List list = new ArrayList<T>();
while (resultSet.next()){
T book = clazz.newInstance();
// 因為只知道返回的屬性名,對屬性名的set方法不好指定,利用反射進行修改
for (int i = 0; i < columnCount; i++) {
String columnLabel = metaData.getColumnLabel(i + 1); // 別名
Object object = resultSet.getObject(columnLabel); // 值
Field declaredField = clazz.getDeclaredField(columnLabel); //
declaredField.setAccessible(true);
declaredField.set(book,object);
}
list.add(book);
}
list.forEach(System.out::println);
pre.close();
connection.close();
if (list.size()==0) return null;
return list;
}
1.7 新增大量資料的優化
- preparedStatement.addBatch(): 快取sql資料
- preparedStatement.executeBatch(): 執行快取的sql
- preparedStatement.clearBatch():清空快取的sql
public void test5()throws Exception{
// 獲取連線
Connection connection = JDBCUtils.getConnection();
// 獲取sql的執行物件
String sql = "insert into books(bookID,bookName,bookCounts,detail) values(?,'java神書',11,'從入門到入獄')";
PreparedStatement pre = connection.prepareStatement(sql);
// 關閉連線的自動提交
connection.setAutoCommit(false);
// 填充sql的佔位符
for (int i = 40; i <= 2000; i++) {
pre.setInt(1,i);
// 儲存sql
pre.addBatch();
if ((i % 200) == 0){
// 執行sql
pre.executeBatch();
// 清空快取的sql
pre.clearBatch();
}
}
// 提交事務 ,真正把資料插入資料庫
connection.commit();
// 關閉資源
pre.close();
connection.close();
}
1.8 PreparedStatement對資料庫中的Blob資料操作
Blob資料型別能夠儲存較大的資料
只能使用PreparedStatement進行操作,Blob不支援StatementSql語句的拼接
存圖片:
把圖片變成一個檔案流傳過去
@Test
public void test3()throws Exception{
// 獲取連線
Connection connection = JDBCUtils.getConnection();
String sql = "update books set photo = ? where bookID = ?";
PreparedStatement pre = connection.prepareStatement(sql);
FileInputStream fis = new FileInputStream("E:\\初音\\test3.png");
int id = 3;
// Blob值注入,佔位符
pre.setBlob(1,fis);
pre.setObject(2,id);
// 提交執行
pre.executeUpdate();
// 關閉資源
fis.close();
connection.close();
}
取圖片:
getBinaryStream(String ):獲取到Blob欄位返回的一個流
@Test
public void test4()throws Exception{
// 獲取連線
Connection connection = JDBCUtils.getConnection();
String sql = "select books.photo from books where bookID=?";
PreparedStatement pre = connection.prepareStatement(sql);
int id = 3;
// Blob值注入,佔位符
pre.setObject(1,id);
// 提交執行
ResultSet resultSet = pre.executeQuery();
// 把Blob型別的欄位下載下來,以檔案的形式儲存在本地中
InputStream photo = null;
if (resultSet.next()){
photo = resultSet.getBinaryStream("photo");
}
byte[] buffer = new byte[1024];
int len;
FileOutputStream fos = new FileOutputStream(new File("E:\\初音\\test3.png"));
while ((len = photo.read(buffer)) != -1){
fos.write(buffer,0,len);
}
// 關閉資源
if (photo == null) photo.close();
fos.close();
connection.close();
}
事務回滾:
@Test
public void test1() throws SQLException {
// 事務考慮自動提交的DML操作
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
// 取消自動提交
connection.setAutoCommit(false);
String sql1 = "delete from books where bookID =1998";
String sql2 = "delete from books where bookID =1997";
PreparedStatement pre1 = connection.prepareStatement(sql1);
// 執行
pre1.executeUpdate();
// 異常
System.out.println(10/0);
PreparedStatement pre2 = connection.prepareStatement(sql2);
pre2.executeUpdate();
// 提交
connection.commit();
if(pre1!=null) pre1.close();
if(pre2!=null) pre2.close();
}catch (Exception e){
// 事務回滾
connection.rollback();
}finally{
// 關閉資源
connection.close();
}
}