-
什么是JDBC,为什么要使用JDBC?
在web开发中,不可避免的地要使用数据库来存储和管理数据。为了在java语言中提供数据库访问的支持,Sun公司于1996年提供了一套访问数据的标准Java类库,即JDBC。
JDBC,全称是Java Database Connectivity,它是一套统一的、基于Java语言的关系数据库编程接口规范,
该规范允许你把SQL语句作为参数通过JDBC接口发送给远端数据库,远端数据库接受到你的SQL后进行语法分析、验证,然后执行、响应。
应用程序使用JDBC访问特定的数据库时,需要与不同的数据库驱动进行连接。
由于不同数据库厂商提供的数据库驱动不同,因此,为了使应用程序与数据库真正建立连接,JDBC不仅需要提供访问数据库的API,还需要封装与各种数据库服务器通信的细节为了帮助大家更好地理解应用程序如何通过JDBC访问数据库。 -
第一个JDBC程序
package com.example.test01; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Date; public class Demo { public static void main(String[] args) throws Exception { //1.注册数据库的驱动 Class.forName("com.mysql.jdbc.Driver"); //2.通过DriverManager获取数据库连接 String url="jdbc:mysql://localhost:3306/test"; String username="root"; String password="123456"; Connection connection=DriverManager.getConnection(url, username, password); //通过connection对象获取statement对象 Statement statement=connection.createStatement(); //使用statement对象执行SQL语句 String sql ="select * from users"; ResultSet resultSet=stmt.executeQuery(sql); //操作ResultSet结果集 System.out.println("id|name|password|email|birthday"); while(rs.next()){ //通过列名获取指定的字段 int id=resultSet.getInt("id"); String name=resultSet.getString("name"); String password=resultSet.getString("password"); String email=resultSet.getString("email"); Date birthday=resultSet.getDate("birthday"); System.out.println(id+"|"+name+"|"+password+"|"+email+"|"+"|"+birthday); } //回收数据库资源 resultSet.close(); statement.close(); connection.close(); } } /* 步骤总结: 1)加载并注册数据库驱动 2)通过DriverManager获取数据库连接 3)通过Connection对象获取Statement对象 4)使用Statement执行SQL语句 5)操作ResultSet结果集 */
-
每一个对象的解释
//固定写法,加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //connection 代表数据库,mysql8之后再写url的时候注意要增加的参数 String url="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&userSSL=true&serverTimezone=GMT%2B8"; Connection connection = DriverManager.getConnection(url, username, password); //数据库设置自动提交 //事务提交 //事务回滚 connection.setAutoCommit(); connection.commit(); connection.rollback(); //编写SQL String sql = "select * from student"; //查询操作,返回ResultSet statement.executeQuery(); //执行任何的SQL statement.execute(); //更新、插入、删除 都是用这个,返回的是一个受影响的行数 statement.executeUpdate(); //获取指定的数据类型 resultSet.getObject(); //在不知道列的类型的情况下使用 resultSet.getInt(); resultSet.getString(); //释放连接 resultSet.close(); statement.close(); //耗资源 ,用完关闭 connection.close();
-
jdbc代码的封装
若每次对数据库操作如果都像上面的案例一样,每操作一次都进行一次数据库的连接,驱动加载,资源关闭会显得非常麻烦,因此,封装一个工具类,帮助我们更方便快捷的进行这些资源的管理。
//新建一个jdbc.properties文件,将连接参数都写入进去 driverClass=com.mysql.jdbc.Driver jdbcUrl=jdbc:mysql://localhost:3306/gp_01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true username=root password=123456 //资源管理工具类 package com.jdbc.utils; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Arrays; import java.util.Properties; public class JdbcUtils { private static String jdbcUrl; private static String username; private static String password; static { //1.找到资源目录下的配置文件 InputStream input = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); //2.实例化 Properties 对象 Properties properties = new Properties(); try { //加载配置文件,完成配置文件的解析 properties.load(input); //获取参数 jdbcUrl = properties.getProperty("jdbcUrl"); username = properties.getProperty("username"); password = properties.getProperty("password"); //加载驱动 Class.forName(properties.getProperty("driverClass")); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (input != null) { input.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 获取连接对象 * * @return java.sql.Connection 数据库连接对象 */ public static Connection getConnection() { Connection connection = null; try { connection = DriverManager.getConnection(jdbcUrl, username, password); } catch (SQLException e) { e.printStackTrace(); } return connection; } /** * 关闭资源方法,无结果集的情况 * * @param connection 数据库连接对象资源 * @param statement 数据库 SQL 语句搬运工资源 */ public static void close(Connection connection, Statement statement) { close(statement, connection); } /** * 关闭资源方法 * * @param connection 数据库连接对象资源 * @param statement 数据库 SQL 语句搬运工资源 * @param resultSet 数据库查询结果集对象 */ public static void close(Connection connection, Statement statement, ResultSet resultSet) { close(resultSet, statement, connection); } /** * 内部私有化方法,统一处理数据库相关资源 * * @param resources 不定长参数,统一关闭对应资源 */ private static void close(AutoCloseable... resources) { if (resources != null && resources.length > 0) { Arrays.stream(resources).forEach(source -> { try { if (source != null) { source.close(); } } catch (Exception e) { e.printStackTrace(); } }); } } } /* 在static静态代码块中,静态代码块随着类的加载而加载,而且只加载一次。通过加载配置文件,获得了必要的数据库参数,同时加载了驱动。 获取数据库连接对象,通过DriverManager.getConnection(jdbcUrl, username, password)获得connection对象。 关闭连接资源对象,这里使用了AutoCloseable 不定长参数来传入资源对象进行关闭,重载了两个close方法,分别对应了两种情况: * 第一种:关闭connection,statement对象(增删查操作) * 第二种:关闭 connection,statement,resultSet对象(查询操作) */
-
SQL注入问题
比如你待拼接的sql语句如下: String sql = "select * from user where name = '" + username + "' and password = '" + password + "'"; --正常情况下你回输入正常的用户名和密码,之后传给sql语句进行拼接: select * from user where name = 'root' and password = '123456'; --会查到正常的你自己的信息; --但是当你输入不合法的字符串,导致sql语句进行拼接,例如你输入用户名为 "xxx" 密码为 "XXX' or '1' = '1" 时: select * from user where name = 'xxx' and password = 'XXX' or '1' = '1'; --由于 '1' = '1' 恒成立, MySQL 将这个表的所有信息都查找出来了, 显然这是不合理的.
-
防止sql注入(PreparedStatement与Statement的区别)
- 使用 PreparedStatement 时,不需要拼接 SQL 语句,因此可以避免因字符串拼接而产生的 SQL 注入问题。
- Prepared Statement 可以对参数进行转义,从而避免输入的参数导致的 SQL 异常。
- Prepared Statement 可以预编译 SQL 语句,并将编译后的语句保存到数据库服务器的缓存中,因此在执行多次相同的 SQL 语句时,可以大幅提高性能。
//举个例子: //编写sql语句 String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; //根据连接字符串连接数据库 Connection conn = DriverManager.getConnection(url, username, password); //创建PreparedStatement 对象 PreparedStatement pstmt = conn.prepareStatement(sql); //传入参数 pstmt.setString(1, "root"); pstmt.setString(2, "123456"); //执行方式 ResultSet rs = pstmt.executeQuery(); while (rs.next()) { String username = rs.getString("username"); String password = rs.getString("password"); } //释放资源 rs.close(); pstmt.close(); conn.close(); /*总结 PreparedStatement通过以下方式防止SQL注入: 1. 参数化查询 PreparedStatement使用参数化查询,将用户输入的值作为参数传递给SQL语句,而不是将它们直接插入到SQL语句中。这样做可以防止攻击者通过恶意注入SQL语句来改变查询的语义。 2. 自动处理转义字符 PreparedStatement会自动处理必要的转义字符,确保特殊字符不会破坏SQL语句的结构。例如,单引号 ' 、反斜杠 \\ 等特殊字符在传递给PreparedStatement时会自动进行转义处理。 3. 数据类型匹配 PreparedStatement会对传递给它的参数进行数据类型匹配和格式验证。这样可以避免将字符串值错误地解析为数值、日期等类型,从而确保查询的准确性和安全性。 4. 编译和重用 PreparedStatement会对SQL语句进行预编译,并缓存编译后的查询计划。这样可以提高性能,并减少对数据库的负载。由于预编译的查询计划是在应用程序启动时创建的,而不是每次查询执行时都进行编译,这样也有助于防止攻击者在SQL注入中插入恶意代码。 */
-
JDBC中给的事务操作
public void testJDBCTransaction() { Connection conn = null; try { // 1.获取数据库连接 conn = JDBCUtils.getConnection(); // 2.开启事务 conn.setAutoCommit(false); // 3.进行数据库操作 String sql1 = "update user_table set balance = balance - 100 where user = ?"; update(conn, sql1, "AA"); // 模拟网络异常 //System.out.println(10 / 0); String sql2 = "update user_table set balance = balance + 100 where user = ?"; update(conn, sql2, "BB"); // 4.若没有异常,则提交事务 conn.commit(); } catch (Exception e) { e.printStackTrace(); // 5.若有异常,则回滚事务 try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { //6.恢复每次DML操作的自动提交功能 conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } //7.关闭连接 JDBCUtils.closeResource(conn, null, null); } }
-
数据库连接池
数据库连接池是一种维护数据库连接的技术,它允许应用程序在需要时从池中获取数据库连接,并在不需要连接时将其释放回池中。
这种技术的主要目的是减少每次访问数据库时都要创建和销毁连接的开销,从而提高性能和资源利用率。主流连接池各项功能对比如下:
参考:https://blog.csdn.net/jarniyy/article/details/121014633