JDBC 源码模拟
JDBC全面复习
1.0什么是JDBC?
Java DataBase Connectivity(java语言联结数据库)
1. JDBC本质是什么?
JDBC是sun公司制定的一套接口(interface)
接口都有调用者和实现者
我们可以有面向接口调用,面向接口写实现类.这都属于面向对象编程
- 复习知识点:为什么要有面向接口编程?
解耦合:降低程序的耦合性,提高程序的扩展力
多态机制就是非常典型:面向抽象编程(不要面向具体编程)
建议:
Animal a=new Cat();
Animal b=new Dog();
//写一个具体方法
public void feed(Animal a)//面向父类型编程
不建议使用具体编程
2.思考:为什么sun制定一套JDBC接口呢?
因为每一个oracle数据库有自己的原理
MySQL也有自己的底层原理
MS sqlserver数据库也有自己的原理
.....
每一个数据库产品都有自己独特的实现原理
2.0编写程序模拟JDBC本质
2.1设计一个接口模拟JDBC
package com.rango;
/*SUN公司负责制定这套JDBC接口
* */
public interface JDBC_SUN {
/*
* 连接数据库的方法
* */
void getConnection();
}
几家数据库公司对数据库进行实现
mysql公司:
package com.rango;
/*mysql厂家负责编写JDBC接口的实现类
* 这就是一个MySQL驱动文件
* */
public class MySql_Implements implements JDBC_SUN {
@Override
/*编写这些代码和我们java程序员来说没有关系
* 这里的代码涉及到数据库的实现原理
* */
public void getConnection() {
System.out.println("连接Mysql数据库成功!");
}
}
oracle公司:
package com.rango;
/*这就是一个Oracle驱动
* */
public class Oracle_Implement implements JDBC_SUN{
public void getConnection() {
System.out.println("连接Oracle数据库成功!");
}
}
sqlserver实现:
package com.rango;
/*实现类就是一个驱动
* 很多.class文件,打成一个jar包
* 这就是一个Sqlserver驱动
*
* */
public class Sqlserver implements JDBC_SUN {
public void getConnection() {
System.out.println("连接Sqlserver数据库成功!");
}
}
程序员需要面向接口进行编程
package com.rango;
/*java程序员角色
* 不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码
* 面向接口编程,面向抽象编程,不用面向具体编程
* */
public class JavaProgram {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// JDBC_SUN jdbc=new MySql_Implements();
// jdbc.getConnection();
// 创建对象可以使用反射机制
Class v = Class.forName("com.rango.MySql_Implements");
JDBC_SUN jdbc=(JDBC_SUN) v.newInstance();
//下面代码是面向接口调用方法
jdbc.getConnection();
}
}
2.2使用Resource Bundle进行读取className
前提:可以要解决一些路径问题:ResourceBundle来读取配置文件及路径问题
此时我们Resource Bundle的配置可以这么写
jdbc_.properties
className=com.rango.MySql_Implements
package com.rango;
/*java程序员角色
* 不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码
* 面向接口编程,面向抽象编程,不用面向具体编程
* */
import java.util.Enumeration;
import java.util.ResourceBundle;
public class JavaProgram {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// JDBC_SUN jdbc=new MySql_Implements();
// jdbc.getConnection();
/* 创建对象可以使用反射机制
Class v = Class.forName("com.rango.MySql_Implements");
JDBC_SUN jdbc=(JDBC_SUN) v.newInstance();
下面代码是面向接口调用方法
jdbc.getConnection();*/
//创建对象还可以使用资源绑定器
ResourceBundle budle = ResourceBundle.getBundle("jdbc_");
String className = budle.getString("className");
// String className="???";
Class<?> v = Class.forName(className);
JDBC_SUN jdbc =(JDBC_SUN) v.newInstance();
jdbc.getConnection();
}
}
3.0JDBC编程六步骤(要背)
JDBC本质是一套接口
JDBC开发前的准备工作.下载架包配置到,环境变量里面去
JDBC编程需要会被
- 第一步:注册驱动(作用:告诉java程序,即将要连接的是哪个品牌的数据库)
- 第二步:获取连接(表示JVM的进程,和数据库进程之间的通道,打开了,这属于进程之间的通信,重量级的,使用完成之后,一定要关闭)
- 第三步:获取数据库操作对象(专门执行sql语句的对象)
- 第四步:执行SQL语句,(DQL,DML)
- 第五步:处理查询结果集(只有当第四步执行的是select语句的时候)
- 第六步;释放资源(使用完资源之后一定要关闭资源,java和数据库属于进程间的通信,开启之后一定要关闭)
3.1注册驱动
简单测试
public class JDBCTest01 {
// 第一步:注册驱动,设置一个驱动的类名字,利用反射机制申请对象
//这里我们需要到JDBC实现类的包下面找
private static final String DBDriver ="com.mysql.jdbc.Driver" ;
public static void main(String[] args){
// 第二步获取连接
try{
Class.forName(DBDriver);
System.out.println("测试成功");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 获取数据库操作对象
// 执行SQL语句
// 处理结果集
// 关闭通道
}
}
驱动常常使用的两个类
public interface Driver
每个驱动程序类必须实现的接口。
Java SQL框架允许多个数据库驱动程序。每个驱动程序都应该提供一个实现Driver接口的类
另外一个
Class DriverManager
java.lang.Object
java.sql.DriverManager
常常使用的几个方法
修饰符和类型(Modifier and Type) | Method and Description |
---|---|
static Driver | getDriver(String url) 尝试查找了解给定URL的驱动程序。 |
static Connection | getConnection(String url) 尝试建立与给定数据库URL的连接。 |
static Connection | getConnection(String url, Properties info) 尝试建立与给定数据库URL的连接。 |
static Connection | getConnection(String url, String user, String password) 尝试建立与给定数据库URL的连接。 |
static void | registerDriver(Driver driver) 注册与给定的驱动程序 DriverManager 。 |
getConnection(String url, Properties info) 注意:如果一个属性被指定为的一部分url和在还指定Properties对象,它是实现定义哪个值将优先。 为了最大可移植性,应用程序应仅指定一次属性。
3.2驱动代表的意义
try{
// 1.注册驱动
// 多态父类型指向子类型对象
Driver driver = new Driver();
DriverManager.registerDriver(driver);
// 获取连接
/*url:统一资源定位符(网络中资源的绝对路径)
*http://61.135.169.121:80/index.html
* 一个url包括什么?
* 协议http://通信协议
* IP服务器IP地址
* 端口服务器软件上的端口
* 资源名index.html
* jdbc:mysql://127.0.0.1:3306/test_db
* 协议:jdbc:mysql://
* IP:127.0.0.1也可以写localhost文件
* 3306位数据库端口号
* 具体 数据库名字
* 什么是通信协议?有什么用?
* 通信协议是通信之前就提前定好的数据传送格式
* 数据表具体怎么传数据,格式提前定好
* 数据包怎么传输,格式提前定好
* */
String url="jdbc:mysql://127.0.0.1:3306/test_db";
String user="root";
String password="123456";
Connection connection = DriverManager.getConnection(url,user,password);
4.0执行sql与释放资源
这里使用一个接口
java.sql
Interface Connection
我们可以使用Connection返回的一个方法
修饰符和类型 | 方法和描述 |
---|---|
Statement | Statement createStatement() 创建一个 Statement对象,用于将SQL语句发送到数据库。 |
Statement | createStatement(int resultSetType, int resultSetConcurrency) 创建一个 Statement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。 |
Statement | createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) 创建一个 Statement对象,将产生 ResultSet对象具有给定类型,并发性和可保存性。 |
创建一个Statement对象,用于将SQL语句发送到数据库。 没有参数的SQL语句通常使用Statement对象执行。 如果相同的SQL语句执行了很多次,那么使用PreparedStatement对象可能会更有效。
4.1mysql jdbc url具体参数全解
mysql jdbc url具体参数全解。mysql jdbc url具体参数详解。mysql jdbc url具体参数什么意思。
MySQL的 JDBC URL格式:
jdbc:mysql://localhost:3306/easonjim?profileSQL=true
对应中文环境,通常MySQL连接URL可以设置为:
jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=true&characterEncoding=gbk&autoReconnect=true&failOverReadOnly=false
需要注意的是,在XML配置文件中,URL中的&符号需要转义。比如在Tomcat的server.xml中配置数据库连接池时,MySQL JDBC URL样例如下:
jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=true&characterEncoding=gbk
&autoReconnect=true&failOverReadOnly=false
常用具体参数:
参数名称 | 参数说明 | 缺省值 |
---|---|---|
useUnicode | 是否使用Unicode字符集,如果参数characterEncoding设置为gb2312或gbk,本参数值必须设置为true | false |
characterEncoding | 当useUnicode设置为true时,指定字符编码。比如可设置为gb2312或gbk,utf8 | false |
autoReconnect | 当数据库连接异常中断时,是否自动重新连接? | false |
autoReconnect | 当数据库连接异常中断时,是否自动重新连接? | false |
5.0类加载方式注册驱动
使用Driver方式进行驱动注册
1.注册驱动
// 多态父类型指向子类型对象
Driver driver = new Driver();
DriverManager.registerDriver(driver);
我们查看Driver中的源码
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
这是我们自己写的静态代码块
try {//执行一个类中的静态代码块
//1.0注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.0获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3360/test_db?useUnicode=true" +
"&characterEncoding=utf8","root","123456");
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}
5.0从属性资源文件中读取连接数据库信息
5.1使用资源绑定器绑定属性文件
我们先编写一个jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=utf8
user=root
password=123456
在开始测试
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class JDBCTest03 {
public static void main(String[] args){
try {//执行一个类中的静态代码块
//1.0注册驱动
ResourceBundle bundle = ResourceBundle.getBundle("jdbc_");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String password = bundle.getString("password");
String user = bundle.getString("user");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
//
System.out.println("连接数据库成功!");
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}
}
}
6.0处理结果集
这里我们使用ResultSet对象返回查询结果
executeQuery
ResultSet executeQuery(String sql)
throws SQLException
这里我们知道
int executeUpdate(insert/delete/update)
ResultSet executeQuery(select)
我们先研究ResultSet 的一些方法
修饰符和类型 | 方法和描述 | 使用 |
---|---|---|
boolean next() | 将光标从当前位置向前移动一行 | 将光标从当前位置向前移动一行。 ResultSet光标最初位于第一行之前; 第一次调用方法next使第一行成为当前行; 第二个调用使第二行成为当前行,依此类推。 |
boolean previous() | 将光标移动到此 ResultSet对象中的上一行。 | 当调用previous方法返回false时,光标位于第一行之前。 任何调用需要当前行的ResultSet方法将导致抛出SQLException 。 |
int getRow() | 检索当前行号。 | 检索当前行号。 第一行是第1行,第2行等等。 注意:对于ResultSet s,支持getRow方法是可选的,结果集类型为TYPE_FORWARD_ONLY |
String getString(int columnIndex) | String getString(int columnIndex) throws SQLException | throws SQLException这个检索的当前行中指定列的值 ResultSet对象为 String的Java编程语言。 |
String getString(String columnLabel) | 这个检索的当前行中指定列的值 ResultSet对象为 String的Java编程语言。 | 这个检索的当前行中指定列的值 ResultSet对象为 String的Java编程语言。参数 columnLabel - 使用SQL AS子句指定的列的标签。 如果未指定SQL AS子句,则该标签是列的名称 |
6.1如何取数据
如何获取查询
ResultSetMetaData
获取有关ResultSet对象中列的类型和属性的信息的对象
import java.sql.*;
import java.util.ResourceBundle;
public class JDBCTest04 {
//打开数据库连接通道,使用数据库查询对象
public static Connection connection=null;
public static Statement statement=null;
public static ResultSet resultSet=null;
//关闭数据库查询对象,以及结果集对象的时候
public static void main(String[] args){
//获取数据库连接属性
ResourceBundle bundle = ResourceBundle.getBundle("jdbc_");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String password = bundle.getString("password");
String user = bundle.getString("user");
//获取完毕
try{
Class.forName(driver);
connection = DriverManager.getConnection(url, user, password);
//获取数据操纵对象
statement = connection.createStatement();
String sql="SELECT `uid`,`uname`,`upwd` \n" +
"FROM `t_user`";
resultSet=statement.executeQuery(sql);
ResultSetMetaData md=resultSet.getMetaData();
int cloumSize=md.getColumnCount();
System.out.println("数据库连接成功!");
//我们先打印字段名
for (int i = 1; i <=cloumSize ; i++) {
System.out.printf("%-20s",md.getColumnName(i));
}
System.out.println();
while (resultSet.next()){
for (int i = 1; i <=cloumSize ; i++) {
System.out.printf("%-20s",resultSet.getObject(i));
}
System.out.println();
}
System.out.println("查询结束");
}catch (Exception e){
e.printStackTrace();
} finally {
//逆向关闭jdbc对象
if(resultSet!=null){
try{
resultSet.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(statement!=null){
try{
resultSet.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(connection!=null){
try{
resultSet.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
7.0 JDBC实践
7.1用户需求设计
/*实现功能:
* 1.需求:模拟用户登录功能的实现
* 2.业务描述:
* 程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
* 用户输入用户名和密码之后,提交信息,java程序收集到用户信息
* java程序连接数据库验证用户名,和密码是否合法
* 合法:显示登录成功
* 不合法:显示登录成功
* 3.数据的准备:
* 在实际开发中,表的设计会使用专业的建模工具,我们在这里安装一个建模工具,powerDesign
* 使用PD工具来进行数据库的设计
* */
设计用户登录表t_user
/*==============================================================*/
/* Table: t_user */
/*==============================================================*/
DROP TABLE t_user IF exists
create table t_user
(
uid bigint auto_increment,
uname varchar(255),
upwd varchar(255),
realname varchar(255),
primary key (uid)
);
insert into t_user ( uname , upwd , realname ) values ('zs1', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs2', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs3', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs4', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs5', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs6', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs7', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs8', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs9', '123','张三1');
7.2设计用户登录页面
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
模拟实现用户登录功能
*/
public class JDBCLogin {
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;
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/test_db","root","123456");
// 3、获取数据库操作对象
stmt = conn.createStatement();
// 4、执行sql语句
String sql = "select * from t_user where uname = '"+ userLoginInfo.get("userName")+ "' and upwd = '" + userLoginInfo.get("userPassword")+ "'";
rs = stmt.executeQuery(sql);
// 5、处理结果集
if(rs.next()) {
loginSuccess = true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
// 6、释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.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 userPassword = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("userName",userName);
userLoginInfo.put("userPassword",userPassword);
return userLoginInfo;
}
}
7.3SQL注入问题
当前程序存在的问题
用户名:fdsa
密码:fdsa' or '1'='1
```![](https://img2020.cnblogs.com/blog/1448883/202010/1448883-20201025144558365-1265547573.png)
这种现象叫做sql注入,我们设置程序断点
```java
String sql = "select * from t_user where uname = '"+ userLoginInfo.get("LoginName")+ "' and upwd = '" + userLoginInfo.get("Loginpwd")+ "'";
//这一段是将sql语句发送给DBMS,DBMS进行sql编译
//正好将用户的"非法语句"执行
导致sql注入问题的根本原因是什么?
用户输入信息中含有sql语句中的关键字,并且这些关键字参数sql的编译过程
导致sql语句的原意被扭曲,进而达到sql注入
7.4解决sql注入问题
只要用户提供的信息参与sql语句的编译过程,问题就解决了
即时用户提供的信息包含sql语句关键字,但是没有参与编译,不起作用
所有要想用户信息不参与sql语句编译,必须使用
public interface PreparedStatement
extends Statement属于预编译的数据库操作对象
PreparedStatement的原理是:预先对SQL语句的框架进行编译,再给SQL语句传值
8.0 statement和PrepareStatement比较
效率方面,由于sql语句在执行过程中如果第一条语句执行之后,第二条语句与第一条完全相等,则第二条不用编译就可执行,速度快,
Statement是每次都传入新的sql语句,每次都要编译速度慢
PrepareStatement是编译一次,下面传值进去,所以速度快
PrepareStatement会在编译阶段进行类型的安全检查
Statement使用较少,PrepareStatement使用较多
8.1PrepareStatement的增删该查
增加一条数据
// 3.获取数据库预编译操作对象
// 增加一条数据
// String sql="INSERT INTO t_user ( uname , upwd , realname ) VALUES (?,?,?);";
// preparedStatement=connection.prepareStatement(sql);
// preparedStatement.setString(1,"wangwu");
// preparedStatement.setString(2,"8888");
// preparedStatement.setString(3,"王五");
修改一条数据
// 修改一条数据
String sql="UPDATE `t_user` SET `uname` =?,`realname`=? WHERE `uid`=?";
preparedStatement=connection.prepareStatement(sql);
preparedStatement.setString(1,"wuwu");
preparedStatement.setString(2,"王五");
preparedStatement.setInt(3,11);
int cnt=preparedStatement.executeUpdate();
System.out.println("执行成功:"+cnt+" 条数据");
删除一条数据
String sql="DELETE FROM t_user WHERE `uid`=?";
preparedStatement =connection.prepareStatement(sql);
preparedStatement.setInt(1,2);
9.0 JDBC的事务自动提交机制的演示
/*JDBC事务机制
* 1.jdbc中的事务都是自动提交的,什么是自动提交?
* 只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为
* 但是实际的业务中,通常都是N条DML语句共同联合才能完成,
* 必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
* 2.一下程序验证JDBC事务自动提交机制
* */
我们先验证jdbc事务机制,插入两条数据,在一条数据之前设置断点,观察数据库的变化
/*JDBC事务机制
* 1.jdbc中的事务都是自动提交的,什么是自动提交?
* 只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为
* 但是实际的业务中,通常都是N条DML语句共同联合才能完成,
* 必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
* 2.一下程序验证JDBC事务自动提交机制
* */
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest {
public static void main(String[] args) {
//搭架子
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
// 1.获取驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.进行连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8",
"root", "123456");
String sql="INSERT INTO t_user ( uname , upwd , realname ) VALUES (?,?,?);";
preparedStatement=connection.prepareStatement(sql);
preparedStatement.setString(1,"wangwu");
preparedStatement.setString(2,"11220");
preparedStatement.setString(3,"王五");
int cnt=preparedStatement.executeUpdate();
System.out.println("执行成功: "+cnt);
preparedStatement.setString(1,"laoliu");
preparedStatement.setString(2,"8888");
preparedStatement.setString(3,"老刘");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if (preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
9.1账户转账演示事务
创建数据表
drop table if exists t_act;
/*==============================================================*/
/* Table: t_act , */
/*==============================================================*/
create table t_act
(
actno bigint(0) not null,
balance double(7,2),
primary key (actno)
);
INSERT INTO t_act (actno,balance) VALUES (111,2223);
INSERT INTO t_act (actno,balance) VALUES (222,4000);
commit;
SELECT *FROM t_act;
执行脚本:
9.2使用Connection中的一个方法setAutoCommit
void setAutoCommit(boolean autoCommit)
throws SQLException
手动打开事务提交机制
简单来说就三行经典代码:
//关闭自动提交事务
connection.setAutoCommit(false);
// 在此处说明执行成功
// 提交事务
connection.commit();
//遇到数据异常,必须回滚事务
if (connection!=null){
try{
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
转账代码
/*JDBC事务机制
* 1.jdbc中的事务都是自动提交的,什么是自动提交?
* 只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为
* 但是实际的业务中,通常都是N条DML语句共同联合才能完成,
* 必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
* 2.一下程序验证JDBC事务自动提交机制
*
* */
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest {
public static void main(String[] args) {
//搭架子
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
// 1.获取驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.进行连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8",
"root", "123456");
// 将自动提交机制改为手动提交机制
connection.setAutoCommit(false);
String sql="UPDATE `t_act` SET `balance`=? WHERE `actno`=?";
preparedStatement=connection.prepareStatement(sql);
preparedStatement.setDouble(1,1223);
preparedStatement.setInt(2,111);
int cnt=preparedStatement.executeUpdate();
preparedStatement.setDouble(1,5000);
preparedStatement.setInt(2,222);
cnt+=preparedStatement.executeUpdate();
System.out.println(cnt==2?"转账成功":"转账失败");
// 在此处说明已经提交成功了
// 提交事务
connection.commit();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
//遇到数据异常,必须回滚事务
if (connection!=null){
try{
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally {
if (preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
执行成功:
10.JDBC工具类的封装
package Jdbc;
/*JDBC工具类,简化编程
*
* */
import java.sql.*;
public class DBUtil {
/*
* 工具类中的构造方法都是私有的
* 因为工具类当中的方法都是静态的,不需要太new对象,直接采用类名调用
*
* */
private DBUtil() {
}
// 静态代码块在类加载时执行,并且只执行一次
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 获取连接对象
/**
* @return 返回连接对象
* @throws SQLException 方法跑出异常,主方法里接收异常
*/
private static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8",
"root", "123456");
}
/**close方法为什么在方法里面跑出异常,我们可以知道关闭类到最后执行,而且一定执行
* 方法重载一个
* @param connection
* @param statement
* @param resultSet
*/
private static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
*
* @param connection
* @param preparedStatement
*/
private static void close(Connection connection,PreparedStatement preparedStatement){
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
10.1通过模糊查询验证我们的工具类
mysql下的like关键字的使用
字符 | 说明 |
---|---|
% | 匹配任何数目的字符,甚至包括零字符 |
_ | 只能匹配一种字符 |
LIKE'Mc%' | 将搜索以字母 Mc 开头的所有字符串(如 McBadden)。 |
LIKE'%inger' | 将搜索以字母 inger 结尾的所有字符串(如 Ringer、Stringer) |
LIKE'%en%' | 将搜索在任何位置包含字母 en 的所有字符串(如 Bennet、Green、McBadden) |
LIKE'_heryl' | 将搜索以字母 heryl 结尾的所有六个字母的名称(如 Cheryl、Sheryl)。 |
11.0悲观锁和乐观锁
我们先使用mysql select语句添加悲观锁
String sql="SELECT `uid`,`uname`,`realname` FROM `t_user` WHERE `uname`=? FOR UPDATE";
package JDBC;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @program: JDBC学习
* @description: 开启一个查询专门进行查询, 使用行级锁/悲观锁,锁住记录
* @author: JiaYadong
* @create: 2020-08-05 17:30
**/
public class JDBCTest12 {
public static void main(String[] args){
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
try{
connection=DBUtil.getConnection();
// 手动开启事务
connection.setAutoCommit(false);
String sql="SELECT `uid`,`uname`,`realname` FROM `t_user` WHERE `uname`=? FOR UPDATE";
preparedStatement= connection.prepareStatement(sql);
preparedStatement.setString(1,"zs3");
resultSet=preparedStatement.executeQuery();
while(resultSet.next()){
System.out.println(resultSet.getString("uid")+","+
resultSet.getString("uname")+","+resultSet.getString("realname"));
}
//sql语句执行完毕,关闭事务
connection.commit();
} catch (SQLException e) {
//如果出现异常,回滚事务
if (connection!=null){
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally {
DBUtil.close(connection, preparedStatement,resultSet);
}
}
}
此处设置断点,再执行另外一个更新操作
package JDBC;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @program: JDBC学习
* @description: 通过修改数据来测试悲观锁
* @author: JiaYadong
* @create: 2020-08-05 17:49
**/
public class JDBCTest13 {
public static void main(String[] args){
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
try{
connection=DBUtil.getConnection();
connection.setAutoCommit(false);
String sql="UPDATE `t_user` SET `uname` =? WHERE `uid`=?";
preparedStatement=connection.prepareStatement(sql);
//填充数据
preparedStatement.setString(1,"马前卒");
preparedStatement.setInt(2,1);
int cnt=preparedStatement.executeUpdate();
System.out.println("执行影响行数: "+cnt);
connection.commit();
} catch (SQLException e) {
//如果事务有异常回滚事务
if(connection!=null){
try{
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally {
DBUtil.close(connection,preparedStatement);
}
}
}
会发现插入数据的程序会被卡着
等待时间过长的时间
java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction
此时终止调试,执行commit
修改事务成功