文章目录
- 1.JDBC是什么?
- 2.JDBC的本质是什么?
- 3.JDBC开发前的准备工作,先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中。
- 4.JDBC编程六步(需要背会)
- 5.案例一:
- 6.案例二(JDBC大致的步骤框架初步版):
- 7.案例三:注册驱动的另一种方法
- 8.案例四:将连接到数据库的所有信息配置到配置文件中
- 9.案例五:处理查询结果集
- 10.使用IDEA开发JDBC代码配置驱动
- 11.总结以上(JDBC代码最终版)
- 12.用户登录业务介绍
- 13.PowerDesigner工具的安装
- 14.数据准备
- 15.用户登录功能界面的初始化
- 16.登录方法的实现
- 17.SQL注入现象
- 18.如何解决SQL注入问题
- 19.Statement和PreparedStatement对比
- 20.演示Statement的用途
- 21.PreparedStatement完成增删改
- 22.总结:JDBC最最终版写法
- 23.JDBC的事务自动提交机制的演示
- 24.演示JDBC事务自动提交机制会引发的问题和解决办法
- 25.总结:JDBC最最最终版写法
- 26.JDBC工具类的封装
- 27.JDBC实现模糊查询+JDBC工具类DBUtil的使用
- 28.总结:JDBC最最最最终版写法
- 29.悲观锁和乐观锁的概念
- 30.演示行级锁(悲观锁)机制
1.JDBC是什么?
Java DataBase Connectivity(Java语言连接数据库)
2.JDBC的本质是什么?
JDBC是SUN公司制定的一套接口(interface)
java.sql.*; (这个软件包下有很多接口)
接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
解耦合:让程序更加灵活(面向接口编程就是面向抽象编程)
灵活:比如下面喂养的方法:
如果写Dog d 只能喂养狗,写Cat c 只能喂养猫
而写Animal a 既能喂养狗也能喂养猫,更加灵活(解耦合)
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
建议:
Animal a = new Cat(); //父类型引用指向子类型对象
Animal a = new Dog();
// 喂养的方法
public void feed(Animal a){ // 面向父类型(抽象)编程。
}
不建议:
Dog d = new Dog();
Cat c = new Cat();
思考:为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理。
MySQL数据库也有自己的原理。
MS SqlServer数据库也有自己的原理。
....
每一个数据库产品都有自己独特的实现原理。
JDBC的本质到底是什么?
一套接口。(通过JDBC来操纵不同的数据库)
模拟角色
3.JDBC开发前的准备工作,先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中。
classpath=.;D:\course\06-JDBC\resources\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
以上的配置是针对于文本编辑器的方式开发,使用IDEA工具的时候,不需要配置以上的环境变量。
IDEA有自己的配置方式。
4.JDBC编程六步(需要背会)
第一步:注册驱动(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道。)
第三步:获取数据库操作对象(专门执行sql语句的对象)
第四步:执行SQL语句(DQL DML....)
第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。)
第六步:释放资源(使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。)
5.案例一:
JDBCTest01
JDBC编程六步
JDBC完成insert
/*
JDBC编程六步
*/
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
public class JDBCTest01{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
//1.注册驱动
//使用java.sql.DriverManager(驱动管理器)中的方法:(static void) registerDiver(Driver driver) 传入一个驱动
//但是jdk中的Driver是java.sql.Driver,是一个接口,无法new对象,所以需要它的实现类
//它的实现类在mysql驱动中:com.mysql.cj.jdbc.Driver() com mysql cj jdbc 是包名。mysql8版本以上要加cj
//registerDriver(...)这个方法存在异常 throw SQLException 受检异常,再往上抛不合适,需要try...catch...
//需要导入包:java.sql.Driver;java.sql.DriverManager;java.sql.SQLException;
Driver driver = new com.mysql.cj.jdbc.Driver();//多态,父类型的引用指向子类型的对象 java.sql.Driver driver = new com.mysql.cj.jdbc.Driver();
//oracle驱动中:Driver driver = new oracle.jdbc.driver.OracleDriver();
DriverManager.registerDriver(driver);
//2.获取连接
//使用java.sql.DriverManager类中的方法:(static Connection) getConnection(String url, String user, String password) 输入url,数据库的用户名和密码;返回一个java.sql.Connection对象(是一个接口)
//该方法同样存在受检异常SQLException;需要导入Connection包
/*
url:统一资源定位符(网络中某个资源的绝对路径)
https://www.baidu.com/ ---> 这就是url
URL包括哪几部分?
协议
IP地址
PORT(端口号)
资源名
http://182.61.200.7:80/index.html
http:// 通信协议
182.61.200.7 服务器的IP地址
80 服务器上软件的端口
index.html 是服务器上某个资源名
要访问某个服务器上的某个软件:需要知道该服务器的IP地址和该软件在该服务器上的端口号
jdbc:mysql://127.0.0.1:3306/bjpowernode
jdbc:mysql:// 协议
127.0.0.1 IP地址(要连接的服务器/电脑的IP地址)
3306 mysql数据库端口号
bjpowernode 具体的数据库实例名
说明:localhost和127.0.0.1都是本机的IP地址
jdbc:mysql://localhost:3306/bjpowernode 和上面相同
什么是通信协议,有什么用?
通信协议是通信之前就提前定好的数据传送格式
数据包具体怎么传送数据,格式提前定好的。
oracle的url:jdbc:oracle:thin:@localhost(IP地址):端口号:数据库名
其他的百度搜
*/
//mysql的url:jdbc:mysql://ip:port/bjpowernode(ip地址 port端口号 bjpowernode数据库名)
String url = "jdbc:mysql://127.0.0.1:3306/bjpowernode";
String user = "root";//数据库用户名
String password = "333";//自己设置的数据库密码
conn = DriverManager.getConnection(url,user,password);
//这里是mysql数据库连接对象(conn) = com.mysql.cj.jdbc.ConnectionImpl@31304f14
System.out.println("数据库连接对象 = " + conn);
//3.获取数据库操作对象
//第二步已经拿到了Connection对象,可以使用Connection的方法:(Statement) createStatement() 获取数据库的操作对象
//该方法存在SQLException异常 并且 需要Statement导包
//Statement专门执行sql语句的。
stmt = conn.createStatement();
//4.执行sql
//例如以下语句
//JDBC的sql语句结尾不需要加分号
String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')";
//使用Statement中的方法:(int) executeUpdate(String sql)
//该方法专门执行DML语句(insert delete update 对于 表中的数据 进行 增删改)
//返回值是“影响数据库中的记录条数” ---> 插入2条返回2/删除3条返回3/更新5条返回5
//即 影响了数据库中表的几条记录
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "保存成功" : "保存失败");
//5.处理查询结果集
}catch(SQLException e){
e.printStackTrace();
}finally{
//6.释放资源
//为了保证资源一定释放,在finally语句块中关闭资源 并且 要遵循 从小到大 依次关闭
//后开先关
//分别对其try..catch
//因此 要把 Connection conn = null; 和 Statement stmt = null;写在try..catch外面
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
6.案例二(JDBC大致的步骤框架初步版):
JDBCTest02
大致的步骤 框架:
导包
import java.sql.*;
public class Test{
public static void main(String[] args){
Connection conn = null; 连接对象
Statement stmt = null; 数据库操作对象
try{
1.注册驱动
DriverManager(驱动管理器)类的registerDriver(Driver driver)方法
多态(父类型的引用指向子类型的对象):
mysql的驱动中的Driver类是对java.sql中的Driver接口的实现
java.sql.Driver driver = new com.mysql.cj.jdbc.Driver();
2.获取连接
通过DriverManager类的getConnection(url,user,password)方法
获得连接对象Connection conn
mysql的url是 jdbc:mysql://localhost:3306/bjpowernode
jdbc:mysql://IP地址:端口号/数据库名
3.获取数据库操作对象
通过Connection类的createStatement()方法
获得数据库的操作对象 Statement stmt
Statement stmt = conn.createStatement();
4.执行sql语句
首先写出sql语句: String sql = "...";
然后通过Statement类的executeUpdate(String sql)方法
执行sql语句
返回int:影响了数据库中的几条数据(插入2条返回2/删除3条返回3/更新5条返回5)
}catch(SQLException e){
e.printStackTrace();
}finally{
6.释放资源
遵循 后开先闭 规则
先创建Connection conn,后创建Statement stmt
先关闭 stmt,后关闭 conn
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
JDBC完成delete
/*
JDBC完成delete
*/
import java.sql.*;
public class JDBCTest02{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
//1.注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句
String sql = "delete from dept where deptno = 40";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "删除成功" : "删除失败");
}catch(SQLException e){
e.printStackTrace();
}finally{
//6.释放资源
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
JDBC完成update
将案例二的第四步改为:
String sql = "update dept set dname = '销售部' , loc = '芝加哥' where deptno = 30";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "更新成功" : "更新失败");
7.案例三:注册驱动的另一种方法
/*
注册驱动的另一种方式(常用的)
*/
import java.sql.*;
public class JDBCTest03{
public static void main(String[] args){
try{
//1.注册驱动
//第一种写法:
//DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//第二种写法
//查看com.mysql.cj.jdbc.Driver类的源代码发现:该类中有一个静态代码块
//静态代码块中存在第一种注册驱动写法的语句
//所以不需要写第一种方法,只让该静态代码块执行就行了
//如何让一个类的静态代码块执行?让这个类加载就行了
//如何加载这个类?使用反射机制(与反射机制相关的java.lang.Class类的forName(String className)方法)
//Class.forName(类名)这个方法的执行会导致类加载,类加载时静态代码块执行
Class.forName("com.mysql.cj.jdbc.Driver");
//为什么使用第二种方法?因为参数是一个字符串,而字符串可以写到xxx.properties配置文件中
//注意:该方法存在ClassNotFoundException异常
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//com.mysql.cj.jdbc.ConnectionImpl@a1153bc
System.out.println(conn);
}catch(SQLException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
8.案例四:将连接到数据库的所有信息配置到配置文件中
//将连接到数据库的所有信息 配置到 配置文件中
/*
为什么这么做?
实际开发中不建议把连接数据库的信息写死到java程序中
以后修改,只需要修改属性配置文件xxx.properties中的内容就好了
*/
import java.sql.*;
import java.util.ResourceBundle;
public class JDBCTest04{
public static void main(String[] args){
//使用资源绑定器java.util.ResourceBundle类 绑定 属性配置文件jdbc.properties
//第一步:使用ResourceBundle类的getBundle(String 属性配置文件路径(不加文件扩展名))方法获得资源绑定器
//第二步:通过getString(String ...)方法从属性配置文件中获得要用的数据
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
try{
//1.注册驱动
Class.forName(driver);
//2.获取连接
conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句
String sql = "update dept set dname = '销售部1' , loc = '芝加哥1' where deptno = 30";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "更新成功" : "更新失败");
}catch(Exception e){ //SQLException异常和ClassNotFoundException异常
e.printStackTrace();
}finally{
//6.释放资源
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
9.案例五:处理查询结果集
JDBC完成查
//处理查询结果集
//此案例先不使用资源绑定器
import java.sql.*;
public class JDBCTest05{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null; //结果集对象 释放资源时先释放rs
try{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql
String sql = "select empno,ename,sal from emp";
//专门执行DQL语句(select)的方法:Statement类的executeQuery(String sql)方法 返回ResultSet结果集对象
rs = stmt.executeQuery(sql);
//5.处理查询结果集(看下图)
/*
首先使用ResultSet类的next()方法,判断下一行有没有数据。有数据返回true,没有返回false
boolean falg1 = rs.next();
然后如果光标指向有数据,则取数据
使用ResultSet类的getString(int index)方法,返回String类型:
不管数据库中的数据是什么类型,都以String形式取出
index从1开始,JDBC中所有下标从1开始
if(flag1 = true){
String empno = rs.getString(1); index = 1 取出第一列的数据
String ename = rs.getString(2); index = 2 取出第二列的数据
String sal = rs.getString(3); index = 3 取出第三列的数据
System.out.println(empno + "," + ename + "," + sal);
}
*/
while(rs.next() == true){
/*
String empno = rs.getString(1);
String ename = rs.getString(2);
String sal = rs.getString(3);
System.out.println(empno + "," + ename + "," + sal);
*/
//也可以-->getString(String 字段名/列名称) 这样更好
//列名称不是表中的列名称,而是查询结果集的列名称(即字段名被修改,要输入修改后的字段名)
String empno = rs.getString("empno");
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno + "," + ename + "," + sal);
/*
注意:除了可以以String类型取出,也可以以特定的类型取出
int empno = rs.getInt("empno");
double sal = rs.getDouble("sal");
根据该数据的实际类型
这样更方便计算
*/
}
}catch(Exception e){//SQLException异常和ClassNotFoundException异常
e.printStackTrace();
}finally{
try{
if(rs != null){
rs.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(stmt != null){
stmt.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
10.使用IDEA开发JDBC代码配置驱动
对模块module右键,点击open module settings,再点击libraries(库)
再点击“+”号,点java,找到mysql的驱动jar包(我的是mysql-connector-java-8.0.28.jar)所在位置
点击应用,点OK
注意:每创建一个新的模块,都要配置一次驱动(导入jar包)
11.总结以上(JDBC代码最终版)
第一种:不使用 将数据库的信息配置到属性配置文件中
JDBC关于DML语句(insert delete update)
import java.sql.*;
public class Test{
public static void main(String[] args){
Connection conn = null; //连接对象
Statement stmt = null; //数据库操作对象
try{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句 DML语句(insert delete update)
//String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')";
//String sql = "delete from dept where deptno = 40";
String sql = "update dept set dname = '销售部' , loc = '芝加哥' where deptno = 30";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "更新成功" : "更新失败");
}
catch(Exception e){
e.printStackTrace();
}finally{
//6.释放资源
try{
if(stmt != null){
stmt.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
JDBC关于DQL语句(select) 处理查询结果集
import java.sql.*;
public class Test{
public static void main(String[] args){
Connection conn = null; //连接对象
Statement stmt = null; //数据库操作对象
ResultSet rs = null; //结果集对象 释放资源时先释放rs
try{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句
String sql = "select empno,ename,sal from emp";
//专门执行DQL语句(select)的方法:Statement类的executeQuery(String sql)方法 返回ResultSet结果集对象
rs = stmt.executeQuery(sql);
//5.处理查询结果集
while(rs.next() == true){
/*
String empno = rs.getString(1); index = 1 取出第一列的数据
String ename = rs.getString(2); index = 2 取出第二列的数据
String sal = rs.getString(3); index = 3 取出第三列的数据
System.out.println(empno + "," + ename + "," + sal);
*/
//也可以-->getString(String 字段名/列名称) 这样更好
//列名称不是表中的列名称,而是查询结果集的列名称(即字段名被修改,要输入修改后的字段名)
String empno = rs.getString("empno");
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno + "," + ename + "," + sal);
/*
注意:除了可以以String类型取出,也可以以特定的类型取出
int empno = rs.getInt("empno");
double sal = rs.getDouble("sal");
根据该数据的实际类型
这样更方便计算
*/
}
}
catch(Exception e){
e.printStackTrace();
}finally{
//6.释放资源
try{
if(rs != null){
rs.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(stmt != null){
stmt.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
第二种:使用 将数据库的信息配置到属性配置文件中 (常用这种)
import java.sql.*;
import java.util.ResourceBundle;
public class JDBCTest04{
public static void main(String[] args){
//使用资源绑定器java.util.ResourceBundle类 绑定 属性配置文件jdbc.properties
//第一步:使用ResourceBundle类的getBundle(String 属性配置文件路径(不加文件扩展名))方法获得资源绑定器
//第二步:通过getString(String ...)方法从属性配置文件中获得要用的数据
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
try{
//1.注册驱动
Class.forName(driver);
//2.获取连接
conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句
String sql = "update dept set dname = '销售部1' , loc = '芝加哥1' where deptno = 30";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "更新成功" : "更新失败");
}catch(Exception e){ //SQLException异常和ClassNotFoundException异常
e.printStackTrace();
}finally{
//6.释放资源
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/bjpowernode
user=root
password=333
总结:
1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root",333);
3.获取数据库操作对象
Statement stmt = conn.getStatement();
4.执行sql
第一种:DML语句(insert delete update)
String sql = "DML语句";
int count = stmt.executeUpdate(sql); //影响数据库数据条数
第二种:DQL语句(select)
String sql ="DQL语句";
ResultSet rs = stmt.executeQuery(sql);
5.处理查询结果集(有第四步的第二种才有第五步)
while(rs.next() = true){
第一种:
String empno = rs.getString(1); //参数为index 从1开始 获取第一列的数据
String ename = rs.getString(2);
String sal = rs.getString(3);
//偏向使用第二、三种
第二种:
String empno = rs.getString("empno"); //参数为字段名
String ename = rs.getString("ename");
String sal = rs.getString("sal");
第三种:
String empno = rs.getString("empno");
int empno = rs.getInt("empno");
double sal = rs.getDouble("sal");
}
6.释放资源(后开先闭原则)
try{
}catch(Exception e){
e.printStackTrace();
}finally{
//是第四步的第二种再写这个rs
try{
if(rs != null){
rs.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(stmt != null){
stmt.close();
}
}catch(Exception e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
注意:推荐将连接到数据库的所有信息 配置到 配置文件中
实际开发中不建议把连接数据库的信息写死到java程序中
以后修改,只需要修改属性配置文件xxx.properties中的内容就好了
//使用资源绑定器java.util.ResourceBundle类 绑定 属性配置文件jdbc.properties
//第一步:使用ResourceBundle类的getBundle(String 属性配置文件路径(不加文件扩展名))方法获得资源绑定器
//第二步:通过getString(String ...)方法从属性配置文件中获得要用的数据
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
属性配置文件:jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/bjpowernode
user=root
password=333
12.用户登录业务介绍
实现功能:
1.需求:模拟用户登录功能的实现
2.业务描述:
程序运行的时候,提供一个用户输入的入口,可以让用户输入用户名和密码;
用户输入用户名和密码之后,提交信息,java程序收集到用户信息;
java程序连接数据库验证用户名和密码是否合法;
合法:显示登录成功
不合法:显示登录失败
3.数据的准备:
在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner
使用PD工具进行数据库表的设计
13.PowerDesigner工具的安装
打开PowerDesigner
选择create module
点击module types
点击physical data module 物理数据模型(建表)
选择DBMS mysql5.0 (新版本无影响)
model name:user-login
点ok
中间是画图区:按住ctrl键滑动滚轮,每一个小格子里都可以放很多张表
name
code:才是真正的用在建表语句的名字
使用这个工具对表进行设计
user-login.sql脚本文件如果太大,用记事本或者navicat打不开
可以使用resource+脚本文件路径名(拉进去) 在DOS命令窗口中打开
resource+脚本文件路径名(拉进去)--->这一步出了问题
总是:ERROR: Failed to open file xx, error: 2
然后发现原因:文件夹的名字不要用中文!
14.数据准备
15.用户登录功能界面的初始化
public class JDBCTest06 {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = initUI();
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*/
private static Map<String, String> initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.println("密码:");
String loginPwd = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
16.登录方法的实现
package com.bjpowernode.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
实现功能:
1.需求:模拟用户登录功能的实现
2.业务描述:
程序运行的时候,提供一个用户输入的入口,可以让用户输入用户名和密码;
用户输入用户名和密码之后,提交信息,java程序收集到用户信息;
java程序连接数据库验证用户名和密码是否合法;
合法:显示登录成功
不合法:显示登录失败
3.数据的准备:
在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner
使用PD工具进行数据库表的设计
*/
public class JDBCTest06 {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = initUI();
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo);
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
/**
* 用户登录
* @param userLoginInfo 用户登录信息
* @return false表示失败,true表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
//打标记
boolean loginSuccess = false;
//单独定义变量
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//JDBC代码
Connection conn = null;
Statement stmt = null;
ResultSet rs =null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql
//String sql = "select * from t_user where loginName = 'xxxx' and loginPwd = 'yyyy'";
String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd+"'";
rs = stmt.executeQuery(sql);
//5.处理查询结构集(这里不需要使用while)
if (rs.next() == true){
//登录成功
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6.释放资源
try {
if (rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (stmt != null){
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*/
private static Map<String, String> initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
17.SQL注入现象
4.当前程序存在的问题:
用户名:fdsa
密码:fdsa' or '1'='1
登录成功
这种现象称为SQL注入(安全隐患)(黑客经常使用)
5.导致SQL注入的根本原因:
用户输入的信息中含有sql语句的关键字,并且这些关
导致sql语句的原意被扭曲,进而达到sql注入
18.如何解决SQL注入问题
package com.bjpowernode.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
1.解决SQL注入问题:
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。
即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,就不起作用。
要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库操作对象
PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”
2.测试结果:
用户名:fdsa
密码:fdsa' or '1'='1
登录失败
3.解决SQL注入的关键是什么?
用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用。
*/
public class JDBCTest07 {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = initUI();
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo);
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
/**
* 用户登录
* @param userLoginInfo 用户登录信息
* @return false表示失败,true表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
//打标记
boolean loginSuccess = false;
//单独定义变量
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//JDBC代码
Connection conn = null;
PreparedStatement ps = null; //使用PreparedStatement
ResultSet rs =null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3.获取预编译的数据库操作对象
//sql语句提前,sql语句的框子,其中一个?表示一个占位符,一个?将来接收一个“值”;注意:占位符不能使用单引号括起来
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
//使用Connection的prepareStatement(String sql)方法
//程序执行到此处,会发送SQL语句框子给DBMS,然后DBMS进行SQL语句的预先编译
ps = conn.prepareStatement(sql);
//给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中的所有下标从1开始)
ps.setString(1,loginName); //如果传值是int 那么setInt(index,...)
ps.setString(2,loginPwd);
//4.执行sql
rs = ps.executeQuery();//不需要再传sql语句了
//5.处理查询结构集(这里不需要使用while)
if (rs.next() == true){
//登录成功
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6.释放资源
try {
if (rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps != null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*/
private static Map<String, String> initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
19.Statement和PreparedStatement对比
-Statement存在SQL注入问题;PreparedStatement解决了SQL注入问题
-Statement是编译一次执行一次;PreparedStatement是编译一次可执行N次;PreparedStatement执行效率较高一些
-PreparedStatement会在编译阶段做类型的安全检查
综上所述:PreparedStatement使用较多,极少数情况下使用Statement
什么情况下必须使用Statement?
业务方面要求必须支持SQL注入时。(PreparedStatement无法支持SQL注入)
Statement支持SQL注入,凡是业务方面需要进行sql语句拼接时,必须使用Statement
只需要给sql语句传值时使用:PreparedStatement
20.演示Statement的用途
如果使用PreparedStatement,使用ps.setString(...)给占位符?传值(字符串)时,编译时会自动给传的值加上单引号
比如:String sql = "select * from t_user where loginName = ? and loginPwd = ?";
如果给?传的是一个值,字符串jack和字符串333,那么会自动加上单引号,即:
ps.setString(1,"jack");
ps.setString(2,"333");
String sql = "select * from t_user where loginName = 'jack' and loginPwd = '333'";
这样是对的
注意:使用ps.setInt(1,100);--->会把第一个问号换成100,不加单引号,因为不是字符串
但是如果传的是desc,想要降序,也会给desc加上单引号,这样就不对了,只能使用Statement
比如:String sql = "select ename from emp order by ename ?";
ps.setString(1,"desc");
String sql = "select ename from emp order by ename 'desc'"; //错误
package com.bjpowernode.jdbc;
import java.sql.*;
import java.util.Scanner;
/*
演示Statement用途:
业务方面要求必须支持SQL注入时。(PreparedStatement无法支持SQL注入)
Statement支持SQL注入,凡是业务方面需要进行sql语句拼接时,必须使用Statement
String sql = "select ename from emp order by ename " + keyWords;
+ keyWords 就是sql语句拼接
*/
public class JDBCTest08 {
public static void main(String[] args) {
//用户在控制台输入desc是降序,输入asc是升序
Scanner s = new Scanner(System.in);
System.out.println("输入desc或asc,desc表示降序,asc表示升序");
System.out.print("请输入:");
String keyWords = s.nextLine();
//sql
Connection conn = null;
Statement stmt = null;
ResultSet rs =null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
/* 错误:
String sql = "select ename from emp order by ename ?";
ps = conn.prepareStatement(sql);
ps.setString(1,keyWords);
rs =ps.executeQuery();*/
stmt = conn.createStatement();
String sql = "select ename from emp order by ename " + keyWords;
rs = stmt.executeQuery(sql);
while (rs.next() == true){
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
21.PreparedStatement完成增删改
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//PreparedStatement完成增删改:insert delete update
public class JDBCTest09 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//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);
} catch (Exception e) {
e.printStackTrace();
}finally {
//6.释放资源
try {
if (ps != null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
22.总结:JDBC最最终版写法
在以前总结的基础上,再加上:
大部分情况下使用PreparedStatement
要根据情况判断是使用PreparedStatement还是Statement
凡是业务方面需要进行sql语句拼接时,必须使用Statement
23.JDBC的事务自动提交机制的演示
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
JDBC事务机制:
1.JDBC中的事务是自动提交的,什么是自动提交?
只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为
但在实际的业务中,通常都是N条DML语句共同联合才能完成,必须保证这些DML语句在同一个事务当中同时成功或同时失败
2.以下程序先来验证一下JDBC的事务是自动提交机制
测试结果:JDBC只要执行任意一条DML语句,则自动提交一次
*/
public class JDBCTest10 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3.获取预处理数据库操作对象
String sql = "update dept set dname = ? where deptno = ?";
ps = conn.prepareStatement(sql);
//第一次给占位符传值
ps.setString(1,"x部门");
ps.setInt(2,30);
int count = ps.executeUpdate();//执行第一条update语句
System.out.println(count);
//重新给占位符传值
ps.setString(1,"y部门");
ps.setInt(2,20);
count = ps.executeUpdate();//执行第二条update语句
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
}finally {
//6.释放资源
try {
if (ps != null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
24.演示JDBC事务自动提交机制会引发的问题和解决办法
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
演示JDBC事务自动提交机制会引发的问题:
账户转账业务
sql脚本:
drop table if exists t_act;
create table t_act(
actno int,
balance double(7,2) //注意:7表示有效数字的个数,2表示小数位的个数
);
insert into t_act(actno,balance) values(111,20000);
insert into t_act(actno,balance) values(222,0);
commit;
select * from t_act;
重点三行代码:将自动提交改为手动提交
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
*/
public class JDBCTest11 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//将自动提交机制改为手动提交 Connection的setAutoCommit(false/true)方法
conn.setAutoCommit(false);//开启事务
//3.获取预处理数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
//111账户给222账户转10000元
//给?传值
ps.setDouble(1,10000);
ps.setInt(2,111);
int count = ps.executeUpdate();
//这里设置一个空指针异常
//正常来说,上下两个sql语句(一个事务)都执行之后,转账才会成功,
//但是因为JDBC的自动提交机制,只提交了上一个sql语句,导致111账户钱少了,但是222账户钱没变
//所以要将jdbc的自动提交机制改为手动提交
String s = null;
s.toString();
//给?传值
ps.setDouble(1,10000);
ps.setInt(2,222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
//程序能够执行到这里说明以上程序没有异常,事务结束,手动提交数据
//Connection的commit()方法
conn.commit();//提交事务
} catch (Exception e) {
//为了保证数据的安全性,遇到异常手动回滚
if(conn != null){
try {
conn.rollback();//回滚事务
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
//6.释放资源
try {
if (ps != null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
25.总结:JDBC最最最终版写法
在以上的基础上:还要考虑将JDBC的自动提交机制改为手动提交机制
26.JDBC工具类的封装
package com.bjpowernode.jdbc.utils;
import java.sql.*;
//JDBC工具类,简化JDBC编程
public class DBUtil {
/**
* 工具类中的构造方法一般都是私有的
* 因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用
*/
private DBUtil(){}
//静态代码块在类加载时执行,并且只执行一次
static {
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据连接对象
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
//有可能调用多次连接对象,但注册驱动只需要一次,所以将注册驱动写在静态代码块中
//Class.forName("com.mysql.cj.jdbc.Driver");
//Class.forName(类名)这个方法的执行会导致类加载,类加载时静态代码块执行
//2.获取连接
//把异常扔出去,不要try..catch,因为自己写代码时会写try..catch
//Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode");
return DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
}
/**
* 关闭资源
* @param conn 连接对象
* @param ps 数据库操作对象 用Statement而不用PreparedStatement 因为PreparedStatement继承Statement
* @param rs 结果集
*/
public static void close(Connection conn, Statement ps, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
27.JDBC实现模糊查询+JDBC工具类DBUtil的使用
package com.bjpowernode.jdbc.utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 这个程序有两个任务:
* 第一:测试DBUtil
* 第二:模糊查询怎么写?
*/
public class JDBCTest12 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//获取连接
conn = DBUtil.getConnection();
//获取预编译的数据库连接对象
String sql = "select ename from emp where ename like ?";
ps =conn.prepareStatement(sql);
ps.setString(1,"_A%");
//执行sql
rs = ps.executeQuery();
//处理查询结果集
while (rs.next() == true){
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放资源
DBUtil.close(conn,ps,rs);
}
}
}
28.总结:JDBC最最最最终版写法
在以前的基础上+JDBC封装的工具类DBUtil
优先使用PreparedStatement
再考虑使用Statement(涉及sql语句的拼接时)
涉及事务(要多个sql语句同时成功/失败)时:将JDBC的自动提交机制改为手动提交机制
和事务相关的语句只有:DML语句(insert delete update)
不是很清楚到底什么时候使用手动提交机制,但是有24中的案例的情况必须使用手动提交
29.悲观锁和乐观锁的概念
行级锁(又叫悲观锁):
select ename,job,sal from emp where job = 'MANAGER';
+-------+---------+---------+
| ename | job | sal |
+-------+---------+---------+
| JONES | MANAGER | 3272.50 |
| BLAKE | MANAGER | 3135.00 |
| CLARK | MANAGER | 2695.00 |
+-------+---------+---------+
select ename,job,sal from emp where job = 'MANAGER' for update;
在select语句后加 for update 就是行级锁
代表在当前事务结束之前,其他事务都无法修改这一行数据
上面select语句表示:那三行数据(其实是对应emp表中的三行数据)都无法修改
乐观锁:
多线程并发 都可以对这行数据修改
30.演示行级锁(悲观锁)机制
package com.bjpowernode.jdbc;
import com.bjpowernode.jdbc.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//演示行级锁(悲观锁)机制
//这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁(悲观锁),锁住相关记录(数据)
public class JDBCTest13 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
//开启事务(关闭JDBC自动提交机制)
conn.setAutoCommit(false);
String sql = "select ename,job,sal from emp where job = ? for update";
ps = conn.prepareStatement(sql);
ps.setString(1,"MANAGER");
rs = ps.executeQuery();
while (rs .next()== true){
System.out.println(rs.getString("ename")+","+rs.getString("job")+","+rs.getDouble("sal"));
}
//提交事务(事务结束)
conn.commit();
} catch (Exception e) {
//回滚事务(事务结束)
if(conn != null){
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,rs);
}
}
}
package com.bjpowernode.jdbc;
import com.bjpowernode.jdbc.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//演示行级锁(悲观锁)机制
//这个程序负责修改被锁定的记录
public class JDBCTest14 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DBUtil.getConnection();
//开启事务(关闭JDBC自动提交机制)
conn.setAutoCommit(false);
String sql = "update emp set sal = sal * 1.1 where job = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"MANAGER");
int count = ps.executeUpdate();
System.out.println(count);
//提交事务(事务结束)
conn.commit();
} catch (Exception e) {
//回滚事务(事务结束)
if(conn != null){
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,null);
}
}
}
通过断点控制test13事务不提交,然后运行test14,发现test14无法输出结果
注:该文章部分转载自动力节点