【JDBC】使用PreparedStatement操作数据库

1.对数据库调用的不同方式

java.sql包下有3个接口
image

  • Statement:用于执行静态SQL语句。
  • PreparedStatement:SQL语句被预编译并存储在此对象中。
  • CallableStatement:用于执行SQL存储过程。

image

2.使用Statement操作数据库

获取到connect对象后,创建statement对象,执行sql

Statememt st = connect.createStatememt();
ResultSet rs = st.executeQuery("select * from table");

(1)使用Statememt操作表的弊端

  • 对于有参数的SQL语句需要拼串。
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName
+ "' AND PASSWORD = '" + password
+ "'";
  • 存在SQL注入问题

SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,从而利用系统的 SQL 引擎完成恶意行为的做法。

public class StatementTest {
@Test
public void login(){
Scanner scanner = new Scanner(System.in);
System.out.println("用户名:");
String userName = scanner.nextLine();
System.out.println("密 码:");
String password = scanner.nextLine();
String sql = "SELECT user,password FROM user_table WHERE user = '"+userName+"' AND password = '"+password+"'";
User user = getUser(sql, User.class);
if (user!=null){
System.out.println("登录成功!");
}else {
System.out.println("用户名或密码错误!");
}
}
public <T> T getUser(String sql,Class<T> clazz){
//放到外面是因为在finally中要用
T t = null;
Connection connect = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载配置文件
InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
//2.读取配置文件
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//3.加载驱动
Class.forName(driverClass);
//4.获取连接
connect = DriverManager.getConnection(url, user, password);
//5.创建statement对象
st = connect.createStatement();
//6.执行sql,得到结果集
rs = st.executeQuery(sql);
//7.获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
//有多少列
int columnCount = rsmd.getColumnCount();
if (rs.next()){
t =clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//1.获取列别名
String columnName = rsmd.getColumnLabel(i+1);
//2.根据列名获取数据
Object columnVal = rs.getObject(columnName);
//3.将得到的数据封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t,columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
}

3.ResultSet和ResultSetMetaData

(1)ResultSet

执行executeQuery()方法返回结果集,是一个ResultSet对象,相当于一张数据表,有一个指针指向第一行记录。使用next()方法检测下一行是否有效,若有效返回true并移动到下一行。相当于Iterator对象的hasNext()和next()方法的结合体。
当指向一行时,通过索引或列名获取该列的值。getInt(1),getString("name")

Java与数据库交互涉及到的相关Java Api中的索引都从1开始。

image

(2)ResultSetMetaData

用于获取ResultSet对象中列的类型和属性信息的对象

ResultSetMetaData rsmd = resultSet.getMetaData();

常用方法

getColumnName(int column); //获取指定列名称
getColumnLabel(int column); //获取指定列别名,没有别名则获取列名
getColumnCount(); //有多少列
getColumnTypeName(int column); //列的数据库类型名称
getColumnDisplaySize(int column); //列的最大标准宽度,以字符为单位
isNullable(int column); //列中的值是否可以为null
isAutoIncrement(int column); //是否为指定列进行自动编号

例如表结构
image

SQL语句为,如果balance不选,则columnCount为2

SELECT user,password,balance FROM user_table WHERE user = 'AA' AND password = '123456'

image

两者之间的联系
image

4.PrepareStatement操作数据库

image

PrepareStatement是Statement的子接口。

public interface PreparedStatement extends Statement

获取对象,表示一条预编译过的SQL语句

PrepareStatement ps = connect.prepareStatement(sql);

SQL语句中的参数用英文问号表示,调用prepareStatement对象的setXxx(1,value)方法设置这些参数,1表示索引(从1开始),value表示值。

@Test
public void login(){
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String user = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
// String sql = "SELECT user,password FROM user_table WHERE user = ? AND password = ? ";
String sql = "SELECT user,password FROM user_table WHERE user = ? and password = ?;";
User returnUser = getUser(User.class, sql, user, password);
if (returnUser!=null){
System.out.println("登录成功");
}else {
System.out.println("用户名或密码错误");
}
}
public <T> T getUser(Class<T> clazz,String sql,Object... args){
Connection connect = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connect = JDBCUtil.getConnection();
ps = connect.prepareStatement(sql);
//遍历参数并设置值
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//ps.execute();//增删改执行这个
rs = ps.executeQuery(); //产生单个结果集
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()){
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = rs.getObject(i + 1); //列值
String columnLabel = rsmd.getColumnLabel(i + 1); //列别名
//通过反射获取对象属性
Field field = clazz.getDeclaredField(columnLabel);
//允许客户端拥有超级权限,不会去检查Java语言权限控制(private之类的)
field.setAccessible(true);
field.set(t,columnValue); //给对象t的属性设值
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtil.closeResource(connect,ps,rs);
}
return null;
}

5.其他

(1)ORM思想

  • 一张数据库表对应一个Java类
  • 表中的一条记录对应Java类的一个对象
  • 表中的一个字段对应Java类的一个属性

写sql时注意别名

(2)Java与SQL对应数据类型

Java类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY,VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP

(3)PrepareStatement的好处

① 可以防止SQL注入
② 预编译SQL语句可以重复使用,不同参数也不用重新编译(Statement每次执行都需要)
③ 不需要拼串

参考
1.field.setAccessible(true) 简介
2.PreparedStatement是如何防止SQL注入的?

posted @   植树chen  阅读(132)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示