JDBC-Java数据库连接
1、JDBC是什么?
Java DataBase Connectivity(Java数据库连接)
2、JDBC的本质?
JDBC是SUN公司制定的一套接口(interface)
java.sql.*; (这个软件包下有很多接口。)
接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
建议:
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的本质到底是什么?
一套接口。
3、JDBC开发前的准备工作: 先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中
classpath= . ; D:\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
前面的“.;”必须要有!
“."代表的是当前路径,以他开头,是为了程序运行时,让他在当前路径去寻找额外的一些资源,比如说,你自己写的一些类。
";"是起着分割的作用,如果在前面的目录中没有找到想要,那么会去很分号后面的目录中查找,就这样一级一级的找下去,
直到classpath末尾,如果还没有找到,就报异常!
classpath= . ; 路径1 ; 路径2 ; 路径3 …
以上的配置是针对于文本编辑器(例如记事本)的方式开发。
如果使用IDEA等开发工具的时候,不需要配置以上的环境变量。IDEA有自己的配置方式。
4、JDBC编程六步(背会)
-
第一步:注册驱动
作用:告诉Java程序,即将要连接的是哪个品牌的数据库 -
第二步:获取连接
表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道。 -
第三步:获取数据库操作对象
专门执行sql语句的对象 -
第四步:执行SQL语句
DQL DML… -
第五步:处理查询结果集
只有当第四步执行的是select语句的时候,才有这第五步 处理查询结果集。 -
第六步:释放资源
使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。
5、手写JDBC
5.1、JDBC编程6步 之 DML(增删改)
【JDBCTest01.java】+【JDBCTest02.java】
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
/*
JDBC编程 6步
JDBC 完成 insert
注:这里是用记事本写的,不是用java开发工具
*/
public class JDBCTest01 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
//1、注册驱动
//多态,父类型引用指向子类型对象。
//另外这里的执行前提是已经在classpath中配置了mysql的驱动jar包,否则编译时会报错 “错误: 程序包com.mysql.jdbc不存在”
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver);
//2、获取连接
/*
url:统一资源定位符(网络中某个资源的绝对路径)
url包括哪几部分?
协议
IP
PORT(端口)
资源名
https://www.baidu.com/ 这就是url
在dos窗口,C:\Users\Administrator>ping www.baidu.com,可以看到百度IP为14.215.177.38
现也可以这样访问百度: http://14.215.177.38:80/index.html
http://14.215.177.38:80/index.html
http://————通信协议
14.215.177.38————服务器IP地址
80————服务器上软件的端口
index.html————服务器上面的某个资源名
jdbc:mysql://127.0.0.1:3306/zt
jdbc:mysql:// ————协议
127.0.0.1 ————IP地址
3306 ————数据库端口号
zt ————具体的数据库名
说明:localhost和127.0.0.1 都是本机IP
什么是通信协议?有什么用?
通信协议是通信之前就提前定好的数据传送格式。
数据包具体怎么传数据,格式提前定好的。
*/
//String url = "jdbc:mysql://localhost:3306/zt";
String url = "jdbc:mysql://127.0.0.1:3306/zt";//最后的为数据库名字
String user = "root";
String password = "123";
conn = DriverManager.getConnection(url,user,password);
System.out.println("数据库连接对象:"+conn); //数据库连接对象:com.mysql.jdbc.JDBC4Connection@41cf53f9
//可以知道该对象的类名为com.mysql.jdbc.JDBC4Connection
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行SQL语句
String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')"; //注意:jdbc中的sql语句不需要写分号结尾
// executeUpdate:专门执行DML语句的(DML:数据操作语言,对表当中的数据进行增删改insert、delete、update)
//返回值是“影响数据库中记录条数”
//executeQuery:专门执行DQL语句的(DQL:数据查询语言,对表当中的数据进行查询select)
//返回值是“单个 ResultSet 对象。”
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "添加成功" : "添加失败");
//5、处理查询结果集
} catch (SQLException e) {
e.printStackTrace();
}finally{
//6、释放资源
//为了保证资源一定释放,在finally语句块中关闭资源
//并且要遵循从小到大依次关闭
//分别try。。catch。。
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
import java.sql.*;
/*
JDBC 完成delete、update
注:这里是用记事本写的,不是用java开发工具
*/
public class JDBCTest02 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
//1、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行SQL语句
//String sql = "delete from dept where deptno = 50";
String sql = "update dept set dname = '张三',loc = '黄土高坡' where deptno = 40";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "操作成功" : "操作失败");
//5、处理查询结果集
} 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();
}
}
}
}
5.2、JDBC 注册驱动的第二种方式(这种方式常用)
import java.sql.*;
/*
JDBC 注册驱动的另一种方式(这种方式常用)
注:这里是用记事本写的,不是用java开发工具
*/
public class JDBCTest03 {
public static void main(String[] args) {
try {
//1、注册驱动
//这是注册驱动的第一种方式
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//第二种方式:通过反射机制
/* 在com.mysql.jdbc.Driver类里面,static静态代码块中有这一句:
“java.sql.DriverManager.registerDriver(new Driver());”,这一行代码也就是注册驱动的第一种方式
在反射机制中,要想只让一个类的静态代码块执行,可以使用:Class.forName("完整类名");
也就是这注册驱动的第二种方式。
为什么这种方式更常用?因为参数是一个字符串,可以写到xxx.properties配置文件当中
以后只需要修改配置文件,不用修改java代码,就可以做到对不同类型数据库的连接,不只是mysql
*/
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
System.out.println(conn); //com.mysql.jdbc.JDBC4Connection@41cf53f9
//com.mysql.jdbc.JDBC4Connection@3d646c37
} catch (SQLException e) {
e.printStackTrace();
} catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
5.3、实际开发中,不建议把连接数据库的信息写死到java程序当中,建议将连接数据库的所有信息配置到xxx.properties配置文件当中
【jdbc.properties】+【JDBCTest04.java】
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/zt
user=root
password=123
import java.sql.*;
import java.util.ResourceBundle;
public class JDBCTest04{
public static void main(String[] args) {
//使用资源绑定器绑定属性配置文件(在javase反射机制中学过)
ResourceBundle bundle = ResourceBundle.getBundle("jdbc"); //jdbc.properties后缀不用写
String driver = bundle.getString("driver"); //通过key来获取value
String url = bundle.getString("url");
String user = bundle.getString("user");
String pwd = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
try {
//1、注册驱动
Class.forName(driver);
//2、获取连接
conn = DriverManager.getConnection(url, user, pwd);
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行SQL语句
//String sql = "delete from dept where deptno = 50";
String sql = "update dept set dname = '张三',loc = '黄土高坡' where deptno = 40";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "操作成功" : "操作失败");
//5、处理查询结果集
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException 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();
}
}
}
}
5.4、处理查询结果集 ————DQL(查)
import java.sql.*;
public class JDBCTest05{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zt","root","123");
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行sql语句
String sql = "select empno, ename as n, sal from emp";
// executeUpdate(insert、delete、update),返回值是“影响数据库中记录条数”
// executeQuery(select),返回值是“单个 ResultSet 对象。”
rs = stmt.executeQuery(sql);
//5、处理查询结果集
while (rs.next()){ //返回值代表下一行是否有数据
//取数据
/*
//getString()方法的特点:不管数据库中的数据类型是什么,都以String的形式取出
//方法内的参数,可以是列的下标,也可以是列名(重点注意:不是表中的列名,是查询结果集的列名)
String no = rs.getString(1); //第 1 列
String name = rs.getString("n"); //重命名之后的列名
String sal = rs.getString(3);
System.out.println(no + "," + name + ","+ sal);
*/
//除了可以以String类型取出,还可以以特定的类型取出(但要“适可而止”,比如数据库中类型是字符串的,就不可能以int类型取出)
int no = rs.getInt(1); //数据库中empno类型本身就是int,那么取出int类型肯定可以
//int name = rs.getInt(2); //报错:java.sql.sql异常:getInt()的值无效-“SMITH”
String name = rs.getString(2); //ename类型是varchar(可变长字符串)
int sal = rs.getInt(3); //sal类型是double ,就可以以int类型取出,当然也可以以double类型取出
System.out.println(no + "," + name + ","+ (sal+100));//sal工资加100块钱
}
}catch(Exception e){
e.printStackTrace();
}finally{
//6、释放资源
//遵循从小到大依次关闭
if (rs != null){ //先释放查询结果集rs
try{
rs.close();
}
catch (Exception e){
e.printStackTrace();
}
}
if (stmt != null){ //再释放数据库操作对象stmt
try{
stmt.close();
}
catch (Exception e){
e.printStackTrace();
}
}
if (conn != null){ //最后释放数据库连接对象conn
try{
conn.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
}
6、使用IDEA工具 开发JDBC
6.1、新建空的工程:
File - New - Project - 选中左边最下面那个 Empty Project - Next -输入工程名字jdbc - Finish
6.2、在jdbc工程里面新建一个模块:
File - New - Module - 就选默认的左边第一个 Java,中间最上面可以选择jdk版本(我选了8)
- Next - 输入模块名字jdbc-test - Finish
6.3、将mysql的驱动jar包引入IDEA
(相当于上面的用记事本写jdbc,运行前需要配置classpath环境变量)
jar包位置:D:\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar 引入IDEA
File –> Project Structure,点左边第三个Libraries - 再点右边上面的+号,选择Java
- 在弹出来的窗口里面,找到自己jar包的位置 - ok -选择模块- ok - 点Apply ,再点ok
6.4、实例:用户登录业务
0)前期准备:
【安装PowerDesigner】
在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具: PowerDesigner
使用PD工具来进行数据库表的设计。
PowerDesigner和Navicat for MySQL的区别:
(1)PowerDesigner 是做物理建模的,系统设计,设计数据库的,系统架构师用的
(2)而Navicat for MySQL,是数据库和表都已经有了,
可以用Navicat for MySQL进行便捷的操作,系统架构师不一定会用这个
使用PowerDesigner工具进行物理建模
建一个user表,放用户登录信息
完成之后,保存sql脚本(相关的sql语句)
然后使用dos窗口,登录mysql,使用sql脚本完成表的创建和数据的导入
登录mysql数据库管理系统 mysql -uroot -p333
查看有哪些数据库 show databases;
使用zt数据库 use zt;
查看当前使用的数据库中有哪些表show tables;
初始化数据 source F:\login-user.sql
1)编写代码,实现用户登录业务
package com.yuming.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
实现功能:
1、需求:模拟用户登录功能的实现
2、业务描述:
程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
用户输入用户名和密码之后,提交信息,java程序收集到用户信息
Java程序连接数据库验证用户名和密码是否合法:
合法:显示登录成功
不合法:显示登录失败
3、数据的准备:
在实际开发中,表的设计会使用专业的建模工具,我们这里安装一一个建模工具: PowerDesigner
使用PD工具来进行数据库表的设计。(参见login-user.sql脚本)
4、当前程序存在的问题:
----------------------
用户名:sdsdsa
密码:cscs' or '1'='1
登录成功
---------------------
这种现象称为sql注入(安全隐患,黑客经常使用)
5、导致sql注入的根本原因是什么?
用户输入的信息中含有sql语句关键字,并且这些关键字参与sql语句的编译过程,
导致sql语句的原意被扭曲,进而达到sql注入
*/
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 true表示登录成功,false表示登录失败
*/
private static boolean login(Map<String, String> userLoginInfo) {
//打标记 (表示登录的状态)
boolean loginSuccess = false ;
//先拿到登录名和密码
String username = userLoginInfo.get("user");//map集合,get方法,根据key获取value
String password = userLoginInfo.get("pwd");
//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/zt","root","123");
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行sql
//String sql = "select * from t_user where loginName = 'xxx' and loginPwd = 'yyy'";
//拼接字符串,将指定位置(上面的xxx和yyy)去掉,加上一对双引号,双引号内加一对加号,加号内写变量名
// "......xxxxx...." ------> "......" + 变量名 + "...."
String sql = "select * from t_user where loginName = '"+username+"' and loginPwd = '"+password+"'";
rs = stmt.executeQuery(sql);
//5、处理查询结果集
if (rs.next()){ //rs.next() == true,表示查询语句返回了结果,也就是说存在该用户
//登录成功
loginSuccess = true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException 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<String, String> initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String username = s.nextLine();
System.out.print("密码:");
String password = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("user",username);
userLoginInfo.put("pwd",password);
return userLoginInfo;
}
}
2)关于SQL注入
当前程序存在的问题:
----------------------
用户名:sdscdca
密码:xscs' or '1'='1
登录成功
---------------------
这种现象称为sql注入(安全隐患,黑客经常使用)
3)导致sql注入的根本原因是什么?
用户输入的信息中含有sql语句关键字,并且这些关键字参与sql语句的编译过程,
导致sql语句的原意被扭曲,进而达到sql注入。
可以看到下面两个sql语句,都会得到查询结果:
select * from t_user where loginName = '正确用户名' and loginPwd = '正确密码'
select * from t_user where loginName = 'sdscdca' and loginPwd = ' xscs' or '1'='1 '
4)解决sql注入问题:
只要用户提供的信息不参与sql语句的编译过程,问题就解决了
即使用户输入的信息中含有sql语句关键字,但是没有参与编译,不起作用。
要想用户的信息不参与SQL语句的编译,就必须使用java.sql.PreparedStatement
PreparedStatement接口继承了Statement
PreparedStatement是属于 预编译的 数据库操作对象
PreparedStatement的原理:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”。
package com.yuming.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
测试结果:
用户名:sdsdsd
密码:cscs' or '1'='1
登录失败
用户名:zhangsan
密码:123
登录成功
*/
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 true表示登录成功,false表示登录失败
*/
private static boolean login(Map<String, String> userLoginInfo) {
//打标记 (表示登录的状态)
boolean loginSuccess = false ;
//先拿到登录名和密码
String username = userLoginInfo.get("user");
String password = userLoginInfo.get("pwd");
//jdbc代码
Connection conn = null;
// Statement stmt = null;
PreparedStatement pstmt = null; //这里使用PreparedStatement(预编译的数据库操作对象)!!!!!!!!!!!!!!!
ResultSet rs = null;
try {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
/*
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行sql
String sql = "select * from t_user where loginName = '"+username+"' and loginPwd = '"+password+"'";
rs = stmt.executeQuery(sql);
*/
//3、获取 预编译的 数据库操作对象 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//SQL语句的框架。其中一个?表示一个占位符,一个?将来接收一个"值",注意:占位符不能加单引号括起来!!!!!
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
//程序执行到此处,会发送SQL语句给DBMS,然后DBMS进行SQL语句的预先编译
pstmt = conn.prepareStatement(sql);
//给占位符?传值(第一个?下标是1,第二个?下标是2.。。。jdbc中所有下标都是从1开始)
pstmt.setString(1,username);
pstmt.setString(2,password);
//4、执行sql
rs = pstmt.executeQuery(); //注意,这里就不用在传sql语句了
//5、处理查询结果集
if (rs.next()){
//登录成功
loginSuccess = true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6、释放资源
if (rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null){
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null){
try {
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 username = s.nextLine();
System.out.print("密码:");
String password = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("user",username);
userLoginInfo.put("pwd",password);
return userLoginInfo;
}
}
7、Statement 和 PreparedStatement
1、对比一下 Statement 和 PreparedStatement :
1、Statement存在SQL注入问题——————PreparedStatement解决了SQL注入问题
2、Statement是编译一次,执行一次——————PreparedStatement是编译一次,可执行n次。
PreparedStatement 效率高一些。
3、PreparedStatement 会在编译阶段做类型的安全检查
综上所述:PreparedStatement使用较多,只有极少情况下会需要使用Statement
2、什么情况下,必须使用Statement?
业务方面,要求必须使用SQL注入的时候!Statement支持SQL注入。
凡是业务方面要求是需要进行SQL拼接注入的,必须使用Statement
演示 Statement的用途:【JDBCTest08.java】
3、PreparedStatement完成增删改:【JDBCTest09.java】
import java.sql.*;
import java.util.Scanner;
/*
演示 Statement的用途
*/
public class JDBCTest08 {
public static void main(String[] args) {
//用户在控制台输入desc就是降序,输入asc就是升序
Scanner s = new Scanner(System.in);
System.out.println("输入asc或者desc,asc表示升序,desc表示降序");
System.out.print("请输入:");
String keyWords = s.next();
/* //执行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/zt","root","123");
//3 获取预编译的数据库操作对象
String sql = "select ename from emp order by ename ?";
ps = conn.prepareStatement(sql);
ps.setString(1,keyWords); //问题在这里————》给占位符?传值的时候,因为是setString,所以默认给加单引号括起来了
//4 执行sql语句
rs = ps.executeQuery();
//5 处理查询结果集
while(rs.next()){
System.out.println(rs.getString("ename"));
}
//运行之后,报异常了: mysql语法错误异常,第1行的''asc''附近的问题,它不应该被当成字符串有单引号引起来
// com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
// You have an error in your SQL syntax;
// check the manual that corresponds to your MySQL server version for the right syntax to use near ''asc'' at line 1
// 。。。
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException 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();
}
}
}
*/
//执行sql
Connection conn = null;
// PreparedStatement ps = null;
Statement stmt = null; //这里就必须使用Statement了!!!!!!!!!!!!!!
ResultSet rs = null;
try {
// 1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","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 (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException 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();
}
}
}
}
}
import java.sql.*;
/*
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.jdbc.Driver");
// 2 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
//3 获取预编译的数据库操作对象
/*
//insert
String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1,60);
ps.setString(2,"营销部");
ps.setString(3,"广东");*/
/*
//update
String sql = "update dept set dname=? , loc=? where deptno = 60 ";
ps = conn.prepareStatement(sql);
ps.setString(1,"销售部");
ps.setString(2,"上海");*/
//delete
String sql = "delete from dept where deptno = ? ";
ps = conn.prepareStatement(sql);
ps.setInt(1,60);
//4 执行sql语句
//int count = ps.executeUpdate(sql);
int count = ps.executeUpdate(); //注意,这里不能再传sql语句了
System.out.println(count == 1 ? "操作成功" : "操作失败");
//5 处理查询结果集
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException 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();
}
}
}
}
}
8、JDBC 事务机制
1、JDBC中的事务是自动提交的,什么是自动提交?
只要执行任意一条DML语句,则自动提交一次。这是JDBc默认的事务行为。
但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须
保证他们这些DML语句在同一个事务中同时成功或者同时失败。
2、账户转账演示————jdbc事务控制
(1) JDBC的事务 自动提交机制 的演示
【JDBCTest10.java】
JDBC的事务是自动提交的,但是我们有时候希望的不是自动提交,而是手动提交
(2) 修改为手动提交机制
重点三行代码:
conn.setAutoCommit(false); //开启事务。将自动提交机制 修改为 手动提交
conn.commit(); //手动提交事务
conn.rollback(); //手动回滚事务
【JDBCTest11.java】
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//(1) JDBC的事务 自动提交机制 的演示
/*
【账户转账演示——jdbc事务控制】
业务需求:111账户 转账给112账户 2000元
SQL脚本:
drop table if exists t_act;
create table t_act(
actno int,
balance double(7,2)
);
insert into t_act(actno,balance) values(111,12000);
insert into t_act(actno,balance) values(112,0);
commit;
select * from t_act;
1、JDBC的事务 自动提交机制 的演示
*/
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/zt","root","123");
//3 获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
//给?传值——111账户由12000变为10000
ps.setDouble(1,10000);
ps.setInt(2,111);
int count = ps.executeUpdate();
//制造一个空指针异常,使程序停止到这里
String s = null;
System.out.println(s.toString());
//最后在数据库中看到,111账户减少了2000元,但是112账户依然是0 !!!转账失败!!!这是因为jdbc的事务自动提交机制!!!!!!但现在这里不是我们所希望的!!
//给?传值 ——112账户增加2000
ps.setDouble(1,2000);
ps.setInt(2,112);
//count = count + ps.executeUpdate();
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
} catch (Exception e) {
e.printStackTrace();
} finally{
if (ps != null ){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null ){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
(2)修改为手动提交机制:(运行之前,先把数据库中的111账户的余额修改回12000)
【重点三行代码】:
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.jdbc.Driver");
// 2 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
//将自动提交机制 修改为 手动提交!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
conn.setAutoCommit(false); //开启事务
//3 获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
//给?传值——111账户由12000变为10000
ps.setDouble(1,10000);
ps.setInt(2,111);
int count = ps.executeUpdate();
/*
//制造一个空指针异常,使程序停止到这里
String s = null;
System.out.println(s.toString());
//第一次运行:在数据库中看到,111账户和112账户都没有发生变化 !!!!是我们所希望的!!!!nice!!!!!!!!!!!!!!!
*/
//第二次运行:把这里的代码去掉之后,程序可以正常结束了,111账户减少了2000,112账户增加了2000,转账成功!!!!!!!!!!!
//给?传值 ——112账户增加2000
ps.setDouble(1,2000);
ps.setInt(2,112);
//count = count + ps.executeUpdate();
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{
if (ps != null ){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null ){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
9、JDBC工具类的封装
1) JDBC工具类的封装 【DBUtil.java】
2) JDBC实现模糊查询【JDBCTest12.java】
package com.yuming.jdbc.utils;
import java.sql.*;
/**
* JDBC工具类的封装:
* JDBC工具类,简化JDBC编程
*/
public class DBUtil {
/**
* 工具类当中的构造方法都是私有的,为了防止别人new对象
* 因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用: 类名.方法名
*/
private DBUtil() {
}
//静态代码块,在类加载时执行,且只执行一次
static {
try {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
*
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() {
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt", "root", "123");
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 释放资源
*
* @param conn 连接对象
* @param stmt 数据库操作对象
* @param rs 结果集
*/
public static void close(Connection conn, Statement stmt, ResultSet rs) {
//方法参数中选择Statement没有选择PreparedStatement,是因为Statement是父接口,更通用,
// 即使未来传过来的是PreparedStatement,也会自动向下转型
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();
}
}
}
//测试
public static void main(String[] args) {
Connection conn = DBUtil.getConnection();
System.out.println(conn); //com.mysql.jdbc.JDBC4Connection@3eb07fd3 ----输出连接对象,说明测试成功
DBUtil.close(conn,null,null);
}
}
package com.yuming.jdbc;
import com.yuming.jdbc.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/*
这个程序两个任务:
1、测试DBUtil是否好用
2、JDBC实现模糊查询:找出名字里面,第二个字是A的
*/
public class JDBCTest12 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1 注册驱动 + 2、获取连接
conn = DBUtil.getConnection();
//3 获取预编译的数据库连接对象
/* String sql = "select ename from emp where ename like '_?%'"; //错误写法,占位符不能加单引号括起来
ps = conn.prepareStatement(sql);
ps.setString(1,"A");*/
String sql = "select ename from emp where ename like ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"_A%");
// 4 执行sql语句
rs = ps.executeQuery();
// 5 处理查询结果集
while(rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally{
//6、释放资源
DBUtil.close(conn, ps, rs);
}
}
}
/*
WARD
MARTIN
JAMES
*/
10、拓展:悲观锁(行级锁)和乐观锁
悲观锁和乐观锁的概念:
演示行级锁机制:
【JDBCTest13.java】+【JDBCTest14.java】(其中引用到了上面的JDBC工具类)
2个程序需要一起运行,这里使用断点,debug运行
使用行级锁之后,对应的表中的记录会被锁住,只要该事务没有结束,其他事务就不可能
对这些锁住的记录进行操作,直到该事务结束,行级锁也就没有了,其他事务才可以操作
package com.yuming.jdbc;
import com.yuming.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 {
//1 注册驱动 + 2、获取连接
conn = DBUtil.getConnection();
//将自动提交机制 修改为 手动提交
conn.setAutoCommit(false); //开启事务
//3 获取预编译的数据库连接对象
String sql = "select ename,job,sal from emp where job = ? for update"; //添加行级锁/悲观锁
ps = conn.prepareStatement(sql);
ps.setString(1,"MANAGER");
// 4 执行sql语句
rs = ps.executeQuery();
// 5 处理查询结果集
while(rs.next()){
System.out.println(rs.getString("ename")+","
+rs.getString("job")+","+rs.getDouble("sal"));
}
//手动提交事务(事务结束)
conn.commit(); //在这行代码最前面添加断点!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/*
因为添加断点,运行后,停在了这里,没有执行这行代码,就是没有提交,这个程序(事务)就没有结束,
事务没有结束,由于emp表中,工作岗位为“MANAGER”的记录添加了悲观锁(行级锁),被锁住了,,,
同时再去运行第二个程序,来修改这些记录,看看到底能不能锁住: 最终第二个程序一直卡住了,没有输出结果!!!说明这些记录确实被锁住了
当结束这个(第一个)程序的时候,第二个程序的结果马上出来了,因为第一个程序结束了,就没有被锁了
*/
} catch (SQLException e) {
// 回滚事务(事务结束)
if (conn != null){
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally{
//6、释放资源
DBUtil.close(conn, ps, rs);
}
}
}
package com.yuming.jdbc;
import com.yuming.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 {
//1 注册驱动 + 2、获取连接
conn = DBUtil.getConnection();
//将自动提交机制 修改为 手动提交
conn.setAutoCommit(false); //开启事务
//3 获取预编译的数据库连接对象
String sql = "update emp set sal = sal * 1.1 where job = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"MANAGER");
// 4 执行sql语句
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{
//6、释放资源
DBUtil.close(conn, ps, null);
}
}
}