PreparedStatement vs Statement 不同及其使用
每博文案
师傅说,人生苦短,但凡想做成大事的人,都要经历三种境界:得到,失去,忘记。
只有从容,坦然地面对这三种事情,才能在生活中轻松自在。
得到是人生的第一重境界:
为了表达自己的想法,因而开始牙牙学语,为了得到更好的发展,孩童
踏上了求学之路,为了活着的基本条件,毕业后开始不辞劳苦的工作,我们时刻都在为了实现目标而努力。
其实这就是为了得到而不断的求索的过程。
失去是人生的第二重境界:
为了更好的前程,未来而离开家乡,去工作,失去了陪伴在父母亲人身边的机会,为了照顾孩子而不得不陪在他左右,
失去了更好的工作机会和发展空间。为了赚更多的钱,而拼命熬夜加班,失去了原来健康的身体素质。
有得必有失,这是亘古不变的真理,得到了一些东西,就要学会接受相对等的失去。
忘记是人生的第三重境界:
相濡以沫,不如相忘于江湖,曾经拥有的,既然已经失去,就不要沉浸在痛苦中,无法自拔。
这并不能改变目前的状况,学会忘记才是最好的选择,展望未来和珍惜当下,永远都比缅怀过去来的可靠。
经历了这三种境界。你便会变得越来越淡然而幸福,用平常心面对人生的起起落落,用乐观的态度面对
未来的机遇,你的人生会更加精彩。
—————— 一禅心灵庙语
文章目录
- PreparedStatement vs Statement 不同及其使用
-
- 每博文案
- 1. DrivarManger 操作和访问数据库
- 2. 使用Statement操作数据表的弊端
- 3. PreparedStatement的使用
- 4. 总结: PreparedStatement vs Statement 不同
- 5. 最后:
1. DrivarManger 操作和访问数据库
-
数据库连接被用于向数据库服务器发送命令和
SQL
语句,并接受数据库服务器返回的结果。其实一个数据库连接就是 一个Socket
连接 -
在
java.sql
包中有 3 接口分别定义了对数据库的调用的不同方式:Statement
:用于执行静态 SQL语句并返回它所生成的结果的对象(主要用于SQL的注入如 对数据进行一个“分组,排序”PrepatedStatement
: SQL 语句被编译并存储在此对象中,所以到达了一次编译,多次执行,提高了SQL的执行效率CallableStatement
: 用于执行 SQL 存储过程
-
数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
-
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
2. 使用Statement操作数据表的弊端
2.1 Statement 的介绍
通过调用 Connection
对象的 createStatement()
方法创建该对象。该对象用于执行静态的 SQL
语句,并且返回执行结果。就是我们上篇文章说到的JDBC 六步骤
中的第三步获取操作数据库的对象 🔜🔜🔜 JDBC 连接数据库的四种方式_ChinaRainbowSea的博客-CSDN博客 。
Connection 接口的文档
createStatement( )的文档
Statement 接口文档
Statement
接口中我们主要使用如下定义的方法,用于执行 SQL 语句:
int executeUpdate(String sql) // 执行更新操作INSERT(插入),UPDATE(修改),DELETE(删除)
ResultSet executeQuery(String sql) // 执行查询操作 SELECT
2.1 Statement 的使用
下面我们就来实际的使用,体验 Statement
接口中的方法
2.1.1 Statement 增删改操作
对于执行 SQL
的insert 插入,update修改,delete删除
的操作,我们需要使用Statement
接口中的 executeUpdate
的方法,该方法的返回类型是,影响数据库行/记录的条数;具体方法的文档如下:
下面是我们操作对象dbtest6数据库的的 user_table的数据表
字符串拼接的技巧 "++"
一个双引号中间包含两个++号
String sql = "insert into user_table(user,password,balance) values('"+user+"','"+password+"','"+balance+"')";
// 在Java当中的sql语句结尾不用加;(分号),加了会报错.
对user_table表进行insert
插入数据操作,具体代码如下:
package Blogs.blogs02;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class StatementTest {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.print("输入user_table中的user: ");
String user = scanner.next();
System.out.print("输入user_table中的password:");
String password = scanner.next();
System.out.print("输入user_table中的balance:");
String balance = scanner.next();
Connection connection = null;
Statement statement = null; // 扩大作用域的范围,用于关闭资源
try {
// jdbc 代码
// 1. 注册驱动(说明你要连接的是什么品牌的数据库)
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver中的静态代码块(注册驱动)
// 注意在com.mysql.jdbc.Driver 包下的Driver是类,而默认的Driver是在java.sql的接口
// 2. 连接驱动的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象
statement = connection.createStatement();
// 4. 执行sql语句
String sql = "insert into user_table(user,password,balance) values('"+user+"','"+password+"','"+balance+"')";
//在Java当中的sql语句结尾不用加;(分号),加了会报错
// 字符串的拼接的技巧 "++"
int count = statement.executeUpdate(sql); // 执行sql语句,返回影响数据库的行数/记录条数
System.out.println(count > 0 ? "插入成功" : "插入失败"); // 当影响数据库的行数 >0 表示成功
// 5. 处理select查询的结果集
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(statement != null) { // !=null表示连接/使用了资源需要关闭,=null表示没有连接/使用资源不用释放资源
try { // 同时防止null引用
statement.close(); // 关闭资源
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try{
connection.close(); // 关闭资源
} catch(SQLException e) {
e.printStackTrace();
}
}
}
}
}
对user_table表进行update
修改数据操作,将 user=test的 balance 修改为 99999具体代码如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class StatementTest {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.print("输入user_table中的user: ");
String user = scanner.next();
System.out.print("输入user_table中修改后的balance:");
String balance = scanner.next();
Connection connection = null;
Statement statement = null; // 扩大作用域的范围,用于关闭资源
try {
// jdbc 代码
// 1. 注册驱动(说明你要连接的是什么品牌的数据库)
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver中的静态代码块(注册驱动)
// 注意在com.mysql.jdbc.Driver 包下的Driver是类,而默认的Driver是在java.sql的接口
// 2. 连接驱动的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象
statement = connection.createStatement();
// 4. 执行sql语句
// 注意与sql关键字存在冲突的名称,使用着重号,区分
String sql = "update user_table set balance = "+balance+" where `user` = '"+user+"'";
// 字符串的拼接的技巧 "++"
int count = statement.executeUpdate(sql); // 执行sql语句,返回影响数据库的行数/记录条数
System.out.println(count > 0 ? "修改成功" : "修改失败"); // 当影响数据库的行数 >0 表示成功
// 5. 处理select查询的结果集
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(statement != null) { // !=null表示连接/使用了资源需要关闭,=null表示没有连接/使用资源不用释放资源
try { // 同时防止null引用
statement.close(); // 关闭资源
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try{
connection.close(); // 关闭资源
} catch(SQLException e) {
e.printStackTrace();
}
}
}
}
}
对user_table表进行delete
修改数据操作,将 user=test的记录删除了具体代码如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class StatementTest {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.print("输入user_table中的user: ");
String user = scanner.next();
Connection connection = null;
Statement statement = null; // 扩大作用域的范围,用于关闭资源
try {
// jdbc 代码
// 1. 注册驱动(说明你要连接的是什么品牌的数据库)
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver中的静态代码块(注册驱动)
// 注意在com.mysql.jdbc.Driver 包下的Driver是类,而默认的Driver是在java.sql的接口
// 2. 连接驱动的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象
statement = connection.createStatement();
// 4. 执行sql语句
// 注意与sql关键字存在冲突的名称,使用着重号,区分
String sql = "delete from user_table where user = '"+user+"'";
// 字符串的拼接的技巧 "++"
int count = statement.executeUpdate(sql); // 执行sql语句,返回影响数据库的行数/记录条数
System.out.println(count > 0 ? "删除成功" : "删除失败"); // 当影响数据库的行数 >0 表示成功
// 5. 处理select查询的结果集
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(statement != null) { // !=null表示连接/使用了资源需要关闭,=null表示没有连接/使用资源不用释放资源
try { // 同时防止null引用
statement.close(); // 关闭资源
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try{
connection.close(); // 关闭资源
} catch(SQLException e) {
e.printStackTrace();
}
}
}
}
}
2.1.2 Statement 处理 select 查询的结果集
对于使用 Statement
处理执行 select
查询语句,使用 executeQuery
的方法,返回的是 ResultSet
, 具体文档如下:
ResultSet 接口文档如下:
2.1.3 ResultSet.next()的使用
if(resultSet.next()) {
}
while(resultSet.next()) {
}
ResultSet.next()
指向当前select 查询显示的结果集的一行/记录的指针,每次调用该方法,指针都就会自动向下移动指向下一条select 查询显示的记录/行,同时并判断该行/记录是否含有数据,有数据返回 true ,没有数据返回 false 。具体文档如下:
-
查询需要调用,在
Statement
接口中的 Statement.executeQuery(String sql) 方法,查询结果是返回一个ResultSet
对象,在PreparedStatement
接口中方法也是一样的,因为 PreparedStatement 是 Statement 的子接口,同样返回的是一个RasultSet
的对象。 -
ResultSet 对象是以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口是由数据库对应的厂家提供并实现的
-
ResultSet 返回的实际上就是一张数据表,有一个指针指向数据表的第一条记录的前面。ResultSet 对象维护了一个指向当前数据行的游标或者理解成指针也是可以的 ,初始的时候, 游标/指针 是在第一行记录的前面,可以通过
ResultSet.next()
的方法移动指针/游标,每调用一次ResultSet.next()
方法指针/游标都会自动向下移动一行(指向下一行的数据记录),同时会判断当前行是否存在数据 ,若存在数据返回true
,若数据不存在返回false
,相当于lterator
对象的 hasNext( ) 和 next( ) 方法的结合体。 -
当指针/游标 指向一行时,可以通过调用 getxxx(int index) 或 getxxx(int columnName) 获取到每行的对应列的数据值
- 例如:getInt(1),getString(“name”) 。
- 注意:Java与数据库交互涉及到的相关Java API 中的索引都从 1 开始。
-
ResultSet 接口的常用方法:
- boolean ResultSet.next( )
- ResultSet.getString( )
2.1.4 ResultSet.getString() / ResultSet.getInt() / ResultSet.getObject
ResultSet.getString()
表示获取 select
查询显示的结果集中对应列/字段的数据内容,参数(存在方法的重载)可以是 表示第几列的数字 / select 查询显示的列名(字段名,注意使用了别名,就是别名了,再使用原来表结构的名称就无法获取到了) 。主要的一点就是: ResultSet.getString() 特点无论我们从数据表中获取到的是什么类型的数据都将转化为 String类型赋值给变量 ,ResultSet.getInt() / ResultSet.getObject 都是同理的。 ResultSet.getObject
表示我们不确定的类型使用 Object 的父类型
相关文档如下:
这里我们实现一个简单的登入界面,通过输入信息,查询 user_table
表中的数据,如果可以查询到该数据信息,则可以登入(因为数据库中记录了,其注册的信息),如果没有查询到,则登入失败 ;具体代码如下:
import java.sql.*;
import java.util.Scanner;
public class StatementTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("用户名user: ");
String user = scanner.nextLine(); // next无法读取到空格,nextLine可以读取到空格
System.out.print("密码password: ");
String password = scanner.nextLine();
// jdbc 代码
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver类的静态代码(注册驱动)
// Driver默认是java.sql的接口,com.mysql.jdbc.Driver包下的Driver类
// 2. 连接驱动的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象
statement = connection.createStatement();
// 4. 执行sql语句的select查询语句,注意与名称与sql关键字冲突的使用`着重号区分`
String sql = "select `user`,password from user_table where user = '"+user+"' and password = '"+password+"'";
// 在mysql中sql语句的结尾需要加;分号,但是在java当中的sql语句是不要加;(分号)的,不然报错
resultSet = statement.executeQuery(sql); // 执行sql语句
// 5. 处理当前select查询到的结果集
if(resultSet.next()) { // next指向当前select 查询显示的行/记录,并判断该行是否有数据,有true,没有false,每次调用都会向下移动指针
System.out.println("登入成功");
} else {
System.out.println("用户不存在或密码/用户名错误");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(resultSet != null) { // !=null的表示连接/使用了资源需要关闭/释放资源,=null表示没有连接/使用资源不用关闭
try { // 防止 null引用
resultSet.close(); // 关闭资源
} catch(SQLException e){
e.printStackTrace();
}
}
if( statement != null) {
try {
statement.close(); // 关闭/释放资源
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try {
connection.close(); // 关闭/释放资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
}
}
}
2.2 SQL注入的问题
我们演示SQL
注入的问题,同样我们使用 上面2.1.2 Statement 处理 select 查询的结果集 的登入界面,说明SQL注入问题。
我们输入如下内容:看看是否可以登入成功
用户名user: abc
密码password: 1' or '1' = '1
从显示的结果上看,明显的发现,我们居然登入成功了,这是不应该的: 因为已知当前我们所使用的数据表(如下图->左上角)当中并没有我们登入时输入的用户的数据。却登入了。
我们调试看看,查看 sql
变量传的值是:
我们会惊奇的发现,sql
我们实际的值是如下:sql语句
select `user`,password from user_table where user = 'abc' and password = '1' or '1' = '1'
我们仔细思考,研究,这段SQL语句,发现了一个十分严重的问题:存在了 or
(或逻辑运算符),在Mysql当中 or
的优先级是比 and
更高的是会被优先执行的,这样这条语句就是真了。1 = 1
,就可以查询到结果了 ,从而导致登入成功。我们可以在 SQLyog
执行这条语句看看结果是什么 ?
这就是所谓了 SQL 注入问题
导致 SQL注入的根本原因是 :? ? ?
用户输入的信息中含有
sql
语句的关键字,并且这些关键字参与了编译,并执行了。导致原来的
sql
语句的意思发生了,改变,从而到达了SQL注入。这是一个十分严重的问题,许多黑客利用系统的 SQL 引擎完成恶意行为的做法。比如注入恶意信息,以及获取权限删库,等等。
2.3 SQL注入的应用
Statement
存在注入的问题,就一无是处了吗,不是这样的。
那我们在什么情况下 必须使用Statement ? ? ?
业务方面要求必须支持 SQL 注入的时候,比如 升序,降序 ,这时候就需要使用 Statement 进行SQL注入了,
凡是业务方面要求是需要进行 SQL 语句拼接的时候,必须使用 Statment。
如下,我们对user_table
进行一个降序的排序
具体代码实现如下:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入desc/asc, desc表示降序,asc表示升序: ");
String keywords = scanner.next();
// jdbc 代码
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver类的静态代码(注册驱动)
// Driver默认是java.sql的接口,com.mysql.jdbc.Driver包下的Driver类
// 2. 连接驱动的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象
statement = connection.createStatement();
// 4. 执行sql语句的select查询语句,注意与名称与sql关键字冲突的使用`着重号区分`
String sql = "select `user`,password,balance from user_table order by balance "+keywords;
// 在mysql中sql语句的结尾需要加;分号,但是在java当中的sql语句是不要加;(分号)的,不然报错
resultSet = statement.executeQuery(sql); // 执行sql语句
// 5. 处理当前select查询到的结果集
while(resultSet.next()) {
String user = resultSet.getString(1);
String password = resultSet.getString(2);
int balance = resultSet.getInt(3);
System.out.println(balance+":"+user+":"+password);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(resultSet != null) { // !=null的表示连接/使用了资源需要关闭/释放资源,=null表示没有连接/使用资源不用关闭
try { // 防止 null引用
resultSet.close(); // 关闭资源
} catch(SQLException e){
e.printStackTrace();
}
}
if( statement != null) {
try {
statement.close(); // 关闭/释放资源
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try {
connection.close(); // 关闭/释放资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
}
}
}
3. PreparedStatement的使用
对于 Java而言,要防范 SQL 注入,只要用 PreparedStatement
(从Statement扩展而来,就是Statement的子接口)取代 Statement
就可以了
3.1 PreparedStatement的介绍
可以通过调用 Connection
对象的 preparedStaement(String sql)
的方法获取到 PreparedStatment
对象
实际上 PreparedStatment 是一个接口,是Statement 的子接口 ,具体信息文档如下:
PreparedStatement
接口中我们主要使用如下定义的方法,用于执行 SQL 语句:因为 PreparedStatement 就是Statement 的子接口所以,使用的方法是一样的。这里就不一一说明了。
int executeUpdate(String sql) // 执行更新操作INSERT(插入),UPDATE(修改),DELETE(删除)
ResultSet executeQuery(String sql) // 执行查询操作 SELECT
3.2 PreparedStatement的增删改操作
对应PreparedStatement
的增删改操作我们使用 executeUpdate(String sql)
的方法,和Statement 是一样的。返回类型是int 型,影响数据库的行数值。
3.2.1 PreparedStatement 的占位符
占位符: 顾名思义,占位符就是先占住一个固定的位置,等着你再往里面添加内容的符号,广泛用于计算机中各类文档的编辑。
PreparedStatement prepareStatement(String sql)
中参数 sql
中可以使用占位符,?(问号)
:表示占位符 ,注意: 一个 ?
表示一个占位符,占位符不可以使用 ‘单引号’ 括起来,括起来就表示字符串了,就失效了。
String sql = "insert into user_table(`user`,password,balance) values(?,?,?) ";
虽然在mysql 当中一个要使用 ;(分号) 结尾,但是在Java当中的 SQL 语句的是不需要使用 ;(分号)结尾的,如果使用了的话,是会报错的。同时注意 sql语句中名称于关键字冲突了使用 “着重号,区分。
?
占位符的填充
对于我们在 sql 语句中设置的 ? 占位符 ,进行一个填充的操作,使用 PreparedStatement.setxxx(int parameterIndex,xxx)
的方法进行一个填充,如:setInt/setString/setObject… 多个类型的数据填充,
需要注意的是第一个参数 int parameterIndex
表示的是对 第几个占位符进行填充,它的起始下标位置是 1
,不是 0 ,在 JDBC中基本上所有的下标都是从 1 开始的,和数组不一样,后面的 xx
表示你对这个占位符填充的数值(需要和你 setxxx的类型一致),这也是 PreparedStatement 的一个好处之一,可以提高代码的健壮性
preparedStatement.setString(1,user); // 第一个 ?占位符
preparedStatement.setString(2,password); // 第二个 ?占位符 setString() String类型
preparedStatement.setInt(3,balance); // 第三个 ? 占位符 setInt()int类型
3.2.2 PreparedStatement的预编译机制
Connection.prepareStatement(String sql)
只是预编译 sql 语句,并没有实际执行。这是这个预编译的机制,解决了 Statment 的SQL注入问题,预编译会先将 sql语句中的 关键字 进行一个编译,而其中的 sql
语句中的 ?
占位符,保留住,不会编译,后面我们需要对占位符的填充。再真正的执行 SQL
语句。
DBServer 会对预编译 语句提供性能优化。因为预编译的语句有可能被重复调用,所以语句在被 DBServer 的编译器编译后的执行代码被缓存下来,那么下次调用时,只要是相同的预编译语句就不需要编译,只要将参数传入到编译过的语句,执行代码,就可以了。
3.2.3 PreparedStatement.executeUpdate() 执行sql语句
preparedStatement.executeUpdate()
执行sql语句中的 增删改,注意: 这里我们使用的是 无 参数的方法,因为上面我们已经对 sql 语句进行了一个预编译的操作,所以这里只要将上面预编译的sql语句执行一下就可以了。
和 Statement 中的一样,返回值是,影响数据库的行数信息。
具体方法的文档如下:
下面介绍一下我们实验对象的数据表,还是上面我们 Statement 使用的 user_table
表,具体信息如下:
使用PreparedStatement 对 user_table 数据表进行一个 insert 插入一条记录的操作 ,具体代码实现如下
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class PreparedStatementTest {
/**
* 使用PreparedStatement 对数据表进行 insert 插入操作
* @param args
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("输入user_table中的user: ");
String user = scanner.next();
System.out.print("输入user_table中的password:");
String password = scanner.next();
System.out.print("输入user_table中的balance:");
int balance = scanner.nextInt();
// jdbc 代码
Connection connection = null;
PreparedStatement preparedStatement = null; // 扩大作用域,用于关闭/释放资源
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver包下的类中的静态代码块,注册驱动
// 2. 连接驱动上的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象(获取到预编译的对象)
// ? 表示占位符,一个?表示一个占位符,占位符不可以加'单引号'包裹,加了单引号就不是占位符了,就是一个字符串了,就失去了占位符的作用了
// 在mysql当中sql语句要加;分号结束,在Java当中使用sql语句不用加;分号,加了会报错
String sql = "insert into user_table(`user`,password,balance) values(?,?,?) ";
preparedStatement = connection.prepareStatement(sql); // 注意只是预编译sql语句,并没有执行
// 4. 执行sql语句
// 4.1 填充占位符
preparedStatement.setString(1,user); // 第一个 ?占位符
preparedStatement.setString(2,password); // 第二个 ?占位符
preparedStatement.setInt(3,balance); // 第三个 ? 占位符 setInt()int类型
int count = preparedStatement.executeUpdate(); // 注意是没有参数的类型,因为上面我们已经编译过sql语句了
System.out.println(count > 0 ? "插入成功" : "删除成功");
// 5. 处理select 查询的结果集,这里没有select 不用处理
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(preparedStatement != null) { // !=null 连接/使用了资源的需要关闭资源,=null没有连接/释放的资源不用关闭
try { // 同时防止null引用的报错
preparedStatement.close(); // 关闭/释放操作数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
if(connection != null) {
try {
connection.close(); // 关闭/释放连接数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
}
}
}
使用PreparedStatement 对 user_table 数据表进行一个 update 更新数据的操作,将user=Tom,中的balance修改为 100000 ,具体代码实现如下
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.print("输入user_table中的user: ");
String user = scanner.next();
System.out.print("输入user_table中的balance:");
int balance = scanner.nextInt();
// jdbc代码
Connection connection = null;
PreparedStatement preparedStatement = null; // 扩大作用域,用于关闭/释放资源
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver包下的类中的静态代码块,注册驱动
// 2. 连接驱动上的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象(获取到预编译的对象)
// ? 表示占位符,一个?表示一个占位符,占位符不可以加'单引号'包裹,加了单引号就不是占位符了,就是一个字符串了,就失去了占位符的作用了
// 在mysql当中sql语句要加;分号结束,在Java当中使用sql语句不用加;分号,加了会报错
String sql = "update user_table set balance = ? where `user` = ?";
preparedStatement = connection.prepareStatement(sql); // 注意只是预编译sql语句,并没有执行
// 4. 执行sql语句
// 4.1 填充占位符
preparedStatement.setInt(1,balance); // 第一个 ?占位符
preparedStatement.setString(2,user); // 第二个 ?占位符
int count = preparedStatement.executeUpdate(); // 注意是没有参数的类型,因为上面我们已经编译过sql语句了
System.out.println(count > 0 ? "修改成功" : "修改失败");
// 5. 处理select 查询的结果集,这里没有select 不用处理
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(preparedStatement != null) { // !=null 连接/使用了资源的需要关闭资源,=null没有连接/释放的资源不用关闭
try { // 同时防止null引用的报错
preparedStatement.close(); // 关闭/释放操作数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
if(connection != null) {
try {
connection.close(); // 关闭/释放连接数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
}
}
使用PreparedStatement 对 user_table 数据表进行一个 delete 删除数据的操作,将user=Tom删除 ,具体代码实现如下
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("输入user_table中的user: ");
String user = scanner.next();
// jdbc代码
Connection connection = null;
PreparedStatement preparedStatement = null; // 扩大作用域,用于关闭/释放资源
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver包下的类中的静态代码块,注册驱动
// 2. 连接驱动上的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象(获取到预编译的对象)
// ? 表示占位符,一个?表示一个占位符,占位符不可以加'单引号'包裹,加了单引号就不是占位符了,就是一个字符串了,就失去了占位符的作用了
// 在mysql当中sql语句要加;分号结束,在Java当中使用sql语句不用加;分号,加了会报错
String sql = "delete from user_table where `user` = ?";
preparedStatement = connection.prepareStatement(sql); // 注意只是预编译sql语句,并没有执行
// 4. 执行sql语句
// 4.1 填充占位符
preparedStatement.setString(1,user); // 第一个 ?占位符
int count = preparedStatement.executeUpdate(); // 注意是没有参数的类型,因为上面我们已经编译过sql语句了
System.out.println(count > 0 ? "删除成功" : "删除失败");
// 5. 处理select 查询的结果集,这里没有select 不用处理
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(preparedStatement != null) { // !=null 连接/使用了资源的需要关闭资源,=null没有连接/释放的资源不用关闭
try { // 同时防止null引用的报错
preparedStatement.close(); // 关闭/释放操作数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
if(connection != null) {
try {
connection.close(); // 关闭/释放连接数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
}
}
}
3.3 PreparedStatement 的查询操作
对于使用 PreparedStatement
处理执行 select
查询语句,使用 executeQuery
的方法,返回的是 ResultSet
, 具体文档如下:和 Statment
是一样的。因为 PreparedStatement 接口是 Statment 的子接口
这里我们使用和 Stamtent那个简单的登入界面一样,看看PreparedStatement 是否存在SQL注入的问题
同样是这张数据表:
具体实现代码如下:
import java.sql.*;
import java.util.Scanner;
public static void main4(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("输入user_table中的user: ");
String user = scanner.next();
// jdbc代码
Connection connection = null;
PreparedStatement preparedStatement = null; // 扩大作用域,用于关闭/释放资源
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver包下的类中的静态代码块,注册驱动
// 2. 连接驱动上的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象(获取到预编译的对象)
// ? 表示占位符,一个?表示一个占位符,占位符不可以加'单引号'包裹,加了单引号就不是占位符了,就是一个字符串了,就失去了占位符的作用了
// 在mysql当中sql语句要加;分号结束,在Java当中使用sql语句不用加;分号,加了会报错
String sql = "delete from user_table where `user` = ?";
preparedStatement = connection.prepareStatement(sql); // 注意只是预编译sql语句,并没有执行
// 4. 执行sql语句
// 4.1 填充占位符
preparedStatement.setString(1,user); // 第一个 ?占位符
int count = preparedStatement.executeUpdate(); // 注意是没有参数的类型,因为上面我们已经编译过sql语句了
System.out.println(count > 0 ? "删除成功" : "删除失败");
// 5. 处理select 查询的结果集,这里没有select 不用处理
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大
if(preparedStatement != null) { // !=null 连接/使用了资源的需要关闭资源,=null没有连接/释放的资源不用关闭
try { // 同时防止null引用的报错
preparedStatement.close(); // 关闭/释放操作数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
if(connection != null) {
try {
connection.close(); // 关闭/释放连接数据库的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
}
}
}
我们试试如下上面我们 Statement SQL注入的问题的用户名和密码,看看是否存在SQL注入
用户名user: abc
密码password: 1' or '1' = '1
登入失败了,从结果上看,并没有出现Statement SQL注入的问题。这是因为我们这里的Connection.prepareStatement(String sql)
只是预编译 sql 语句,并没有实际执行,多出了这样一个预编译 SQL关键字的过程,率先将 SQL语句的关键字,进行了编译,为填写的筛选参数使用了 ? 占位符
,暂时代替了参数,后面再对占位符(参数)进行一个填充,并不会将填充的内容识别成 SQL语句的关键字,会加上“着重号,用于区分。具体我们看如下的调试:使用了占位符的代替
3.4 PreparedStatement 通用增删改的工具类的封装
上面我们已经使用 **PreparedStatement **操作数据库进行了增删改的操作了,接下来,我们对其增删改进行一个统一的封装,减少代码的冗余
具体代码如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 使用PreparedStatement 对增删改操作进行一个工具的封装
*/
public class JdbcUpdate {
/**
* 操心数据表执行sql语句
*/
public void jdbcUpdate(String sql,Object...args) {
/*
Object...args 可变参数,用于存放你填充占位符的所传的信息,
Object...args 可变参数,必须在参数的最后一个(不然无法识别是否,是可变参数的值),只能有一个可变参数
可变参数可以不传参数(但是不用传null,防止引用类型的空引用报错),起始下标是0 ,旧(数组)
Object 类型的可变参数,因为不要写死了,你所填充的占位符的数据类型
*/
Connection connection = null;
PreparedStatement preparedStatement = null; // 扩大作用域,用于关闭/释放资源
try {
// 1. 注册驱动,2.连接数据库
connection = this.Login();
// 3. 获取操作数据库的对象(Connection.prepareStatement()预编译对象)
preparedStatement = connection.prepareStatement(sql); // 只是预编译sql语句,并没有执行
// 3.1 填充占位符
for(int i = 0; i < args.length; i ++) {
preparedStatement.setObject(i+1,args[i]);
// 占位符的起始下标位置是 1 ,而可变参数的起始下标位置是 0
}
// 4. 执行sql语句
int count = preparedStatement.executeUpdate(); // 注意是无参数的,因为上面我们已经对sql语句进行编译了
System.out.println(count > 0 ? "成功": "失败");
// 5. 处理select 查询的结果集,这里没有不用处理
} catch (SQLException e) {
e.printStackTrace();
}
// 6. 关闭/释放资源
this.close(preparedStatement,connection);
}
/**
* 2. 关闭/释放资源,从小到大,最晚使用的资源,最先释放资源
*/
public void close(PreparedStatement preparedStatement, Connection connection) {
/*
!=null,防止空引用,!=null 表示连接/使用了资源才需要关闭,=null没有连接/使用的资源不需要关闭
*/
if(preparedStatement != null) {
try {
preparedStatement.close(); // 关闭/释放获取到操作数据库的资源
} catch (SQLException e) {
throw new RuntimeException(e); // 将编译异常转为为运行异常抛出
}
}
if(connection != null) {
try {
connection.close(); // 关闭/释放连接的数据库资源
} catch(SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* 1.注册驱动,2. 连接数据库
* @return Connection
*/
public Connection Login() {
Connection connection = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 通过反射加载类,执行com.mysql.jdbc.Driver中静态代码块(注册驱动)
// 2. 连接数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306" +
"/dbtest6?useUnicode=true&characterEncoding=utf8", "root","MySQL123");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
} catch (SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出;
}
return connection;
}
}
测试 insert 插入数据
import java.util.Scanner;
/**
* 对JdbcUpdate 封装的工具类测试
*/
public class JdbcUpdateTest {
/**
* insert 插入数据测试
* @param args
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
JdbcUpdate jdbcUpdate = new JdbcUpdate(); // 实例化对象,静态方法访问非静态方法
System.out.print("输入user_table中的user: ");
String user = scanner.next();
System.out.print("输入user_table中的password:");
String password = scanner.next();
System.out.print("输入user_table中的balance:");
int balance = scanner.nextInt();
String sql = "insert into user_table(user,password,balance) values(?,?,?)"; // ? 不要加单引号,Java当中的sql语句不用加;分号结尾
jdbcUpdate.jdbcUpdate(sql,user,password,balance);
}
}
测试修改数据
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
JdbcUpdate jdbcUpdate = new JdbcUpdate(); // 实例化对象,静态方法访问非静态方法
System.out.print("输入user_table中的user: ");
String user = scanner.next();
System.out.print("输入user_table中修改后的的password:");
String password = scanner.next();
String sql = "update user_table set password = ? where user = ?";
jdbcUpdate.jdbcUpdate(sql,password,user);
}
}
测试delete 数据库
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
JdbcUpdate jdbcUpdate = new JdbcUpdate(); // 实例化对象,静态方法访问非静态方法
System.out.print("输入user_table中要删除的user: ");
String user = scanner.next();
String sql = "delete from user_table where user = ?";
jdbcUpdate.jdbcUpdate(sql,user);
}
}
3.5 PreparedStatement 的配合ORM 的映射的数据表的类
3.5 .1 ORM 映射介绍
ORM概念
对象关系映射(Object Relational Mapping)简称 ORM 模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
简单的说,ORM 是通过使用描述对象和数据库之间的映射的元数据 ,将程序中的对象自动 持久化 到关系数据库中。ORM在业务逻辑层和数据层之间充当了桥梁的作用。
ORM 解决的主要的问题是对象和关系的映射。
它通常将一个类和一张表一一对应,类的每个实例对应表中的一条(行)记录,类的每个属性对应表中的每个字段
ORM 提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。让软件开发人员专注于业务逻辑的处理,提高了开发效率。
Java与SQL对应数据类型转换表
SQL类型 | Java类型 |
---|---|
bit | boolean |
tinyint | byte |
smallint | short |
integer | int |
bigint | long |
char,varchar(),longvarchar() | String |
binary,varbinary | byte array |
date | java.sql.Date |
time | java.sql.Time |
timestamp | java.sql.Timestamp |
3.6 PreparedStatement 配合ORM映射处理结果集数据表的类的工具类的封装
3.6.1 ResultSetMetaData 接口
ResultSetMetaData
通过 PreparedStatement.getMetaData()
返回类型获得,给对象接口用于得到当前select查询显示结果集的元数据信息。
元数据被定义为:描述数据的数据,对数据及信息资源的描述性信息。
ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData();
该接口中存在大量就的获取到 select 查询的结果集的方法
-
如下具体:
- ResultSetMetaData.getColumnCount( ) 返回当前 select 查询显示的列(的总个数)
- ResultSetMetaData.getColumnName(int column) 获取到指定下标列数的名称(下标的起始位置是 1) ,(这里获取到是对应表结构的列名,并不能获取到select的别名 ),所以当使用
ORM
映射的Java类中的属性值赋值时,可能存在错误,无法找到对应匹配的字段,因为在Java当中变量的命名格式时大小驼峰,没有下划线,而在Mysql当中的命名格式是使用下划线的。从而导致对应获取到的列名与Java当中的属性名不一致,导致赋值错误,所以大多数是使用如下的方法:列名是字符串类型
- ResultSetMetaData.getColumnLabel(int column) 获取指定下标(下标的起始位置是 1)的列名,这里的列名是 select查询显示的列名,包含了别名 ,如果 select 查询没有使用别名就是默认的(数据表结构的列名),这个方法就很好的解决了对于 java 类中属性名格式的匹配问题,我们可以通过 select 查询使用别名的方式(别名命名上使用Java的大小驼峰的方式)
- ResultSetMetaData.getColumnTypeName(int column) :检索指定列的数据库特定的类型名称
- ResultSetMetaData.getColumnDisplaySize(int column) : 指示列的最大标准宽度,以字符为单位
- ResultSetMetaData.isNullable(int column) : 指示指定列中的值是否可以为
null
- ResultSetMetaData.isAutoIncrement(int column) :指示是否可以自动为指定列进行编号,这样这些列仍然是只读的
问题1:得到结果集后,如何知道该结果集中有哪些列?列名是什么?
通过
PreparedStatement.getMetaData()
方法获取到当前select 查询显示的结果集ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData();
再通过对
ResultSetMetaData.getColumnCount()
的方法获取到 select 查询显示的总列数int columnCount = resultSetMetaData.getColumnCount();
最后同样使用上该
ResultSetMetaData.getColumnLabel
的方法获取到对应下标列的列名,注意起始下标是从 1 开始的。String columnName = resultSetMetaData.getColumnLabel(i+1);
问题2:关于ResultSetMetaData
- 如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
- 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
- 获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的getColumnLabel() 方法
上面介绍了 ORM
的映射: DB中的一个数据表对应Java当中的一个类,DB数据表中的一行记录对应Java当中的一个对象,DB数据表中的一列(字段)对应Java当中的一个属性
下面我们就来根据 ORM
创建类,用来处理存储 select 查询显示的结果集
- 第一个 :我们对
user_table
这个数据表,进行 ORM 映射的 select 结果集的处理
- 首先根据 user_talbe 表结构 创建 ORM映射的Java类 ,这里我们命名为
User
类
public class User {
private String user; // 用户名
private String password; // 密码
private int balance; // 账号余额
public User() {
// 无参构造器,就算没有使用,也要创建提高代码的健壮性
}
public User(String user,String password, int balance) {
this.user = user;
this.password = password;
this.balance = balance;
}
public void setUser(String user) {
this.user = user;
}
public String getUser() {
return this.user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "User{" +
"user='" + user + '\'' +
", password='" + password + '\'' +
", balance=" + balance +
'}';
}
}
- 对 user_table 数据表进行一个 select 查询处理结果集的工具类的封装
import java.lang.reflect.Field;
import java.sql.*;
/**
* 对 user_table 数据表 select 处理查询结果集的工具类的封装
*/
public class UserSelect {
/**
* 针对 user_talbe 表的通用查询操作
* @param sql
* @param args
* @return User
*/
public User queryForUser(String sql,Object...args) {
/*
Object...args 可变参数,用于表示填充的占位符的信息,因为我们不可以将填充的信息写死了,所以我们使用 Object的类型表示
可变参数(必须位于最后一个参数,不然无法识别其中参数是否属于可变参数的),可变参数只能有一个,可变参数可以不传参数,但是不要
传null,特别是对于引用类型来说,存在null引用的异常,可变参数的起始下标是 0 ,旧数组
*/
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null; // 扩大作用域,用于关闭资源
// 1. 注册驱动, 2. 连接驱动中的数据库
connection = this.Login();
try {
// 3. 获取操作数据库的对象(Connection.prepareStatement(sql)预编译对象)
preparedStatement = connection.prepareStatement(sql); // 只是预编译sql语句,并没有执行
// 3.1 填充占位符
for(int i = 0 ; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
// 占位符起始下标是 1,可变参数的起始下标是 0
}
// 4. 执行sql语句
resultSet = preparedStatement.executeQuery(); // 注意是:无参数的方法,因为上面我们已经编译sql语句了
// 5. 处理select 查询显示的结果集
// 5.1 获取到select 查询显示的结果集的元数据
ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData();
// 5.2 通过获取到的元数据对获取到一行记录中select 查询所显示的列数
int columnCount = resultSetMetaData.getColumnCount();
while(resultSet.next()) { // 指向当前记录行的指针,同时判断当前行是否存有数据,有返回true,并向下移动指针,没有返回false,跳出
User user = new User(); // 创建对应 orm 数据表映射的Java类的对象,存储从select 查询结果获取到的信息,一行记录对应一个Java对象
// 5.3 处理一行所有的列的字段信息
for(int i = 0 ; i<columnCount; i++) {
// 通过元数据对象,获取到对应下标位置的select 查询显示的列名getColumnLabel()的方法包含select 显示的别名,起始下标位置是 1
String columnName = resultSetMetaData.getColumnLabel(i+1);
// String columnName2 = resultSetMetaData.getColumnName(i+1); //getColumnName() 这个方法是仅仅获取到的是数据表结构的的列名信息
// 再通过执行sql语句返回ResultSet的接口,获取到对应列名的数值,参数可以是(列名(包括别名,使用了别名就必须使用别名了)),也可以是下标位置(从1 开始的)
Object columnValue = resultSet.getObject(columnName);
// Object columnValue = resultSet.getObject(i+1); // 也可以是列所对应的下标位置 1,起始是 1 下标
// 再通过反射的机制,将对象指定名为 xxx的属性赋值从数据表中获取到的值 columnValue,数据表一个列名对应一个Java类当中的属性
Field field = User.class.getDeclaredField(columnName); // columnName 列名
field.setAccessible(true);
field.set(user,columnValue); // 赋值到对应属性当中去
}
return user; // 返回 user 存储到的数据信息
}
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大,最晚使用的最先释放资源
this.close(resultSet,preparedStatement,connection);
}
return null;
}
/**
* 关闭资源
* @param resultSet
* @param preparedStatement
* @param connection
*/
public void close(ResultSet resultSet, PreparedStatement preparedStatement, Connection connection) {
// 关闭/释放资源,从小到大,最后使用的,最先关闭/释放
/*
!=null,防止null引用,连接/使用资源的需要关闭资源,而==null 没有连接/使用的资源是不需要关闭的
*/
if(resultSet != null ) {
try {
resultSet.close(); // 关闭处理select 结果集的资源
} catch(SQLException e ) {
e.printStackTrace();
}
}
if(preparedStatement != null) {
try {
preparedStatement.close(); // 关闭连接操作数据库的对象的资源
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try{
connection.close(); // 关闭注册数据库的对象的资源
} catch(SQLException e) {
e.printStackTrace();
}
}
}
/**
* 1.注册驱动, 2. 连接数据库
*/
public Connection Login() {
Connection connection = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 静态加载类,执行com.mysql.jdbc.Driver包下的类中的静态代码块(注册驱动)
// 2. 连接数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true&characterEncoding=utf8","root","MySQL123");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
查询测试
package Blogs.blogs02;
/**
* 对应 UserSelect 对 user表的工具类封装的测试
*/
public class UserSelectTest {
public static void main(String[] args) {
UserSelect userSelect = new UserSelect(); // 实例化对象,静态方法调用非静态方法
String sql = "select user,password,balance from user_table where user = ?";
User user = userSelect.queryForUser(sql, "AA");
System.out.println(user);
User user2 = userSelect.queryForUser(sql,"CC");
System.out.println(user2);
}
}
- 第二: 我们再对
orders
这个数据表,进行 ORM 映射的 select 结果集的处理,再次练练手
- 对
obers
表进行一个 ORM 映射的表在Java当中使用 类创建一个名为 obers 的Java类
package Blogs.blogs02;
import java.sql.Date;
public class Obers {
private int orderId; // 用户id
private String orderName; // 订单名字
private Date orderDate; // 订单日期
public Obers() {
// 无参数构造器,无论是否会被使用到都创建出来,提高代码的健壮性
}
public Obers(int orderId,String orderName, Date orderDate) {
this.orderId = orderId;
this.orderName = orderName;
this.orderDate = orderDate;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public int getOrderId() {
return this.orderId;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
@Override
public String toString() {
return "Obers{" +
"orderId=" + orderId +
", orderName='" + orderName + '\'' +
", orderDate=" + orderDate +
'}';
}
}
- 对 obers 数据表进行一个 select 查询处理结果集的工具类的封装
package Blogs.blogs02;
import java.lang.reflect.Field;
import java.sql.*;
/**
* 对Obers 数据表 select 结果集的工具类的封装
*/
public class ObersSelect {
/**
* 针对 user_talbe 表的通用查询操作
* @param sql
* @param args
* @return User
*/
public Obers queryForObers(String sql, Object...args) {
/*
Object...args 可变参数,用于表示填充的占位符的信息,因为我们不可以将填充的信息写死了,所以我们使用 Object的类型表示
可变参数(必须位于最后一个参数,不然无法识别其中参数是否属于可变参数的),可变参数只能有一个,可变参数可以不传参数,但是不要
传null,特别是对于引用类型来说,存在null引用的异常,可变参数的起始下标是 0 ,旧数组
*/
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null; // 扩大作用域,用于关闭资源
// 1. 注册驱动, 2. 连接驱动中的数据库
connection = this.Login();
try {
// 3. 获取操作数据库的对象(Connection.prepareStatement(sql)预编译对象)
preparedStatement = connection.prepareStatement(sql); // 只是预编译sql语句,并没有执行
// 3.1 填充占位符
for(int i = 0 ; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
// 占位符起始下标是 1,可变参数的起始下标是 0
}
// 4. 执行sql语句
resultSet = preparedStatement.executeQuery(); // 注意是:无参数的方法,因为上面我们已经编译sql语句了
// 5. 处理select 查询显示的结果集
// 5.1 获取到select 查询显示的结果集的元数据
ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData();
// 5.2 通过获取到的元数据对获取到一行记录中select 查询所显示的列数
int columnCount = resultSetMetaData.getColumnCount();
while(resultSet.next()) { // 指向当前记录行的指针,同时判断当前行是否存有数据,有返回true,并向下移动指针,没有返回false,跳出
Obers obers = new Obers(); // 创建对应 orm 数据表映射的Java类的对象,存储从select 查询结果获取到的信息,一行记录对应一个Java对象
// 5.3 处理一行所有的列的字段信息
for(int i = 0 ; i<columnCount; i++) {
// 通过元数据对象,获取到对应下标位置的select 查询显示的列名getColumnLabel()的方法包含select 显示的别名,起始下标位置是 1
String columnName = resultSetMetaData.getColumnLabel(i+1);
// String columnName2 = resultSetMetaData.getColumnName(i+1); //getColumnName() 这个方法是仅仅获取到的是数据表结构的的列名信息
// 再通过执行sql语句返回ResultSet的接口,获取到对应列名的数值,参数可以是(列名(包括别名,使用了别名就必须使用别名了)),也可以是下标位置(从1 开始的)
Object columnValue = resultSet.getObject(columnName);
// Object columnValue = resultSet.getObject(i+1); // 也可以是列所对应的下标位置 1,起始是 1 下标
// 再通过反射的机制,将对象指定名为 xxx的属性赋值从数据表中获取到的值 columnValue,数据表一个列名对应一个Java类当中的属性
Field field = Obers.class.getDeclaredField(columnName); // columnName 列名
field.setAccessible(true);
field.set(obers,columnValue); // 赋值到对应属性当中去
}
return obers; // 返回 user 存储到的数据信息
}
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
// 6. 关闭/释放资源,从小到大,最晚使用的最先释放资源
this.close(resultSet,preparedStatement,connection);
}
return null;
}
/**
* 关闭资源
* @param resultSet
* @param preparedStatement
* @param connection
*/
public void close(ResultSet resultSet, PreparedStatement preparedStatement, Connection connection) {
// 关闭/释放资源,从小到大,最后使用的,最先关闭/释放
/*
!=null,防止null引用,连接/使用资源的需要关闭资源,而==null 没有连接/使用的资源是不需要关闭的
*/
if(resultSet != null ) {
try {
resultSet.close(); // 关闭处理select 结果集的资源
} catch(SQLException e ) {
e.printStackTrace();
}
}
if(preparedStatement != null) {
try {
preparedStatement.close(); // 关闭连接操作数据库的对象的资源
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try{
connection.close(); // 关闭注册数据库的对象的资源
} catch(SQLException e) {
e.printStackTrace();
}
}
}
/**
* 1.注册驱动, 2. 连接数据库
*/
public Connection Login() {
Connection connection = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 静态加载类,执行com.mysql.jdbc.Driver包下的类中的静态代码块(注册驱动)
// 2. 连接数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true&characterEncoding=utf8","root","MySQL123");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
- 查询测试
package Blogs.blogs02;
import java.util.Scanner;
public class ObersSelectTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入你要查询的 Order_id 的值: ");
int orderId = scanner.nextInt();
ObersSelect obersSelect = new ObersSelect(); // 实例化对象,静态方法调用非静态方法
String sql = "select order_id as orderId,order_name as orderName, order_date as orderDate from obers where " +
"order_id = ?"; // 注意这里需要使用上别名,因为在Java当中的属性命名是小驼峰的方式没有下划线,
Obers obers = obersSelect.queryForObers(sql, orderId);
System.out.println(obers);
System.out.print("请输入你要查询的 Order_id 的值: ");
orderId = scanner.nextInt();
Obers obers1 = obersSelect.queryForObers(sql,orderId);
System.out.println(obers1);
}
}
3.7 PreparedStatement 实现处理查询select 结果集的通用的工具类的封装
上面 3.6
对两个数据表 user_table,obers 进行了一定的 select 的工具类的封装但是并不适用所有的数据表 select 查询处理。下面我们就实现一个所有数据表通用的 select 查询结果集的处理。
分别:
- 使用 PreparedStatement 实现针对于不同的数据表的一个条记录 的通用查询操作
- 使用 PreparedStatement 实现针对于不同的数据表的多个条记录的通用查询操作
3.7.1 使用 PreparedStatement 实现针对于不同的数据表的 一个条记录 的通用查询操作
这里我们使用集合的方法。具体代码实现如下:
package Blogs.blogs02;
import java.lang.reflect.Field;
import java.sql.*;
/**
* PreparedStatement 实现处理查询select 结果集的通用的工具类的封装
*/
public class JdbcPreparedStatement {
/**
* 使用 PreparedStatement 实现针对于不同的数据表的一个条记录 的通用查询操作
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public <T> T getInStance(Class<T> clazz,String sql,Object...args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null; // 扩大作用域,用于关闭资源
try {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 反射加载类,执行静态代码块
// 2. 连接驱动上的数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象(connection.prepareStatement()预编译对象)
preparedStatement = connection.prepareStatement(sql); // 只是预编译了sql语句,并没有执行
// 3.1 填充占位符
for(int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
// 占位符从起始下标是 1,可变参数的起始下标是0 旧数组
}
// 4. 执行sql语句
resultSet = preparedStatement.executeQuery(); // 是无参数的,因为上面我们已经预编译过了
// 5. 处理select 查询到的结果集
// 5.1 获取到当前select 查询的元数据对象
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
// 5.2 通过获取到的元数据对象,再获取到select 查询显示的一行的(总列数)
int columnCount = resultSetMetaData.getColumnCount();
if(resultSet.next()) { // 指向select查询显示的结果集的一行记录,该行有数据返回true并向下移动,没有数据返回false
T t = clazz.newInstance(); // 创建orm映射的Java类的对象用于存放,数据表读取到的数据,注意所放位置,不要在循环里
// 处理一行的列的数据
for(int i = 0; i < columnCount; i++) {
String columnLabel = resultSetMetaData.getColumnLabel(i+1); // 通过元数据对象获取到对应下标的列名(包含别名),起始下标是 1
Object columnValue = resultSet.getObject(i+1); // 通过下标访问获取到对应下标列的值,起始下标是 1
// Object cloumnValue = resultSet.getObject(columnLabel); // 或者通过列名获取
// 通过反射给 t 对象指定的 columnLabel(数据表中读取到的列名) 属性,赋值为 columnValue(对应列名的值)
Field field = clazz.getDeclaredField(columnLabel); // 数据表获取到的列名
field.setAccessible(true);
field.set(t,columnValue); // 数据表columnLabel列名的数据内容
}
return t;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} finally {
// 6.关闭资源,最晚使用的最先关闭
// !=null 连接/使用了资源的需要关闭,而==null的没有连接/使用资源的不需要关闭,
if( resultSet!= null) { // 防止 null引用
try{
resultSet.close(); // 关闭处理select 查询结果集的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
if(preparedStatement != null) { // 防止null引用
try{
preparedStatement.close();
}catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
if(connection != null) {
try{
connection.close();
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转化为运行异常抛出
}
}
}
return null;
}
}
测试
package Blogs.blogs02;
import java.util.Scanner;
/**
* JdbcPreparedStatement 的测试
*/
public class JdbcPreparedStatementTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
JdbcPreparedStatement jdbcPreparedStatement = new JdbcPreparedStatement(); // 实例化对象,静态方法调用非静态方法
// user_table表
System.out.print("请输入你要在user_table查询的user: ");
String user = scanner.next();
String sql = "select user,password,balance from user_table where user = ?"; // ?占位符不要加单引号,不然就是字符串了,失效了
User users = jdbcPreparedStatement.getInStance(User.class, sql, user);
// obers表
System.out.println(users);
System.out.print("请输入你要在obers表中查询的id: ");
int id = scanner.nextInt();
String sql2 = "select order_id as orderId,order_name as orderName,order_date as orderDate from obers where " +
"order_id = ?"; // 注意sql语句的执行顺序,别名的作用范围
Obers obers = jdbcPreparedStatement.getInStance(Obers.class, sql2, id);
System.out.println(obers);
}
}
3.7.2 使用 PreparedStatement 实现针对于不同的数据表的 多个条记录 的通用查询操作
对于需要存储多个记录的信息,我们在这里可以运行集合中的List
链表存储信息
具体代码如下:
public <T> List<T> getForList(Class<T> clazz,String sql,Object...args) { // 可变参数类型为 Object
Connection connection = null;
ResultSet resultSet = null;
PreparedStatement preparedStatement = null; // 扩大作用域的范围,用于关闭资源
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 反射加载类,执行其中类的静态代码
// 2. 连接数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbtest6?useUnicode=true" +
"&characterEncoding=utf8","root","MySQL123");
// 3. 获取操作数据库的对象( connection.prepareStatement()预编译对象)
preparedStatement = connection.prepareStatement(sql); // 只是预编译sql语句,并没有执行sql语句
// 3.1 填充占位符
if(args != null) {
for(int i = 0 ; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]); // 占位符的起始下标是 1,可变参数的起始下标是 0 (旧数组)
}
}
// 4. 执行sql语句
resultSet = preparedStatement.executeQuery(); // 注意是无参的方法,因为上面已经预编译过了
// 5. 处理select 查询显示的结果集
// 5.1 获取到select 查询显示的结果集的元数据
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
// 5.2 通过元数据的对象,获取到select 查询显示的一行的所有列数的个数
int columnCount = resultSetMetaData.getColumnCount();
// 5.3 创建 集合对象链表,存放多条查询到的数据
ArrayList<T> list = new ArrayList<T>();
while(resultSet.next()) {
T t = clazz.newInstance(); // orm映射的对象的Java类,存放数据
// 处理select 一行的数据中的每一个列的数据
for(int i = 0; i < columnCount; i++) {
// 1. 通过元数据对象获取到列名,列名字符串
String columnLabel = resultSetMetaData.getColumnLabel(i+1); // 起始下标是 1,该方法可以获取到select 查询使用的别名
// 2. 获取到对应列名下的值
Object columnValue = resultSet.getObject(i+1); // 这里是通过列名,获取对应列名下的数据内容
// 或者 Object columnValue = resultSet.getObject(i + 1); // 通过访问列的下标,获取到对应列下的内容,起始下标是1
// 通过给 t 对象指定的columnLabel 属性名,赋值为columnValue ,通过反射
Field field = clazz.getDeclaredField(columnLabel); // columnLabel 从数据表中获取到的列名
field.setAccessible(true);
field.set(t,columnValue); // columnValue 从数据表中获取到的数据内容,并赋值该对应Java对象中的属性
}
list.add(t); // 将存储到的对象的数据,存入到集合List链表当中
}
return list; // 返回集合中的list链表的地址e
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源,最晚的使用的最先释放
// != null 连接/使用了资源需要关闭,==null没有连接/使用的资源不需要关闭
if(resultSet!=null) { // 防止null引用
try {
resultSet.close(); // 关闭处理select 查询结果集的资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转为为运行异常抛出
}
}
if(preparedStatement != null) { // 防止null引用
try{
preparedStatement.close(); // 关闭操作数据库预编译的资源
}catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转为为运行异常抛出
}
}
if(connection != null) { // 防止null引用
try {
connection.close(); // 关闭连接的数据库资源
} catch(SQLException e) {
throw new RuntimeException(e); // 将编译异常转为运行异常抛出
}
}
}
return null;
}
}
测试
public class JdbcPreparedStatementTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
JdbcPreparedStatement jdbcPreparedStatement = new JdbcPreparedStatement(); // 实例化对象,静态方法调用非静态方法
// user_table表
System.out.print("请输入你要在user_table查询的大于balance的值: ");
int balance = scanner.nextInt();
String sql = "select user,password,balance from user_table where balance >= ?"; // ?占位符不要加单引号,不然就是字符串了,失效了
List<User> userList = jdbcPreparedStatement.getForList(User.class,sql,balance);
userList.forEach(System.out::println);
System.out.println("***********************************");
// // obers表
System.out.print("请输入你要在obers表中查询的id的值: ");
int id = scanner.nextInt();
String sql2 =
"select order_id as orderId,order_name as orderName,order_date as orderDate from obers where order_id" +
" < ?"; //
// 注意sql语句的执行顺序,别名的作用范围
List<Obers> obersList = jdbcPreparedStatement.getForList(Obers.class,sql2,id);
obersList.forEach(System.out::println);
}
}
4. 总结: PreparedStatement vs Statement 不同
- Statement 存在 SQL 注入的问题,**PreparedStatement ** (通过预编译的机制)解决了这一问题。
- Statement 是编译一次,执行一次,**PreparedStatement ** 是编译一次,执行多次,存在着一个预编译的机制
- PreparedStatement 会在编译阶段做类型的安全检查,匹配你所填充的占位符的信息内容是否匹配
- PreparedStatement 执行效率更高效,存在预编译一次,可以执行多次,特别是对于批量处理操作
- PreparedStatement 可以操作
BloB
(图片) 的数据,而 Statement 做不到 - 综上所述:PreparedStatement 使用的比较多,只有极少数的情况下需要使用Statement
- 什么情况下,必须使用 Statement呢 ???
业务方面要求必须支持 SQL注入 的时候,比如对数据进行 升序,降序 操作,Statement支持 SQL注入,凡是业务方面要求进行SQL语句的拼接执行的,使用 Statement。
都可以采用两种思想:
- 面向接口编程的思想
- ORM思想
- 一个数据表对应Java中的一个类
- 表中的一条(行)记录对应Java类中的一个对象
- 表中的一个字段(列)对应Java类中的一个属性
- 注意: sql是需要结合列名和Java类中的属性名相匹配的,所以起用上 select 查询显示的别名
- 两种技术
- JDBC结果集的元数据:ResultSetMetaData
- 获取列数:getColumnCount()
- 获取列的别名:getColumnLabel()
- 通过反射,创建指定类的对象,获取指定的属性并赋值
- JDBC结果集的元数据:ResultSetMetaData
5. 最后:
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后悔有期,江湖再见 !!!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/83012.html