day44
登录功能
代码实现
DBUtil.java
package com.saas.util; import java.sql.*; public class DBUtil { private static final String DB_DRIVER = "com.mysql.jdbc.Driver"; private static final String DB_URL = "jdbc:mysql://localhost:3306/saas"; private static final String DB_USER = "root"; private static final String DB_PASS = "Abc@1234"; private static Connection conn = null; static { try { // 加载驱动,放在静态代码块中可以优先执行该驱动的加载 Class.forName(DB_DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * 数据库连接对象的获取 * @return 数据库连接对象 */ public static Connection getConn(){ try { conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS); } catch (SQLException e) { throw new RuntimeException(e); } return conn; } /** * 数据库连接资源的关闭,要注意关闭的顺序 * @param rs // 要关闭的ResultSet对象 * @param stmt // 要关闭的Statement对象 * @param conn // 要关闭的Connection对象 */ public static void closeAll(ResultSet rs, Statement stmt, Connection conn){ try { if(rs != null){ rs.close(); rs = null; } if(stmt != null){ stmt.close(); stmt = null; } if(conn != null){ conn.close(); conn = null; } } catch (SQLException e) { throw new RuntimeException(e); } } }Account.java
package com.saas.entity; public class Account { private int aid; private String name; private String pass; private double money; @Override public String toString() { return "Account{" + "aid=" + aid + ", name='" + name + '\'' + ", pass='" + pass + '\'' + ", money=" + money + '}'; } public int getAid() { return aid; } public void setAid(int aid) { this.aid = aid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPass() { return pass; } public void setPass(String pass) { this.pass = pass; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }IAccountDao.java
package com.saas.dao; public interface IAccountDao { /** * 登录方法,判断用户名密码是否匹配 * @param name 用户名 * @param pass 密码 * @return 用户名和密码是否和数据库匹配 */ boolean login(String name, String pass); }AccountDaoImpl.java
package com.saas.dao.impl; import com.saas.dao.IAccountDao; import com.saas.util.DBUtil; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class AccountDaoImpl implements IAccountDao{ private Connection conn = null; private Statement stmt = null; private ResultSet rs = null; @Override public boolean login(String name, String pass) { boolean flag = false; conn = DBUtil.getConn(); String sql = "select * from account where name = '" + name + "' and pass = '" + pass + "'"; try { stmt = conn.createStatement(); rs = stmt.executeQuery(sql); if (rs.next()){ flag = true; } } catch (SQLException e) { throw new RuntimeException(e); } return flag; } }TestAccount.java
package com.saas.test; import com.saas.dao.IAccountDao; import com.saas.dao.impl.AccountDaoImpl; import java.util.Scanner; public class TestAccount { public static void main(String[] args) { IAccountDao iad = new AccountDaoImpl(); Scanner input = new Scanner(System.in); System.out.println("请输入用户名:"); String name = input.nextLine(); System.out.println("请输入密码:"); String pass = input.nextLine(); // System.out.println(iad.login(name, "000' or 1 = 1 or '1' = '")); System.out.println(iad.login(name, pass)); } }mysql> select * from account; +-----+----------+--------+-------+ | aid | name | pass | money | +-----+----------+--------+-------+ | 1 | wukong | 999999 | 5000 | | 2 | tangtang | 000000 | 6000 | +-----+----------+--------+-------+运行TestAccount测试类
请输入用户名: wukong 请输入密码: 999999 true 用户名密码都正确得到true请输入用户名: wukong 请输入密码: 123 false 用户名密码不匹配false这个结果看似没有问题
SQL注入
概念
用户输入的数据中含有SQL关键字或者语法并且参与了SQL语句的编译,导致SQL语句编译后的条件含义为true,一直得到正确的结果,这种现象被称之为SQL注入
请输入用户名: wukong 请输入密码: 000' or 1 = 1 or '1' = ' true 用户名密码不匹配,但是也得到了true的结果,因为用户输入的输入中含有SQL关键字,导致SQL语句执行后都有结果
如何避免
由于编写SQL语句时在用户输入数据,整合后再进行编译,所以为了避免SQL注入的问题,我们要使SQL语句再用户输入数据前就已经进行编译成完整的SQL语句,再进行数据填充
JDBC中的PreparedStatement对象具备这样的功能
PreparedStatement
PreparedStatement接口继承自Statement接口,执行SQL语句的方法会更加有效
应用
作用:
预编译SQL语句,效率更高
安全,避免SQL注入
可以动态填充数据,执行多个SQL语句
实现
参数标记
String sql = "select * from account where name = ? and pass = ?";动态绑定参数:
stmt.setXxx(index下标, 值) 参数下标从1开始,为指定参数下标绑定值stmt.setString(1, name); stmt.setString(2, pass);执行结果
请输入用户名: wukong 请输入密码: 000' or '1' = '1 false 即使输入了带有SQL关键字的语句,也可以保证不会有正确结果
jdbc实现事务
使用jdbc中connection对象的setAutoCommit(false)强制让连接对象不默认执行,每次都不直接执行,直到出现connection对象的commit或者rollback为止
@Override public boolean transfer(String from, String to, double money) { boolean flag = false; try { conn = DBUtil.getConn(); Account fromAccout = getAccountByName(from); if(fromAccout == null){ System.out.println("请检查转出账户是否存在!"); return false; } Account toAccout = getAccountByName(to); if(toAccout == null){ System.out.println("请检查转入账户是否存在!"); return false; } double fromMoney = fromAccout.getMoney(); if(fromMoney < money){ System.out.println("请确保转出账户有足够的余额!"); return false; } toAccout.setMoney(toAccout.getMoney() + money); updateAccount(toAccout); System.out.println(1 / 0); fromAccout.setMoney(fromAccout.getMoney() - money); updateAccount(fromAccout); flag = true; conn.commit(); } catch (Exception e) { try { conn.rollback(); } catch (Exception ex) { ex.printStackTrace(); } e.printStackTrace(); } return flag; }jdbc相对与MySQL命令框的好处是,Java中有强大的异常处理机制
在try中“尝试”正确执行所有的代码,直到最后一行都没有异常,则让连接对象进行commit操作
一旦尝试过程中出现任何问题,则Java异常处理机制将走catch代码块,则在catch代码块中进行rollback处理
由Java的异常处理机制动态自动决定到底进行哪一步操作
注意:想要实现事务处理,必须要要在同一个connection对象上操作
更安全的方式使用事务应该使用ThreadLocal保证连接对象是同一个