JDBC简介、JDBC开发步骤详解、SQL注入攻击解决方案

1 JDBC和数据库驱动程序

  1. Java提供访问数据库规范称为JDBC;
  2. 生产厂商提供规范的实现类称为驱动;
    在这里插入图片描述
  3. JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库。
  4. 每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生产厂商提供。

2 准备数据

2.1 创建数据库和表结构

#创建数据库
create database mybase;
#使用数据库
use mybase;
#创建分类表
create table sort(
  sid int PRIMARY KEY AUTO_INCREMENT,
  sname varchar(100),
  sprice DOUBLE,
  sdesc VARCHAR(500)
);

2.2 向表中插入数据

#初始化数据
insert into sort(sname,sprice,sdesc) values('家电',2000, '优惠的促销');
insert into sort(sname,sprice,sdesc) values('家具',8900, '家具价格上调,原材料涨价');
insert into sort(sname,sprice,sdesc) values('儿童玩具',290, '赚家长的钱');
insert into sort(sname,sprice,sdesc) values('生鲜',500.99, '生鲜商品');
insert into sort(sname,sprice,sdesc) values('服装',24000, '换季销售');
insert into sort(sname,sprice,sdesc) values('洗涤',50, '洗发水促销');			

3 JDBC的开发步骤

  1. 注册驱动
    告知JVM使用的是哪一个数据库的驱动
  2. 获得连接
    使用JDBC中的类,完成对MySQL数据库的连接
  3. 获得语句执行平台
    通过连接对象获取对SQL语句的执行者对象
  4. 执行sql语句
    使用执行者对象,向数据库执行SQL语句
    获取到数据库的执行后的结果
  5. 处理结果
  6. 释放资源
    close()

3.1 导入数据库驱动程序jar包

  1. 创建lib目录,用于存放当前项目需要的所有jar包;
  2. 选择jar包,放到lib目录下,这里我们选择mysql-connector-java-8.0.16;

3.2 注册数据库驱动

public class JDBCDemo {
	public static void main(String[] args)throws ClassNotFoundException,SQLException{
		//1 注册驱动,利用反射技术将驱动类加入到内容
		Class.forName("com.mysql.cj.jdbc.Driver");					
	}
}

3.3 获取数据库连接对象

public class JDBCDemo {
	public static void main(String[] args)throws ClassNotFoundException,SQLException{
		//1 注册驱动
		Class.forName("com.mysql.cj.jdbc.Driver");
		
		//2.获得数据库连接,利用DriverManager类中的静态方法getConnection
		//public static Connection getConnection(String url, String user, String password)
		//url: 数据库地址  jdbc:mysql://连接主机IP:端口号//数据库名字
		//用户名和密码自己设置
		//返回值是Connection接口的实现类对象,即数据库连接对象
		//MySQL 8.0 以上版本不需要建立 SSL 连接的,需要显示关闭。
		//allowPublicKeyRetrieval=true 允许客户端从服务器获取公钥。
		//最后还需要设置 CST。
		String url = "jdbc:mysql://localhost:3306/mybase?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
		String username="root";
		String password="123456";
		Connection con = DriverManager.getConnection(url, username, password);
		System.out.println(con);					
	}
}

3.4 获取SQL语句的执行对象

public class JDBCDemo {
	public static void main(String[] args)throws ClassNotFoundException,SQLException{
		//1 注册驱动
		Class.forName("com.mysql.cj.jdbc.Driver");
		
		//2 获取连接对象
		String url = "jdbc:mysql://localhost:3306/mybase?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
		String username="root";
		String password="123456";
		Connection con = DriverManager.getConnection(url, username, password);
		System.out.println(con);
		
		//3 通过数据库连接对象,获取到SQL语句的执行对象
		//con对象调用方法Statement createStatement(),返回值是Statement接口的实现类对象
		Statement stat = con.createStatement();
		System.out.println(stat);
	}
}

3.5 执行insert语句获取结果

public class JDBCDemo {
	public static void main(String[] args)throws ClassNotFoundException,SQLException{
		//1 注册驱动
		Class.forName("com.mysql.cj.jdbc.Driver");
		
		//2 获取连接对象
		String url = "jdbc:mysql://localhost:3306/mybase?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
		String username="root";
		String password="123456";
		Connection con = DriverManager.getConnection(url, username, password);
		System.out.println(con);
		
		//3 获取执行SQL语句的对象
		Statement stat = con.createStatement();
		System.out.println(stat);

		//4 执行sql语句
		//通过执行对象调用方法执行SQL语句,获取结果
		//int executeUpdate(String sql) ,返回值int,操作成功数据表多少行
		int row = stat.executeUpdate("INSERT INTO sort(sname,sprice,sdesc) VALUES('汽车用品',50000,'疯狂涨价')");
		System.out.println(row);
		
		//5.释放资源
		stat.close();
		con.close();
	}
}

3.6 执行select语句获取结果集

public class JDBCDemo1 {
	public static void main(String[] args) throws Exception{
		//1 注册驱动
		Class.forName("com.mysql.cj.jdbc.Driver");
		
		//2 获取连接对象
		String url = "jdbc:mysql://localhost:3306/mybase?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
		String username="root";
		String password="123456";
		Connection con = DriverManager.getConnection(url, username, password);
		
		//3 获取执行SQL语句的对象
		Statement stat = con.createStatement();
		
		//4 调用执行者对象方法,执行SQL语句获取结果集
		// ResultSet executeQuery(String sql),返回值是ResultSet接口的实现类对象
		String sql = "SELECT * FROM sort";
		ResultSet rs = stat.executeQuery(sql);
		
		//5 处理结果集
		//ResultSet接口方法boolean next():返回true有结果集,返回false没有结果集
		while(rs.next()){
			//获取每列数据,使用是ResultSet接口的方法 getXX方法参数中,建议写String列名
			System.out.println(rs.getInt("sid")+" "+rs.getString("sname")+ " "+rs.getDouble("sprice")+" "+rs.getString("sdesc"));
		}
		
		//6 释放资源
		//与IO流一样,使用后的东西都需要关闭
		//关闭的顺序是先得到的后关闭,后得到的先关闭。
		rs.close();
		stat.close();
		con.close();
	}
}

3.7 处理结果集(执行insert、update、delete无需处理)

ResultSet实际上就是一张二维的表格,我们可以调用其boolean next()方法指向某行记录,当第一次调用next()方法时,便指向第一行记录的位置,这时就可以使用ResultSet提供的getXXX(int col)方法(与索引从0开始不同,列从1开始)来获取指定列的数据:

rs.next();//指向第一行
rs.getInt(1);//获取第一行第一列的数据

常用方法:

  • Object getObject(int index) / Object getObject(String name) 获得任意对象
  • String getString(int index) / Object getObject(String name) 获得字符串
  • int getInt(int index) / Object getObject(String name) 获得整型
  • double getDouble(int index) / Object getObject(String name) 获得双精度浮点型

4 SQL注入攻击

4.1 SQL注入攻击语句

假设有登录案例SQL语句如下:

SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;

此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:'XXX’ OR ‘a’=’a时,则真正执行的代码变为:

SELECT * FROM 用户表 WHERE NAME = 'XXX' AND PASSWORD ='XXX' OR 'a'='a';

此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。

案例演示

CREATE TABLE users(
	 id INT PRIMARY KEY AUTO_INCREMENT,
	 username VARCHAR(100),
	 PASSWORD VARCHAR(100)
);

INSERT INTO users (username,PASSWORD) VALUES ('a','1'),('b','2');

SELECT * FROM users;

// SQL注入攻击
SELECT * FROM users WHERE username='dsfsdfd' AND PASSWORD='wrethiyu' OR 'a'='a';

在这里插入图片描述

4.2 SQL注入攻击用户登录案例

public class JDBCDemo {
    public static void main(String[] args)throws Exception {
        Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/mybase?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
        String username = "root";
        String password = "123456";
        Connection con = DriverManager.getConnection(url, username, password);
        Statement stat = con.createStatement();
        Scanner sc = new Scanner(System.in);
        //String user = sc.nextLine();
        //String pass = sc.nextLine();

        //执行SQL语句,数据表,查询用户名和密码,如果存在,登录成功,不存在登录失败
        String sql = "SELECT * FROM users WHERE username='dsfsdfd' AND PASSWORD='wrethiyu' OR 1=1";
        //String sql = "SELECT * FROM users WHERE username='"+user+"' AND PASSWORD='"+pass+"'";
        System.out.println(sql);
        ResultSet rs = stat.executeQuery(sql);
        while(rs.next()){
            System.out.println(rs.getString("username")+"   "+rs.getString("password"));
        }

        rs.close();
        stat.close();
        con.close();
    }
}

SELECT * FROM users WHERE username='dsfsdfd' AND PASSWORD='wrethiyu' OR 1=1
a   1
b   2

为此,我们使用PreparedStatement来解决对应的问题

5 PrepareStatement接口预编译SQL语句

5.1 获取预处理对象

// SQL语句中的参数全部采用问号占位符
String sql = "insert into sort(sid,sname) values(?,?)";
PreparedStatement pst = conn.prepareStatement(sql)

5.2 执行SQL语句的方法介绍

  • int executeUpdate():执行insert update delete语句
  • ResultSet executeQuery():执行select语句
  • boolean execute():执行select返回true,执行其他的语句返回false

5.3 设置实际参数

void setXxx(int index, Xxx xx):将指定参数设置为xx。

//把SQL语句中第2个位置的占位符?替换成实际参数 "家用电器"。
setString(2, "家用电器");

5.4 演示:PrepareStatement接口预编译SQL语句执行查询

public class JDBCDemo3 {
	public static void main(String[] args)throws Exception {
	    Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/mybase?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
        String username = "root";
        String password = "123456";
        Connection con = DriverManager.getConnection(url, username, password);
        
		//设计sql查询语句
		String sql = "SELECT * FROM users WHERE username=? AND PASSWORD=?";
        //调用Connection接口的方法prepareStatement,获取PrepareStatement接口的实现类
		PreparedStatement pst =  con.prepareStatement(sql);
		System.out.println(pst);
		
		// 由终端输入用户名和密码
        Scanner sc = new Scanner(System.in);
        String user = sc.nextLine();
        String pass = sc.nextLine();
		//调用pst对象set方法,设置问号占位符上的参数
		pst.setObject(1, user);
		pst.setObject(2, pass);
		
		//调用方法,执行SQL,获取结果集
		ResultSet rs = pst.executeQuery();
		while(rs.next()){
			System.out.println(rs.getString("username")+"   "+rs.getString("password"));
		}
		rs.close();
		pst.close();
		con.close();
	}
}
a
1
com.mysql.cj.jdbc.ClientPreparedStatement: SELECT * FROM users WHERE username=** NOT SPECIFIED ** AND PASSWORD=** NOT SPECIFIED **
a   1

6 编写JDBC获得数据库连接类

“获得数据库连接”操作,将在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils。提供获取连接对象的方法,从而达到代码的重复利用。

该工具类提供方法:public static Connection getConn ()。

代码如下:

public class JDBCUtils {
	private static Connection con;
	static{
		try{
			Class.forName("com.mysql.cj.jdbc.Driver");
	        String url = "jdbc:mysql://localhost:3306/mybase?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
	        String username = "root";
	        String password = "123456";
			con = DriverManager.getConnection(url, username, password);
		}catch(Exception ex){
			throw new RuntimeException(ex+"数据库连接失败");
		}
	}
	
	/*
	 * 定义静态方法,返回数据库的连接对象
	 */
	public static Connection getConnection(){
		return con;
	}
	
	public static void close(Connection con,Statement stat){
		 if(stat!=null){
			 try{
				 stat.close();
			 }catch(SQLException ex){}
		 }
		 if(con!=null){
			 try{
				 con.close();
			 }catch(SQLException ex){}
		 }
	}
	
	public static void close(Connection con,Statement stat , ResultSet rs){
		 if(rs!=null){
			 try{
				 rs.close();
			 }catch(SQLException ex){}
		 }
		 if(stat!=null){
			 try{
				 stat.close();
			 }catch(SQLException ex){}
		 }
		 if(con!=null){
			 try{
				 con.close();
			 }catch(SQLException ex){}
		 }
		 
	}
}
//测试JDBCUtils工具类的代码
public class TestJDBCUtils {
    public static void main(String[] args)throws Exception {
        Scanner sc = new Scanner(System.in);
        String user = sc.nextLine();
        String pass = sc.nextLine();
        Connection con = JDBCUtils.getConnection();
        PreparedStatement pst = con.prepareStatement("SELECT * FROM users WHERE username=? AND PASSWORD=?");
        //调用pst对象set方法,设置问号占位符上的参数
        pst.setObject(1, user);
        pst.setObject(2, pass);
        ResultSet rs = pst.executeQuery();
        while(rs.next()){
            System.out.println(rs.getString("username")+"   "+rs.getString("password"));
        }
        JDBCUtils.close(con, pst, rs);
    }
}
a
1
a   1

7 编写数据表数据的存储类

定义实体类Sort

public class Sort {
	private int sid;
	private String sname;
	public Sort(int sid, String sname) {
		this.sid = sid;
		this.sname = sname;
	}
	public int getSid() {
		return sid;
	}
	public void setSid(int sid) {
		this.sid = sid;
	}
	public String getSname() {
		return sname;
	}
	public void setSname(String sname) {
		this.sname = sname;
	}
	
	@Override
	public String toString() {
		return "Sort [sid=" + sid + ", sname=" + sname + "]";
	}				
}
/*
 *  JDBC读取数据表sort,每行数据封装到Sort类的对象中
 *  很多个Sort类对象,存储到List集合中
 */
public class JDBCDemo {
	public static void main(String[] args) throws Exception{
		//使用JDBC工具类,直接获取数据库连接对象
		Connection con = JDBCUtils.getConnection();
		//连接获取数据库SQL语句执行者对象
		PreparedStatement pst = con.prepareStatement("SELECT * FROM sort");
		//调用查询方法,获取结果集
		ResultSet rs = pst.executeQuery();
		//创建集合对象
		List<Sort> list = new ArrayList<Sort>();
		while(rs.next()){
			//获取到每个列数据,封装到Sort对象中
			Sort s = new Sort(rs.getInt("sid"),rs.getString("sname"));
			//封装的Sort对象,存储到集合中
			list.add(s);
		}
		JDBCUtils.close(con, pst, rs);
		//遍历List集合
		for(Sort s : list){
			System.out.println(s);
		}
	}
}
Sort [sid=1, sname=家电]
Sort [sid=2, sname=服饰]
Sort [sid=3, sname=化妆品]

8 properties配置文件

8.1 简介

开发中获得连接的4个参数(驱动、URL、用户名、密码)通常都存在配置文件中,方便后期维护,程序如果需要更换数据库,只需要修改配置文件即可。

通常情况下,我们习惯使用properties文件,此文件我们将做如下要求:

  1. 文件位置:任意,建议src下
  2. 文件名称:任意,扩展名为properties
  3. 文件内容:一行一组数据,格式是“key=value”

8.2 properties文件的创建和编写

  1. properties文件的创建

src路径下建立database.properties(其实就是一个文本文件)

  1. properties文件的编写

内容如下:

driverClass=com.mysql.cj.jdbc.Driver 
url=jdbc:mysql://localhost:3306/mybase useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username=root 	  
password=123456

8.3 加载配置文件并连接数据库

/*
 *  加载properties配置文件
 *  IO读取文件,键值对存储到集合
 *  从集合中以键值对方式获取数据库的连接信息,完成数据库的连接
 */
public class PropertiesDemo {
	public static void main(String[] args) throws Exception{
		//使用类的加载器
		InputStream in = PropertiesDemo.class.getClassLoader().getResourceAsStream("database.properties");
		System.out.println(in);
		Properties pro = new Properties();
		pro.load(in);
		//获取集合中的键值对
		String driverClass=pro.getProperty("driverClass");
		String url = pro.getProperty("url");
		String username = pro.getProperty("username");
		String password = pro.getProperty("password");
		Class.forName(driverClass);
		Connection con = DriverManager.getConnection(url, username, password);
		System.out.println(con);
	}
}
java.io.FileInputStream@74a14482
java.io.BufferedInputStream@1540e19d
com.mysql.cj.jdbc.ConnectionImpl@3f2a3a5

8.4 编写读取配置文件的工具类

/*
 *  编写数据库连接的工具类,JDBC工具类
 *  获取连接对象采用读取配置文件方式
 *  读取文件获取连接,执行一次,static{}
 */
public class JDBCUtilsConfig {
	private static Connection con ;
	private static String driverClass;
	private static String url;
	private static String username;
	private static String password;
	
	static{
		try{
			readConfig();
			Class.forName(driverClass);
			con = DriverManager.getConnection(url, username, password);
		}catch(Exception ex){
			throw new RuntimeException("数据库连接失败");
		}
	}
	
	private static void readConfig()throws Exception{
		InputStream in = JDBCUtilsConfig.class.getClassLoader().getResourceAsStream("database.properties");
		 Properties pro = new Properties();
		 pro.load(in);
		 driverClass = pro.getProperty("driverClass");
		 url = pro.getProperty("url");
		 username = pro.getProperty("username");
		 password = pro.getProperty("password");
	}
	
	public static Connection getConnection(){
		return con;
	}
}			
public class TestJDBCUtils {
	public static void main(String[] args) {
		Connection con = JDBCUtilsConfig.getConnection();
		System.out.println(con);
	}
}
com.mysql.cj.jdbc.ConnectionImpl@60addb54
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值