- preparedStatement是一种带有占位符(?)的sql语句,它可以将预编译的sql语句存储起来,然后可以使用这个预编译好的sql语句多次高效地执行传入具体参数的sql语句。
- 例如执行 insert into table xxx values(1); insert into table xxx values(2); ... 这样结构相同,只是具体的数据不通过的语句,就可以使用PreparedStatement对象将一个insert into table xxx valuse(?);预编译好,然后再在具体执行的时候调用PreparedMent对象的setInt(1,x);方法传入具体的值。 这里的1是指第一个?。
- 具体的讲解穿插在代码中。
import java.io.FileInputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.Statement; import java.util.Properties; public class PreparedStatementTest { private String driver; private String url; private String user; private String pass; // 通过读取属性问价来获得属性 public void iniParam(String paramFile) throws Exception{ Properties properties = new Properties(); // 以输入流的方式打开属性文件 properties.load(new FileInputStream(paramFile)); // 依次读取属性文件里的各种属性 driver = properties.getProperty("driver"); url= properties.getProperty("url"); user = properties.getProperty("user"); pass = properties.getProperty("pass"); Class.forName(driver); } // 这是通过常规的方式将一百条记录添加到表中。 public void insertUserStatement() throws Exception{ long start = System.currentTimeMillis(); try( // 先获得数据库链接Connection对象,并且通过这个数据库链接创建Statement对象,在由statement对象来执行sql语句 Connection connection = DriverManager.getConnection(url,user,pass); Statement statement = connection.createStatement() ){ for(int i=0; i<100; i++){ // 执行一百次添加数据。 statement.executeUpdate("insert into insert100 values(i)"); } System.out.println("使用Statement费时:" + (System.currentTimeMillis()-start)); } } // 通过PreparedStatement来添加一百条记录 public void inserUserPrepare() throws Exception{ long start = System.currentTimeMillis(); try( // PreparedStatement同样是通过Connection对象来或获得的。 // 具体的构造方法是将要执行的sql语句传进其构造方法里。但是要将具体的数据用?代替 // 然后再在具体执行sql语句的时候将?代替成具体的值 Connection connection = DriverManager.getConnection(url,user,pass); PreparedStatement preparedStatement = connection.prepareStatement("insert into insert100 values(?)")) { for(int i=0; i<100; i++){ preparedStatement.setInt(1,i); preparedStatement.execute(); } System.out.println("使用PreparedStatement费时" + (System.currentTimeMillis() - start)); } } public static void main(String args[]) throws Exception{ PreparedStatementTest preparedStatementTest = new PreparedStatementTest(); preparedStatementTest.iniParam("mysql.ini"); preparedStatementTest.insertUserStatement();a preparedStatementTest.inserUserPrepare(); } } //运行上面程序,看到以下输出: // 使用Statement费时:5000 // 使用PreparedStatement费时4094
-
使用PreparedStatement不仅可以提高效率,而且可以防止sql注入。我理解的sql注入是使用一般的sql语句处理过程中如果用户输入的内容不是合法信息,但是可以和系统预先设定的sql语句部分合成一条语法和逻辑都没有问题的sql语句。并让这条语句执行某种非法操作。
-
如下面这个例子,在不使用PreparedStatement的情况下,如果用户名输入‘or ture or'这样的内容的话,会显示登陆成功。
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.io.FileInputStream; import java.io.FileReader; import java.sql.*; import java.util.Properties; public class LoginFrame { private final String PROP_FILE = "mysql.ini"; private String driver; private String url; private String user; private String pass; private JFrame jf = new JFrame("登陆"); private JTextField userField = new JTextField(20); private JTextField passField = new JTextField(20); private JButton loginButton = new JButton("登录"); public void init() throws Exception{ Properties connProp = new Properties(); connProp.load(new FileInputStream(PROP_FILE)); driver = connProp.getProperty("driver"); url = connProp.getProperty("url"); user = connProp.getProperty("user"); pass = connProp.getProperty("pass"); Class.forName(driver); loginButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if(validate(userField.getText(), passField.getText())){ JOptionPane.showMessageDialog(jf, "登陆成功"); }else{ JOptionPane.showMessageDialog(jf, "登陆失败"); } } }); jf.add(userField, BorderLayout.NORTH); jf.add(passField); jf.add(loginButton, BorderLayout.SOUTH); jf.pack(); jf.setVisible(true); } private boolean validate(String userName, String userPass){ String sql = "select * from students where Sname = '" + userName + "' and Sno= '" + userPass + "';"; System.out.println(sql); try( Connection connection = DriverManager.getConnection(url, user, pass); Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql)) { if(rs.next()){ return true; } }catch (Exception e){ e.printStackTrace(); } return false; } private boolean valicate1(String userName, String userPass){ try( Connection conn = DriverManager.getConnection(url, user, pass); PreparedStatement pstmt = conn.prepareStatement( "select * from students where Sname = ? and Sno = ?")){ pstmt.setString(1, userName); pstmt.setString(2,userPass); try( ResultSet rs = pstmt.executeQuery()) { if(rs.next()){ return true; } } }catch (Exception e){ e.printStackTrace(); } return false; } public static void main(String args[]) throws Exception{ new LoginFrame().init(); } }
这是因为当输入 'or ture or'之后,“拼接而成”的sql语句为
select * from students where Sname = '‘or true or’' and Sno= '';
这样的语句是返回true的。
-
当使用了PaparedStatememt之后,用户输入的内容只能是代替了‘’里的内容 ,所以也就不存在sql注入了。
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.io.FileInputStream; import java.io.FileReader; import java.sql.*; import java.util.Properties; public class LoginFrame { private final String PROP_FILE = "mysql.ini"; private String driver; private String url; private String user; private String pass; private JFrame jf = new JFrame("登陆"); private JTextField userField = new JTextField(20); private JTextField passField = new JTextField(20); private JButton loginButton = new JButton("登录"); public void init() throws Exception{ Properties connProp = new Properties(); connProp.load(new FileInputStream(PROP_FILE)); driver = connProp.getProperty("driver"); url = connProp.getProperty("url"); user = connProp.getProperty("user"); pass = connProp.getProperty("pass"); Class.forName(driver); loginButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if(valicate1(userField.getText(), passField.getText())){ JOptionPane.showMessageDialog(jf, "登陆成功"); }else{ JOptionPane.showMessageDialog(jf, "登陆失败"); } } }); jf.add(userField, BorderLayout.NORTH); jf.add(passField); jf.add(loginButton, BorderLayout.SOUTH); jf.pack(); jf.setVisible(true); } private boolean validate(String userName, String userPass){ String sql = "select * from students where Sname = '" + userName + "' and Sno= '" + userPass + "';"; System.out.println(sql); try( Connection connection = DriverManager.getConnection(url, user, pass); Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql)) { if(rs.next()){ return true; } }catch (Exception e){ e.printStackTrace(); } return false; } private boolean valicate1(String userName, String userPass){ try( Connection conn = DriverManager.getConnection(url, user, pass); PreparedStatement pstmt = conn.prepareStatement( "select * from students where Sname = ? and Sno = ?")){ pstmt.setString(1, userName); pstmt.setString(2,userPass); try( ResultSet rs = pstmt.executeQuery()) { if(rs.next()){ return true; } } }catch (Exception e){ e.printStackTrace(); } return false; } public static void main(String args[]) throws Exception{ new LoginFrame().init(); } }
-
总的看来PreparedStetement比一般的sql语句处理有两个有点
-
它是采用预编译的原理,性能更好。
-
可以防止sql注入,安全性更高。
-
这是我看李刚编著的《疯狂Java讲义》后总结出来的。