JDBC在mysql8.x上的使用
-
mysql8.0和之前版本的區別,首先驅動換了,不是
com.mysql.jdbc.Driver
而是com.mysql.cj.jdbc.Driver
5.5版本的註冊
Class.forName(com.mysql.jdbc.Driver());
8.x版本的註冊
Calss.forName(com.mysql.cj.jdbc.Driver());
-
mysql8.x是不需要建立ssl連接的,需要顯示關閉。需要配置
serverTimezone
屬性設置時區Url="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&autoReconnect=true"; //連接地址+ssl連接關閉+時區+字符集爲utf-8+數據庫自動連接
UTC代表的是全球標準時間 ,但是我們使用的時間是北京時區也就是東八區,領先UTC八個小時。時區使用中國標準時間,也是就serverTimezone=Asia/Shanghai。
用jdbc連接數據庫
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.cj.jdbc.Driver;
public class JDBCDemo1 {
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
ResultSet resultSet=null;
try {
//1、加載驅動
//DriverManager.registerDriver(new Driver());//會進行兩次註冊驅動(加載Driver類一次,執行這條語句又一次)
//加載Driver類,Driver類中的靜態代碼塊中含有DriverManager.registerDriver(new Driver());代碼,只要加載Driver類就會註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2、獲得連接
//三個參數1、localhost是主機名,3306是端口號,jdbctest是數據庫 2、用戶名 3、密碼
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai","root","123456");
//3、創建執行SQL語句的對象,並執行SQL
//3.1創建執行sql語句的對象
stmt=conn.createStatement();
String sql="select * from user";
//3.2、執行sql語句獲得結果集
resultSet=stmt.executeQuery(sql);
//3.3、讀取結果集中的數據
while(resultSet.next()) {
int id=resultSet.getInt("uid");
String username=resultSet.getString("username");
String password=resultSet.getString("password");
String name=resultSet.getString("name");
System.out.println(id+" "+username+" "+password+" "+name);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//4、釋放資源
if(resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet=null;
}
if(stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt=null;
}
if(conn!=null) {
try {
//這裏雖然關閉了資源,但不會馬上被垃圾回收機制回收
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
//手動置爲空,讓這個連接資源馬上被回收
conn=null;
}
}
}
}
- 釋放資源必須放在
finally
中,因爲放在try
塊中,如果在前面發生了異常,那麼這些資源將不會被正常回收。但是放在finally
塊中,無論是否會有異常,都會執行這個塊中的代碼,進行資源的釋放。 - 特別是
Connection
對象,它是非常稀有的資源(數據庫中規定了最大的連接數),用完後必須馬上釋放,如果Connection
不能及時、正確的關閉,極易導致系統宕機。Connection
的使用原則是儘量晚創建,儘量早的釋放。
DriverManager :驅動管理類
主要作用:
-
註冊驅動
- 實際開發中註冊驅動會使用如下的方式:
Class.forName("com.mysql.cj.jdbc.Driver");
-
獲得連接
Connection conn=DriverManager.getConnection(String url,String username,String password)
url寫法: jdbc:mysql://localhost:3306/jdbc?.....
- jdbc :協議
- mysql:子協議
- localhost :主機名
- 3306:端口號
Connection:獲得連接對象(Connection是接口)
主要作用:
- 創建執行SQL語句的對象(調用下面的方法獲得不同的執行SQL語句的對象)
Statement createStatement()
:獲得執行SQL語句的對象,有SQL注入的漏洞存在。PreparedStatement prepareStatement(String sql)
:這個接口繼承了Statement
,預編譯SQL語句,解決SQL注入漏洞的問題。CallableStatement prepareCall(String sql)
:執行SQL中的存儲過程。
- 進行事務的管理
setAutoCommit(boolean autoCommit)
:設置事務是否自動提交,true爲自動,false爲禁止自動commit()
:事務提交rollback()
:事務回滾
Statement(接口):執行SQL語句
主要作用:
-
執行SQL語句
boolean execute(String sql)
:執行SQL,執行select
語句返回true,其它返回false。ResultSet executeQuery(String sql)
:執行SQL中的select
語句int executeUpdate(String sql)
:執行SQL中的insert/update/delete
語句,返回影響的行數
-
執行批處理操作
addBatch(String sql)
:添加到批處理executeBatch()
:執行批處理,返回一個數組,保存着每執行一次sql語句影響的行數clearBatch()
:清空批處理
PreparedStatement
的這些函數在傳參等方面略有不同
//用PreparedStatement的批處理執行多條delete操作
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDemo3 {
public static void main(String[] args) {
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai";
String username="root";
String password="123456";
Connection conn=null;
PreparedStatement pstmt=null;
try {
Class.forName(driver);
conn=DriverManager.getConnection(url,username,password);
String sql="delete from user where username=?";
pstmt=conn.prepareStatement(sql);
//設置爲不自動提交事務,自動提交不能實現真正的批處理
conn.setAutoCommit(false);
for(int i=1;i<10;i++) {
pstmt.setString(1, "lewis"+String.valueOf(i));
pstmt.addBatch();
}
int[] cnt=pstmt.executeBatch();//返回每次操作影響了幾行的數組
conn.commit();//手動提交事務
System.out.println("總共執行了"+cnt.length+"次刪除操作");
for(int n:cnt) {
System.out.println(n);
}
conn.setAutoCommit(true);//恢復自動提交事務
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(pstmt!=null) {
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pstmt=null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conn=null;
}
}
}
}
ResultSet:結果集
結果集:其實就是查詢語句(select)查詢結果的封裝。
主要作用:
- 通過結果集獲取到查詢結果
next()
:針對不同類型的數據可以使用getXXX()
獲取數據,通用的獲取方法:getObject()
提取工具類
properties
文件:
- 以鍵值對的形式保存信息,存儲格式
鍵=值
- 我這裏的文件是放在
ClassPath
根目錄下的
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=123
工具類:
- 一些可能變化的信息從屬性文件中讀取,如果改變,只需改變屬性文件就行
package com.lxc.jdbc.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static String driverClass;
private static String url;
private static String username;
private static String password;
//靜態代碼塊,在類加載的時候執行
static {
//加載屬性文件並解析
Properties properties=new Properties();
//使用類加載器的方式獲取屬性文件的輸入流
/*不能使用FileInputStream()的方式獲得文件的輸入流,這種方法只能用在java程序中,這種方式如果在web項目中程序會到tomcat/bin目錄下加載該屬性文件*/
//jdbc.properties放在classpath根目錄下,下面是到classpath根目錄下讀取jdbc.properties文件
//同樣也可以使用JDBCUtils.class.getResourceAsStream("/jdbc.properties")獲得該文件輸入流
InputStream is=JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
//解析.properties文件中的鍵值對,存進HashTable中,供後面的getProperty(Stirng key)使用
properties.load(is);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//從屬性文件中獲取鍵值對中的值
//本質是通過鍵獲取HashTable中的值
driverClass=properties.getProperty("driverClass");
url=properties.getProperty("url");
username=properties.getProperty("username");
password=properties.getProperty("password");
}
/**
* 註冊驅動的方法
*/
public static void loadDriver() throws ClassNotFoundException {
Class.forName(driverClass);
}
/**
* 獲得連接的方法
* @throws ClassNotFoundException
* @throws SQLException
*/
public static Connection getConnection() throws ClassNotFoundException, SQLException {
JDBCUtils.loadDriver();
return DriverManager.getConnection(url,username,password);
}
/**
* 釋放資源
*/
public static void release(Connection conn,PreparedStatement pstmt) {
if(pstmt!=null) {
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pstmt=null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conn=null;
}
}
public static void release(Connection conn,PreparedStatement pstmt,ResultSet rs) {
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rs=null;
}
if(pstmt!=null) {
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pstmt=null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conn=null;
}
}
}
類名.class.getClassLoader.getResourceAsStream(path)
中路徑問題path
不能以“/”開頭。path
是從ClassPath
(項目中的src
,在Eclipse
項目文件中的bin目錄)根下獲取。
[關於Class.getResource和ClassLoader.getResource的路徑問題]
Class.getResource和ClassLoader.getResource的區別分析
使用工具類:
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import com.lxc.jdbc.utils.JDBCUtils;
public class JDBCDemo3 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstmt=null;
try {
//使用工具類中的方法獲得連接對象
conn=JDBCUtils.getConnection();
String sql="delete from user where username=?";
pstmt=conn.prepareStatement(sql);
conn.setAutoCommit(false);
for(int i=1;i<10;i++) {
pstmt.setString(1, "lewis"+String.valueOf(i));
pstmt.addBatch();
}
int[] cnt=pstmt.executeBatch();
conn.commit();
System.out.println("總共執行了"+cnt.length+"次刪除操作");
for(int n:cnt) {
System.out.println(n);
}
conn.setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}finally {
//使用工具類中的方法釋放資源
JDBCUtils.release(conn,pstmt);
}
}
}
SQL注入漏洞的解決
產生的原因
public static boolean login(String username,String password){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
boolean flag = false;
try{
conn = JDBCUtils.getConnection();
stmt = conn.createStatement();
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
rs = stmt.executeQuery(sql);
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
return flag;
}
上面是驗證登錄的代碼,如果我們在傳入參數的時候,在參數中包含一些SQL的關鍵字(and\or\註釋)。都會被拼接到SQL語句中,成爲SQL語句的一部分。比如login("aaa or '1==1'",password)
,無論密碼是什麼都會登錄成功,因爲傳入參數後SQL語句變成了select * from user where username='aaa' or '1==1' and password='asdfj';
,只要用戶名對了就能登錄成功。
SQL注入漏洞的解決
PreparedStatement
能很好的解決這個問題PreparedStatement
是Statement
的子接口PreparedStatement
相對於Statement
而言PreparedStatement
可以避免SQL注入的問題,因爲在PreparedStatement
中的SQL語句中會使用佔位符,傳入參數的時候,儘管傳入一些含有SQL關鍵字的字符串,但是SQL語句並不會識別這些關鍵字,而僅僅是當作普通的字符串來處理。(SQL語句的格式已固定)Statement
會使數據庫頻繁編譯SQL,可能會造成數據庫緩衝區的溢出。而PreparedStatement
可對SQL語句進行預編譯(只要是同一種格式的SQL語句,只編譯一次,可使用多次),從而提高數據庫的執行效率。- 並且
PreparedStatement
對於sql中的參數,允許使用佔位符的形式進行替換,簡化SQL語句的編寫。
public static boolean login2(String username,String password){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean flag = false;
try{
conn = JDBCUtils.getConnection();
String sql = "select * from user where username = ? and password = ?";
pstmt = conn.prepareStatement(sql);
//第一個參數代表第幾個佔位符,從1開始
pstmt.setString(1, username);
pstmt.setString(2, password);
rs = pstmt.executeQuery();
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
return flag;
}
JDBC連接池—C3P0
-
C3P0
是開源的JDBC連接池 -
連接池是創建和管理一個連接的緩衝池的技術,這些連接準備好被任何需要他們的線程使用。
-
原來沒有連接池的缺點
沒有連接池的時候,這些連接都是需要的時候創建,用完後馬上銷燬,所以會導致數據庫一直在創建和銷燬連接。 -
有連接池的優點
在數據庫連接池中存在着一定數量的連接,當需要使用的時候就取出來使用,等到用完後不會銷燬,而是歸還到連接池中,這樣就避免了大量的創建連接耗費資源等問題。[外鏈圖片轉存失敗(img-FGRZICiR-1565245636240)(assets/1565192749899.png)]
C3P0的使用
- 下載後需要把``c3p0-0.9.5.4.jar
和依賴包
mchange-commons-java-0.2.15.jar兩個包導入到工程的
ClassPath`中
配置c3p0-config.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="mysql">
<!-- 配置數據庫用戶名 -->
<property name="user">root</property>
<!-- 配置數據庫密碼 -->
<property name="password"></property>
<!-- 配置數據庫鏈接地址 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/cdcol?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai</property>
<!-- 配置數據庫驅動 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!-- 數據庫連接池一次性向數據庫要多少個連接對象 -->
<property name="acquireIncrement">20</property>
<!-- 初始化連接數 -->
<property name="initialPoolSize">10</property>
<!-- 最小連接數 -->
<property name="minPoolSize">5</property>
<!--連接池中保留的最大連接數。Default: 15 -->
<property name="maxPoolSize">30</property>
<!--JDBC的標準參數,用以控制數據源內加載的PreparedStatements數量。但由於預緩存的statements 屬於單個connection而不是整個連接池。所以設置這個參數需要考慮到多方面的因素。如果maxStatements與maxStatementsPerConnection均爲0,則緩存被關閉。Default:0 -->
<property name="maxStatements">0</property>
<!--maxStatementsPerConnection定義了連接池內單個連接所擁有的最大緩存statements數。Default: 0 -->
<property name="maxStatementsPerConnection">0</property>
<!--c3p0是異步操作的,緩慢的JDBC操作通過幫助進程完成。擴展這些操作可以有效的提升性能 通過多線程實現多個操作同時被執行。Default:3 -->
<property name="numHelperThreads">3</property>
<!--用戶修改系統配置參數執行前最多等待300秒。Default: 300 -->
<property name="propertyCycle">3</property>
<!-- 獲取連接超時設置 默認是一直等待單位毫秒 -->
<property name="checkoutTimeout">1000</property>
<!--每多少秒檢查所有連接池中的空閒連接。Default: 0 -->
<property name="idleConnectionTestPeriod">3</property>
<!--最大空閒時間,多少秒內未使用則連接被丟棄。若爲0則永不丟棄。Default: 0 -->
<property name="maxIdleTime">10</property>
<!--配置連接的生存時間,超過這個時間的連接將由連接池自動斷開丟棄掉。當然正在使用的連接不會馬上斷開,而是等待它close再斷開。配置爲0的時候則不會對連接的生存時間進行限制。 -->
<property name="maxIdleTimeExcessConnections">5</property>
<!--兩次連接中間隔時間,單位毫秒。Default: 1000 -->
<property name="acquireRetryDelay">1000</property>
<!--c3p0將建一張名爲Test的空表,並使用其自帶的查詢語句進行測試。如果定義了這個參數那麼屬性preferredTestQuery將被忽略。你不能在這張Test表上進行任何操作,它將只供c3p0測試使用。Default: null -->
<property name="automaticTestTable">Test</property>
<!-- 獲取connnection時測試是否有效 -->
<property name="testConnectionOnCheckin">true</property>
</named-config>
</c3p0-config>
-
在xml文件中可以配置多個數據庫源連接信息,比如可以是mysql、oracle
可以使用c3p0-config.xml文件配置連接信息,使用xml作爲配置信息的話,
comboPoolDataSource
還可以接受一個String參數,這個參數的名稱是在c3p0-config.xml文件中配置的,也可以使用無參的默認配置。這樣就能連接不同的數據庫、連接不同廠商的數據庫public ComboPooledDataSource() //無參構造使用默認配置(使用xml中default‐config標籤中對應的參數) public ComboPooledDataSource(String configName) //有參構造使用命名配置(configName:xml中配置的名稱,使用xml中named‐config標籤中對應的參數) //例如在xml中:<named-config name="oracle">各種配置信息</named-config>
我的c3p0-config.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!--默認配置,無參時使用這個--> <default-config> <!--連接參數 --> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <!--使用&對&進行轉義--> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest?useSSL=false&serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">123456</property> <!-- 連接池參數 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> <!--命名配置,配置mysql數據庫,傳入"mysql"參數使用這個 --> <named-config name="mysql"> <!-- 配置數據庫用戶名 --> <property name="user">root</property> <!-- 配置數據庫密碼 --> <property name="password">123456</property> <!-- 配置數據庫鏈接地址 --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai</property> <!-- 配置數據庫驅動 --> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <!-- 數據庫連接池一次性向數據庫要多少個連接對象 --> <property name="acquireIncrement">20</property> <!-- 初始化連接數 --> <property name="initialPoolSize">10</property> <!-- 最小連接數 --> <property name="minPoolSize">5</property> <!--連接池中保留的最大連接數。Default: 15 --> <property name="maxPoolSize">30</property> </named-config> <!-- 配置ORACLE數據庫 --> <named-config name="oracle">各種配置信息</named-config> </c3p0-config>
-
配置文件的連接方式
-
C3P0默認會在
classpath(src)
根目錄讀取配置文件c3p0-config.xml
,不需要任何的設置。所以我們最好把配置文件放在classpath
根目錄下,而且文件名爲c3p0-config.xml
(不要更改)。 -
也可以把
c3p0-config.xml
放到自己想放的位置,但必須在加載程序的時候進行設置System.setProperties(“ com.mchange.v2.c3p0.cfg.xml”,”config/c3p0-config.xml”);
-
工具類:
package com.lxc.jdbc.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Utils {
private static ComboPooledDataSource dataSource;
static {
dataSource=new ComboPooledDataSource();
//也可以使用命名配置的數據庫
//dataSource=new ComboPooledDataSource("mysql");
}
/**
* 獲得連接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 釋放ResultSet和PreparedStatement資源,並把Connection歸還到連接池中
* @param stmt
* @param conn
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
操作
package com.lxc.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.lxc.jdbc.utils.C3P0Utils;
public class C3P0Test {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstmt=null;
ResultSet rs=null;
try {
//利用工具類獲得連接
conn=C3P0Utils.getConnection();
String sql="select * from user";
pstmt=conn.prepareStatement(sql);
rs=pstmt.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//以前調用conn.close()是釋放資源,但是C3P0中對方法進行了升級,把銷燬連接改成了把連接歸還到連接池中
C3P0Utils.release(rs, pstmt,conn);
}
}
}