一、PreparedStatement的使用
PreparedStatement是Statement的子接口,属于预处理操作。
获取PreparedStatement的方法:
使用Connection对象的PreparedStatement prepareStatement(sql)
使用PreparedStatement的好处
1、对于结构相同的SQL,可以提高执行效率
在创建PreparedStatement对象时就指定了sql语句,该语句将被立即发送给DBMS进行编译。预编译的语句被存储在DBMS缓存中,下次执行相同的SQL语句时,则可以直接从缓存中取出来。
2、可以有效预防SQL注入
在使用参数化查询的情况下,数据库系统(DBMS)不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,即使参数中含有恶意的的指令,也不会被数据库所运行。
二、事务
概念:
数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作,要么全部成功,要么全部失败。
事务的四个特性(ACID)
1、原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2、一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
3、隔离性(Isolation)
事务的隔离性是指一个事物的执行不能被其他事务干扰,即一个事物内部的操作及使用的数据对并发的其他事务是隔离的,并发的执行各个事务之间不能相互干扰。
4、持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
事务的隔离级别
前提概念:
1、脏读(dirty read):一个事务读取到另一个事务未提交的数据(脏数据)的现象。
2、不可重复读:一个事务两次读取到的数据不一致的现象。
3、幻读:一个事务在读取数据时,另一个事务添加了数据记录,则第一个事务读取到了新添加的数据(与第一次读取数据比较)。
事务的隔离级别:
1、读未提交级别(read uncommitted)
读取到了另一个事务还未提交的数据。可能会发生脏读、不可重复读、幻读。
2、读提交级别(read committed)
只能读取其他事务已经提交的数据。可能会发生不可重复读、幻读。
3、可重复读级别(repeatable read,MySQL默认隔离级别)
在一个事务中可以重复读取相同的数据 。在InnoDB数据库存储引擎(此存储引擎支持事务)中,已经解决了幻读问题。
4、串行化级别(serializable)
最安全,但并发效率低。
隔离级别多线程并发读取数据时的正确性
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
V:可能出现,X:不会出现
Java对事务的支持
当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
为了让多个SQL语句作为一个事务执行:
1、调用Connection对象的setAutoCommit(false);以取消自动提交事务
2、在所有的SQL语句都成功执行后,调用commit();方法提交事务
3、在出现异常时,调用rollback();方法回滚事务
4、若此时Connection没有被关闭,则需要恢复其自动提交状态
对数据库的一些小操作
1、查看当前会话(session)的隔离级别:
select @@tx_isolation;
2、设置当前会话的隔离级别
set session transaction isolation level read uncommitted | read committed | repeatable read
3、开启事务
start transaction;
4、提交事务
commit;
5、回滚整个事务
rollback;
6、设置保存点
savepoint 保存点名称;
7、回滚到保存点
rollback to 保存点名称;
PreparedStatement实例:
先创建账户表,并添加数据
create table account(
id int auto_increment primary key,
accname varchar(20) not null,
accpwd varchar(20) not null,
balance double
);
insert into account(accname,accpwd,balance)values('tom','123456',1000),('jerry','123456',200);
package util;
import java.io.*;
import java.sql.*;
import java.util.*;
//操作数据库的工具类
public class DBUtil {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getDBConnection(String url) {
Properties pro = new Properties();
InputStream input = null;
Connection conn = null;
try {
input = DBUtil.class.getClassLoader().getResourceAsStream("db.Properties");
pro.load(input);
conn = DriverManager.getConnection(url,pro);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
package preparedstatement;
import java.sql.*;
import util.DBUtil;
public class InsertDataDemo {
public static void main(String[] args) {
Connection conn = DBUtil.getDBConnection("jdbc:mysql://localhost:3306/mydb");
PreparedStatement ps = null;
String sql = "insert into student(stu_name,score)values(?,?)";
try {
//将SQL发送到DBMS中,进行预编译,将预编译好的SQL存储于DBMS缓冲区中
ps = conn.prepareStatement(sql);
for(int i = 1;i <= 72;i++) {
ps.setString(1,"孙悟空"+i);
ps.setDouble(2,89.5);
ps.executeUpdate();//执行SQL
}
System.out.println("添加成功!");
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
运行结果(数据库是部分截图):
事务实例:
package transaction;
import java.sql.*;
import util.DBUtil;
public class AccountDemo {
public static void main(String[] args) {
boolean isSuccess=transforMoney(1,2,200);
if(isSuccess) {
System.out.println("交易成功!");
}else {
System.out.println("sorry,ATM发生故障,转账失败!");
}
}
public static boolean transforMoney(int fromId,int toId,double money) {
boolean flag = false;
Connection conn = DBUtil.getDBConnection("jdbc:mysql://localhost:3306/mydb");
PreparedStatement ps = null;
try {
//取消自动提交
conn.setAutoCommit(false);
String sql1 = "update account set balance=balance-? where id=?";
String sql2 = "update account set balance=balance+? where id=?";
ps = conn.prepareStatement(sql1);
ps.setDouble(1,money);
ps.setInt(2,fromId);
ps.executeUpdate();
ps.close();
ps = conn.prepareStatement(sql2);
ps.setDouble(1,money);
ps.setInt(2,toId);
ps.executeUpdate();
conn.commit();//提交当前事务
flag = true;
} catch (SQLException e) {
try {
conn.rollback();//回滚事务
flag = false;
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return flag;
}
}
运行结果:
注入(inject)实例:
package transaction;
import java.sql.*;
import util.DBUtil;
public class StopInject {
public static void main(String[] args) {
Connection conn = DBUtil.getDBConnection("jdbc:mysql://localhost:3306/mydb");
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select * from account where accname=? and accpwd=?";
try {
ps = conn.prepareStatement(sql);
String username = "tom";
String password = "123456";
ps.setString(1,username);
ps.setString(2,password);
rs = ps.executeQuery();
if(rs.next()) {
System.out.println("恭喜登陆成功~~~");
}else {
System.out.println("用户名或密码错误!请重新登陆。。。");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
运行结果: