目录
JDBC是什么?
JDBC的本质是什么?
JDBC开发前的准备工作:
资源指路:
idea配置MySQL驱动的方法:
JDBC编程六步(重要):
示例:
JDBC执行增删改操作(无需处理查询结果集):
注册驱动的另一种方式(常用):
使用配置文件的JDBC示例:
处理查询结果集:
用户登录业务实现:
SQL注入:
解决SQL注入(使用PreparedStatement代替Statement):
什么情况下必须使用statement呢?
使用preparedStatement进行增删改操作:
JDBC的事务机制:
JDBC工具类的封装:
JDBC模拟查询(使用上述封装的工具类):
简述行级锁(悲观锁与乐观锁):
JDBC是什么?
Java DataBase Connectivity(Java语言连接数据库)
JDBC的本质是什么?
JDBC是SUN公司制定的一套接口(interface),存储在 java.sql.*; 包下。
思考:为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样。Oracle数据库有自己的原理,MySQL数据库也有自己的原理,MS SqlServer数据库也有自己的原理。每一个数据库产品都有自己独特的实现原理。
为了使得Java软件工程师所写的代码可以操纵所有的数据库,SUN公司定义了一套接口(JDBC),接口中含有许多对数据库进行操作的抽象方法,而各大数据库厂商就负责分别使用自己数据库的底层原理实现该方法,使得Java软件工程师可以通过调用一个抽象方法而适用于所有的数据库。
JDBC开发前的准备工作:
先从官网下载对应的驱动jar包(厂家对抽象方法的实现),再配置到自己的代码运行环境中。(以idea为例)
资源指路:
MySQL版本(本文演示版本):MySQL8.0.28.0安装包-MySQL文档类资源-CSDN下载
MySQL5.0驱动:JDBC:MySQL5.1.23驱动-Java文档类资源-CSDN下载
MySQL8.0驱动(本文演示版本):JDBC:MySQL8.0.29驱动-Java文档类资源-CSDN下载
idea配置MySQL驱动的方法:
JDBC编程六步(重要):
第一步:注册驱动(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道。)
第三步:获取数据库操作对象(专门执行sql语句的对象)
第四步:执行SQL语句(DQL DML....)
第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。)
第六步:释放资源(使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。)
示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; // 异常也要导包
import java.sql.Statement;
// 或者直接import java.sql.*;
public class test{
public static void main(String[] args) {
// 基于8.0版本的MySQL
// 5.0版本的MySQL稍微有点不同
try {
// 1、注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
// DriverManager(驱动管理)这个类下有一个名为registerDriver(注册驱动)的方法
// 参数需要一个Driver的对象
// 但Driver是一个接口,是SUN定义的接口,我们需要它的实现类,即数据库厂家对他的实现类(驱动)
// 上述代码可以拆分为:
// java.sql.Driver driver = new com.mysql.cj.jdbc.Driver(); // mysql的驱动。
// 多态,父类型引用指向子类型对象。
// java.sql.Driver driver = new oracle.jdbc.driver.OracleDriver(); // oracle的驱动。
// java.sql.DriverManager.registerDriver(driver);
// 建议不能直接DriverManager.registerDriver(new Driver());
// 如果要这样写,就必须导入com.mysql.cj.jdbc.Driver同时不能导入java.sql.Driver
// 2、获取连接(连接具体数据库,需ip、端口、数据库名、用户名、密码)
String url="jdbc:mysql://localhost:3306/hyf";
// jdbc:mysql:// 协议(固定写法)
// localhost 本机ip,也可以写其他电脑的ip
// 3306 MySQL的默认端口号
// hyf是该ip的MySQL中的一个数据库名称
String user="root";
String password="root";
Connection conn = DriverManager.getConnection(url,user,password);
// DriverManager(驱动管理)这个类下有一个名为getConnection(获取连接对象)的方法
// 该方法需要三个参数(String url,String user,String password)
// 该方法会返回一个连接对象,该连接对象是Connection接口的实现类对象
// oracle的URL:
// jdbc:oracle:thin:@localhost:1521:orcl
// 3、获取数据库操作对象
Statement stmt = conn.createStatement();
// Connection类中有一个createStatement的方法可以获取到数据库操作对象
// 数据库操作对象是Statement接口的一个实现类对象
// 我们可以通过这个对象对数据库进行一系列的操作
// 我们以后无论是对什么数据库进行操作,都只要调用stmt对象的方法即可,
// 具体对Statement抽象类方法实现写在各大厂家的驱动里面
// 4、执行SQL语句
String sql = "insert into dept(deptno,dname,loc) values(50,'测试部','广东')";
// Statement类中有一个executeUpdate方法,专门执行DML语句(insert,delete,update)
// 返回值是”影响数据库中的记录条数“
int count = stmt.executeUpdate(sql);
System.out.println(count==1 ? "保存成功" : "保存失败");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
接下来,我们就要释放资源:
为了保证资源一定释放,在finally语句块中释放资源,并且遵循从小到大依次关闭。
如先得到Connection对象再得到Statement对象,那么得先关闭Statement再关闭Connection,还要分别对其try...catch,因为如果都在一个try...catch语句块中可能会出现关闭Statement发生错误直接执行catch语句而不执行关闭Connection语句的情况。
注册驱动没有对象产生,不用释放资源。
注意:如果对象变量语句的定义写在try语句中,那么finally语句块就不能访问也就不能关闭,所有上述代码的变量定义要写在try语句的外面。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; // 异常也要导包
import java.sql.Statement;
// 或者直接import java.sql.*;
public class test{
public static void main(String[] args) {
// 基于8.0版本的MySQL
// 5.0版本的MySQL稍微有点不同
Connection conn = null;
Statement stmt = null; // 对象变量移到外面
try {
// 1、注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
// DriverManager(驱动管理)这个类下有一个名为registerDriver(注册驱动)的方法
// 参数需要一个Driver的对象
// 但Driver是一个接口,是SUN定义的接口,我们需要它的实现类,即数据库厂家对他的实现类(驱动)
// 上述代码可以拆分为:
// java.sql.Driver driver = new com.mysql.cj.jdbc.Driver(); // mysql的驱动。
// 多态,父类型引用指向子类型对象。
// java.sql.Driver driver = new oracle.jdbc.driver.OracleDriver(); // oracle的驱动。
// java.sql.DriverManager.registerDriver(driver);
// 建议不能直接DriverManager.registerDriver(new Driver());
// 如果要这样写,就必须导入com.mysql.cj.jdbc.Driver同时不能导入java.sql.Driver
// 2、获取连接(连接具体数据库,需ip、端口、数据库名、用户名、密码)
String url="jdbc:mysql://localhost:3306/hyf";
// jdbc:mysql:// 协议(固定写法)
// localhost 本机ip,也可以写其他电脑的ip
// 3306 MySQL的默认端口号
// hyf是该ip的MySQL中的一个数据库名称
String user="root";
String password="root";
conn = DriverManager.getConnection(url,user,password);
// DriverManager(驱动管理)这个类下有一个名为getConnection(获取连接对象)的方法
// 该方法需要三个参数(String url,String user,String password)
// 该方法会返回一个连接对象,该连接对象是Connection接口的实现类对象
// oracle的URL:
// jdbc:oracle:thin:@localhost:1521:orcl
// 3、获取数据库操作对象
stmt = conn.createStatement();
// Connection类中有一个createStatement的方法可以获取到数据库操作对象
// 数据库操作对象是Statement接口的一个实现类对象
// 我们可以通过这个对象对数据库进行一系列的操作
// 我们以后无论是对什么数据库进行操作,都只要调用stmt对象的方法即可,
// 具体对Statement抽象类方法实现写在各大厂家的驱动里面
// 4、执行SQL语句
String sql = "insert into dept(deptno,dname,loc) values(50,'测试部','广东')";
// Statement类中有一个executeUpdate方法,专门执行DML语句(insert,delete,update)
// 返回值是”影响数据库中的记录条数“
int count = stmt.executeUpdate(sql);
System.out.println(count==1 ? "保存成功" : "保存失败");
} catch (SQLException e) {
e.printStackTrace();
}finally { // 为了保证资源一定释放,在finally语句块中释放资源
// 并且遵循从小到大依次关闭
// 如先得到Connection对象再得到Statement对象,那么得先关闭Statement再关闭Connection
// 且要分别对其try...catch,
// 如果都在一个try...catch语句块中可能会出现关闭Statement发生错误直接执行catch语句而不执行关闭Connection语句的情况
// 注册驱动没有对象产生,不用释放资源
// 第6步,关闭资源(没有进行查询操作,不需要进行第5步处理查询结果集)
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
JDBC执行增删改操作(无需处理查询结果集):
import java.sql.*;
public class test{
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1、注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
// 2、获取连接(连接具体数据库,需ip、端口、数据库名、用户名、密码)
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
// 3、获取数据库操作对象
stmt = conn.createStatement();
// 4、执行SQL语句
int count = stmt.executeUpdate("delete from dept where deptno=50");
System.out.println(count==1 ? "删除成功" : "删除失败");
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 第6步,关闭资源(没有进行查询操作,不需要进行第5步处理查询结果集)
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
我们只要在 stmt.executeUpdate() 方法中填写增删改语句,都可以直接运行方法对数据库进行增删改操作。
注意:
在JDBC中,SQL语句都不需要添加分号结束,添加会报错!!
注册驱动的另一种方式(常用):
MySQL驱动源码中对Driver抽象类的实现:
我们可以看到,Driver实现类中有一个静态代码块,而这个代码块中正是我们注册驱动的代码。
所以可以直接使用反射机制反射这个class文件,这个类就会被加载进JVM,静态代码块在类加载时执行,从而执行注册驱动的语句。
import java.sql.*;
public class test{
public static void main(String[] args) {
try {
// 原写法:
// DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 参数是MySQL驱动的位置
// 获得链接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
System.out.println(conn);
}catch (SQLException e){ // 这里抛出了两个异常:ClassNotFoundException 和 SQLException
e.printStackTrace();
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
}
用反射机制的好处:
因为 Class.forName() 方法的参数是一个字符串,字符串可以写到 xxx.properties 的配置文件中,其余的 url、user、password 也都是字符串,可以一并写入配置文件当中。这样我们的客户如果想修改注册的驱动、修改数据库的url、修改数据库的登录者,都不需要修改Java程序的源代码,只需修改配置文件即可,代码没有写死,耦合度得到了降低。同时我们也可以在不获取用户的账户密码的前提下完成我们的开发工作,一举多得。
使用配置文件的JDBC示例:
Java源程序:
import java.sql.*;
import java.util.*;
public class test {
public static void main(String[] args){
// 使用资源绑定器绑定属性配置文件
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 = "insert into dept(deptno,dname,loc) values(50,'测试部','广东')";
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(conn != null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
配置文件:
处理查询结果集:
import java.sql.*;
public class test {
public static void main(String[] args){
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/hyf","root","root");
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行sql
String sql = "select empno as a,ename,sal from emp";
// executeUpdate方法是专门执行insert、delete、update操作的
// 如果要执行查询操作,就要调用java.sql.executeQuery()方法
// 该方法会返回一个ResultSet接口的实现类对象,该对象存储了SQL语句得到的结果集
// 我们可以通过调用ResultSet接口的抽象方法遍历查询得到的结果集
//5、处理查询结果集
rs = stmt.executeQuery(sql); // 专门执行DQL语句的方法。
// ResultSet接口下有一个next()方法,该方法会返回还有没有下一行数据可以读取
// ResultSet接口有许多getXXX()的方法,如getString、getInt等,
// getXXX()方法中参数可以是一个数字表示第几列,也可以是一个具体的字段名(表头)、
// 注意:如果查询时给字段起了别名,则getXXX()方法的参数中也应该是别名而不是原来的字段名
// 如果以String类型去取,则就算原本是int类型也会转为String类型
while(rs.next()){
// 除了可以以String类型取出之外,还可以以特定的类型取出。
int empno = rs.getInt("a");
String ename = rs.getString("ename");
double sal = rs.getDouble("sal");
System.out.println(empno + "," + ename + "," + (sal + 200));
}
}catch(Exception e){
e.printStackTrace();
}finally{
//6、释放资源
if(rs != null){ // rs变量所属的ResultSet接口最后开启,所以最先关闭
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){ // 再关闭Statement
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){ // 再关闭Connection
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
用户登录业务实现:
1、需求:
模拟用户登录功能的实现。
2、业务描述:
程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码用户输入用户名和密码之后,提交信息,java程序收集到用户信息后,连接数据库验证用户名和密码是否存在。存在:显示登录成功。不存在:显示登录失败。
3、数据库:
4、代码实现:
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class test {
public static void main(String[] args){
// 初始化登陆界面
Map userLoginInfo = initUI();
// 验证用户名和密码
boolean loginSuccess = Login(userLoginInfo);
// 输出结果
System.out.println(loginSuccess ? "登陆成功" : "登陆失败");
}
private static boolean Login(Map userLoginInfo) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
boolean loginSuccess =false;
try {
// 1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2、获得连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
// 3、获得数据库操作对象
stmt = conn.createStatement();
// 4、执行sql
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd+"'";
rs = stmt.executeQuery(sql);
if(rs.next()){ // 结果为true则说明结果集中有数据,登陆成功
loginSuccess=true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException 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();
}
}
}
return loginSuccess;
}
private static Map initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
SQL注入:
上述用户登录业务存在BUG!
BUG演示:
这种现象被称作SQL注入。
BUG原因:
当上述密码输入后,原 SQL 查询语句将被拼接为:
select * from t_user where loginName = 'svd' and loginPwd = 'vad' or '1'='1'
并参与 SQL 编译。因为 and 的优先级比 or 高,所以原 where 语句块就变成了
where (loginName = 'svd' and loginPwd = 'vad') or '1'='1'
这是一个百分百为 true 的语句,就会将 t_user 表中的所有成员全部查询出来,
if(rs.next()){ // 结果为true则说明结果集中有数据,登陆成功
loginSuccess=true;
}
if 语句中永远为 true ,就会登陆成功。
SQL注入的根本原因是什么:
用户输入的信息中含有sql语句的关键字,并且这个关键字参与了sql语句的编译,导致原sql语句的意思被扭曲了,进而达成sql注入。
解决SQL注入(使用PreparedStatement代替Statement):
导致SQL注入的原因是用户输入的SQL语句参与了编译,只要用户输入的SQL语句不参与编译,那么就无法达成SQL注入。
PrepareStatement是statement的子接口,是一个预编译的数据库操作对象。
prepareStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”。
import java.sql.*;
import java.util.Scanner;
public class test {
public static void main(String[] args){
// 初始化登陆界面
Map userLoginInfo = initUI();
// 验证用户名和密码
boolean loginSuccess = Login(userLoginInfo);
// 输出结果
System.out.println(loginSuccess ? "登陆成功" : "登陆失败");
}
private static boolean Login(Map userLoginInfo) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
boolean loginSuccess =false;
try {
// 1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2、获得连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
// 3、获得预编译的数据库操作对象
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
// sql语句的位置改变了,并且不再使用字符串的拼接,而是使用占位符?来表示接收用户输入的信息所在位置。
// 注意,占位符不能使用单引号括起来,不然就会被认为是一个字符
// 这不能再使用 conn.createStatement() 了,而是要使用 conn.prepareStatement();
ps = conn.prepareStatement(sql); // 需要传参,原conn.createStatement()不用传参
// 程序运行到此处,会发送sql语句的框架给DBMS,然后DBMS进行sql语句的预先编译。
// 4、执行sql
// rs = ps.executeQuery(sql);
// 这里不能再传参了,如果传参,sql语句会再编译一次,还是会把用户输入的信息进行编译从而导致sql注入
// 上面已经把sql语句的框子传给了ps,ps是已经有sql语句的了,不需要再次传给sql语句
// 给占位符传值(第1个问号下标是1,第2个问号的下标是2,数据库的下标都是从1开始)
// 调用setXXX()方法来给占位符传值,第一个参数是下标,第二个参数是值
// setXXX()中XXX是所传递的值的类型
ps.setString(1,loginName); // 先获得预编译的sql操作对象再插入值!!!!!!!!
ps.setString(2,loginPwd);
// 即便用户输入了sql语句也不会参与sql语句的编译,也就不会导致sql语句的注入
// 即便 ps.setString(1,100); 100也会被当作字符串加上单引号
rs = ps.executeQuery(); // 先传值再执行SQL
if(rs.next()){ // 结果为true则说明结果集中有数据,登陆成功
loginSuccess=true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e){
e.printStackTrace();
} finally {
// 释放资源
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;
}
private static Map initUI() {
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
对比一下Statement和Preparedstatement?
1、statement存在sgI注入问题,Preparedstatement解决了sql注入问题。
2、因为SQL语句在第一次编译只后,如果下一次还是一模一样的SQL语句,那么就不会再次编译而是直接执行。因为statement每一次都先进行字符串的拼接所有每一次的SQL语句都不相同,属于编译一次执行一次。而Preparedstatement因为编译的是一个SQL框架,框架是不会发生改变的,所有只需编译一次,可执行N次。Preparedstatement效率较高一些。
3、Preparedstatement会在编译阶段做输入类型的安全检查。
综上所述:Preparedstatement使用较多。只有极少数的情况下需要使用statement。
什么情况下必须使用statement呢?
当业务方面要求必须支持sou注入,需要进行sql语句拼接的的时候,就必须使用statement。
项目要求:
输入desc降序输出,输入asc升序输出。
在这里面,因为会有sql语句的改变,所有必须使用sql注入。
import java.sql.*;
import java.util.Scanner;
public class test {
public static void main(String[] args){
// 输入desc表示降序,输入asc表示升序
Scanner s = new Scanner(System.in);
System.out.print("输入desc表示降序,输入asc表示升序:");
String keyWord = s.nextLine();
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
// 获取数据库操作对象
stat = conn.createStatement();
// 执行SQL
String sql = "select ename from emp order by ename "+keyWord;
rs = stat.executeQuery(sql);
while (rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e){
e.printStackTrace();
} finally {
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat != null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
使用preparedStatement进行增删改操作:
package A;
import java.sql.*;
public class test {
public static void main(String[] args){
Connection conn = null;
PreparedStatement ps = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
// 获取预编译的数据库操作对象
String sql = "insert into dept(deptno,dname,loc) values (?,?,?)";
ps = conn.prepareStatement(sql);
// 执行SQL
ps.setInt(1,60);
ps.setString(2,"销售部");
ps.setString(3,"上海");
ps.executeUpdate(); // 记住,这里使用executeUpdate()而不是executeQuery()
System.out.println("添加成功!");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException 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();
}
}
}
}
}
修改和删除操作同理,只需改变SQL语句即可。
JDBC的事务机制:
JDBC中的事务是自动提交的,即只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DMI语句在同一个事务中同时成功或者同时失败。
在 java.sql.connection 接口下有一个 setAutoCommit() 方法,该方法可设置JDBC的事务提交机制。当用该接口的实现类对象调用该方法时,传递的实参是 false ,则表示取消自动提交机制,为 true 表示开始自动提交机制。
在 java.sql.connection 接口下有一个 commit() 方法,用该接口的实现类对象调用该方法表示提交事务。
在 java.sql.connection 接口下有一个 rollback() 方法,用该接口的实现类对象调用该方法表示提交事务。
转账业务的实现:
import java.sql.*;
public class test {
public static void main(String[] args){
Connection conn = null;
PreparedStatement ps = null;
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
// 将自动提交机制改为手动提交
conn.setAutoCommit(false); // 开启事务
// 获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
// 执行SQL
ps.setInt(1,10000); // 转账后所剩余额
ps.setInt(2,111); // 用户代号
int count = ps.executeUpdate(); // 记住,这里使用executeUpdate()而不是executeQuery()
ps.setInt(1,10000); // 转账后所剩余额
ps.setInt(2,222); // 用户代号
count += ps.executeUpdate(); // 记住,这里使用executeUpdate()而不是executeQuery()
System.out.println(count==2 ? "转账成功" : "转账失败");
// 程序运行到此处表示以上程序没有出现异常
// 提交事务
conn.commit();
} catch (Exception e){ // 如果使用多个catch语句则要分别在所有的catch语句中调用方法回滚事务
if(conn != null){ // 记得非空判断
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
finally {
// 关闭资源
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
JDBC工具类的封装:
JDBC有太多重复的代码了,我们可以将它封装起来。
import java.sql.*;
public class DBUtil {
// 构造方法
private DBUtil(){
}
static { // 注册驱动最好写在静态代码块中,因为只需注册一次,如果放在getConnection中则每一个获取连接对象都会注册一次驱动,重复累赘
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
// 因为sql语句不是固定的,sql语句要在其他要使用这个工具类的方法中编写
// 执行sql语句就得调用Connection的Statement方法或prepareStatement。
// 执行这些方法会抛SQLException异常,而获取连接也会抛这个异常,所以可以干脆将异常上抛不捕捉
return DriverManager.getConnection("jdbc:mysql://localhost:3306/hyf","root","root");
}
public static void close(Connection conn,Statement ps,ResultSet rs){ // 使用Statement可以接收Statement和prepareStatement两种对象
// 如果没有查询结果集则可以传null,传null就不会执行close()方法
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}



