JDBC
1>JDBC是什么
全称 Java DataBase Connectivity(Java语言连接数据库)
2>JDBC的本质什么
JDBC是SUN公司制定的一套接口(interface)
java.sql.*这个软件包下的。
接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程
2.1、为什么SUN要制定一套JDBC接口
因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理。
MySQL数据也有自己的原理。
MS SqlServer也有自己的原理。
- 作为Java程序员不需要针对于每一种数据库进行特定编程,只需要面向接口编程,面向抽象编程,不需要面向具体编程,提高了程序的扩展力
2.2、为什么要面向接口编程
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
Animal a = new Cat();
Animal a = new Dog();
//喂养的方法
public void feed(Animal a){//面向父类型编程
}
//不建议:
Dog d = new Dog();
Cat c = new Cat();
3>JDBC开发准备
4>JDBC编程六步骤
- 注册驱动(作用:告诉JAVA程序,即将连接哪一个品牌数据库)
- 获取连接(表示JVM进程和数据库进程之间的通道打开了,这属于进程与进程之间的通信,重量级的,使用完一定要需要关闭)
- 获取数据库对象(专门执行SQL语句的对象)
- 执行SQL语言(DQL、DML、…)
- 处理查询结果集
- 释放资源
import java.lang.*;
import java.sql.*;
public class JDBCTest01{
public static void main(String[] args){
Connection connection = null;
Statement stat = null;
try{
//1>注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2>获取连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","111");
//三个变量url user password
//3>获取数据库操作对象
stat = connection.createStatement();
//4>执行SQL语句
String sql = "delete from dept where deptno = 40";
int count = stat.executeUpdate(sql);
//executeUpdate()方法专门执行DML方法(insert delete update)
//返回值是"影响数据库中的记录条数"
System.out.println(count == 1?"执行成功":"执行失败");
}
catch(SQLException e){
e.printStackTrace();
}finally{
//6>释放资源
//从小到大释放资源
if(stat!=null){
try{
stat.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(connection!=null){
try{
connection.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
4.1、第二种注册驱动方式(常用)
public static void main(String[] args){
class.forName("com.mysql.jdbc.Driver");//反射机制
//因为参数为字符串可以写道xxx.properties文件中
//利用了反射机制 类记载的时候自动启动静态代码块
//而静态代码块中帮助我们实现了第一种注册
}
4.2、使用资源绑定器绑定属性配置文件
import java.sql.*;
import java.util.*;
public class JDBCTest02{
public static void main(String[] args){
ResourceBundle resourcebundle = ResourceBundle.getBundle("jdbc");
String driver = resourcebundle.getString("driver");
String url = resourcebundle.getString("url");
String user = resourcebundle.getString("user");
String password = resourcebundle.getString("password");
Connection connection = null;
Statement stat = null;
try{
//1>注册驱动
Class.forName(driver);
//2>获取连接
connection = DriverManager.getConnection(url,user,password);
//3>获取数据库操作对象
stat = connection.createStatement();
//4>执行SQL语句
String sql = "update dept set dname ='销售部2',loc = '天津2' where deptno =20";
int count = stat.executeUpdate(sql);
System.out.println(count == 1?"修改成功":"修改失败");
}catch(SQLException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}finally{
try{
//6>释放资源
if(stat!=null){
stat.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(connection!=null){
stat.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
4.3、遍历结果集
import java.sql.*;
import java.util.*;
public class JDBCTest03{
public static void main(String[] args){
ResourceBundle resourcebundle = ResourceBundle.getBundle("jdbc");
String driver = resourcebundle.getString("driver");
String url = resourcebundle.getString("url");
String user = resourcebundle.getString("user");
String password = resourcebundle.getString("password");
Connection connection = null;
Statement stat = null;
ResultSet rs = null;
try{
//1>注册驱动
Class.forName(driver);
//2>获取连接
connection = DriverManager.getConnection(url,user,password);
//3>获取数据库操作对象
stat = connection.createStatement();
//4>执行SQL语句
String sql = "select emp,ename,sal from emp";
//int executeUpdate(insert/delete/update)
//ResultSet executeQuery(select) 返回查询结果集
rs = stat.executeQuery(sql);
//5>处理查询结果集
while(rs.next()){
//getString()参数是列的下标也可以改成列名
String empno = rs.getString(1);//第一列 第一字段
String ename = rs.getString(2);//第二字段
String sal = rs.getString(3);//第三字段
//String empno = rs.getString("empno");//注意列名称不是表中的列名
//String ename = rs.getString("ename");//而是查询结果集的列名
//String sal = re.getString("sal");
//建议改成字段名,因为数据库进行操作时可能会交换列
System.out.println(empno+","+ename+","+sal);
}
}catch(SQLException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}finally{
//6>释放资源
try{
if(rs!=null){
re.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(stat!=null){
stat.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(connection!=null){
stat.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
4.3.1、查询结果集衍生
- 除了得到String以外也可以得到int double(前提是数字)
- 针对于进行计算
5>使用IDEA工具操作JDBC
- 1、打开模块配置
- 2、引入mysql驱动
6>设置一个登录系统
6.1、利用PowerDesigner创建登录数据库表
- 1、创建物理图
- 2、创建一个表
- 3、做出如下设置
- 4、Ctrl+S 保存
- 5、保存sql文件
- 6、初步设置sql文件
- 7、把表导入数据库
use bjpowernode;
show tables;//确定不存在名为t_user的表
source +sql文件
6.2、初步实现
import javax.xml.transform.Result;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class JDBCTest01 {
public static void main(String[] args) {
Map<String,String> userLoginInfo = initUI();
login(userLoginInfo);
}
public static Map<String, String> initUI() {
Map<String,String> userLoginInfo = new HashMap<>();
Scanner scanner = new Scanner(System.in);
System.out.println("用户名:");
String loginName = scanner.nextLine();
System.out.println("密码:");
String loginPwd = scanner.nextLine();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
public static void login(Map<String,String> userLoginInfo){
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
Connection con = null;
Statement stat = null;
ResultSet rs = null;
try {//1>注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2>获取连接
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","111");
//3>获取数据库操作对象
stat = con.createStatement();
//4>执行SQL语句
String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd+"'";
//5>操作结果集
rs = stat.executeQuery(sql);
if(rs.next()){
System.out.println("登录成功");
}else{
System.out.println("登陆失败");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally{
//6>释放连接
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
6.3、SQL注入问题
用户名:fdsa
密码:fdsa' or '1'='1
输入如上信息时,照样登录成功了
- 通过debug查出此时sql语句为
- 这就是sql注入,通过在密码中写入sql相关语句从而达到破解登录的目的
6.4、如何解决sql注入
- preparedStatament这个接口继承了java,sql.Statement
- PreparedStatement是属于预编译的数据库操作对象
- PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传"值"
- 解决SQL注入的关键是:用户提供的信息中即使含有sql语句的关键字,但是这些关键字
并不参与编译,不起作用
import javax.xml.transform.Result;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class JDBCTest01 {
public static void main(String[] args) {
Map<String,String> userLoginInfo = initUI();
login(userLoginInfo);
}
public static Map<String, String> initUI() {
Map<String,String> userLoginInfo = new HashMap<>();
Scanner scanner = new Scanner(System.in);
System.out.println("用户名:");
String loginName = scanner.nextLine();
System.out.println("密码:");
String loginPwd = scanner.nextLine();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
public static void login(Map<String,String> userLoginInfo){
boolean flag = false;
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {//1>注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2>获取连接
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","111");
//3>获取数据库操作对象
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
ps = con.prepareStatement(sql);
ps.setString(1,loginName);
ps.setString(2,loginPwd);
//ps.setInt(1,xxx)这里还有setInt方法
//4>操作SQL
rs = ps.executeQuery();
//5>操作结果集
if(rs.next()){
flag = true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally{
//6>释放连接
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
System.out.println(flag?"登陆成功":"登录失败");
}
}
}
6.5、Statement和PreparedStatement区别
- Statement存在sql注入问题,PreparedStatement解决了SQL注入问题
- Statement是编译一下执行一次。PreparedStatement是编译一次,可执行N次。(第二次输入时候与之前完全一样的sql语句,dbms不会编译,而是直接执行)所有PreparedStatement效率也更高
- PreparedStatement会在编译阶段做类型的安全检查。
- 99%的情况下是使用PreparedStatement
6.5.1、 什么时候使用Statement?
- 业务方面要求必须支持SQL注入的时候
- 例如需要根据输入的字符串选择升序还是降序时(浏览商品时,价格的升序降序)
7>使用JDBC完成数据的增删改
package ustc.java.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
使用PreparedStatement完成insert、update、delete
*/
public class JDBCTest10 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","146");
// 3、获取预编译的数据库操作对象
/*String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1,60);
ps.setString(2,"研发部");
ps.setString(3,"北京");*/
/*String sql = "update dept set dname = ?, loc = ? where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"销售二部");
ps.setString(2,"西安");
ps.setInt(3,60);*/
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,60);
// 4、执行sql语句
int count = ps.executeUpdate();
System.out.println(count == 1? "修改成功" : "修改失败");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
// 6、释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
8>JDBC的事务提交机制
- 以下代码证明JDBC的事务提交机制为自动提交
- 模拟转账A有2w,B有0元
- 如果中途出现故障,由于自动提交机制
- A转出1w给B,但B依然是0元
package ustc.java.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest11 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","111");
// 获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ? ";
ps = conn.prepareStatement(sql);
ps.setInt(1,10000);
ps.setDouble(2,111);
// 执行sql语句
int count = ps.executeUpdate();
String s = null;
s.toString();
ps.setInt(1,10000);
ps.setDouble(2,222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
- 将自动提交改成手动提交,如果出现异常回滚
package ustc.java.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest12 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","146");
// 将自动提交改为手动提交
conn.setAutoCommit(false);//重要
// 获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ? ";
ps = conn.prepareStatement(sql);
ps.setInt(1,10000);
ps.setDouble(2,111);
// 执行sql语句
int count = ps.executeUpdate();
/*String s = null;
s.toString();*/
ps.setInt(1,10000);
ps.setDouble(2,222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
// 程序能执行到此处,说明没有异常,事务结束,手动提交数据
conn.commit();//重要
} catch (Exception e) {
// 遇到异常,回滚
if (conn != null) {
try {
conn.rollback();//重要
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
9>创建JDBC工具类
package ustc.java.jdbc.DBUtil;
import java.sql.*;
/*
JDBC工具类,简化JDBC编程
*/
public class DBUtil {
/**
* 工具类中的构造方法是私有的
* 因为工具类中的方法都是静态的,直接通过类名去调即可。
*/
private DBUtil(){}
/**
* 静态代码块,类加载的时候执行
* 把注册驱动程序的代码放在静态代码块中,避免多次获取连接对象时重复调用
*/
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","111");
}
public static void close(Connection conn, Statement ps, ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
10>悲观锁和乐观锁
- 悲观锁(行级锁):
select * from emp where job ='MANAGER' for update
- 重要就是这个
for update
- 事务没结束之前,事务必须排队,一整行记录不准改动,不允许并发。
- 乐观锁:支持并发,事务不排队,需要一个版本号。一个事务发现前后的版本号不一致了 就执行回滚操作 ,本次操作不执行。