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; @Overridepublic 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; @Overridepublic 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保证连接对象是同一个