Java JDBC用法详解

第一章 JDBC

1.1 基本概念

概念:( Java DataBase Connectivity standard Java数据库连接,Java语言操作数据库) 他定义了操作所有关系型数据库的规则(接口)。

具体操作什么数据库用接口实现类实现,这个实现类叫做数据库驱动

JDBC本质: 其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码时驱动jar包中的实现类 (其实就是使用了多态来编程)

1.2 快速入门

步骤

  • 导入驱动jar包 (导入到项目中的libs文件夹里面,然后右键这个包,选择将这个包加入到库)
  • 注册驱动
  • 获取数据库连接对象 Connection
  • 定义sql
  • 获取执行sql语句的对象 statement
  • 执行sql , 接受返回结果
  • 处理结果
  • 释放资源

JDBC快速入门

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class JDBCDemo {
	public static void main(String[] args) throws Exception {

    //2 注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //3 获取数据库的连接对象
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cjy?			serverTimezone=GMT%2B8", "root", "root");//注意数据库后面的设置时区,不然会报错

    //4 定义sql语句
    String sql = "update account set balance = 1500";

    //5 执行sql的对象 Statement
    Statement stmt = conn.createStatement();
    //6 执行sql
    int count = stmt.executeUpdate(sql);//返回值为受影响的行数

    //7 处理结果
    System.out.println(count);//返回2, 因为这里有两行数据被修改了

    //8 释放资源
    stmt.close();
    conn.close();
	}
}

对象

  • DriverManager:驱动管理对象

  • Connection: 数据库连接对象

  • Statement: 执行sql的对象
    当然,除此之外,JDBC中还有两个常用的接口东西

  • ResultSet: 结果集对象

  • PreparedStatement: 执行sql的对象

1.3 DriverManager驱动管理对象

在API文档中,他是一个类;如下功能:

1.3.1注册驱动(其作用就是告诉该程序要使用哪个数据库驱动jar包)

//方法如下(一个**静态方法**):
static void registerDriver (Driver driver) //使用 DriverManager注册给定的驱动程序。 
  
//写代码的时候,用下面这个语句来注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");//自动调用DriverManager.registerDriver(new Driver());

两者之间的关系

我们观察这个字节码文件的源码

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }//这里时一个静态代码块
		static {
    	try {
        DriverManager.registerDriver(new Driver());//这里
    	} catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    	}
    }
}

在加载这个字节码文件时,在com.mysql.cj.jdbc.Driver类中存在静态代码块(里面的方法一定会被执行),在这里面就有DriverManger的这个注册方法,也就是说,加载这个字节码文件的时候,这个方法就被执行了

其作用就是告诉该程序要使用哪个数据库驱动jar包(再次强调)

注意: Mysql 5 之后的驱动jar包可以省略注册驱动的步骤Class.forName(“com.mysql.cj.jdbc.Driver”)😭 因为jar包里面保存的注册驱动的配置文件)

图片所指位置就是配置保存的地方

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V7xrV8pC-1624000127676)(…/…/…/Library/Application Support/typora-user-images/image-20210613143231693.png)]

1.3.2获取数据库连接

方法如下(一个静态方法)

static Connection getConnection (String url, String user, String password) //尝试建立与给定数据库URL的连接。  
//例如
  Connection conn = DriverManager.getConnection(
  "jdbc:mysql://localhost:3306/cjyserverTimezone=GMT%2B8", "root", "root");

Mysql的url语法(适用于mysql)参数是可选项

//**url: 指定连接的路径**
jdbc:mysql://ip地址(域名):端口号/数据库名称[?一些参数]
//user指用户名
//password指密码

细节: 如果连接的是本机的服务器,并且端口也是默认的3306,那么语法可以简写为

jdbc:mysql:///数据库名称[?一些参数]

1.4 Connection数据库连接对象(接口)

功能1:1.4.1获取执行sql的对象

//方法1
Statement createStatement() //创建一个 Statement对象,用于将SQL语句发送到数据库。  

Statement createStatement(int resultSetType, int resultSetConcurrency) //创建一个 Statement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。  

Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) 
//方法2
PreparedStatement prepareStatement(String sql) //创建一个 PreparedStatement对象,用于将参数化的SQL语句发送到数据库。  

PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) //创建一个默认的 PreparedStatement对象,该对象具有检索自动生成的密钥的能力。 

PreparedStatement prepareStatement(String sql, int[] columnIndexes) //创建一个默认的 PreparedStatement对象,能够返回由给定数组指定的自动生成的键。

PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) //创建一个 PreparedStatement对象,该对象将使用给定类型和并发性生成 ResultSet对象。  

PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) //创建一个 PreparedStatement对象,该对象将生成具有给定类型,并发和 ResultSet对象。 

PreparedStatement prepareStatement(String sql, String[] columnNames) //创建一个默认的 PreparedStatement对象,能够返回给定数组指定的自动生成的键 

功能2: 1.4.2管理事务

//开启事务
void setAutoCommit(boolean autoCommit) //将此连接的自动提交模式设置为给定状态。  (将这个布尔值设为false则是开启事务)

//提交事务

void commit() //使上次提交/回滚之后所做的所有更改都将永久性,并释放此 Connection对象当前持有的任何数据库锁。 

//回滚事务
 void rollback() //撤消在当前事务中所做的所有更改,并释放此 Connection对象当前持有的任何数据库锁。  
    

1.5 Statement执行sql的对象(接口)

功能:

掌握 executeUpdate方法 (执行的是DML和DDL语句,

这个两个语句就是表的修改的语句(insert,update,delete)和创建表和库(create,drop,alter)的语句)

1.5.1执行静态SQL语句

//long返回值的意思是影响的行数,如果影响的行数是0,那么有可能就是执行失败了,注意,如果我们用这个运行的是DDL语句,那么我们返回值就是0

default long executeLargeUpdate(String sql) //执行给定的SQL语句,这可能是 INSERT , UPDATE ,或 DELETE声明,或者不返回任何内容,如SQL DDL语句的SQL语句。  
    
default long executeLargeUpdate(String sql, int autoGeneratedKeys) //执行给定的SQL语句,并用给定的标志来向驱动程序发出信号,指出这个 Statement对象生成的自动生成的密钥是否应该可用于检索。  
    
default long executeLargeUpdate(String sql, int[] columnIndexes) //执行给定的SQL语句,并向驱动程序发出信号,指出给定数组中指示的自动生成的键应该可用于检索。  
    
default long executeLargeUpdate(String sql, String[] columnNames) //执行给定的SQL语句,并向驱动程序发出信号,指出给定数组中指示的自动生成的键应该可用于检索。 

1.5.2 executeQuery语句(执行DQL语句)

ResultSet executeQuery(String sql) //执行给定的SQL语句,返回一个 ResultSet对象。  
    
//了解execute方法即可 (可以执行任意sql语句)
boolean execute(String sql) //执行给定的SQL语句,这可能会返回多个结果。
    
boolean execute(String sql, int autoGeneratedKeys) //执行给定的SQL语句,这可能返回多个结果,并向驱动程序发出信号,指出任何自动生成的密钥应该可用于检索。  
    
boolean execute(String sql, int[] columnIndexes) //执行给定的SQL语句,这可能返回多个结果,并向驱动程序发出信号,指出给定数组中指示的自动生成的键应该可用于检索。  
    
boolean execute(String sql, String[] columnNames) //执行给定的SQL语句,这可能返回多个结果,并向驱动程序发出信号,指出给定数组中指示的自动生成的键应该可用于检索。  

练习

//account 添加一条记录
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBC {
    public static void main(String[] args) {
        Statement sta = null;
        Connection conn = null;
        try {
          //注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver"); 
          //定义sql
    				String sql = "insert into account values(NULL,'wangwu',3000)";
					//获取Connection对象
        		conn = DriverManager.getConnection("jdbc:mysql:///cjy?	serverTimezone=GMT%2B8", "root", "root");
					//获取执行sql的对象Statement
        		sta = conn.createStatement();
					//执行sql
        		System.out.println(sta.executeUpdate(sql) > 0 ? "添加成功" : "添加失败");
        } catch (Exception e) {
        		e.printStackTrace();
    		} finally {
          //释放资源
        		//为了避免空指针异常
        		if (sta != null) {
            		try {
               			sta.close();
            		} catch (SQLException throwables) {
                		throwables.printStackTrace();
            		}
        		}
        		if (conn != null) {
            		try {
                		conn.close();
            		} catch (SQLException throwables) {
                		throwables.printStackTrace();
            }
        }
    }	
}
//同上
//account 修改一条记录
String sql = "update account set balance = 1500 where  id = 3";

//account 删除一条记录
String sql = "delete from account where id = 3";

//创建表的语法
String sql = "create table student (id int, name varchar(20))";

1.6 ResultSet对象

1.6.1 概述

使用DQL语句返回的表就是一个结果集对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-87OLbmPm-1624000127678)(…/…/…/Library/Application Support/typora-user-images/image-20210613153251769.png)]

我们查找数据是要通过**"游标"来查找的**,而这个游标默认的位置是第一行(就是id name balance这些字样的位置)

因此,我们如果要读取到数据,那么我们就要把游标指到正确的位置并且读取数据

boolean next() //将光标从当前位置向前移动一行。  
    
XXX getXxx(参数)  //一次只获取某一行某一列的数据;XXX就是游标指针指向的数据类型,
    //参数有两种
    	//1 int类型    int类型是要获取列的列号,从1开始
    	//2 String    String类型是要获取列的名字

query这些数据练习
代码如下

import java.sql.*;

public class JDBC {
    public static void main(String[] args) {
        Statement sta = null;
        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
						String sql = "select * from account";

        		conn = DriverManager.getConnection("jdbc:mysql:///cjy?								  serverTimezone=GMT%2B8", "root", "root");

        		sta = conn.createStatement();
          //执行查询的方法
        		ResultSet res = sta.executeQuery(sql);

        	//我们来查看结果
       	 	//1 让游标向下移动一行
        		res.next();
        	//2 获取数据
        		int id = res.getInt(1);
        		String name = res.getString(2);
        		double balance = res.getDouble(3);
        		System.out.println(id + "  " + name + "  " + balance);
        		res.next();
        		id = res.getInt(1);
        		name = res.getString(2);
        		balance = res.getDouble(3);
        		System.out.println(id + "  " + name + "  " + balance);

      		} catch (Exception e) {
        	e.printStackTrace();
      		} finally {
        	//为了避免空指针异常
        			if (sta != null) {
            		try {
               			sta.close();
            		} catch (SQLException throwables) {
                		throwables.printStackTrace();
            		}
        		}
        		if (conn != null) {
            		try {
                		conn.close();
            		} catch (SQLException throwables) {
                		throwables.printStackTrace();
           	 		}
        		}
     		}
	  }
}
//运行结果
1  zhangsan  2000.0
2  lisi  2000.0
//不错,很巴适

真正的遍历方法
步骤

  • 游标向下移动一行
  • 判断是否有数据 (用next()方法,他的返回值是布尔,判断游标是否超过界限,超过了就会返回false)
  • 有数据就获取数据
    代码如下
while (res.next()) {
    int id = res.getInt(1);
  	String name = res.getString(2);
    double balance = res.getDouble(3);
    System.out.println(id + "  " + name + "  " + balance);
}
//和iterator迭代器和字节流的读取方法有异曲同工之妙

1.7 工具类

我们会发现,每次我们想要调用数据库,总是要 导入字节码文件,连接到数据库,获取数据库连接对象,还有获取statement对象来执行sql语句,最后还要释放资源,特别麻烦,特别多冗余的步骤,因此,我们自己写一个工具类,来简化这些步骤

注意

首先,写工具类,一般命名最后都会加s, 例如: collections, Arrays,Objects等工具类
其次,工具类里面我们很容易发现一般里面都是静态方法,所以我们写的方法也尽量保持是静态方法
所以将自定义的工具类取名叫做JDBCUtils

分析:

自动注册驱动的抽取(调用这个类的时候自动就会注册驱动,不需要专门执行方法)

抽取一个方法获取连接对象

我这个方法不想传递参数,还得保证工具类的通用性

解决方法: 配置文件(jdbc.properties)

//文件内容
url = xxx
user = xxx
password = xxx
driver = xxx
  
//例如
url = jdbc:mysql://localhost:3306/door
user = root
password = bpqbfq769
driver = com.mysql.cj.jdbc.Driver

注意,这种方法很常用,注意一定要掌握!

抽取一个方法释放资源

下面我们直接放代码

//配置文件放在根目录,取名jdbc.properties
className = demo11.domain.Student
methodName = sleep
//我们做的工具类
package demo12.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 JDBC工具类
   */

public class JDBCUtils {
    private static final String url;
    private static final String user;
    private static final String password;
    private static final String driver;
//配置文件的读取,只需要一次就可以了,用静态代码块
		static {
    	Properties pro = new Properties();
    	ClassLoader cl = JDBCUtils.class.getClassLoader();
    	InputStream is = cl.getResourceAsStream("jdbc.properties");
    	try {
        pro.load(is);
    	} catch (IOException e) {
        e.printStackTrace();
    	}

    	url = pro.getProperty("url");
    	user = pro.getProperty("user");
    	password = pro.getProperty("password");
    	driver = pro.getProperty("driver");

    	try {
        Class.forName(driver);
    	} catch (ClassNotFoundException e) {
        e.printStackTrace();
    	}

	}
	//获取连接对象
	public static Connection getConnection() throws SQLException {
    return DriverManager.getConnection(url, user, password);
	}

	//关闭资源
	public static void close(Statement stmt, Connection conn) {
    judge(stmt, conn);
    }

	public static void judge(Statement stmt, Connection conn) {
    if (stmt != null) {
        try {
            stmt.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
	}
}
//测试函数
import demo12.util.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBCDemo {
	public static void main(String[] args) throws Exception {
		Connection conn = JDBCUtils.getConnection();
 		//4 定义sql语句
    String sql = "select * from account";

    //5 执行sql的对象 Statement
    Statement stmt = conn.createStatement();
    //6 执行sql
    ResultSet res = stmt.executeQuery(sql);

    //7 处理结果
    while (res.next()){
        System.out.println(res.getInt(1) + " \t" + res.getString(2) + "\t" +  res.getDouble(3));
    }

    //8 释放资源
    JDBCUtils.close(stmt,conn);
	}
}

1.8 综合练习

通过工具类来实现

某用户通过键盘录入用户名和密码

判断用户是否登陆成功

分析

我们的数据库里面保存了许多用户的用户名其密码
通过检测数据库的用户名和密码来提示用户成功还是失败
我们创建一个数据库,然后创建一个表,里面保存账户和密码

– 创建代码如下

CREATE DATABASE db4;
USE db4;
CREATE TABLE USER(
	id INT  PRIMARY KEY AUTO_INCREMENT,
	username VARCHAR(32),
	PASSWORD VARCHAR(32)
);

INSERT INTO USER VALUES(NULL,'zahngsan','123');
INSERT INTO USER VALUES(NULL,'lisi','666');
INSERT INTO USER VALUES(NULL,'wangwu','888');

数据库图示:

现在我们来实现(个人方法)

import demo12.util.JDBCUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class JDBCRegister {
    public static void main(String[] args) throws SQLException {
        Scanner cin = new Scanner(System.in);   
   			System.out.println("请输入账号:");

    		String userName = cin.next();

    		System.out.println("请输入密码:");

    		String password = cin.next();

    		//先做一个判断,如果输入为空,那么直接return
    		if (userName == null || password ==null){
        		System.out.println("您输入的是空值,请重新输入!");
        		return;
    		}

    		//获取连接
    		Connection conn = JDBCUtils.getConnection();

    		//获取statement对象
    		Statement stmt = conn.createStatement();

    		String sql  = "select * from user where username = '" + userName + "' and password = '" + password + "'";

    		//结果
    		ResultSet res = stmt.executeQuery(sql);

    		//因为结果只有可能有一个,所以直接判断有没有下一行
    		boolean flag = res.next();

    		//做判断
    		if (flag){
        		System.out.println("恭喜您登录成功!");
    		}else{
        		System.out.println("您输入的用户名或者账号有误,请重新输入!");
    		}

    		//释放资源
    		JDBCUtils.close(stmt,conn);
    }
}
//运行结果
请输入账号:
lisi
请输入密码:
666
恭喜您登录成功!

1.9 PreparedStatement接口

分析上面这个案例,我们发现输入的时候存在严重的bug, 我们可以在密码输入一些sql的条件(例如: 在password的地方输入a’ or ‘a’ = 'a),让他的条件判断为真,这样就算我们账号或密码是乱输的,照样可以登录成功,这就是MySQL注入问题

**MySQL注入问题: **

在拼接sql的时候,有一些sql的特殊关键字参与字符串的拼接 , 会造成字符串安全性问题.
那么如何解决呢?

使用PreparedStatement接口来解决这个问题,这个接口专门用来解决sql的注入问题
这个接口遗传自Statement类
那么Statement和这个PreparedStatement有什么区别呢?

Statement用的是静态的sql语句(即拼接完成之后就固定了)
PreparedStatement使用的是预编译的SQL: 参数使用 ? 作为 占位符
使用PreparedStatement的步骤(无法运行,知道意思就好)

Scanner cin = new Scanner(System.in);

System.out.println("请输入账号:");

String userName = cin.next();

System.out.println("请输入密码:");

String password = cin.next();
//获取连接
Connection conn = JDBCUtils.getConnection();

//上面sql语句我们使用?来作为占位符
String sql  = "select * from user where username = ?  AND  password = ?";

//用Connection的 prepareStatement()方法获取prepareStatement对象 (这里代替了Statement),并且这一步相比于statement,这里直接提前传入了sql语句(但是?要赋值)
PreparedStatement pstmt = conn.prepareStatement(sql);

//给? 赋值 对象名.setXXX(?的位置编号(从1开始), ?的值)  XXX是参数类型
pstmt.setString(1,userName);
pstmt.setString(2,password);

//这里不用传递sql了,直接执行sql语句
ResultSet res = pstmt.executeQuery();//不需要传参数

//剩下的都一样了

这样我们就可以解决注入问题了,我们其实一直使用的是这个类来工作的

解决注入问题
效率更高

  • 0
    点赞
  • 1
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论1
请先登录 后发表评论~
©️2021 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页

打赏作者

杨青葱

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值