文章目录
JDBC
一、连接数据库
//JDBC:Java为连接数据库提供的一套接口(规范)
//1、导入数据库厂商提供的驱动
// 导入jar包,依赖jar包
//2、加载驱动
//驱动在5.0以上,此步骤可以省略不写
Class.forName("com.mysql.jdbc.Driver");
//3、建立连接
//URL:统一资源定位符
//格式:"主协议:子协议://ip:端口/资源"
//本地连接:localhost:3306可以省略不写
// 即:"jdbc:mysql:///mydb"
String url="jdbc:mysql://localhost:3306/mydb";
String username="root";
String password="123456";
Connection conn=DriverManager.getConnection(url,username,password);
//4、获取操作对象
Statement st=conn.createStatement();
//5、编写SQL语句
String sql="select * from student"; //SQL语句
6、执行SQL语句
st.executeQuery(sql);//执行SQL语句
7、释放资源
conn.close();
st.close();
二、JDBC相关类及常用方法
DriverManager //驱动管理类
getConnection(); //试图建立到给定数据库 URL 的连接
Connection//接口:与特定数据库的连接(会话)
createStatement(String sql);//创建一个 Statement 对象来将 SQL 语句发送到数据库
prepareStatement(String sql);
//创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库
prepareStatement(String sql, int autoGeneratedKeys)
//创建一个默认 PreparedStatement 对象,该对象能获取自动生成的键。
Statement//接口:用于执行静态 SQL 语句并返回它所生成结果的对象
executeUpdate();//执行DML语句,返回值是影响行数
executeQuery();//执行DQL语句,返回值是查询的结果集
execute();//执行任何语句
addBatch(String sql)//将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中
executeBatch()//将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组
ResultSet//接口
/*表示数据库结果集的数据表,通常通过执行查询数据库的语句生成;
ResultSet 对象具有指向其当前数据行的光标。最初,光标被置于第一行之前。next 方法将光标移动到下一行;
因为该方法在 ResultSet 对象没有下一行时返回 false,所以可以在 while 循环中使用它来迭代结果集。
beforeFirst() //将光标移动到此 ResultSet 对象的开头,正好位于第一行之前。*/
//遍历取出结果集中对象
ResultSet re=st.executeQuery(sql);
while(re.next()){
int id=re.getInt("id");//参数为表头序号或者表头字段名
String username=re.getString(2);
/*处理这些零碎数据的方法:
把查询出来的数据封装到类里
再把对象存到集合里*/
}
演示一:模拟登录
import java.sql.*;
import java.util.Scanner;
public class Example03 {
public static void main(String[] args) throws Exception{
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password=sc.nextLine();
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql:///mydb";
Connection conn = DriverManager.getConnection(url, "root", "123456");
String sql="select * from users where username=? and password=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);
ResultSet resultSet = ps.executeQuery();
//如果查到结果,说明登录成功
if(resultSet.next()){
System.out.println("登录成功!");
}else {
System.out.println("登录失败!");
}
ps.close();
resultSet.close();
conn.close();
}
}
演示二:批量操作
//部分代码省略
String sql="insert into demo values(?)";
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 1; i < 10000; i++) {
ps.setInt(1,i);
ps.addBatch();
}
ps.executeBatch();
演示三:获取自增长键的值
//部分代码省略
//要获取自增长键的值,需要在获取操作对象时声明一个参数 Statement.RETURN_GENERATED_KEYS
PreparedStatement preparedStatement = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//获取自增长键的结果集
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
while (generatedKeys.next()){
keyValue = generatedKeys.getInt(1);
}
三、安全问题
SQL注入:通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
1、SQL注入案例:使用拼串的形式写SQL语句
//部分代码省略
Statement st=conn.createStatement();
String name="1'or'1'='1";
"select * from student name='"+name+"'";
st.executeQuery(sql);//执行SQL语句,此语句可以查出student表中所有信息
2、防止SQL注入:使用PrepareStatement 预编译操作对象
//部分代码省略
//SQL语句中的参数先用 ? 占位
String name="张三";
String sql="select * from student where name=?"
PrepareStatement ps=conn.prepareStatement(sql);
//给 ? 赋值
ps.setString(1,name);//参一为 ? 的索引,注意索引从1开始计算,参二是 ? 的值
ps.executeQuery(); //执行SQL语句,注意不再传入SQL语句
四、调用存储过程和函数
1、调用存储过程
SQL语句格式 {call <procedure-name>[(<arg1>,<arg2>, ...)]}
Java代码:部分省略
String sql="{call myPro(?,?)}";
CallableStatement callableStatement = connection.prepareCall(sql);
callableStatement.setInt(1,-1);//给第一个问号赋值
//确定第二个问号的数据类型
callableStatement.registerOutParameter(1, Types.VARCHAR);
callableStatement.execute();//执行SQL语句
String r = callableStatement.getString(2);//获取返回值
System.out.println(r);
myPro 存储过程代码
DELIMITER $$
CREATE PROCEDURE pro_testIf(IN num INT,OUT str VARCHAR(2))
BEGIN
IF num>0 THEN
SET str='正数'; -- 注意要用分号结束
ELSEIF num<0 THEN --注意elseif 连写
SET str='负数';
ELSE
SET str='零';
END IF; --注意要结束if,要写分号
END $$
DELIMITER;
2、调用函数
SQL语句格式: {?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
Java代码:部分省略
String sql="{?=call myFun(?)}";
CallableStatement callableStatement = connection.prepareCall(sql);
callableStatement.setInt(2,1);//给第二个 ? 赋值,删除 id=1 的人
callableStatement.registerOutParameter(1,Types.INTEGER);//确定第一个 ? 数据的类型
callableStatement.execute();//执行sql语句
int r = callableStatement.getInt(1);//获取函数返回值
System.out.println(r);
myFun函数代码
create function myFun(num int)
returns int
BEGIN
declare i int default 0;-- 定义i;相当于java中 //int i=0;
delete from mydemo where id=num;-- 删除id=1的人
select count(*) from mydemo into i;-- 统计剩余人数,赋给i
return i;-- 返回i
END;
表格
五、事务
概述:事务是指一组最小逻辑操作单元,里面有多个操作组成。组成事务的每一部分必须要同时提交成功,如果有一个操作失败,整个操作就回滚。
1、事务四大特性(ACID):
原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性(Isolation):
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间相互隔离。持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
2、事务的提交
默认情况下,Connection 对象处于自动提交模式,这意味着它在执行每个语句后都会自动提交更改,即它的所有 SQL 语句将被执行并作为单个事务提交。
如果禁用了自动提交模式,那么它的 SQL 语句将聚集到事务中,要提交更改就必须显式调用 commit或rollback 方法,否则无法保存数据库更改。
void setAutoCommit ( boolean autoCommit) //将此连接的自动提交模式设置为给定状态。
rollback()//取消在当前事务中进行的所有更改,并释放此 Connection 对象当前持有的所有数据库锁。
commit()//使所有上一次提交/回滚后进行的更改成为持久更改,并释放此 Connection 对象当前持有的所有数据库锁。
setSavepoint()//在当前事务中创建一个未命名的保存点 (savepoint),并返回表示它的新 Savepoint 对象。
示例:简易模拟转账
import utils.JDBC_Utils;
import java.sql.*;
public class Example00 {
public static void main(String[] args){
Connection conn=null;
PreparedStatement statement1=null;
PreparedStatement statement2=null;
PreparedStatement statement3=null;
PreparedStatement statement4=null;
Savepoint savepoint=null;
try {
conn = JDBC_Utils.getConnection();//获取Connection对象方法,篇幅有限,不作展示
conn.setAutoCommit(false);//将conn 自动提交设置为手动提交
//模拟第一次转账
String sql1 = "update bank set money=money-1000 where username='zhangsan'";
statement1 = conn.prepareStatement(sql1);
statement1.executeUpdate();
//System.out.println(1/0);
String sql2="update bank set money=money+1000 where username='lisi'";
statement2 = conn.prepareStatement(sql2);
statement2.executeUpdate();
//将以上两个操作看做一个事务,只有这两操作都完成才会提交保存,否则回滚至初始状态
savepoint = conn.setSavepoint();//创建一个保存点,可以指定回滚至此状态
//模拟第二次转账,假设在此过程出现异常
String sql3 = "update bank set money=money-1000 where username='zhangsan'";
statement3 = conn.prepareStatement(sql3);
statement3.executeUpdate();
System.out.println(1/0);//模拟转账过程中的异常
String sql4="update bank set money=money+1000 where username='lisi'";
statement4 = conn.prepareStatement(sql4);
statement4.executeUpdate();
//将以上两个操作看做第二个事务
} catch (Exception e) {
try {
if (savepoint != null) {
//如果savepoint不为null且出现异常,说明第一次转账成功,第二次失败,此时只需回滚至指定保存点即可
conn.rollback(savepoint);
}else {
//如果savepoint为null,说明第一次转账异常,则回滚至初始状态,即第一次之前
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
conn.commit();//手动提交事务,并释放Conn 对象当前持有的所有数据库锁
conn.close();
if (statement1 != null) {
statement1.close();
}
if (statement2 != null) {
statement2.close();
}
if (statement3 != null) {
statement3.close();
}
if (statement4 != null) {
statement4.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3、事务的隔离级别
1、Read uncommitted 读未提交
当隔离级别设置为Read uncommitted时,就可能出现脏读。
2、Read committed 读提交 ==>(Oracle默认级别)
当隔离级别设置为Read committed时,避免了脏读,但是可能会造成不可重复读。
3、Repeatable read 重复读 ==>(MySQL默认级别)
当隔离级别设置为Repeatable read时,可避免脏读、不可重复读的发生,但可能会出现幻读(错误读取)。为此级别。
4、Serializable 串行化
最高级别,可以避免所有问题,但效率很低,一般不使用
四种隔离级别的效率:
read uncommitted>read committed>repeatable read>serializable
四种隔离级别的安全性:
read uncommitted<read committed<repeatable read<serializable
设置查看事务的隔离级别
将数据库的隔离级别设置成 读未提交
set session transaction isolation level read uncommitted;
查看数据库的隔离级别
select @@tx_isolation;
java中控制隔离级别: Connection
void setTransactionIsolation(int level) //level是常量