PreparedStatement的使用
一、PreparedStatement的简介
PreparedStatement是Statement的子接口,属于预处理操作。获取PreparedStatement的方法:
使用Connection对象的PreparedStatement prepareStatement(String sql)
这里简单说下使用Statement对象执行sql语句时和使用PreparedStatement对象执行sql语句。
画张图看下sql语句的执行过程:
区别:
1.使用Statement对象执行sql时,每次都要对指定的静态sql语句进行编译再执行,这样效率便会大大降低。
2.使用PreparedStatement对象执行sql时,由于执行的sql可以是动态sql,就可以实现对sql语句的预编译,存入缓冲区,下次执行相同的sql语句时就可以直接从缓存中读出来。
- 使用PreparedStatement的好处
1.对于结构相同的SQL,可以提高执行效率,在创建PreparedStatement对象时就指定了SQL语句,该语句将被立即发送给DBMS进行编译,预编译的语句被存储在DBMS的缓存中,下次执行相同的SQL语句时,则可以直接从缓存中取出来。
2.可以有效预防SQL注入
在使用参数化查询的情况下,数据库系统(DBMS)不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,即使参数中含有恶意的指令,也不会被数据库所运行。
接下来使用一下PreparedStatement类对象执行sql语句。
使用的数据库表为:
写一个连接数据库的工具类:
package DBMSUtil;
import java.io.File;
import java.sql.*;
import java.io.*;
import java.util.Properties;
public class DBUtil {
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConn(String sql){
Properties pro = new Properties();
Connection conn=null;
InputStream is = null;
try {
is = new FileInputStream("f:"+File.separator+"db.properties");
pro.load(is);
conn = DriverManager.getConnection(sql,pro);
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
//conn.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return conn;
}
}
借助以上工具类可以完成今天的工作了,写一个利用PreparedStatement对象执行sql的例子:
package prepared;
import java.sql.*;
import DBMSUtil.DBUtil;
public class PreparedDemo {
public static void main(String[] args) {
Connection conn = DBUtil.getConn("jdbc:mysql://localhost:3306/mydb");
PreparedStatement pre=null;
String sql="update student set stuname=? where stuname=?";
try {
pre=conn.prepareStatement(sql);
pre.setString(1,"亚索");
pre.setString(2,"究极皮皮怪");
pre.executeUpdate();
System.out.println("执行成功。。。");
} catch (SQLException e) {
e.printStackTrace();
} finally{
try {
pre.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
从上面的代码例子可以看出,动态sql执行语句即是sql语句中含有占位符?,pre对象先对该动态sql语句进行了预编译,接下来只需添加参数再进行执行即可。动态sql的执行由此也解决了sql注入的一些问题,在利用Statement对象执行sql时,由于sql语句是写死的,极易引发由于字符串拼接的问题而导致的sql注入问题,引发一系列数据安全的问题。
SQL注入的简单了解
SQL注入就是通过在客户端输入内容向数据库中进行查询时,利用字符串的拼接问题故意拼接一些数据库能够识别的关键字进行一个对数据的破坏。
看一下:
利用Statement对该表进行一下查询:
package prepared;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import DBMSUtil.DBUtil;
public class SQLInject {
public static void main(String[] args) {
Connection conn=DBUtil.getConn("jdbc:mysql://localhost:3306/mydb");
Statement sta=null;
ResultSet rs=null;
try {
String username="Yasuo";
String password="sss' or '1=1";
String sql="select * from user where username='"+username+"' and password='"+password+"'";
sta=conn.createStatement();
rs=sta.executeQuery(sql);
if(rs.next()){
System.out.println("查询成功");
}else{
System.out.println("查询失败。。。");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
从代码中可以看到,传递的password在数据库中并不存在,但在利用字符串拼接的漏洞之下,照样访问到了数据库中的信息,这是很不安全的,因此,使用PreparedStatement能够防止该问题的产生。
二、事务
1概念:
数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作,要么全部成功,要么全部失败。
2.事务的四个特性(ACID)
- 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 - 一致性(Consistency)
事务必须使数据库从一个一个一致性状态变换到另外一个一致性状态。 - 隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 - 持久性(Durability)
持久性是指一个事务一旦被提交,他对数据库中数据改变就是永久性的,接下来的其他操作和数据库不应该对其有任何影响。
3.事务的隔离级别
前提概念:
1.脏读(dirty read):一个事务读取到了另一个事务未提交的数据(脏数据)的现象。
2.不可重复读:一个事务两次度渠道的数据不一致的现象。
3.幻读:一个事务在读取数据时,另一个事务添加了数据记录,则第一个事务读取到了新添加的数据(与第一次读取数据比较。)
事务的隔离级别
- 读未提交级别(read uncommitted)
读取到了另一个事务还未提交的数据,肯能会发生脏读、不可重复读和幻读。 - 读提交级别(read committed)
只能读取到其他事务已提交的数据,可能会发生不可重复读、幻读。 - 可重复读级别(repeatable read,MySQL默认的隔离级别)
在一个事务中可以重复读取相同的数据。在InnoDB数据库存储引擎(此存储引擎支持事务)中,已经解决了幻读问题。 - 串行化级别(serializable)
最安全,但并发效率低
下图为不同的隔离级别解决的读的问题的限度:
在数据库中事务的操作过程:
1.查看当前会话的隔离级别:select @@tx_isolation;
2.设置当前会话的隔离级别(4种隔离级别):set session transaction isolation level [read uncommitted]/[read committed]/[repeatable read]/[serializable]
3.开启一个事务:start transaction;
4.开启一个事务后,可以在该事务下执行多条sql语句,这些SQL语句便属于同一个事务,若想提交事务:commit;如果执行完这些sql语句之后想放弃这些sql语句的执行,便使用回滚:rollback;这就是的rollback之前的sql操作全部作废,如果在一个事务中执行了多条sql且执行回滚时只想时rollback之前的某些sql作废,可以在不想作废的sql之后写一个事务保存点:savepoint 事务保存点名称,这样的话只要使用:rollback 事务保存点名称,这样就可以使的事务回滚到事务保存点那里.
下面看一个java对事务操作的支持Demo:
一个转账操作便是一个事务,转账操作的具体流程是一方账户余额的减少和另一方账户余额的增加,这两步就必须放在一个事务中避免操作执行到一半因为种种原因而被恶意终止,导致发生财产损失什么的:
package transaction;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import Util.DBUtil;
public class AccountDemo {
public static boolean transferMoney(int fromId,int toId,double money){
boolean flag=false;
Connection conn =DBUtil.getConn("jdbc:mysql://localhost:3306/mydb");
PreparedStatement pre=null;
String sql1 = "update account set balance=balance-? where uid=?";
String sql2 = "update account set balance=balance+? where uid=?";
try {
conn.setAutoCommit(false); //取消自动提交事务
//第一条sql语句的执行
pre=conn.prepareStatement(sql1);
pre.setDouble(1,money);
pre.setInt(2,fromId);
pre.executeUpdate();
pre.close();
//第二条sql语句的执行
pre=conn.prepareStatement(sql2);
pre.setDouble(1,money);
pre.setInt(2,toId);
pre.executeUpdate();
conn.commit(); //执行成功后提交事务
flag=true; //执行成功后将flag设为true
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
//pre.close();
} finally{
try {
pre.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return flag;
}
public static void main(String[] args) {
boolean isSuccess=transferMoney(1,2,200);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入:");
String str;
try {
str = br.readLine();
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
if(isSuccess){
System.out.println("很遗憾,交易成功!!");
}else{
System.out.println("恭喜,交易失败,钱还是你自己的哈哈哈哈哈。。。");
}
}
}
这便是事务操作是典型的一个例子,但对于事务来说,由现实情况繁杂而又多变,怎样将一系列操作归于一个事务中也是一个很重要的问题,同时最重要的是事务的隔离级别,只有弄清楚事务之间的关系,定义好事务的隔离级别,才能保证事务执行的安全性。因此,在这方面就是要弄懂事务的隔离级别,这是非常关键的。
以上。