一、Java程序完成对数据库的操作
我们做为后端程序开发人员,通常会使用Java程序来完成对数据库的操作。Java程序操作数据库的技术呢,有很多啊。
-JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。 【是操作数据库最为基础、底层的技术】
那现在在企业项目开发中呢,一般都会使用基于JDBC的封装的高级框架,比如:Mybatis、MybatisPlus、Hibernate、SpringDataJPA。 而这些技术,目前的市场占有份额如下图所示:
目前最为主流的就是Mybatis,其次是MybatisPlus。
我们需要学习一下操作数据库的基础基础 JDBC。
二、JDBC
2.1 JDBC概述
JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。
本质:
sun公司官方定义的一套操作所有关系型数据库的规范,即接口。
各个数据库厂商去实现这套接口,提供数据库驱动jar包。
我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
2.2 JDBC快速入门
需求:通过JDBC程序,执行update语法,更新用户表中的数据。
步骤:
准备工作:
创建项目,引入mysql的驱动、junit依赖。
注册驱动。
获取连接对象 Connection。
获取SQL语句执行对象 Statement。
执行SQL语句。
释放资源。
三、JDBC API详解
3.1 DriverManager
作用:
注册驱动。
获取数据库链接 Connection。
3.1.1 注册驱动
而通过上述的代码,大家可以看到注册驱动,我们是通过 Class.forName("com.mysql.cj.jdbc.Driver") 来注册的,看似和DriverManager没什么联系。其实不然,Class.forName("com.mysql.cj.jdbc.Driver") 只是将 Driver 这个类加载到JVM中。 而在 Driver 这个类中定义了静态代码块,内容如下:
当 Driver 这个类被加载时,就会自动执行静态代码块中的代码,然后就完成了注册驱动的操作。
备注:
而其实啊,Class.forName("com.mysql.cj.jdbc.Driver") 这句代码呢,是可以省略的,省略了,也可以正常的完成驱动的注册。 原因是因为在MySQL的驱动jar包中,在 META-INF/services 目录中提供了一个文件 java.sql.Driver ,在这个文件中编写了一行内容,就是驱动类的全类名 :
当在程序需要用到这个类时,java会自动加载这个类,这个类一加载 就会自动执行静态代码块中的内容,就完成了注册驱动的操作 ,而java中的这个机制称之为 SPI。
SPI机制:Service Provider Interface,JDK内置的一种服务提供发现机制,可以轻松的扩展你得程序(切换实现),实现接口与实现类之间的解耦。
3.1.2 获取链接
DriverManager.getConnection(url, username, password);
url: 数据库连接url。
语法:jdbc:mysql://ip地址(域名):端口号/数据库名?参数键值对1&参数键值对2。
说明:如果连接的是本机的默认端口的mysql,url可以简写为:jdbc:mysql:///数据库名?参数键值对…
user:用户名。
password:密码。
3.2 Connection & Statement
Connection的作用:获取执行SQL的对象。
执行普通SQL对象的Statement:connection.createStatement();
执行预编译SQL对象PreparedStatement:connection.prepareStatement(); 【后面单独讲解】。
Statement的作用:执行SQL。
执行DDL、DML语句:executeUpdate(sql); 如果是执行DML语句完毕,返回值int代表 DML 语句影响的函数 。
执行SQL语句:executeQuery(sql); 返回值为ResultSet,里面封装了查询结果。
3.3 ResultSet
需求:请根据 用户名 与 密码 查询用户的信息,实现用户登录操作。
实现如下:
/** * 根据用户名和密码查询用户的基本信息 - 参数化测试 */ @ParameterizedTest @CsvSource({"lvbu,123456", "xiaoqiao,123456"}) public void testQuery2(String _username , String _password) throws Exception { //1. 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2. 获取链接 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/web01", "root", "root@1234"); //3. 获取数据库执行对象 Statement Statement statement = connection.createStatement(); //4. 执行SQL ResultSet resultSet = statement.executeQuery("select * from user where username = '"+_username+"' and password = '"+_password+"'"); //5. 获取结果 while (resultSet.next()){ int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String name = resultSet.getString("name"); int age = resultSet.getInt("age"); System.out.println(new User(id,username,password,name,age)); } //6. 释放资源 statement.close(); connection.close(); }
ResultSet(结果集对象):封装了DQL查询语句查询的结果。
next():将光标从当前位置向前移动一行,并判断当前行是否为有效行,返回值为boolean。
true:有效行,当前行有数据
false:无效行,当前行没有数据
getXxx(…):获取数据,可以根据列的编号获取,也可以根据列名获取(推荐)。
而上述的单元测试中,在SQL语句中,用户名 和密码的值是动态的,是将来页面传递到服务端的。 那么,我们可以基于JUnit中的参数化测试进行单元测试。
如果在测试时,需要传递一组参数,可以使用 @CsvSource 注解。
3.4 PreparedStatement
作用:预编译SQL语句并执行,可以防止SQL注入问题。
SQL注入:通过控制输入来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
3.4.1 SQL注入演示
为什么会出现SQL注入这种现象呢?
在进行登录操作时,怎么样才算登录成功呢? 如果我们查询到了数据,就说明用户名密码是对的。 如果没有查询到数据,就说明用户名或密码错误。
而出现上述现象,原因就是因为,我们我们编写的SQL语句是基于字符串进行拼接的 。 我们输入的用户名无所谓,比如:shfhsjfhja ,而密码呢,就是我们精心设计的,如:' or '1' = '1 。
那最终拼接的SQL语句,如下所示:
我们知道,or 连接的条件,是或的关系,两者满足其一就可以。 所以,虽然用户名密码输入错误,也是可以查询返回结果的。
3.4.2 SQL注入解决
PreparedStatement 就可以解决SQL注入的问题。那么接下来,我们就来演示一下 PreparedStatement 的基本使用:
/** * 根据用户名和密码查询用户的基本信息 - PreparedStatement */ @ParameterizedTest @CsvSource({"lvbu,123456"}) public void testQuery3(String _username , String _password) throws Exception { //1. 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2. 获取链接 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/web01", "root", "root@1234"); //3. 获取数据库执行对象 Statement PreparedStatement preparedStatement = connection.prepareStatement("select * from user where username = ? and password = ?"); preparedStatement.setString(1, _username); preparedStatement.setString(2, _password); //4. 执行SQL ResultSet resultSet = preparedStatement.executeQuery(); //5. 获取结果 while (resultSet.next()){ int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String name = resultSet.getString("name"); int age = resultSet.getInt("age"); System.out.println(new User(id,username,password,name,age)); } //6. 释放资源 preparedStatement.close(); connection.close(); }
而 select * from user where username = ? and password = ? 这种SQL呢,我们称之为预编译SQL 。那么基于这种预编译SQL呢,是可以解决SQL注入问题的 。
通过控制台,可以看到输入的SQL语句,是预编译SQL语句。
而在预编译SQL语句中,当我们执行的时候,会把整个' or '1'='1作为一个完整的参数,赋值给第2个问号(' or '1'='1进行了转义,只当做字符串使用)那么此时再查询时,就查询不到对应的数据了,登录失败。
注意:在项目开发中,我们使用的基本全部都是预编译SQL语句。
预编译SQL的优势:
安全(防止SQL注入)
性能更高