目录
完善工具类,实现所有增删改查
昨天实际上只在工具类中完成了查询多行多列的封装方法,那么会发现还有查询一列、查询一行甚至查询单个的变态查询方法,另外还有增删改等方法需要我们去完成。所以今天我们先来完成对这些方法的封装
public class JdbcUtils {
// 查询多行多列
// 前面单独的<T>表示声明一个泛型T,后面List<T>就是使用泛型T了
public static <T> List<T> list(String sql, Class<T> c){
List<T> tList = new ArrayList<>();
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
Statement stat = conn.createStatement();
// 5、执行sql查询语句,返回结果集
ResultSet result = stat.executeQuery(sql);
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
// 6、处理查询结果集
while ( result.next()) {
// 将每一行数据封装成一个对象,这里通过反射机制创建泛型对象
T t = c.newInstance();
// 取出某一行的每一列数据,封装到对象t的属性中
for (int i = 1; i <= columnCount; i++) {
// 通过类的序号,获取每一列的值
Object value = result.getObject(i);
// 对数据进行非空验证
if (value != null){
// 通过列的序号,获取每一类的列名
String columnName = md.getColumnName(i);
// 因为表中的类名和实体类t中的属性名相同,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
// 赋予私有属性赋值权限
f.setAccessible(true);
// 使用反射,把value值给到对象属性t中
f.set(t,value);
}
}
// 将这个泛型对象,添加到泛型集合中去
tList.add(t);
}
// 7、关闭资源(先开的资源后关闭)
result.close();
stat.close();
conn.close();
}catch (Exception e){
e.printStackTrace();
}
return tList;
}
// 查询一行
public static <T> T SelectRow(String sql, Class<T> c){
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
Statement stat = conn.createStatement();
// 5、执行sql查询语句,返回结果集
ResultSet result = stat.executeQuery(sql);
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
T t = null;
// 6、处理查询结果集
if ( result.next()) {
// 将每一行数据封装成一个对象,这里通过反射机制创建泛型对象
t = c.newInstance();
// 取出某一行的每一列数据,封装到对象t的属性中
for (int i = 1; i <= columnCount; i++) {
// 通过类的序号,获取每一列的值
Object value = result.getObject(i);
// 对数据进行非空验证
if (value != null){
// 通过列的序号,获取每一类的列名
String columnName = md.getColumnName(i);
// 因为表中的类名和实体类t中的属性名相同,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
// 赋予私有属性赋值权限
f.setAccessible(true);
// 使用反射,把value值给到对象属性t中
f.set(t,value);
}
}
}
// 7、关闭资源(先开的资源后关闭)
result.close();
stat.close();
conn.close();
return t;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// 查询一列
public static <T> List<T> SelectCol(String sql, Class<T> c){
List<T> tList = new ArrayList<>();
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
Statement stat = conn.createStatement();
// 5、执行sql查询语句,返回结果集
ResultSet result = stat.executeQuery(sql);
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
// 6、处理查询结果集
while ( result.next()) {
// 通过类的序号,获取每一列的值
T t = (T) result.getObject(1);
// 将这个泛型对象,添加到泛型集合中去
tList.add(t);
}
// 7、关闭资源(先开的资源后关闭)
result.close();
stat.close();
conn.close();
return tList;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// 查询单个元素
public static <T> T SelectOne(String sql, Class<T> c){
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
Statement stat = conn.createStatement();
// 5、执行sql查询语句,返回结果集
ResultSet result = stat.executeQuery(sql);
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
T t = null;
// 6、处理查询结果集
if ( result.next()) {
t = (T) result.getObject(1);
}
// 7、关闭资源(先开的资源后关闭)
result.close();
stat.close();
conn.close();
return t;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// 增删改方法
public static int update(String sql){
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
Statement stat = conn.createStatement();
// 5、执行sql查询语句,返回结果集
int i = stat.executeUpdate(sql);
// 7、关闭资源(先开的资源后关闭)
stat.close();
conn.close();
return i;
}catch (Exception e){
e.printStackTrace();
}
return -1;
}
}
Statement方法漏洞,sql注入问题
首先我们先创建一个用来测试的数据库t_user,并添加两条数据。
为了方便测试,我们创建一个User的封装类,来接收t_user的对象
public class User {
private int id;
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
接着我们来调用工具类中的查询方法,查询所有信息,测试statemet方法的安全漏洞
@Test
public void TestUser(){
String username = "aa";
String password = "aa";
User user = SelectRow("select * from t_user where username = '" + username +
"' and password= '" + password + "'", User.class);
System.out.println(user!=null?"数据查询成功~":"数据查询失败~");
}
因为数据库中有该用户名和密码,所以数据查询成功
@Test
public void TestUser(){
String username = "cc";
String password = "cc";
User user = SelectRow("select * from t_user where username = '" + username +
"' and password= '" + password + "'", User.class);
System.out.println(user!=null?"数据查询成功~":"数据查询失败~");
}
因为数据库中没有该用户名和密码,所以数据查询失败
但是如果我将用户名和密码修改一下,就可以完美规避掉验证用户名和密码,直接获取信息
String username = "cc' or '1' = '1";
String password = "cc' or '1' = '1";如果我将这里的用户名和密码修改为这种样式,那么在数据库中就会显示成
SELECT * FROM
t_user
WHERE username = 'cc' OR '1' = '1' AND PASSWORD= 'cc' OR '1' = '1'"
因为cc肯定是没有的就变成了1=1,为true。所以完美规避掉用户验证,产生sql注入问题
PreparedStatement解决sql注入问题
我们已经发现Statement方法无法解决sql注入问题。所以我们选择使用PreparedStatement方法来解决问题。
prepareStatement类
- 可以提前传入sql语句并对sql语句进行验证
- 执行时不需要传入sql语句
- 解决sql注入的逻辑漏洞
- 提高执行效率
Object ... params可变参数数组
在调用函数时,可以传入任意个任意类型的参数
修改源代码:
第一步:我们将这两句代码中的Statement修改掉:
// 4、获取处理sql语句的对象
Statement stat = conn.createStatement();
// 5、执行sql查询语句,返回结果集
ResultSet result = stat.executeQuery(sql);’第二步:我们需要给参数添加一个可以数组
public static <T> T SelectRow(String sql, Class<T> c, Object ... params)
第三步:将sql语句中需要填入参数的地方使用 "?" 占位符占位,在最后面添加参数,参数回依次向前替换掉展位符中的内容
User user = SelectRow("select * from t_user where username = ? " + "and password= ?", User.class,username,password);
最后看看修改后的完整代码
@Test
public void TestUser(){
String username = "cc' or '1' = '1";
String password = "cc' or '1' = '1";
User user = SelectRow("select * from t_user where username = ? " +
"and password= ?", User.class,username,password);
System.out.println(user!=null?"数据查询成功~":"数据查询失败~");
}
public static <T> T SelectRow(String sql, Class<T> c, Object ... params){
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
// Statement stat = conn.createStatement();
PreparedStatement prepState = conn.prepareStatement(sql);
// 在执行前给sql传递参数
for (int i = 0; i < params.length; i++) {
prepState.setObject(i+1,params[i]);
}
// 5、执行sql查询语句,返回结果集
ResultSet result = prepState.executeQuery();
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
T t = null;
// 6、处理查询结果集
if ( result.next()) {
// 将每一行数据封装成一个对象,这里通过反射机制创建泛型对象
t = c.newInstance();
// 取出某一行的每一列数据,封装到对象t的属性中
for (int i = 1; i <= columnCount; i++) {
// 通过类的序号,获取每一列的值
Object value = result.getObject(i);
// 对数据进行非空验证
if (value != null){
// 通过列的序号,获取每一类的列名
String columnName = md.getColumnName(i);
// 因为表中类名和实体类t中的属性名相同,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
// 赋予私有属性赋值权限
f.setAccessible(true);
// 使用反射,把value值给到对象属性t中
f.set(t,value);
}
}
}
// 7、关闭资源(先开的资源后关闭)
result.close();
prepState.close();
conn.close();
return t;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
使用PreparedStatement再次完善工具类
public class JdbcUtilPlus {
// 查询多行多列
// 前面单独的<T>表示声明一个泛型T,后面List<T>就是使用泛型T了
public static <T> List<T> list(String sql, Class<T> c,Object ... params){
List<T> tList = new ArrayList<>();
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
// Statement stat = conn.createStatement();
PreparedStatement predState = conn.prepareStatement(sql);
// 在执行前,给sql传递参数
for (int i = 0; i < params.length; i++) {
predState.setObject(i+1,params[i]);
}
// 5、执行sql查询语句,返回结果集
ResultSet result = predState.executeQuery(sql);
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
// 6、处理查询结果集
while ( result.next()) {
// 将每一行数据封装成一个对象,这里通过反射机制创建泛型对象
T t = c.newInstance();
// 取出某一行的每一列数据,封装到对象t的属性中
for (int i = 1; i <= columnCount; i++) {
// 通过类的序号,获取每一列的值
Object value = result.getObject(i);
// 对数据进行非空验证
if (value != null){
// 通过列的序号,获取每一类的列名
String columnName = md.getColumnName(i);
// 因为表中类名和实体类t中的属性名相同,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
// 赋予私有属性赋值权限
f.setAccessible(true);
// 使用反射,把value值给到对象属性t中
f.set(t,value);
}
}
// 将这个泛型对象,添加到泛型集合中去
tList.add(t);
}
// 7、关闭资源(先开的资源后关闭)
result.close();
predState.close();
conn.close();
}catch (Exception e){
e.printStackTrace();
}
return tList;
}
/*
这里我们选择使用statement类的子类prepareStatement
prepareStatement类
1、可以提前传入sql语句并对sql语句进行验证
2、执行时不需要传入sql语句
3、解决sql注入的逻辑漏洞
4、提高执行效率
Object ... params 可变形参数组
在调用函数时,可以传入任意个任意类型的参数
*/
// 查询一行
public static <T> T SelectRow(String sql, Class<T> c, Object ... params){
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
// Statement stat = conn.createStatement();
PreparedStatement prepStat = conn.prepareStatement(sql);
// 在执行前,给sql传递参数
// 这里通过set方法来设置值,将传递的参数数组按照顺序传递
for (int i = 0; i < params.length; i++) {
prepStat.setObject(i+1,params[i]);
}
// 5、执行sql查询语句,返回结果集
ResultSet result = prepStat.executeQuery();
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
T t = null;
// 6、处理查询结果集
if ( result.next()) {
// 将每一行数据封装成一个对象,这里通过反射机制创建泛型对象
t = c.newInstance();
// 取出某一行的每一列数据,封装到对象t的属性中
for (int i = 1; i <= columnCount; i++) {
// 通过类的序号,获取每一列的值
Object value = result.getObject(i);
// 对数据进行非空验证
if (value != null){
// 通过列的序号,获取每一类的列名
String columnName = md.getColumnName(i);
// 因为表中的类名和实体类t中的属性名相同,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
// 赋予私有属性赋值权限
f.setAccessible(true);
// 使用反射,把value值给到对象属性t中
f.set(t,value);
}
}
}
// 7、关闭资源(先开的资源后关闭)
result.close();
prepStat.close();
conn.close();
return t;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// 查询一列
public static <T> List<T> SelectCol(String sql, Class<T> c,Object ... params){
List<T> tList = new ArrayList<>();
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
// Statement stat = conn.createStatement();
PreparedStatement prepStat = conn.prepareStatement(sql);
// 在执行之前给sql添加参数
for (int i = 0; i < params.length; i++) {
prepStat.setObject(i+1,params[i]);
}
// 5、执行sql查询语句,返回结果集
ResultSet result = prepStat.executeQuery();
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
// 6、处理查询结果集
while ( result.next()) {
// 通过类的序号,获取每一列的值
T t = (T) result.getObject(1);
// 将这个泛型对象,添加到泛型集合中去
tList.add(t);
}
// 7、关闭资源(先开的资源后关闭)
result.close();
prepStat.close();
conn.close();
return tList;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// 查询单个元素
public static <T> T SelectOne(String sql, Class<T> c,Object ... params){
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
// Statement stat = conn.createStatement();
// 使用PreparedStatement后,在创建对象时传入sql参数,后面执行时不传入参数
PreparedStatement prepStat = conn.prepareStatement(sql);
// 在执行前给sql语句添加参数
for (int i = 0; i < params.length; i++) {
prepStat.setObject(i+1,params[i]);
}
// 5、执行sql查询语句,返回结果集
ResultSet result = prepStat.executeQuery();
// 通过结果集,得到结果集元数据
ResultSetMetaData md = result.getMetaData();
// 获取结果集总列数
int columnCount = md.getColumnCount();
T t = null;
// 6、处理查询结果集
if ( result.next()) {
t = (T) result.getObject(1);
}
// 7、关闭资源(先开的资源后关闭)
result.close();
prepStat.close();
conn.close();
return t;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
// 增删改方法
public static int update(String sql,Object ... params){
try{
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
String url = "jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf8";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3、定义sql语句
// 4、获取处理sql语句的对象
// Statement stat = conn.createStatement();
PreparedStatement prepStat = conn.prepareStatement(sql);
// 在执行sql之前,给sql传递参数
for (int i = 0; i < params.length; i++) {
prepStat.setObject(i+1,params[i]);
}
// 5、执行sql查询语句,返回结果集
int i = prepStat.executeUpdate();
// 7、关闭资源(先开的资源后关闭)
prepStat.close();
conn.close();
return i;
}catch (Exception e){
e.printStackTrace();
}
return -1;
}
}