JDBC 是什么
Java DataBase Connectivity(Java语言连接数据库)
JDBC的本质是什么? JDBC是SUN公司制定的一套接口(interface) 接口都有调用者和实现者。 面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程? 解耦合:降低程序的耦合度,提高程序的扩展力. 多态机制就是非常典型的面向抽象编程(不要面向具体编程)
建议: Animal a = new Cat(); Animal b = new Dog();
// 喂养的方法 public void feed(Animal a){//面向父类编程}
不建议: Dog d=new Dog(); Cat c=new Cat();
JDBC编程六步
-
- 注册驱动(作用:告诉java程序,即将要连接的是哪个品牌的数据库)
-
- 获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完一定要释放)
-
- 获取数据库操作对象
-
- 执行SQL(DQL ,DML……)
-
- 处理查询结果集(只有当第4步执行的是select的时候才有第五步处理查询结果集)
-
- 释放资源(使用完资源后一定要关闭资源,Java和数据库之间属于进程间通信,使用后一定要关闭)
登录案例(使用Statement的存在SQL注入问题,使用PreparedStatement不存在该问题)
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
public class Utils {
public static Map<String, String> initUI() {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名");
String name = scanner.nextLine();
System.out.print("请输入密码");
String password = scanner.nextLine();
Map<String, String> userInfoMap = new HashMap<>();
userInfoMap.put("name", name);
userInfoMap.put("password", password);
return userInfoMap;
}
public static void loginByStatement(Map<String, String> userInfoMap) {
/**
* JDBC连接数据库6步骤
* 1 加载驱动
* 2 获取连接对象
* 3 创建数据库操作对象
* 4 执行sql
* 5 处理resultSet
* 6 释放资源
* */
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
String loginName = userInfoMap.get("name");
String loginPassword = userInfoMap.get("password");
try {
Class.forName("com.mysql.cj.jdbc.Driver");
ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
String url = resourceBundle.getString("url");
String user = resourceBundle.getString("user");
String password = resourceBundle.getString("password");
connection = DriverManager.getConnection(url, user, password);
statement = connection.createStatement();
String sql = "select * from t_user where name='" + loginName + "' and password='" + loginPassword + "'";
resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (resultSet != null) {
resultSet.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void loginByPreparedStatement(Map<String, String> userInfoMap) {
/**
* JDBC连接数据库6步骤
* 1 加载驱动
* 2 获取连接对象
* 3 创建数据库操作对象
* 4 执行sql
* 5 处理resultSet
* 6 释放资源
* */
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
String loginName = userInfoMap.get("name");
String loginPassword = userInfoMap.get("password");
try {
Class.forName("com.mysql.cj.jdbc.Driver");
ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
String url = resourceBundle.getString("url");
String user = resourceBundle.getString("user");
String password = resourceBundle.getString("password");
connection = DriverManager.getConnection(url, user, password);
String sql = "select * from t_user where name=? where password=?";
statement = connection.prepareStatement(sql);
statement.setString(1,loginName);
statement.setString(2,loginPassword);
resultSet = statement.executeQuery();
if (resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (resultSet != null) {
resultS et.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
解决sql注入的关键
用户提供的信息中即使含有sql语句的关键字,但是这些关键字不能参与编译,就可以法解决
对比Statement和PreparedStatement
- statement存在sql注入问题,PreparedStatement不存在
- statement是编译一次执行一次,PreparedStatement是编译一次执行多次,PreparedStatement 效率高一些
- PreparedStatement 会在类型编译阶段做类型安全检查
- 当需要用到sql注入的时候必须使用Statement,不需要的时候使用PreparedStatement
JDBC事务
1 JDBC中的事务是自动提交的,什么是自动提交? 只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。但是在实际的业务当中,通常都是 N条SQL语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者失败。
2 JDBC转账实例
/**
* 转账方法
*/
public static void transferAccount() {
/**
* JDBC连接数据库6步骤
* 1 加载驱动
* 2 获取连接对象
* 3 创建数据库操作对象
* 4 执行sql
* 5 处理resultSet
* 6 释放资源
* */
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
String url = resourceBundle.getString("url");
String user = resourceBundle.getString("user");
String password = resourceBundle.getString("password");
connection = DriverManager.getConnection(url, user, password);
// JDBC 开启事务
connection.setAutoCommit(false);
String sql = "update t_act set balance=? where actno=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setDouble(1, 1000);
preparedStatement.setInt(2, 111);
int count = preparedStatement.executeUpdate();
preparedStatement.setDouble(1, 19000);
preparedStatement.setInt(2, 222);
count += preparedStatement.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
connection.commit();
} catch (Exception e) {
try {
if (connection != null) {
connection.rollback();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (preparedStatement != null) {
preparedStatement.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
当需要执行多个sal来完成一个业务的时候,为了保证业务数据的正常,需要保证每个sql都执行完成才能确定成功,可以通过connection.setAutoCommit(false) 方法开启事务,当DML语句都成功时,执行connection.commit()方法提交事务。当catch到异常时候,执行connection.rollback()将事务进行回滚,以此来保证业务的完整性和正确性。
悲观锁(行级锁)和乐观锁
实例: select * from emp where job ='MANAGER' for update;
悲观锁关键词: for update 悲观锁不允许并发,一个事务锁定后,其他事务只能等待. 乐观锁允许并发操作,但是有一个版本号,如果有两个事务同时读一条数据,其中一个事务进行了数据修改,并进行了提交,第二个进行修改的事务则会发现版本号不一致,将修改进行回滚