面向物件系列講解—面向物件的含義&工廠模式
-
資料庫訪問技術簡介:
-
-
JDBC的工作原理
JDBC訪問資料庫步驟
步驟1:載入驅動
-
使用Class類的forName方法,將驅動程式類載入到JVM(Java虛擬機器)中;
private static String driverName= "com.mysql.cj.jdbc.Driver";//mysql 8.0 Class.forName(driver);//載入驅動
步驟2:獲取資料庫連線
-
成功載入驅動後,必須使用DriverManager類的靜態方法getConnection來獲得連線物件;
- jdbc:mysql://localhost:3306/資料庫名稱?引數名稱=引數值&引數名稱=引數值
-
String url = "jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";`
-
URL引數
-
useUnicode:是否使用Unicode字符集,如果引數characterEncoding設定為gb2312或gbk,本引數值必須設定為true。
-
characterEncoding:當useUnicode設定為true時,指定字元編碼。比如可設定為gb2312或gbk。
-
useSSL:MySQL在高版本需要指明是否進行SSL連線,在mysql連線字串url中加入ssl=true或者false。
-
serverTimezone:設定時區 例如 serverTimezone=UTC(統一標準世界時間)或serverTimezone=Asia/Shanghai(中國時區)
-
步驟3:建立Statement執行SQL語句
-
通過Connection物件建立
-
用於執行SQL語句
步驟4:處理ResultSet結果集
-
用於儲存查詢結果
-
只在執行select語句時返回
-
步驟5:釋放資源 close();
案例
User定義
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'jim', '11111');
INSERT INTO `user` VALUES ('2', 'tom', '22222');
INSERT INTO `user` VALUES ('3', 'lucy', '3333');
INSERT INTO `user` VALUES ('4', 'laowang', '4444');
INSERT INTO `user` VALUES ('5', 'laoli', '5555');
使用Statement查詢User
引入MySql驅動jar
user表的實體類
public class User {
private Long id;//主鍵
private String username;//使用者名稱
private String password;//密碼
public User(String username, String password) {
this.username = username;
this.password = password;
}
public Long getId() {return id;}
public void setId(Long id) {this.id = id; }
public String getUsername() {return username; }
public void setUsername(String username) {this.username = username; }
public String getPassword() {return password; }
public void setPassword(String password) {this.password = password;
}
@Override
public String toString() {
return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' + '}';
}
}
使用Statement查詢User
import java.sql.*;
import java.util.Scanner;
public class JdbcTest {
public static void main(String[] args) {
login();
}
//模擬登陸
private static void login() {
Scanner sc = new Scanner(System.in);
System.out.println("請輸入使用者名稱:");
String username = sc.nextLine();
System.out.println("請輸入密碼:");
String password = sc.nextLine();
User user = query(username, password);
if (user != null) {
System.out.println("歡迎" + username + "登陸成功");
} else {
System.out.println("使用者名稱或密碼不正確!");
}
}
private static User query(String username, String password) {
Connection conn = null;
Statement statement = null;
ResultSet rs = null;
try {
//1、通過反射,載入與註冊驅動類
Class.forName("com.mysql.cj.jdbc.Driver");/*載入mysql8.0+的驅動*/
/*mysql5.0+的驅動 : Class.forName("com.mysql.jdbc.Driver");*/
//2、獲取連線
String url = "jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
conn = DriverManager.getConnection(url, "root", "123456");
/*DriverManager.getConnection();*/
//3、建立Statement物件並執行 執行sql返回ResultSet
statement = conn.createStatement();
String sql = "select * from `user` where username='" + username + "' and `password`='" + password + "'";
rs = statement.executeQuery(sql);
//4、處理結果集
User user = null;
/*boolean next()
將游標從當前位置向前移動一行。 ResultSet游標最初位於第一行之前; 第一次呼叫方法next使第一行成為當前行; 第二個呼叫使第二行成為當前行,依此類推。
當呼叫next方法返回false時,游標位於最後一行之後。
如果當前行的輸入流已開啟,則對方法next的呼叫將隱式關閉它。 當讀取新行時, ResultSet物件的警告鏈將被清除。
結果 true如果新的當前行有效; false如果沒有更多的行
*/
if (rs.next()) {
/*Object getObject(int columnIndex)
獲取此的當前行中指定列的值 ResultSet作為物件
引數 columnIndex - 第一列是1,第二列是2,...
結果 一個 java.lang.Object持有列值
*/
Long id = (Long) rs.getObject(1);
String username1 = (String) rs.getObject(2);
String password1 = (String) rs.getObject(3);
user = new User(id, username1, password1);
}
return user;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//5、關閉資源
finally {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
}
SQL 注入(SQL Injection)是發生在 Web 程式中資料庫層的安全漏洞。主要原因是程式對使用者輸入資料的合法性沒有判斷和處理,導致攻擊者可以在 Web 應用程式中事先定義好的 SQL 語句中新增額外的 SQL 語句,在管理員不知情的情況下實現非法操作。
簡而言之,SQL 注入就是在使用者輸入的字串中加入 SQL 語句,如果在設計不良的程式中忽略了檢查,那麼這些注入進去的 SQL 語句就會被資料庫伺服器誤認為是正常的 SQL 語句而執行,攻擊者就可以執行計劃外的命令或訪問未被授權的資料。
statement 只能拼接sql語句,可能會出現sql注入的風險
例如:sql注入 使用者名稱輸入 ' or 1=1 # 密碼隨便輸入,就會查出資料),不推薦用。
JDBC的PreparedStatement
-
PreparedStatement的概述:
- 繼承自Statement介面
- 能夠對SQL語句進行預編譯
- PreparedStatement的優點
-
- 提高SQL語句執行效率
- 提高安全性
- 注意:
-
- SQL語句使用“ ?”作為資料佔位符
- 在建立時對SQL語句進行預編譯
- 使用setXxx()方法設定資料
使用PreparedStatement查詢
private static User query(String username, String password) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1、通過反射,載入與註冊驅動類
Class.forName("com.mysql.cj.jdbc.Driver");/*載入mysql8.0+的驅動*/
//2、獲取連線
String url = "jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
conn = DriverManager.getConnection(url, "root", "123456");
//3、獲取PreparedStatement物件 並執行sql返回ResultSet
String sql = "select * from `user` where username= ? and `password` = ?";
ps = conn.prepareStatement(sql);
ps.setObject(1, username);
ps.setObject(2, password);
rs = ps.executeQuery();
//4、處理結果集
User user = null;
if (rs.next()) {
Long id = (Long) rs.getObject(1);
String username1 = (String) rs.getObject(2);
String password1 = (String) rs.getObject(3);
user = new User(id, username1, password1);
}
return user;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//5、關閉資源
finally {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (ps != null) {
ps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
解決SQL注入問題
新增
/**
* 新增
*
* @throws ClassNotFoundException
* @throws SQLException
*/
private static void insert() throws ClassNotFoundException, SQLException {
//1、載入jdbc驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2、獲取連線
//在mysql8.0的資料庫 serverTimezone=Asia/shanghai是必須的引數
String url = "jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
Connection conn = DriverManager.getConnection(url, "root", "123456");
//3、獲取PreparedStatement物件
//注意點:sql語句中使用?作為引數的佔位符
String sql = "insert into `user`(username,password) values(?,?)";
PreparedStatement ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//把問號替換成引數
ps.setObject(1, "jim");
ps.setObject(2, 30);
//4、執行sql,新增 要使用executeUpdate()
ps.executeUpdate();
// 執行此 Statement 物件而建立的所有自動生成的鍵
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
// 指定返回生成的主鍵
Object id = rs.getObject(1);
System.out.println("新增的主鍵為:"+id);
}
//5、關閉資源
ps.close();
conn.close();
}
修改和刪除
/**
* 更新
*
* @throws ClassNotFoundException
* @throws SQLException
*/
private static void update() throws ClassNotFoundException, SQLException {
//1、載入jdbc驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2、獲取連線
//在mysql8.0的資料庫 serverTimezone=Asia/shanghai是必須的引數
String url = "jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
Connection conn = DriverManager.getConnection(url, "root", "123456");
//3、獲取PreparedStatement物件
//注意點:sql語句中使用?作為引數的佔位符
String sql = "update user set password=? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
//把問號替換成引數
ps.setObject(1, "888");
ps.setObject(2, 1);
//4、執行sql,更新或者刪除 要使用executeUpdate(),處理返回值,返回值代表的含義:影響的行數
int result = ps.executeUpdate();
if (result > 0) {
System.out.println("更新密碼成功");
} else {
System.out.println("更新密碼失敗");
}
//5、關閉資源
ps.close();
conn.close();
}
模糊查詢
private static List<User> queryForLike(String usernameLike) {
//假如:usernameLike 為a
//sql語句
String sql = "select * from `user` where username like ?";
//引數
Object[] params = {"%" + usernameLike + "%"};
List<User> users = new ArrayList<>();
try {
List<Map<String, Object>> datas = DbUtils.selectList(sql, params);
for (Map<String, Object> data : datas) {
Long id = (Long) data.get("id");
String username = (String) data.get("username");
String password = (String) data.get("password");
User user = new User(id, username, password);
users.add(user);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return users;
}
分頁查詢
/**
* 分頁查詢
*
* @param pageNum 頁碼
* @param pageSize 每頁數量
* @return
*/
private static List<User> queryPage1(int pageNum, int pageSize) {
List<User> users = new ArrayList<>();
String sql = "select * from `user` order by id asc limit ?,?";
Object[] params = {(pageNum - 1) * pageSize, pageSize};
try {
List<Map<String, Object>> datas = DbUtils.selectList(sql, params);
for (Map<String, Object> data : datas) {
Long id = (Long) data.get("id");
String username = (String) data.get("username");
String password = (String) data.get("password");
User user = new User(id, username, password);
users.add(user);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return users;
}
封裝DbUtils工具類
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.*;
public class DbUtils {
private static String driverName;
private static String url;
private static String username;
private static String password;
static {
InputStream inputStream = null;
try {
//建立db.properties的流
inputStream = DbUtils.class.getClassLoader().getResourceAsStream("db.properties");
//建立Properties物件
Properties p = new Properties();
//把資料流讀入Properties物件中
p.load(inputStream);
//從Properties物件中獲取配置資料
url = p.getProperty("url");
username = p.getProperty("username");
password = p.getProperty("password");
driverName = p.getProperty("driverName");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* insert語句使用,返回新增資料的自增主鍵。
*
* @param sql
* @return
*/
public static Object insert(Connection connection, String sql, Object[] params) throws SQLException {
PreparedStatement ps = null;
Object id = null;
try {
//建立PreparedStatement
ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//設定引數
setPreparedStatementParam(ps, params);
//執行sql
ps.executeUpdate();
// 執行此 Statement 物件而建立的所有自動生成的鍵
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
// 指定返回生成的主鍵
id = rs.getObject(1);
}
} finally {
close(ps);
}
return id;
}
/**
* insert語句使用,返回新增資料的自增主鍵。
*
* @param sql
* @return
*/
public static Object insert(String sql, Object[] params) throws SQLException, ClassNotFoundException {
Connection conn = null;
Object id;
try {
//建立連線
conn = getConnection();
id = insert(conn, sql, params);
} finally {
close(conn);
}
return id;
}
/**
* 更新、刪除
*
* @param sql
* @param params
* @return
* @throws SQLException
*/
public static boolean update(Connection connection, String sql, Object[] params) throws SQLException {
PreparedStatement ps = null;
try {
//步驟2:設定SQL語句以及對應的引數
ps = connection.prepareStatement(sql);
setPreparedStatementParam(ps, params);
//步驟3:執行update
int result = ps.executeUpdate();
//返回執行的結果
return result > 0 ? true : false;
} finally {
//步驟4:關閉資源
close(ps);
}
}
/**
* 更新、刪除
*
* @param sql
* @param params
* @return
* @throws SQLException
*/
public static boolean update(String sql, Object[] params) throws SQLException, ClassNotFoundException {
Connection connection = null;
try {
//步驟1:獲取連結
connection = getConnection();
return update(connection, sql, params);
} finally {
//步驟2:關閉連線資源
close(connection);
}
}
/**
* 查詢一個
*
* @param sql
* @param params
* @return
* @throws SQLException
*/
public static Map<String, Object> selectOne(Connection connection, String sql, Object[] params) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//步驟2:設定SQL語句以及對應的引數
ps = connection.prepareStatement(sql);
setPreparedStatementParam(ps, params);
//步驟3:執行查詢,把查詢結果的列作為key,列對應的值作為value,儲存到Map中
rs = ps.executeQuery();
if (rs.next()) {
return getResultMap(rs);
}
} finally {
//步驟4:關閉資源
close(rs, ps);
}
return null;
}
/**
* 獲取ResultMap
*
* @param rs
* @return
* @throws SQLException
*/
private static Map<String, Object> getResultMap(ResultSet rs) throws SQLException {
//獲取到result的元資料,包含了列的資訊
ResultSetMetaData metaData = rs.getMetaData();
//獲取到當前表的所有的列的列數
int columnCount = metaData.getColumnCount();
//儲存資料庫列與值的map
Map<String, Object> map = new HashMap<>();
//根據列的數量,獲取到每一個列的列名以及對應的值
for (int i = 0; i < columnCount; i++) {
//能夠獲取到每一個列的名稱,引數是每個列的序號值
String columnLabel = metaData.getColumnLabel(i + 1);
Object columnValue = rs.getObject(columnLabel);
map.put(columnLabel, columnValue);
}
return map;
}
/**
* 查詢一個
*
* @param sql
* @param params
* @return
* @throws SQLException
*/
public static Map<String, Object> selectOne(String sql, Object[] params) throws SQLException, ClassNotFoundException {
Connection connection = null;
try {
//步驟1:獲取連結
connection = getConnection();
return selectOne(connection, sql, params);
} finally {
//步驟4:關閉資源
close(connection);
}
}
/**
* 查詢集合
*
* @param sql
* @param params
* @return
*/
public static List<Map<String, Object>> selectList(Connection connection, String sql, Object[] params) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
List<Map<String, Object>> list;
try {
//步驟2:設定SQL語句以及對應的引數
ps = connection.prepareStatement(sql);
setPreparedStatementParam(ps, params);
//步驟3:執行查詢,把查詢結果的列作為key,列對應的值作為value,儲存到Map中
rs = ps.executeQuery();
list = new ArrayList<>();
while (rs.next()) {
list.add(getResultMap(rs));
}
} finally {
//步驟4:關閉資源
close(rs, ps);
}
return list;
}
/**
* 查詢集合
*
* @param sql
* @param params
* @return
*/
public static List<Map<String, Object>> selectList(String sql, Object[] params) throws SQLException, ClassNotFoundException {
Connection connection = null;
try {
connection = getConnection();
return selectList(connection, sql, params);
} finally {
close(connection);
}
}
/**
* 設定引數
*
* @param ps
* @param params
* @throws SQLException
*/
private static void setPreparedStatementParam(PreparedStatement ps, Object[] params) throws SQLException {
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
}
/**
* 獲取連線
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws ClassNotFoundException, SQLException {
//載入資料庫驅動
Class.forName(driverName);
return DriverManager.getConnection(url, username, password);
}
//開啟事務
public static void beginTransaction(Connection conn) throws SQLException {
conn.setAutoCommit(false);
}
//提交事務
public static void commit(Connection conn) throws SQLException {
conn.commit();
}
//回滾事務
public static void rollback(Connection conn) throws SQLException {
conn.rollback();
}
public static void close(Connection connection) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 釋放PreparedStatement,Connection
*/
private static void close(PreparedStatement ps, Connection connection) {
close(ps);
close(connection);
}
/**
* 釋放ResultSet,PreparedStatement
*/
private static void close(ResultSet rs, PreparedStatement ps) {
close(rs);
close(ps);
}
/**
* 釋放ResultSet,PreparedStatement,Connection
*/
private static void close(ResultSet rs, PreparedStatement ps, Connection conn) {
close(rs);
close(ps);
close(conn);
}
private static void close(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static void close(PreparedStatement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static void close(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
呼叫儲存過程
-
呼叫儲存過程的步驟
(1)prepareCall(sql):建立執行儲存過程的callableStatement物件。
(2)CallableStatement:繼承自PreparedStatement介面,由方法prepareCall建立,用於呼叫儲存過程。
(3)執行:和其它物件使用的方法一樣 eg:cs.executeUpdate()
(4)呼叫儲存過程的sql: String sql = “{call 儲存過程名字(引數1,引數2....)}”
-
案例
-
定義儲存過程
/* -- 儲存過程:分頁查詢使用者列表 -- 輸入引數:page_index:當前頁碼 -- 輸入引數:page_size:分頁大小 -- 輸出引數:total_count:資料總數 -- 輸出引數:total_page:總頁數 */ DROP PROCEDURE IF EXISTS proc_search_user; CREATE PROCEDURE proc_search_user(IN page_index INT,IN page_size INT, OUT total_count INT, OUT total_page INT) BEGIN DECLARE begin_no INT; SET begin_no = (page_index-1)*page_size; -- 分頁查詢列表 SELECT * FROM `user` ORDER BY id ASC LIMIT begin_no,page_size; -- 計算資料總數 SELECT COUNT(1) INTO total_count FROM `user`; -- 計算總頁數 SET total_page = FLOOR((total_count + page_size - 1) / page_size); END;
-
Navicat工具呼叫儲存過程
CALL proc_search_user(1,2,@total_count,@total_page); select @total_count,@total_page;
-
JDBC呼叫儲存過程
/** * 呼叫儲存過程 * * @throws ClassNotFoundException * @throws SQLException */ private static void selectPro() throws ClassNotFoundException, SQLException { //1、載入jdbc驅動 Class.forName("com.mysql.cj.jdbc.Driver"); //2、獲取連線 //在mysql8.0的資料庫 serverTimezone=Asia/shanghai是必須的引數 String url = "jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"; Connection conn = DriverManager.getConnection(url, "root", "123456"); //3、建立CallableStatement物件 CallableStatement callStatement = conn.prepareCall("{CALL proc_search_user(?,?,?,?)}"); // 設定輸入引數 callStatement.setInt(1, 1); // 查詢第1頁資料 callStatement.setInt(2, 2); // 每頁2條資料 // 註冊輸出引數 callStatement.registerOutParameter(3, Types.INTEGER); callStatement.registerOutParameter(4, Types.INTEGER); // 執行呼叫儲存過程,並獲取結果集 ResultSet rs = callStatement.executeQuery(); //4、執行sql返回ResultSet ResultSet rs = ps.executeQuery(); while (rs.next()) { int id = (Integer) rs.getObject(1); String username1 = (String) rs.getObject(2); String password1 = (String) rs.getObject(3); User user = new User(); user.setId(id); user.setUsername(username1); user.setPassword(password1); System.out.println(user); } //5、關閉資源 rs.close(); ps.close(); conn.close(); }
-