【JDBC】使用PreparedStatement操作数据库
1.对数据库调用的不同方式
java.sql包下有3个接口
- Statement:用于执行静态SQL语句。
- PreparedStatement:SQL语句被预编译并存储在此对象中。
- CallableStatement:用于执行SQL存储过程。
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开始。
(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); //是否为指定列进行自动编号
例如表结构
SQL语句为,如果balance不选,则columnCount为2
SELECT user,password,balance FROM user_table WHERE user = 'AA' AND password = '123456'
两者之间的联系
4.PrepareStatement操作数据库
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注入的?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!