✅作者简介:大家好我是@每天都要敲代码,希望一起努力,一起进步!
个人主页:@每天都要敲代码的个人主页
系列专栏:MySQL专栏
目录
一:JDBC概述
1. JDBC本质的理解
2. 模拟JDBC本质
3. 将驱动jar配置到环境变量classpath中
4. JDBC编程六步(重点)
二:JDBC编程实操
1. JDBC编程六步实操
2. JDBC执行删除与更新
3. 类加载的方式注册驱动(常用)
4. 从属性资源文件中读取连接数据库信息(重要)
5. 处理查询结果集
6. 使用IDEA开发JDBC代码配置驱动
7. 模拟用户登录功能
7.1 具体需求
7.2 使用PowerDesigner工具进行物理建模
7.3 具体代码
8. SQL注入
8.1 解决SQL注入
8.2 演示Statement的用途
9. PreparedStatement完成增删改
10. JDBC的事务提交机制
10.1 JDBC的事务自动提交机制演示
10.2 账户转账演示事务(经典例题)
11. JDBC工具类的封装
12. 悲观锁(行级锁)和乐观锁
12.1 基本概念
12.2 演示行级锁机制
(1)JDBC是什么
Java DataBase Connectivity(Java语言连接数据库)(2)JDBC的本质是什么?
JDBC是SUN公司制定的一套接口(interface),java.sql.*; (这个软件包下有很多接口)
接口都有调用者和实现者; 面向接口调用、面向接口写实现类,这都属于面向接口(3)为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
Animal a = new Cat(); // 父类型的引用指向子类型的对象
Animal a = new Dog();
// 喂养的方法
public void feed(Animal a){ // 面向父类型编程。
}
(4)思考:为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样:
Oracle数据库有自己的原理。
MySQL数据库也有自己的原理。
每一个数据库产品都有自己独特的实现原理。
(5)JDBC的本质到底是什么:是一套接口
总共有三种角色
SUN角色:定义接口
实现者(厂家)角色:实现接口
调用者(程序员)角色:进行调用
(1)模拟SUN角色:定义接口
// SUN公司负责制定这套JDBC接口
public interface JDBC {
// 连接数据库的方法
void getConnection();
}
(2)实现者(厂家)角色:实现接口
// MySQL的数据库厂家负责编写JDBC接口的实现类
public class MySQL implements JDBC{ // 实现类被称为驱动(mysql驱动)
public void getConnection(){
// 具体这里的代码怎么写,我们不需要管
// 这段代码涉及到mysql底层数据库的实现原理。
System.out.println("连接MYSQL数据库成功!");
}
}
(3) 调用者(程序员)角色:进行调用
import java.util.ResourceBundle;
/*
Java程序员角色。
不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码。
面向接口编程,面向抽象编程,不要面向具体编程。
*/
public class JavaProgrammer {
public static void main(String[] args) throws Exception{
//1.
JDBC jdbc = new MySQL();
//2. 也可以通过反射机制创建对象
Class c = Class.forName("MySQL");
JDBC jdbc = (JDBC)c.newInstance();
//3. 当然我们也可以通过配置文件+反射进行读取,以后不需要更改Java代码,只需要改配置文件就行
// 配置文件是jdbc.properties,内容是:className=MySQL
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String className = bundle.getString("className");
Class c = Class.forName(className);
JDBC jdbc = (JDBC)c.newInstance();
// 以下代码都是面向接口调用方法,不需要修改
jdbc.getConnection();
}
}
配置文件:jdbc.properties
className=MySQL
(1)JDBC开发前的准备工作,先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中。
(2)我们使用MySQL的时候需要把MySQ安装路径下的bin目录配置环境变量Path当中,这样才能正常使用
(3)我使用的MySQL,就需要去官网下载对应的jar包,并把jar包所在的路径配置到环境变量classpath当中;这样JDBC才可以根据这个驱动连接数据库!例如:
// path
C:\Program Files (x86)\MySQL\MySQL Server 5.5\bin
// classpath
.;C:\Java学习\3.JDBC\相关学习资源\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
// 注意:要加上 . 表示当前路径,路径与路径之间用分号 ; 隔开
如果不配置,就相当于没有jar包;没有jar包就表示实现接口的实现类没有
(3)以上的配置是针对于文本编辑器的方式开发,使用IDEA工具的时候,不需要配置以上的环境变量;IDEA有自己的配置方式!
第一步:注册驱动
作用:告诉Java程序,即将要连接的是哪个品牌的数据库
第二步:获取连接
表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道
第三步:获取数据库操作对象
专门执行sql语句的对象
第四步:执行SQL语句
主要是DQL DML....
第五步:处理查询结果集
只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集
第六步:释放资源
使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭
1、注册驱动
(1)使用一个静态的方法:static void registerDriver(Driver driver)
这个方法作用:向DriverManager(驱动管理器)注册给定驱动程序
Driver是一个接口,不能直接new对象,我们需要找到这个接口的实现类,有一个com.mysql.jdbc.Driver()类实现了java.sql.Driver()接口
2、获取连接
(1)调用DriverManager里的一个getConnection()方法,返回一个Connection;getConnection()方法需要三个参数url、username、password;例如:
String url = "jdbc:mysql://127.0.0.1:3306/bjpowernode";
String username = "root";
String password = "***";
Connection con = DriverManager.getConnection(url,username,password);
(2)补充url
url:统一资源定位符(网络中某个资源的绝对路径)
https://www.baidu.com/ 这就是URL
URL包括哪几部分:协议、IP、PORT(协议)、资源名例如:http://163.177.151.109:80/index.html(也通过这种方式访问百度)
1)http:// 通信协议
2)163.177.151.109 服务器IP地址
3)80 服务器上软件的端口
4)index.html 是服务器上某个资源名
解释:jdbc:mysql://127.0.0.1:3306/bjpowernode
1)jdbc:mysql:// 协议
2)127.0.0.1 IP地址
3)3306 mysql数据库端口号
4)bjpowernode 具体的数据库实例名
注意:localhost和127.0.0.1都是本机IP地址;当然也可以写成我们用ipconfig /all查出来的IP地址
补充:什么是通信协议,有什么用?
通信协议是通信之前就提前定好的数据传送格式;数据包具体怎么传数据,格式提前定好的。
Oracle的URL: jdbc:oracle:thin:@localhost:1521:orcl3、获取数据库操作对象(Statement专门执行sql语句的)
调用createStatement对象来将SQL语句发送到数据库,返回的是一个Statement,Statement抛SQLException异常4、执行sql
调用executeUpdate(String sql),返回的是int;执行给定的SQL语句,该语句可能是insert、update、delete;或者不返回任何内容的SQL语句,如:DDL语句5、处理查询结果集:不是select语句,不用执行
6、释放资源
为了保证资源一定释放,在finally语句块中关闭资源,并且要遵循从小到大依次关闭,分别对其try..catch
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 con = null;
Statement stmt = null;
try{
//1、注册驱动
Driver driver = new com.mysql.jdbc.Driver(); // 多态,父类型的引用指向子类型的对象,前面也可以进行导包
DriverManager.registerDriver(driver); // registerDriver有异常
//合并:DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2、获取连接
String url = "jdbc:mysql://127.0.0.1:3306/bjpowernode";
String username = "root";
String password = "***";
con = DriverManager.getConnection(url,username,password);
//拿到连接对象
System.out.println("数据库连接对象 = "+con); // 数据库连接对象 = com.mysql.jdbc.JDBC4Connection@41cf53f9
//3、获取数据库操作对象(Statement专门执行sql语句的)
stmt = con.createStatement();
//4、执行sql
String sql = "insert into dept(deptno,dname,loc) values (50,'人事部','北京')";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "保存成功":"保存失败");
//5、处理查询结果集:不是select语句,不用执行
}
catch (SQLException e){
e.printStackTrace();
}finally{
//6、释放资源
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(con != null){
con.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
import java.sql.*;
public class JDBCTest02{
public static void main(String[] args) {
Connection con = null;
Statement stmt = null;
try {
// 1、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2、获取连接
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123");
// 3、获取数据库操作对象
stmt = con.createStatement();
// 4、执行sql
// 4.1删除
String sql = "delete from dept where deptno = 50";
// 4.2更新
String sql = "update dept set dname = '销售部',loc = '安徽省' where deptno = 20";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1?"删除成功":"删除失败");
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6、释放资源
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
(1)为什么采用类加载的这种方式常用?
因为参数是一个字符串,字符串可以写到xxx.properties配置文件中;并且以下方法不需要接收返回值,因为我们只想用它的类加载动作
(2)使用Class.forName("com.mysql.jdbc.Driver");原理是类加载,让静态代码块执行!
import java.sql.*;
public class JDBCTest03{
public static void main(String[] args){
try{
//1、注册驱动
// 第一种方式
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 第二种方式:采用类加载(常用)
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","123");
System.out.println(con); // com.mysql.jdbc.JDBC4Connection@41cf53f9
}
catch (SQLException e){
e.printStackTrace();
} catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
jdbc.properties配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/bjpowernode
username=root
password=***
以后直接修改配置文件就可以更改整个代码,并且不需要重新进行编译!
import java.sql.*;
import java.util.*;
public class JDBCTest04{
public static void main(String[] args) {
// 使用资源绑定器绑定属性配置文件
ResourceBundle bundle = ResourceBundle.getBundle("jdbc"); //配置文件的名字
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String username = bundle.getString("username");
String password = bundle.getString("password");
Connection con = null;
Statement stmt = null;
try {
// 1、注册驱动
Class.forName(driver);
// 2、获取连接
con = DriverManager.getConnection(url,username,password);
// 3、获取数据库操作对象
stmt = con.createStatement();
// 4、执行sql,修改
String sql = "update dept set dname = '销售部1',loc = '安徽省1' where deptno = 20";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1?"修改成功":"修改失败");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6、释放资源
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
(1)对于DML(insert、update、delete)我们调用的是executeUpdate(sql),返回的是int
例如:int count = stmt.executeUpdate(sql);
(2)对于DQL(select)我们调用的是executeQuery(sql),返回的是结果集ResultSet对象
例如:ResultSet rs = stmt.executeQuery(sql);
(3)拿到了ResultSet结果集,我们怎么把数据取出来呢?
我们调用next()方法,返回boolean,将光标从当前位置向前移一行;如果新的当前行有效,返回true,如果不存在下一行,返回false;
之后在调用getString()方法得到每一列;getString()方法的特点是:不管数据库中的数据类型是什么,都以String的形式取出。
jdbc.properties配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/bjpowernode
username=root
password=123
具体代码:
import java.util.*;
import java.sql.*;
public class JDBCTest05{
public static void main(String[] args) {
ResourceBundle boundle = ResourceBundle.getBundle("jdbc");
String driver = boundle.getString("driver");
String url = boundle.getString("url");
String username = boundle.getString("username");
String password = boundle.getString("password");
Connection con = null;
Statement stmt = null;
ResultSet rs = null; //结果集
try {
//1、注册驱动
Class.forName(driver);
//2、获取连接
con = DriverManager.getConnection(url,username,password);
//3、获取数据库操作对象
stmt = con.createStatement();
//4、执行sql
String sql = "select empno,ename,sal from emp";
rs = stmt.executeQuery(sql); //专门执行DQL语句的方法
//5、处理查询结果集
while(rs.next()){ //tru表示有数据
//取数据,getString(列的下标)
//getString()方法的特点是:不管数据库中的数据类型是什么,都以String的形式取出。
// 第一种方式:使用列的下标
String empno = rs.getString(1);
String ename = rs.getString(2);
String sal = rs.getString(3);
System.out.println(empno+","+ename+","+sal);
// 第二种方式:使用列名,更加的健壮
// 这个列名不是表中的列表,而是我们实际查询结果的列名
// 比如:我们利用as进行了重命名,那么就是列名就是重命名的名字
String empno = rs.getString("empno");
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno+","+ename+","+sal);
// 第三种:我们也可以以其它类型取出来,例如empno按照int类型取出,sal按照double类型取出
int empno = rs.getInt("empno");
String ename = rs.getString("ename");
double sal = rs.getDouble("sal");
System.out.println(empno+","+ename+","+(sal+100)); //这样取出来就可以进行数学运算
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6、释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
执行结果如下:
(1)先引包,就相当于我们前面使用文本编辑器开发时,配置的环境变量calsspath;我们引包时,要想每个模块都能使用mysql必须每个都导入:
选择指定的模块右键---》Open Module Settings---》Libraries---》点击右侧的+号选择Java---》找到包mysql-connector-java-5.1.23-bin.jar导进去---》导入对应的模块(例如:jdbc)
package com.bjpowernode.jdbc;
public class JDBCTest6 {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
再次查看,出现以下界面说明我们导入成功
实现功能:
1、需求: 模拟用户登录功能的实现。
2、业务描述: 程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码 用户输入用户名和密码之后,提交信息,java程序收集到用户信息 Java程序连接数据库验证用户名和密码是否合法 合法:显示登录成功 不合法:显示登录失败
3、数据的准备: 在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner 使用PD工具来进行数据库表的设计。(参见user-login.sql脚本)
(1)选择Create Model
(2)创建model
(3)按ctrl+滑轮,里面有很多小格子,可以放很多表;在里面创建表
(4)对表进行设计
点击,小箭头,再双击表,对表进行设计
(5)设计好以后,再次打开sql语句脚本自动生成
(6)保存这张表和sql语句脚本
直接ctrl+s保存当前的表,是一个.pdm文件
把sql语句,保存成.sql脚本的形式
(7)找到sql脚本,在对其进行操作 ,然后初始化到数据库当中
我们使用source执行sql脚本
发现中文有乱码:因为 我们存储的时候使用的是utf-8,但是dos窗口使用的是GBK,显示肯定会乱码;我们使用navicat显示就没问题了
package com.bjpowernode.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class JDBCTest6 {
public static void main(String[] args) {
//1. 初始化一个界面
Map userLoginInfo = initUI();
//2. 验证用户名和密码
boolean loginSuccess = login(userLoginInfo); //把集合传进去
System.out.println(loginSuccess?"登陆成功":"登陆失败");
}
/**
*
* @param userLoginInfo 包装了用户登录的信息
* @return true登录成功,false登录失败
*/
private static boolean login(Map userLoginInfo) {
// 打标记
boolean loginSuccess = false;
//从集合里拿到用户和密码
String loginName = userLoginInfo.get("loginName");
String logPwd = userLoginInfo.get("logPwd");
//JDBC代码
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123");
//3. 获取数据库操作对象
stmt = conn.createStatement();
//4. 执行sql
String sql = "select * from t_user where loginName = '"+loginName+"' and logPwd = '"+logPwd+"'";
// 以上正好完成了sql语句的拼接,以下代码的含义是,发送sql语句给DBMS,DBMS进行sql编译。
// 正好将用户提供的“非法信息”编译进去。导致了原sql语句的含义被扭曲了。
rs = stmt.executeQuery(sql);
//5. 处理结果集
if(rs.next()){ //里面有数据,说明登录成功
// 登录成功
loginSuccess = true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
//6. 释放资源
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();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登陆信息
*/
private static Map initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String logPwd = s.nextLine();
// 把数据组装到Map集合里面
Map userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("logPwd",logPwd);
return userLoginInfo; //返回Map集合
}
}
登录成功
登陆失败
我们先来看一个明显的bug,就算我们输入的账户和密码都不对,也能正常成功登录
(1)当前程序存在的问题:
用户名:fdsa
密码:fdsa' or '1'='1
登录成功
这种现象被称为SQL注入(安全隐患)。(黑客经常使用)
(2)导致SQL注入的根本原因是什么?
用户输入的信息中含有sql语句的关键字(or),并且这些关键字参与sql语句的编译过程,导致sql语句的原意被扭曲,进而达到sql注入。
(1)解决SQL注入问题?
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。
即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用。
要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库操作对象。
PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”。
(2)测试结果:
用户名:fdas
密码:fdsa' or '1'='1
登录失败
(3)解决SQL注入的关键是什么?
用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译。不起作用。(4)在MySQL中,如果输入的两条语句完全一模一样,第二次不会编译,直接执行!
(5)对比一下Statement和PreparedStatement?
1)Statement存在sql注入问题,PreparedStatement解决了SQL注入问题。
2)Statement是编译一次,执行一次,例如:JDBCTest6例题中每次密码和账户可能不同,都需要编译才能执行。PreparedStatement是编译一次,可执行N次;例如:JDBCTest07例题中每次都是用?占位符,在编译之前SQL语句都不会变,只需要后面进行传址就行;所以PreparedStatement效率较高一些。
3)PreparedStatement会在编译阶段做类型的安全检查
综上所述:PreparedStatement使用较多。只有极少数的情况下需要使用Statement
(6)什么情况下必须使用Statement呢?
业务方面要求必须支持SQL注入的时候。Statement支持SQL注入,凡是业务方面要求是需要进行sql语句拼接的,必须使用Statement
public class JDBCTest07 {
public static void main(String[] args) {
// 初始化一个界面
Map userLoginInfo = initUI();
// 验证用户名和密码
boolean loginSuccess = login(userLoginInfo);
// 最后输出结果
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
/**
* 用户登录
* @param userLoginInfo 用户登录信息
* @return false表示失败,true表示成功
*/
private static boolean login(Map userLoginInfo) {
// 打标记的意识
boolean loginSuccess = false;
// 单独定义变量
String loginName = userLoginInfo.get("loginName");
String logPwd = userLoginInfo.get("logPwd");
// JDBC代码
Connection conn = null;
PreparedStatement ps = null; // 这里使用PreparedStatement(预编译的数据库操作对象)
ResultSet rs = null;
try {
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123");
// 3、获取预编译的数据库操作对象
// SQL语句的框子。其中一个?,表示一个占位符,一个?将来接收一个“值”,注意:占位符不能使用单引号括起来。
String sql = "select * from t_user where loginName = ? and logPwd = ?";
// 程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译。
ps = conn.prepareStatement(sql);
// 给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有下标从1开始。)
ps.setString(1, loginName);
ps.setString(2, logPwd);
// 4、执行sql
rs = ps.executeQuery();
// 5、处理结果集
if(rs.next()){
// 登录成功
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6、释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*/
private static Map initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String logPwd = s.nextLine();
Map userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName", loginName);
userLoginInfo.put("logPwd", logPwd);
return userLoginInfo;
}
}
结论:如果只是给SQL语句传值,使用PreparedStatement
如果进行sql语句拼接的,必须使用Statement
(1)使用PreparedStatement的方式,不支持SQL注入:回复MySQL语法异常,实际上在占位符?替换的时候,替换过去的是desc字符串:‘desc’
package com.bjpowernode.jdbc;
import java.sql.*;
import java.util.Scanner;
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;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123");
//3. 获取预编译数据库操作对象
String sql = "select ename from emp order by ename ?";
ps = conn.prepareStatement(sql);
ps.setString(1,keyWords);
//4. 执行sql
rs = ps.executeQuery();
//5. 处理查询结果集
while(rs.next()){
// 遍历结果集
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6. 释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
(2)使用Statement的方式,支持SQL注入:用户输入的信息中含有sql语句的关键字也会参与编译,可以实现排序功能
package com.bjpowernode.jdbc;
import java.sql.*;
import java.util.Scanner;
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 {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123");
//3. 获取数据库操作对象
stmt = conn.createStatement();
//4. 执行sql
String sql = "select ename from emp order by ename " +keyWords;
rs = stmt.executeQuery(sql);
//5. 处理查询结果集
while(rs.next()){
// 遍历结果集
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6. 释放资源
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();
}
}
}
}
}
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest09 {
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/bjpowernode","root","123");
//3. 获取预编译数据库操作对象\
//3.1 增加
String sql = "insert into dept (deptno,dname,loc) values (?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1,60);
ps.setString(2,"销售部");
ps.setString(3,"上海");
//3.2 更改
String sql = "update dept set dname = ?,loc = ? where deptno = ? ";
ps = conn.prepareStatement(sql);
ps.setString(1,"研发部");
ps.setString(2,"北京");
ps.setInt(3,60);
//3.3 删除
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 (Exception e) {
e.printStackTrace();
} finally {
//6. 关闭资源
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
(1)增
(2)改
(3)删
JDBC事务机制:
(1)JDBC中的事务是自动提交的,什么是自动提交?
只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。
但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。
(2)以下程序先来验证一下JDBC的事务是否是自动提交机制!
测试结果:JDBC中只要执行任意一条DML语句,就提交一次。(3)重点掌握:
将自动提交机制修改为手动机制 conn.setAutoCommit(false); ------开启事务
手动提交 conn.commit(); ------提交事务
为了保证数据的安全性 conn.rollback(); ------回滚事务
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
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/bjpowernode", "root","123");
// 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、释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
(1)先写一个sql脚本,然后source运行
drop table if exists t_act;
create table t_act(
actno int,
balance double(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;
(2)具体代码
package com.bjpowernode.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 {
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root","123");
//将自动提交机制修改为手动机制
conn.setAutoCommit(false); //------开启事务
// 3、获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
// 给?传值
ps.setDouble(1,10000);
ps.setInt(2,111);
int count = ps.executeUpdate();
// 在这里增加异常,验证上面执行,下面不执行,数据会不会变化
String s = null;
s.toString();
//再次给?传值
ps.setDouble(1,10000);
ps.setInt(2,222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功":"转账失败");
// 手动提交
conn.commit(); //------提交事务
} catch (Exception e) {
//为了保证数据的安全性,回滚
if (conn != null) {
try {
conn.rollback(); //------回滚事务
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 6、释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
我们增加在两个update语句中间插了一个空指针异常,验证一下:一个数据更改,一个不更改;最终数据不会变化:
我们把空指针异常代码屏蔽,让两个update语句都执行,看最终数据都会改变!这样就能做到要么都改变,要么都不改变,数据比较安全:
(1)封装好的工具类
package com.bjpowernode.jdbc;
import java.sql.*;
// JDBC工具类,简化JDBC编程
public class JDBCUtil {
//工具类中的构造方法都是私有的。
//因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用。
private JDBCUtil(){} //私有的构造方法,防止new对象
// 静态代码块在类加载时执行,并且只执行一次
// (注册驱动我们主需要一次,写在静态代码块里)
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
*
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
// 在主代码块里已经try...catch了,这里上抛就行
return DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123");
}
/**
* 关闭资源
* @param conn 连接对象
* @param ps 数据库操作对象
* @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(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
(2)写一个模糊查询,来测试工具类的使用
这个程序两个任务:
第一:测试DBUtil是否好用
第二:模糊查询怎么写
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCTest12 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 注册驱动和获取连接对象
conn = JDBCUtil.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()){
System.out.println(rs.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 释放资源
JDBCUtil.close(conn,ps,rs);
}
}
}
(3)测试结果
悲观锁(行级锁):事务必须排队执行,数据被锁住了,不允许并发;例如:
select ename,job,sal from emp where job='manager' for update;
加上for update以后,只要是job='manager'的数据,这一行都会被锁起来,只要当前事务没有结束;别的事务没法对这些数据进行修改
乐观锁:支持多线程并发,事务也不需要排队,都可以对数据进行修改,只不过在数据上会有一个版本号
例如:事务1---》读取到版本号1.1
事务2---》读取到版本号1.1
其中事务1先修改了,修改之后看了版本号是1.1,于是提交修改的数据,将版本号修改为2.2
其中事务2后修改的,修改之后准备提交的时候,发现版本号是1.2,和它最初读取的版本号不一致;会rollback回滚数据
这个程序开启一个事物,这个事务专门进行查询,并且使用行级锁/悲观锁,锁住相关数据
package com.bjpowernode.jdbc;
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 = JDBCUtil.getConnection();
//---开启事务
conn.setAutoCommit(false);
// 获取预编译数据库操作对象
String sql = "select ename,job,sal from emp where job = ? for update";
ps = conn.prepareStatement(sql); //编译
ps.setString(1,"manager");
// 执行sql
rs = ps.executeQuery();
// 处理结果集
while(rs.next()){
System.out.println(rs.getString("ename")+","+rs.getString("job")+","+rs.getString("sal"));
}
//---提交事务(事务结束)
conn.commit();
} catch (Exception e) {
if (conn != null) {
try {
//---回滚事务(事务结束)
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 释放资源
JDBCUtil.close(conn,ps,rs);
}
}
}
这个程序负责修改被锁住的数据
package com.bjpowernode.jdbc;
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 = JDBCUtil.getConnection();
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 (SQLException e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
} finally {
JDBCUtil.close(conn,ps,null);
}
}
}
我们需要先给第一个程序打断点,让事务不结束;再去运行第二个程序!动图效果展示: