【MySql】数据库操作对象详解
数据库的操作对象有三种,PreparedStatement可以有效防止Sql注入问题,常用
Statement 操作数据库的CRUD(增删改查) PreparedStatement 操作数据库的CRUD(增删改查) CallableStatement 操作数据库的存储过程
3.1、Statement
弊端:我们如果编写带条件的sql语句需要拼凑,并且存在sql注入的问题
下面是控制台模拟的一个登陆系统,在其中sql语句的拼凑很容易出错
@Test public void test() throws Exception { /** * 模拟登陆功能:登陆就是去验证后台的用户名和密码 * 分析: * 创建用户时输入的用户名和密码:插入语句到数据库保存起来。(insert) * 登陆:查询登陆时输入的用户名和密码是否与创建时的一样 (select) */ // 模拟前端发送到后台的内容 Scanner sc = new Scanner(System.in); System.out.println("请输入您的用户名:"); String userName = sc.nextLine(); System.out.println("请输入您的密码:"); String pwd = sc.nextLine(); // 去验证用户,是否通过 // 连接数据库 InputStream config = JdbcTest.class.getClassLoader().getResourceAsStream("resources/config.properties"); Properties prop = new Properties(); prop.load(config); String driver = prop.getProperty("driver"); String url = prop.getProperty("url"); String user = prop.getProperty("user"); String password = prop.getProperty("password"); // 加载驱动 Class.forName(driver); // 获得数据库连接对象 Connection connection = DriverManager.getConnection(url,user,password); // 获得数据库操作对象 Statement statement = connection.createStatement(); // sql查询语句,查询数据库中是否有此用户 String sql = "select * from login where userName= '"+userName+"'and password='"+pwd+"'"; // 提交sql语句,得到返回的结果 ResultSet res = statement.executeQuery(sql); /* 判断结果 1.直接判断res.next() 2.把结果封装成java对象,判断对象是否为null */ // next判断当前行有没有数据,所以下面就是当前行如果查到数据,则登陆成功 if(res.next()) { System.out.println("登陆成功!"); } else { System.out.println("用户名或密码错误"); } }
SQL注入问题:sql语句的随意添加
在上面的登陆系统中,如果用户输入了密码为
' or 1 = '1
,则会形成恒成立// 原本的sql查询语句是这样,如果加入了`' or 1 = '1`,就变成了恒成立 "select * from login where userName= '"+userName+"'and password='"+pwd+"'"; // 加入`' or 1 = '1`,这样就对我们的sql语句进行变化,所以无论用户名是什么可以获取所有的信息 "select * from login where userName= '"+userName+"'and password='' or 1='1'";
3.2、PreparedStatement
PreparedStatement执行sql语句和Statement执行方法一样,不同的是,Statement是在执行的时候需要提交sql语句就可以,但PreparedStatement在创建时就需要sql语句的格式进行预编译,从而保证了sql注入问题不会出现,也解决了Statement的拼接sql语句的问题
在预编期间,sql语句中的值可以使用
?
代替,来把位置占着String sql = "insert into student(s_id,s_name) values(?,?)"; // ?:占位符 PreparedStatement prepareStatement = connection.prepareStatement(sql);
在执行之前,需要把内容填充进占位符占了的位置,为此PreparedStatement提供了set系列方法
// 第一个参数表示当前设置的第几个占位符,第二个参数表示当前占位符的值 prepareStatement.setInt(1, 555); prepareStatement.setString(2, "小明");
执行的时候直接使用提交就可以
// 此时的提交方式中不用带任何参数,返回类型和Statement一样 int res = prepareStatement.executeUpdate();
为什么PreparedStatement操作对象可以防止sql注入问题
因为我们在创建PreparedStatement之前,sql语句已经预编过了,相当于刚开始编译前只有and的话,后面不管输入什么都是以字符串形式了,不会有其他的逻辑连接。
PreparedStatement还可以操作二进制数据,而Statement不能
注意:虽然PreparedStatement可以存储二进制文件,但是一般不会把文件存储到数据库,如果要传输大文件,需要修改mysql配置,允许最大保存为16M
max_allowed_packet=16M
插入二进制文件:
// 预编译sql String sql = "insert into student(s_name,s_image) values(?,?)"; // 创建PreparedStatement对象 PreparedStatement prepareStatement = connection.prepareStatement(sql); // 添加数据,解释?占位符 prepareStatement.setString(1, "宁荣荣"); // 通过setBlob的方法可以把文件二进制的存放到数据库 prepareStatement.setBlob (2,new FileInputStream("src/resources/1.jpg")); // 提交操作 int res = prepareStatement.executeUpdate();
读取二进制文件:
// 预编译sql String sql = "select s_image from student where s_name=?"; // 创建PreparedStatement对象 PreparedStatement prepareStatement = connection.prepareStatement(sql); // 添加数据,解释?占位符 prepareStatement.setString(1, "宁荣荣"); // 提交操作,返回结果集合 ResultSet res = prepareStatement.executeQuery(); // 判断结果集 while(res.next()) { // 获取图片的二进制数据 Blob blob = res.getBlob("s_image"); // 将二进制数据读取到内存 InputStream binaryStream = blob.getBinaryStream(); // 将内存中的二进制文件通过io写出到磁盘 FileOutputStream fos = new FileOutputStream("src/resources/2.jpg"); // io流写文件 byte[] buffer = new byte[1024]; int len; while((len=binaryStream.read(buffer))!=-1) { fos.write(buffer,0,len); } // 关闭流 fos.close(); binaryStream.close(); }
sql提交详解
Sql的提交基本上有三种方式,其中execute不常用
方式 返回值 详解 executeQuery 查询结果集合 做查询语句时用此方式提交,可以返回查询到的结果,遍历集合则可以获得数据 executeUpdate 操作受影响的行数 做增删改操作时用此方式提交,返回操作受影响的行数,判断返回值大于1,说明操作成功,不然则是操作失败 execute boolean值操作的方式 所有的sql都可以用此方式提交,返回true,说明做了查询操作,返回false说明做了增删改操作