木木的Java知识整理——JDBC(Java数据库连接)

Java知识整理——JDBC

  • 一、JDBC概述
  • 二、JDBC的API
    • 2.1 JDBC的连接
    • 2.2 DriverManager:驱动管理类
    • 2.3 Connection:连接对象
    • 2.4 Statement:执行SQL语句
    • 2.5 ResultSet: 结果集
    • 2.6 JDBC的资源释放
  • 三、JDBC的工具类——JDBCUtils
    • 3.1 方法一:属性文件
    • 3.2 方法二:连接池
  • 四、JDBC的SQL注入漏洞
    • 4.1 SQL注入漏洞如何产生的?
    • 4.2 SQL注入漏洞如何避免(PreparedStatement)

一、JDBC概述

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
木木的Java知识整理——JDBC(Java数据库连接)_第1张图片
通过JDBC将应用程序与数据库相连,从而对数据库进行增删查改等操作。

二、JDBC的API

2.1 JDBC的连接

jdbc建立连接的五大步骤:

  1. 加载(注册)数据库
  2. 建立链接
  3. 执行SQL语句
  4. 处理结果集
  5. 关闭数据库
	@Test
	public void demo1() {
		Connection conn = null;
		Statement stmt = null;
		ResultSet resultSet = null;
		try {
			//1、加载驱动
			//DriverManager.registerDriver(new Driver());
			Class.forName("com.mysql.cj.jdbc.Driver");
			//2、获得连接
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/数据库名?serverTimezone=UTC&characterEncoding=utf-8", "用户名" ,"密码");
			//3、创建执行SQL语句的对 象,并且执行SQL
			//3.1 创建执行sql的对象
			String sql = "SELECT id,name,price,desp FROM goods WHERE price<3500";
			stmt = conn.createStatement();
			//3.2 执行sql语句
			resultSet = stmt.executeQuery(sql);
			//4、处理结果集
			while(resultSet.next()) {
				int id = resultSet.getInt("id");
				String name = resultSet.getString("name");
				float price = resultSet.getFloat("price");
				String desp = resultSet.getString("desp");
				
				System.out.println(id + "  " + name + "  " + price + "  " + desp);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//5、释放资源
			if(resultSet != null) {
				try {
					resultSet.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				resultSet = null;
			}
			
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				stmt = null;
			}
			
			if(conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				conn = null;
			}
		}
		
	}

2.2 DriverManager:驱动管理类

主要作用

1、注册驱动
DriverManager .registerDriver(new Driver());
但是这种方式会导致驱动注册两次(在加载DriverManager的时候会自动注册一次)
实际开发中注册驱动会使用如下方式:

//1、加载驱动
//DriverManager.registerDriver(new Driver());
//开发中常用:
Class.forName("com.mysql.cj.jdbc.Driver");

2、获得连接

//2、获得连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3307/imooc?serverTimezone=UTC&characterEncoding=utf-8", "用户名" ,"密码");

Connection getConnection(String url,String username,String password);
*url写法:jdbc:mysql://localhost:3307/数据库名称
**jdbc:协议
**mysql:子协议
**localhost:主机名
**3307:端口号
*url简写:jdbc:mysql:///jdbc(数据库默认端口号是3306,如果你的数据库不是在默认端口号,最好还是写全)

2.3 Connection:连接对象

主要作用

1、创建执行SQL语句的对象:

方法 作用
Statement createStatement(); 执行SQL语句,有SQL注入的漏洞存在
PreparedStatement prepareStatement(String sql); 预编译SQL语句并且执行,解决SQL注入的漏洞(实际开发中用的)
CallableStatement prepareCall(String sql) ; 执行SQL中存储过程

2、进行事务的管理(保证多个操作是在同一个事务当中)

方法 作用
setAutoCommit(boolean autoCommit) 设置事务是否自动提交(默认自动提交)
commit() 事务提交
rollback() 事务回滚

若想开启JDBC的事务,需要先将自动提交关闭,设置为false(之后再详细整理事务)

2.4 Statement:执行SQL语句

主要作用

1、 执行SQL语句:

方法 作用
boolean excute(String sql); (少用)执行sql,如果执行select语句返回true,否则返回false
ResultSet executeQuery(String sql); (常用)执行sql中的select语句,返回表格,结果集
int excuteUpdate(String sql); (常用)执行sql中的insert/update/delete语句,返回影响的行数 int 类型 , 当返回值>0时,操作成功

*executeQuery:执行select语句(查询)
*executeUpdate:执行insert/update/delete语句(增删改)

2、执行批处理操作:-- 执行一批SQL语句

方法 作用
addBatch(String sql); 将sql语句添加到批处理当中
excuteBatch(); 执行批处理
clearBatch(); 清空批处理

2.5 ResultSet: 结果集

ResultSet [结果集]的使用
是查询语句(select)查询到的结果的封装;

主要作用
结果集:获取查询到的结果;

主要方法
boolean next();
最开始是0行,第一次判断第一行有无…
判断是否有下一行的记录,如果有就指向下一行;

getXXX();
获取数据,
针对不同的类型的数据可以使用 getInt() / getString() 等等获取数据;
通用的获取数据的方法:getObject();

2.6 JDBC的资源释放

Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResiltSet,Statement和Connection对象。

特别是Connection对象,它非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。

Connection的使用原则是尽量晚的创建,尽量早的释放。

释放的代码【通常写在finally代码块】里。但是格式语法需要修改。
finally块中代码,不管是否有异常发生,总是会被执行,可以保证JDBC的资源的释放。

//写在finally块中,以释放Connection对象为例:
if(conn != null) {
	try {
		conn.close();
	} catch (SQLException e) {
		e.printStackTrace();
	}
	conn=null;//垃圾回收机制会更早回收对象。
}

三、JDBC的工具类——JDBCUtils

为了简化JDBC的开发,可以将一些重复的代码进行提取。
对注册驱动、获得连接、资源释放进行抽取。

3.1 方法一:属性文件

首先在src包中新建properties属性文件,命名为:jdbc.properties,在该文件中填写自己数据库相关信息(端口号一般为3306):

driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3307/数据库名?serverTimezone=UTC&characterEncoding=utf-8
username=数据库用户名
password=数据库密码

然后新建一个JDBCUtils类,通过加载属性文件获取数据库信息:

/**
 * JDBC的工具类
 */
public class JDBCUtils {
	private static final String driverClass;
	private static final String url;
	private static final String username;
	private static final String password;
	
	static{
		// 加载属性文件并解析:
		Properties props = new Properties();
		// 如何获得属性文件的输入流?
		// 通常情况下使用类的加载器的方式进行获取:
		InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
		try {
			props.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		driverClass = props.getProperty("driverClass");
		url = props.getProperty("url");
		username = props.getProperty("username");
		password = props.getProperty("password");
	}

	/**
	 * 注册驱动的方法
	 * @throws ClassNotFoundException 
	 */
	public static void loadDriver() throws ClassNotFoundException{
		Class.forName(driverClass);
	}
	
	/**
	 * 获得连接的方法:
	 * @throws SQLException 
	 */
	public static Connection getConnection() throws Exception{
		loadDriver();
		Connection conn = DriverManager.getConnection(url, username, password);
		return conn;
	}
	
	/**
	 * 资源释放
	 */
	public static void release(Statement stmt,Connection conn){
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
	
	public static void release(ResultSet rs,Statement stmt,Connection conn){
		if(rs!= null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}

以向数据库中插入一条信息为例,测试使用JDBCUtils:

	@Test
	// 保存记录
	public void demo1(){
		Connection conn = null;
		Statement stmt  = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 创建执行SQL语句的对象
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "insert into user values (null,'ggg','123','小六')";
			// 执行SQL:
			int num = stmt.executeUpdate(sql);
			if(num > 0){
				System.out.println("保存成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 释放资源:
			JDBCUtils.release(stmt, conn);
		}
	}

对比没有使用工具类的代码,可以看出简化了很多冗余的代码。

3.2 方法二:连接池

应用程序直接获取连接的缺点
用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。如果说一个网站每天的访问量为10万,则数据库服务器就需要创建连接10万次,极大的浪费了数据库的资源,也极易造成数据库服务器内存溢出。

连接池概念:连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。

连接池特点:用数据库连接池时,不需要再临时创建到数据库的连接,而是直接去使用某个容器或者地址中创建好的connection,用完后归还,从而提高程序的执行效率。

常用的连接池有 : DPCP、C3P0

使用C3P0时,需要导入该jar包:
在这里插入图片描述
建议使用XML文档来设置连接池参数(c3p0-config.xml):

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>

  <default-config>
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
	<property name="jdbcUrl">jdbc:mysql://localhost:3307/数据库名?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF8&amp;serverTimezone=GMT%2B8</property>
	<property name="user">数据库用户名</property>
	<property name="password">数据库密码</property>
	<property name="initialPoolSize">5</property>
	<property name="maxPoolSize">20</property>
  </default-config>
  
</c3p0-config>

使用c3p0连接池的JDBC工具类:

/**
 * JDBC的工具类,连接池
 */
public class JDBCUtils2 {
	//创建连接池:
	private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();
	
	/**
	 * 获得连接的方法:
	 * @throws SQLException 
	 */
	public static Connection getConnection() throws Exception{
		Connection conn = dataSource.getConnection();
		return conn;
	}
	
	/**
	 * 资源释放
	 */
	public static void release(Statement stmt,Connection conn){
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
	
	public static void release(ResultSet rs,Statement stmt,Connection conn){
		if(rs!= null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}

测试使用连接池工具类:

	@Test
	/**
	 * 使用配置文件的方式
	 */
	public void demo(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try{
			// 获得连接:
			conn = JDBCUtils2.getConnection();
			// 编写Sql:
			String sql = "select * from user";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数
			// 执行SQL:
			rs = pstmt.executeQuery();
			while(rs.next()){
				System.out.println(rs.getInt("uid")+"   "+rs.getString("username")+"   "+rs.getString("password")+"   "+rs.getString("name"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils2.release(rs, pstmt, conn);
		}
	}

四、JDBC的SQL注入漏洞

4.1 SQL注入漏洞如何产生的?

现象:已知用户名,但不输入密码,点击登录,也可以登陆
产生原因:输入用户名时输入了sql的关键字,or 或 - -(两个’-’,中间没有空格),对原有程序中的SQL语句,产生了破坏

SQL注入漏洞:
select * from user where username=‘aaa’ or ‘1=1’ and password=‘asddanjdnajsnj’;
select * from user where username=‘aaa’ - - and password=‘asddanjdnajsnj’;
or或者- -(注释)注入程序sql语句中,使得程序偏离想要的结果,产生漏洞

	/**
	 * 产生SQL注入漏洞的方法
	 * @param username
	 * @param password
	 * @return
	 */
	public static boolean login(String username,String password){
		Connection conn = null;
		Statement stmt  = null;
		ResultSet rs = null;
		boolean flag = false;
		try{
			conn = JDBCUtils.getConnection();
			// 创建执行SQL语句的对象:
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
			// 执行SQL:
			rs = stmt.executeQuery(sql);
			// 判断结果集中是否有数据。
			if(rs.next()){
				flag = true;
			}else{
				flag = false;
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, stmt, conn);
		}
		return flag;
	}

通过对username和password进行赋值,可通过sql关键字对原本的sql语句进行破坏:

	@Test
	/**
	 * 测试SQL注入漏洞的方法
	 */
	public void demo(){
		boolean flag = JDBCDemo.login("aaa' or '1=1", "asddanjdnajsnj");
		if(flag == true){
			System.out.println("登录成功!");
		}else{
			System.out.println("登录失败!");
		}
	}

JDBCDemo是所创建的类,调用login方法,将username和password代回原sql中:
select * from user where username=‘aaa’ or ‘1=1’ and password=‘asddanjdnajsnj’;
or前面为true,后面无论为true或false,结果返回均为true,从而可以随意输入密码而进入用户账号;

同理另一条sql语句:
select * from user where username=‘aaa’ - - and password=‘asddanjdnajsnj’;
在sql中,- -为注释,因此这条语句中将后面的密码注释了,而前面即可判断为true。

4.2 SQL注入漏洞如何避免(PreparedStatement)

SQL注入漏洞的解决:利用PreparedStatement接口的实例对象

PreparedStatement是Statement的子接口,它的实例对象可以通过调用Connection.preparedStatement(sql);方法获得;

与Statement对象相比

  • PreparedStatement可以避免SQL注入的问题;
  • Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出;
    PreparedStatement可对SQL进行预编译,从而提高数据库的执行效率;
  • PreparedStatement对于SQL中的参数,允许使用占位符(?)的形式进行替换,简化了sql语句的编写。
	/**
	 * 避免SQL注入漏洞的方法
	 */
	public static boolean login2(String username,String password){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		boolean flag = false;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "select * from user where username = ? and password = ?";
			// 预处理SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setString(1, username);//第一个?代表的参数
			pstmt.setString(2, password);//第二个?代表的参数
			// 执行SQL:
			rs = pstmt.executeQuery();
			// 判断结果集
			if(rs.next()){
				flag = true;
			}else{
				flag = false;
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, pstmt, conn);
		}
		return flag;
	}

PreparedStatement 是用占位符 ?的形式,固定SQL的形式。

而为什么使用占位符就可以解决注入漏洞问题
一个sql 是 select * from where id=?;
后面是需要传递的参数。
假设传递的参数是:“2 or 1=1”
如果是占位符:sql编译后id的值是 “2 or 1=1”
如果不是占位符:sql编译后id的值是2, 而后面的 or 1=1 会作为另一个查询条件来进行拼接。

关于JDBC就整理到这里啦,下一篇整理JDBC和Mybatis区别!!

你可能感兴趣的:(Java知识整理)