JDBC
JDBC 概述
JDBC:Java DataBase Connectivity Java 连接数据库
JDBC,其实就是Java定义的一套和数据库建立连接的规范(接口),那么各家数据库厂商,想要Java去操作各家的数据库,必须实现这套接口,我们把数据库厂商写的这套实现类,称之为数据库驱动
JDBC标准代码
1.导入数据库厂商提供的的驱动jar包 ,记得依赖一下
2.加载驱动
3.获取连接对象。
4.获取操作对象。
5.发送SQL语句执行操作。
6.释放资源。
注意:
1.使用8.0的驱动jar包
String url = "jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&userSSL=false&serverTimezone=GMT%2B8";
2.JDBC driver 由“com.mysql.jdbc.Driver”改为“com.mysql.cj.jdbc.Driver”
代码:
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/homework?useUnicode=true&characterEncoding=UTF-8&userSSL=false&serverTimezone=GMT%2B8&useSSL=false";
String username="root";
String password="123456";
Connection connection = DriverManager.getConnection(url, username, password);
Statement statement = connection.createStatement();
String sql="insert into student values(8,'lisi',23)";
int i = statement.executeUpdate(sql);
if (i==0){
System.out.println("操作失败");
}else {
System.out.println("操作成功");
}
jdbc查询数据
String sql="select student.* from student";
ResultSet resultSet = statement.executeQuery(sql);
ArrayList<Student> students = new ArrayList<>();
while (resultSet.next()){
int id = resultSet.getInt(1);
String name=resultSet.getString("sname");
int age=resultSet.getInt("age");
** 注意:实体类中的属性名和数据类型要跟表中的字段名和数据类型保持一致
Student student = new Student();
student.setId(id);
student.setSname(name);
student.setAge(age);
students.add(student);
}
jdbc几个类的介绍
Class.forName("com.mysql.jdbc.Driver"); 加载驱动,也可以省略不写
注册驱动这个动作是Driver里面有个静态代码块在做
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
类 DriverManager Java提供的 管理一组 JDBC 驱动程序的基本服务。
接口 Connection 试图建立到给定数据库 URL 的连接。
接口 Statement 用于执行静态 SQL 语句并返回它所生成结果的对象。
boolean b = statement.execute(sql);
如果第一个结果为 ResultSet 对象,则返回 true;如果其为更新计数或者不存在任何结果,则返回 false
int i = statement.executeUpdate(sql);
statement.executeQuery(sql);
结果集对象
结果集对象,是我们执行了查询语句之后返回的一个查询结果对象
ResultSet 对象具有指向其当前数据行的光标。 最初,光标被置于第一行之前。next 方法将光标移动到下一行;因为该方法在 ResultSet 对象没有下一行时返回 false,所以可以在 while循环中使用它来迭代结果集。
while (resultSet.next()){
String username = resultSet.getString("username");
int age = resultSet.getInt("age");
Student student = new Student(username, age);
list.add(student);
}
sql注入
问题的产生
public class Test4 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String name="1' or '1'='1";
String pwd="1' or '1'='1";
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/homework?useUnicode=true&characterEncoding=UTF-8&userSSL=false&serverTimezone=GMT%2B8&useSSL=false";
Connection connection = DriverManager.getConnection(url,"root","123456");
Statement statement = connection.createStatement();
String sql="select * from users where username='"+name+"' and password='"+pwd+"'";
ResultSet resultSet = statement.executeQuery(sql);
if (resultSet.next()){
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
connection.close();
statement.close();
resultSet.close();
}
}
防止sql注入
预编译操作对象 PreparedStatement
这个操作对象,可以在安全方面防止SQL注入
使用步骤:1.conn.prepareStatement(sql);
2.sql语句中的字段的值用?问号占位
3.给sql语句中的问号赋值
String sql="select * from users where username=? and password=?";
PreparedStatement prep = connection.prepareStatement(sql);
prep.setString(1,"李四");
prep.setString(2,"123456");
ResultSet resultSet = prep.executeQuery();
JDBC批处理
插入大量数据时,建议使用批处理来做
statement.addBatch();
statement.executeBatch();
statement.clearBatch();
public class Test {
public static void main(String[] args) throws SQLException {
ArrayList<Student> list = new ArrayList<>();
for (int i = 1; i <=1000 ; i++) {
Student student = new Student();
student.setId(i);
student.setSname("ddd");
student.setAge(23);
list.add(student);
}
Connection connection = JdbcUtils.getConnection();
String sql="insert into test1 values (?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (Student student : list) {
int id=student.getId();
String name=student.getSname();
int age=student.getAge();
preparedStatement.setInt(1,id);
preparedStatement.setString(2,name);
preparedStatement.setInt(3,age);
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
preparedStatement.clearBatch();
JdbcUtils.releaseResource(connection,preparedStatement);
}
}
JDBC 调用存储过程和自定义函数
调用存储过程
调用存储过程 {call <procedure-name>[(<arg1>,<arg2>, ...)]}
public class Test2 {
public static void main(String[] args) throws SQLException {
Connection connection = JdbcUtils.getConnection();
String sql="{call xq(?,?)}";
CallableStatement call = connection.prepareCall(sql);
call.setInt(1,2);
call.registerOutParameter(2, Types.VARCHAR);
boolean b = call.execute();
System.out.println(b);
String string = call.getString(2);
System.out.println(string);
JdbcUtils.releaseResource(connection,call);
}
}
调用函数
调用函数 {?=call<procedure -name >[( < arg1 >,<arg2 >, ...)]}
public class Test3 {
public static void main(String[] args) throws SQLException {
Connection connection = JdbcUtils.getConnection();
String sql="{?=call md5(?)}";
CallableStatement callableStatement = connection.prepareCall(sql);
callableStatement.setString(2,"123456");
callableStatement.registerOutParameter(1, Types.VARCHAR);
boolean execute = callableStatement.execute();
String string = callableStatement.getString(1);
System.out.println(string);
JdbcUtils.releaseResource(connection,callableStatement);
}
}
获取自增长键的值
要获取自增长键的值 需要在获取操作对象时声明一个参数 Statement.RETURN_GENERATED_KEYS
当数据插入成功后,就可以取出这个自增长键的值
public class Test4 {
public static void main(String[] args) throws SQLException {
Connection connection = JdbcUtils.getConnection();
String sql="insert into student(sname, age) VALUES (?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1,"赵六");
preparedStatement.setInt(2,22);
int i = preparedStatement.executeUpdate();
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
while (generatedKeys.next()){
int t=generatedKeys.getInt(1);
System.out.println(t);
}
JdbcUtils.releaseResource(connection,preparedStatement,generatedKeys);
}
}
事务***
基本概念:
事务是指一组最小逻辑操作单元,里面有多个操作组成。组成事务的每一部分必须要同时提交成功,如果有一个操作失败,整个操作就回滚
***事务ACID特性---事务的四大特性★★★
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
★★★默认情况下,Connection 对象处于自动提交模式,这意味着它在执行每个语句后都会自动提交更改
如果禁用了自动提交模式,那么要提交更改就必须显式调用commit 方法;否则无法保存数据库更改。
★ 调用方法 void setAutoCommit ( boolean autoCommit)将此连接的自动提交模式设置为给定状态
如果连接处于自动提交模式下,则它的所有 SQL 语句将被执行并作为单个事务提交。否则,它的 SQL 语句将聚集到事务中,直到调用 commit 方法或 rollback 方法为止
默认情况下,新连接处于自动提交模式
★ 回滚事务表示该事务对数据库的所有操作都将取消
如果事务遇到错误而被取消或者回滚,事务的所有sql语句操作都会被清除,数据库恢复到事务执行前的状态
事物的隔离级别
不考虑隔离性会出现的读问题★★
脏读:在一个事务中读取到另一个事务没有提交的数据
不可重复读:在一个事务中,两次查询的结果不一致(针对的update操作) 不可重复读,是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。
虚读(幻读):在一个事务中,两次查询的结果不一致(针对的insert操作) 无法演示出来,MySQL已经默认避免了
MySQL 有四种隔离级别
read uncommitted 读未提交 上面的三个问题都会出现
read committed 读已提交 可以避免脏读的发生 Oracle 默认级别
repeatable read 可重复读 可以避免脏读和不可重复读的发生 MySQL 默认级别
serializable 串行化 可以避免所有的问题
修改数据:update bank set money=1500 where username='lisi';
让另一个窗口开启事务 查询数据 他查到了 就是脏读
我这边窗口 一回滚(rollback),钱又没过去
事务A修改用户姓名,此时年龄为20,事务B修改年龄为25,等事务A修改完再查询,发现年龄由20变成了25。此时事务B回滚,A再返回页面发现年龄变回了20,多次读取,年龄20-25-20,重复读出来的数据内容不一致。
例子:A将数据库中所有学生的成绩从具体分数重置为0,但是B就在这个时候插入了一条具体分数100的记录,当A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读
四种隔离级别的效率
read uncommitted>read committed>repeatable read>serializable
四种隔离级别的安全性
read uncommitted<read committed<repeatable read<serializable
开发中绝对不允许脏读发生.
mysql中默认级别:repeatable read
oracle中默认级别:read committed