JDBC及衍生知识(上)

前言

补了补MySQL,终于又回到了Java的怀抱。

今天来开始学习JDBC等知识。

 

 

 


JDBC

概念

JDBC,Java  DataBase  Connectivity,即Java数据库连接。

就是通过Java去连接数据库。

JDBC的本质:官方定义的一套操作所有关系型数据库的规则(接口)。各个数据库厂商去实现这套接口提供,提供数据库驱动jar包,我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

 Java代码可能操作不同的数据库,为了让不同数据库的操作能够统一,sun公司的前辈开发了一套操作所有关系型数据库的接口,即JDBC,Java中的JDBC只是做到了定义接口,而接口具体的实现是由各个数据库厂商(例如MySQL、Oracle、DB2)自己继承接口来实现。这样,不同的厂商提供不同的实现类,不同的实现类就可以操作不同的数据库,这些实现类就是“数据库驱动”。

底层原理:接口类型  接口声明=new  接口实现类();    接口声明.方法(); ,利用多态去调用具体实现类中对接口的实现。

步骤

  1. 导入对应的驱动jar包进入工程。
  2.  注册驱动(让程序知道使用的是哪一个jar包)
  3. 获取数据库连接对象 Connection
  4. 定义sql语句
  5. 获取执行sql语句的对象  Statement
  6. 执行sql,接受返回结果
  7. 处理结果
  8. 释放资源(避免内存泄露)

示例

数据库db3中有表account:

    id  name    balance  
------  ------  ---------
     1  Tom          1000
     2  Jack         1000

下面是一套简单的JDBC操作代码,其中对url的写法格式及对版本的影响什么的请了解一下这篇文章

public class JdbcDemo1 {
    //定义JDBC的url地址(MySQL8+版本)
    static final String DB_URL =
            "jdbc:mysql://localhost:3306/db3?useSSL=false&serverTimezone=UTC";
    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "964939451";

    public static void main(String[] args) throws Exception {
        //1.导入驱动jar包(此处以mysql的为例)
        //2.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //3.获取数据库连接对象
        Connection conn=DriverManager.getConnection(DB_URL,USER,PASS);
        //4.定义SQL语句
        String sql="UPDATE account SET balance = 500 WHERE id = 1";
        //5.获取执行sql的对象 Statement
        Statement stmt=conn.createStatement();
        //6.执行sql
        int count=stmt.executeUpdate(sql);
        //7.处理结果
        System.out.println(count);
        //8.释放资源
        conn.close();
        stmt.close();
    }
}

执行之后,控制台打印了一个1,而数据库中检索一下account表:

    id  name    balance  
------  ------  ---------
     1  Tom           500
     2  Jack         1000

执行成功。

 

讲解各个对象

详解各个重要类/接口的对象:

  1. DriverManager类对象:驱动管理对象
  2. Connection接口对象:数据库连接对象
  3. Statement接口对象:执行sql的对象
  4. ResultSet接口对象:结果集对象
  5. PreparedStatement接口对象:执行sql的对象(继承自Statement接口)

 

DriverManager类对象:驱动管理对象

功能:

  • 注册驱动:告诉程序该使用哪一个jar包(某些驱动jar包在目录META-INF->services->java.sqlDriver中写了配置,使得你可以不必在主程序中手写注册驱动)。

上例中我们注册驱动使用的是Class.forName(com.mysql.cj.jdbc.Driver)可以理解为将指定字节码文件加载进内存,我们追踪这个类文件(即com.mysql.cj.jdbc.Driver),会发现Driver.java文件中存在一个静态代码块,也就是说我们手动将这个类文件加载进内存,然后该类文件就调用了静态代码块,就是这个静态代码块调用了DriverManager的静态方法registerDriver,从而注册了驱动。

static {
    try {
        DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    }
}
  • 获取数据库连接
static Connection   getConnection(String url, String user, String password)

参数中,user、password是登录的用户及密码,而url是一个指令连接的路径。

如上例:

定义连接url:jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC

MySQL 8.0 以上版本不需要建立 SSL 连接的,需要显式关闭,还有就是要设置时区。

另外说一个简写:

jdbc:mysql://localhost:3306/数据库名

如果数据库地址是本机且端口为默认的3306,则可以直接写成:

jdbc:mysql:///数据库名

Connection接口对象:数据库连接对象

功能:

  • 获取执行sql的对象
Statement    createStatement();
PreparedStatement  prepareStatement(String sql)  
  • 管理事务
  1. 开启事务:void   setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
  2. 提交事务:void   commit()
  3. 回滚事务:void   rollback()

 

Statement接口对象:执行sql的对象

执行静态SQL语句

  • boolean  execute(String sql)
  • int  executeUpdate(String sql):执行DML语句(增删改数据表中的数据)、DDL语句(CRUD库/表) 。返回值是操作影响的行数,可以通过这个影响的行数来判断DML语句是否执行成功,即返回值大于0则执行成功。如果是DDL,不管成功失败都是返回0
  • ResultSet   executeQuery(String sql):执行DQL语句,返回值是一个结果集对象

 

练习:

为了更好地学习后面的内容,这里我们巩固一下前面的内容,来做几个练习:

account表 添加一条记录

public class JdbcDemo2 {
    //定义JDBC的url地址(MySQL8+版本)
    static final String DB_URL =
            "jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC";
    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "964939451";
    public static void main(String[] args) {
        Statement stmt=null;
        Connection conn=null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.定义sql
            String sql="INSERT INTO account VALUES(null,'Lisa',3000)";
            //3.获取Connection对象
            conn=DriverManager.getConnection(DB_URL,USER,PASS);
            //4.获取执行SQL的对象
            stmt=conn.createStatement();
            //5.执行SQL
            int count=stmt.executeUpdate(sql); //影响的行数
            //6.处理结果
            System.out.println(count);
            if(count>0){
                System.out.println("添加成功!");
            }else{
                System.out.println("添加失败!");
            }
        }catch(ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            //7.释放资源
            //后建立的(stmt)先释放
            //避免空指针异常(stmt被赋值前就进入catch)
            if(stmt!=null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //避免空指针异常(conn被赋值前就进入catch)
            if(conn!=null){
                try {
                    //后建立的先释放
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

account表 修改记录

public class JdbcDemo3 {
    //定义JDBC的url地址(MySQL8+版本)
    static final String DB_URL =
            "jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC";
    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "964939451";


    public static void main(String[] args) {
        Statement stmt=null;
        Connection conn=null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.定义sql
            String sql="UPDATE account SET balance=1500 WHERE id=3";
            //3.获取Connection对象
            conn=DriverManager.getConnection(DB_URL,USER,PASS);
            //4.获取执行SQL的对象
            stmt=conn.createStatement();
            //5.执行SQL
            int count=stmt.executeUpdate(sql); //影响的行数
            //6.处理结果
            System.out.println(count);
            if(count>0){
                System.out.println("修改成功!");
            }else{
                System.out.println("修改失败!");
            }
        }catch(ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            //7.释放资源
            //后建立的(stmt)先释放
            //避免空指针异常(stmt被赋值前就进入catch)
            if(stmt!=null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //避免空指针异常(conn被赋值前就进入catch)
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

account表 删除一条记录

上例改一下SQL语句即可(String sql=”DELETE FROM account WHERE id=3″;),我就不重新复制这些代码了。

PreparedStatement接口对象:执行sql的对象(继承自Statement接口)

执行预编译的SQL。

不同于Statement接口执行静态的SQL语句,直接将完整的SQL语句执行,预编译的SQL会先用“?”来作为占位符替代参数,可以有效预防SQL注入的问题。

我们在定义sql语句的时候,sql的参数使用?作为占位符,然后我们在获取sql执行对象的时候(PrepareStatement),传入预编译的SQL,然后给占位符“?”赋值,然后再执行即可。

给占位符赋值方法:setXXX(参数1,参数2)

  • 参数1:?的位置编号 (从1开始)
  • 参数2:?的值

后面我们执行execute的时候就不需要传入sql语句了(这个executeUpdate/executeQuery被PreparedStatement重写了无参方法)

            //2.定义sql
            String sql="SELECT * FROM user WHERE username=? and password=?";
            //3.获取执行sql的对象
            pstmt=conn.prepareStatement(sql);
            //4.设置参数
            pstmt.setString(1,username);
            pstmt.setString(2,password);
            //5.执行查询
            rs=pstmt.executeQuery();

后期我们会经常使用这个PreparedStatement对象来替代Statement对象,因为可以有效防止SQL注入,效率更高。

ResultSet接口对象:结果集对象

 上面我们说了Statement的对象的executeQuery方法执行DQL语句,返回的是一个ResultSet对象,即结果集对象。

我们的ResultSet对象是对查询结果的一个封装。

一个ResultSet对象维护一个游标指向其当前行的数据,我们主要就是通过这个游标来访问数据。最初游标位于第一行之前的位置。通过next方法移动游标到下一行,如果移动后它返回false即说明ResultSet对象没有更多的行,即游标已经到头了。

 

常用方法:

  • boolean  next():移动游标到下一行,判断当前行是否是最后一行末尾,如果是则返回true,否则返回false
  • getXXX(参数):获取数据
    • 参数情况;
      • Int:代表列的编号,这里是从1开始,例如getString(1),得到第一列的值,返回String
      • String:代表列的名称。如:getDouble(“balance”),得到字段balance的值,返回Double

使用示例:

public class JdbcDemo5 {
    //定义JDBC的url地址(MySQL8+版本)
    static final String DB_URL =
            "jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC";
    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "964939451";


    public static void main(String[] args) {
        Statement stmt=null;
        Connection conn=null;
        ResultSet rs=null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.定义sql
            String sql="SELECT * FROM account";
            //3.获取Connection对象
            conn=DriverManager.getConnection(DB_URL,USER,PASS);
            //4.获取执行SQL的对象
            stmt=conn.createStatement();
            //5.执行SQL
            rs=stmt.executeQuery(sql); //影响的行数
            //6.处理结果
            //6.1 游标向下移动一行
            rs.next();
            int id=rs.getInt(1);
            String name=rs.getString("name");
            double balance=rs.getDouble(3);
            System.out.println(id+"---"+name+"---"+balance);
        }catch(ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            //7.释放资源
            //后建立的(rs)先释放
            //避免空指针异常(rs被赋值前就进入catch)
            if(rs!=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //避免空指针异常(stmt被赋值前就进入catch)
            if(stmt!=null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //避免空指针异常(conn被赋值前就进入catch)
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

这个示例只是一个基础使用的案例,其实在实际开发中我们不会这样使用的。

我们也可以用个while循环,来遍历所有结果

            while(rs.next()){
                int id=rs.getInt(1);
                String name=rs.getString("name");
                double balance=rs.getDouble(3);
                System.out.println(id+"---"+name+"---"+balance);
            }

 

检索示例

基本效果

为了更好地学习后面的内容,这里我们巩固一下前面的内容,来做一个练习:

数据库db3中有表,内容如下:

    id  ename    job_id     mgr  joindate    salary    bonus     dept_id  
------  ---------  ------  ------  ----------  --------  --------  ---------
  1001  孙悟空       4    1004  2000-12-17  8000.00   (NULL)          20
  1002  卢俊义       3    1006  2001-02-20  16000.00  3000.00          30
  1003  林冲         3    1006  2001-02-22  12500.00  5000.00          30
  1004  唐僧         2    1009  2001-04-02  29750.00  (NULL)           20
  1005  李逵         4    1006  2001-09-28  12500.00  14000.00         30
  1006  宋江         2    1009  2001-05-01  28500.00  (NULL)           30
  1007  刘备         2    1009  2001-09-01  24500.00  (NULL)           10
  1008  猪八戒        4    1004  2007-04-19  30000.00  (NULL)           20
  1009  罗贯中        1  (NULL)  2001-11-17  50000.00  (NULL)           10
  1010  吴用         3    1006  2001-09-08  15000.00  0.00             30
  1011  沙僧         4    1004  2007-05-23  11000.00  (NULL)           20
  1012  李逵         4    1006  2001-12-03  9500.00   (NULL)           30
  1013  小白龙       4    1004  2001-12-03  30000.00  (NULL)           20
  1014  关羽         4    1007  2002-01-23  13000.00  (NULL)           10

定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。

  1. 定义Emp类
  2. 定义方法 public  List<Emp> findAll(){}
  3. 实现方法 SELECT  *  FROM  emp;

然后我们在pers.luoluo.domain中定义一个Emp类

public class Emp {
    private int id;
    private String ename;
    private int job_id;
    private int mgr;
    private Date joindate;
    private double salary;
    private double bonus;
    private int dept_id;

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", ename='" + ename + '\'' +
                ", job_id=" + job_id +
                ", mgr=" + mgr +
                ", joindate=" + joindate +
                ", salary=" + salary +
                ", bonus=" + bonus +
                ", dept_id=" + dept_id +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public int getJob_id() {
        return job_id;
    }

    public void setJob_id(int job_id) {
        this.job_id = job_id;
    }

    public int getMgr() {
        return mgr;
    }

    public void setMgr(int mgr) {
        this.mgr = mgr;
    }

    public Date getJoindate() {
        return joindate;
    }

    public void setJoindate(Date joindate) {
        this.joindate = joindate;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public int getDept_id() {
        return dept_id;
    }

    public void setDept_id(int dept_id) {
        this.dept_id = dept_id;
    }
}

然后在pers.luoluo.jdbc中创建一个新类:

public class JdbcDemo6 {
    public List<Emp> findAll(){
        //定义JDBC的url地址(MySQL8+版本)
        final String DB_URL =
                "jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC";
        // 数据库的用户名与密码,需要根据自己的设置
        final String USER = "root";
        final String PASS = "964939451";

        Statement stmt=null;
        Connection conn=null;
        ResultSet rs=null;

        //创建emp对象
        Emp emp=null;
        List<Emp> empList=new ArrayList<Emp>();

        try{
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            conn=DriverManager.getConnection(DB_URL,USER,PASS);
            //3.定义SQL
            String sql="SELECT * FROM emp";
            //4.获取执行SQL的对象
            stmt=conn.createStatement();
            //5.执行SQL语句
            rs=stmt.executeQuery(sql);
            //6.遍历结果集、封装对象、装载集合
            while(rs.next()){
                emp=new Emp();
                //获取数据(rs的getXXX的参数传入名必须与数据库端字段名相同)
                emp.setId(rs.getInt("id"));
                emp.setEname(rs.getString("ename"));
                emp.setJob_id(rs.getInt("job_id"));
                emp.setMgr(rs.getInt("mgr"));
                emp.setJoindate(rs.getDate("joindate"));
                emp.setSalary(rs.getDouble("salary"));
                emp.setBonus(rs.getDouble("bonus"));
                emp.setDept_id(rs.getInt("dept_id"));
                //将员工加入
                empList.add(emp);
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            if(rs!=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(stmt!=null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

        return empList;
    }

    public static void main(String[] args) {
        List<Emp> empList=new JdbcDemo6().findAll();

        for (Emp emp:empList) {
            System.out.println(emp);
        }
    }
}

控制台打印效果为数据库数据效果(即成功了),这里就不复制了。

制作JDBC工具类

审视上述代码,我们在每一次的fineAll中都需要连接数据库再释放资源,如果我们要实现类似的方法,那么又需要重复写这些繁琐的代码,所以我们可以整合成一个JDBC的工具类,这样可以为我们简化书写:

src下创建一个注解文件jdbc.properties:

url=jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC
user=root
password=964939451
driver=com.mysql.cj.jdbc.Driver

然后在pers.luoluo.util下的JDBCUtils.java中:

public class JDBCUtils {
    private static String url;
    private static String user;
    private static String password;
    private static String driver;

    /**
     * 文件的读取,只需要读取一次即可拿到这些值
     * 我们使用静态代码块,随着类的加载而调用(仅一次)
     **/
    static{
        //读取资源文件,获取值
        try {
            //1.创建Properties集合类
            Properties pro=new Properties();
            //2.加载文件
            /*利用ClassLoader获取src路径下的文件的方式
              ClassLoader是一个“类加载器”,可以加载字节码文件进入内容,
              并且可以获得字节码文件的路径
            */
            ClassLoader classLoader = JDBCUtils.class.getClassLoader();
            URL res = classLoader.getResource("jdbc.properties");
            String path = res.getPath();
            path=java.net.URLDecoder.decode(path,"utf-8"); //url编码转utf-8
            pro.load(new FileReader(path));
            //3.获取数据,赋值
            url=pro.getProperty("url");
            user=pro.getProperty("user");
            password=pro.getProperty("password");
            driver=pro.getProperty("driver");
            //4.注册驱动
            Class.forName(driver);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description 获取连接的工具方法(集合配置文件)
     **/
    public static Connection getConnection( ) throws SQLException {
        return DriverManager.getConnection(url,user,password);
    }

    /**
     * @Description 针对DDL、DML语言的释放资源方法
     **/
    public static  void close(Statement stmt,Connection conn){
        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * @Description 针对DQL语言的释放资源方法(重载close方法)
     **/
    public static  void close(ResultSet rs, Statement stmt, Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

然后回到我们的JdbcDemo6.java

public class JdbcDemo6 {
    public List<Emp> findAll(){
        Statement stmt=null;
        Connection conn=null;
        ResultSet rs=null;

        //创建emp对象
        Emp emp=null;
        List<Emp> empList=new ArrayList<Emp>();

        try{
            conn= JDBCUtils.getConnection();
            //3.定义SQL
            String sql="SELECT * FROM emp";
            //4.获取执行SQL的对象
            stmt=conn.createStatement();
            //5.执行SQL语句
            rs=stmt.executeQuery(sql);
            //6.遍历结果集、封装对象、装载集合
            while(rs.next()){
                emp=new Emp();
                //获取数据(rs的getXXX的参数传入名必须与数据库端字段名相同)
                emp.setId(rs.getInt("id"));
                emp.setEname(rs.getString("ename"));
                emp.setJob_id(rs.getInt("job_id"));
                emp.setMgr(rs.getInt("mgr"));
                emp.setJoindate(rs.getDate("joindate"));
                emp.setSalary(rs.getDouble("salary"));
                emp.setBonus(rs.getDouble("bonus"));
                emp.setDept_id(rs.getInt("dept_id"));
                //将员工加入
                empList.add(emp);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.close(rs,stmt,conn);
        }

        return empList;
    }

    public static void main(String[] args) {
        List<Emp> empList=new JdbcDemo6().findAll();

        for (Emp emp:empList) {
            System.out.println(emp);
        }
    }
}

然后就又成功打印了数据库中的表内容。

代码精妙之处我已写在各个注释的地方,请详读。

模拟登陆

我们再来重新做个需求:

  1. 通过键盘录入用户名和密码
  2. 判断用户是否登录成功

我们来创建一个数据库db4,再创建一个user表:

CREATE TABLE user(
	id INT PRIMARY KEY AUTO_INCREMENT,
	username VARCHAR(32),
	password VARCHAR(32)
);
INSERT INTO user VALUES(NULL,'Tom','123');
INSERT INTO user VALUES(NULL,'Jack','123');
SELECT * FROM user;
    id  username  password  
------  --------  ----------
     1  Tom       123       
     2  Jack      123       

数据表就创建完成,接下来我们的任务是实现代码访问,利用JDBC工具类模拟登陆:

工具类还是不变,我们只要修改src/jdbc.properties下的url后面数据库指定好db4即可(体会到我们的代码的可扩展性了吗?这也体现了设计模式中 开放-封闭原则 ),然后,在JdbcDemo7.java中,内容如下:

public class JdbcDemo7 {
    /**
     * @Description  登录方法
     **/
    public boolean login(String username,String password){
        if(username==null||password==null){
            return false;
        }
        Connection conn=null;
        Statement stmt=null;
        ResultSet rs=null;
        //连接数据库判断是否登录成功
        try {
            //1.获取连接
            conn=JDBCUtils.getConnection();
            //2.定义sql
            String sql="SELECT * FROM user WHERE username='"+
                    username+"' and password='" +
                    password+"'";
            //3.获取执行sql的对象
            stmt=conn.createStatement();
            //4.执行查询
            rs=stmt.executeQuery(sql);
            //5.判断
            return rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.close(rs,stmt,conn);
        }
        return false;
    }

    public static void main(String[] args) {
        //1.键盘录入,接收用户名和密码
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username=sc.nextLine();
        System.out.println("请输入密码:");
        String password=sc.nextLine();
        //2.调用方法
        boolean flag=new JdbcDemo7().login(username,password);
        //3.判断结果
        if(flag)
            System.out.println("登录成功!");
        else
            System.out.println("用户名或密码错误!");
    }
}
请输入用户名:
Jack
请输入密码:
123
登录成功!

升级安全性

了解网络安全的朋友肯定很敏感地发现了,这样的登录机制根本挡不住sql注入的攻击,例如,用户名你随便输入,密码输:a’ or ‘a’ = ‘a,最后sql语句就变成了:SELECT * FROM user WHERE username=’xxx’ and password=’a’ or ‘a’=’a’;,利用后面的恒等式会将所在用户都检索出来,自然结果肯定有next(大于0),故就会登录成功。

SQL注入问题:在拼接SQL时,有一些SQL的特殊关键字参与字符串的拼接,会造成安全性的问题。

这就需要我们之前聊过的PreparedStatement对象来解决SQL注入问题,利于预编译的SQL语句来代替静态的SQL语句,从而预防SQL注入问题。

我们修改login方法即可:

    public boolean login(String username,String password){
        if(username==null||password==null){
            return false;
        }
        Connection conn=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;
        //连接数据库判断是否登录成功
        try {
            //1.获取连接
            conn=JDBCUtils.getConnection();
            //2.定义sql
            String sql="SELECT * FROM user WHERE username=? and password=?";
            //3.获取执行sql的对象
            pstmt=conn.prepareStatement(sql);
            //4.设置参数
            pstmt.setString(1,username);
            pstmt.setString(2,password);
            //5.执行查询
            rs=pstmt.executeQuery();
            //6.判断
            return rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.close(rs,pstmt,conn);
        }
        return false;
    }

测试:

请输入用户名:
waefrwe
请输入密码:
a' or 'a' = 'a
用户名或密码错误!

非常成功!

 

JDBC管理事务

概述

事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败(原子性)。

对事务的操作

  • 开启事务
  • 提交事务
  • 回滚事务

在JDBC中,我们会使用Connection对象来管理事务:

  1. 开启事务:void   setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
  2. 提交事务:void   commit()
  3. 回滚事务:void   rollback()

操作示例

首先,在我们的数据库db3中有这样的一个account表:

mysql> SELECT* FROM account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
|  1 | Tom  |    2000 |
|  2 | Jack |    2000 |
+----+------+---------+
2 rows in set (0.00 sec)

好了,然后在jdbc.properties配置文件中修改url访问db3数据库,下面是Java代码:

public class JdbcDemo8 {
    public static void main(String[] args) {
        Connection conn=null;
        PreparedStatement pstmt1=null;
        PreparedStatement pstmt2=null;

        try {
            //1.获取连接
            conn=JDBCUtils.getConnection();
            //2.定义SQL
            //2.1 Tom-500
            String sql1="UPDATE account SET balance=balance-? WHERE name=?";
            //2.2 Jack+500
            String sql2="UPDATE account SET balance=balance+? WHERE name=?";
            //3.获取执行SQL对象
            pstmt1=conn.prepareStatement(sql1);
            pstmt2=conn.prepareStatement(sql2);
            //4.设置参数
            pstmt1.setDouble(1,500);
            pstmt1.setString(2,"Tom");
            pstmt2.setDouble(1,500);
            pstmt2.setString(2,"Jack");
            //5.执行语句
            pstmt1.executeUpdate();
            pstmt2.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.close(pstmt1,conn);
            JDBCUtils.close(pstmt2,null);
        }
    }
}

执行不出意外会成功,最后Tom的账户为1500,Jack的账户为2500,那么,如果我们在pstmt1.executeUpdate();后面添加一个异常(例如3/0),那么只有Tom会扣钱,Jack不会增加钱,这不是我们想要的结果。

所以我们要使用到事务。

public class JdbcDemo8 {
    public static void main(String[] args) {
        Connection conn=null;
        PreparedStatement pstmt1=null;
        PreparedStatement pstmt2=null;

        try {
            //1.获取连接
            conn=JDBCUtils.getConnection();
            //开启事务
            conn.setAutoCommit(false);
            //2.定义SQL
            //2.1 Tom-500
            String sql1="UPDATE account SET balance=balance-? WHERE name=?";
            //2.2 Jack+500
            String sql2="UPDATE account SET balance=balance+? WHERE name=?";
            //3.获取执行SQL对象
            pstmt1=conn.prepareStatement(sql1);
            pstmt2=conn.prepareStatement(sql2);
            //4.设置参数
            pstmt1.setDouble(1,500);
            pstmt1.setString(2,"Tom");
            pstmt2.setDouble(1,500);
            pstmt2.setString(2,"Jack");
            //5.执行语句
            pstmt1.executeUpdate();
            pstmt2.executeUpdate();

            //提交事务
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //回滚事务
            try {
                if(conn!=null) {
                    conn.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            JDBCUtils.close(pstmt1,conn);
            JDBCUtils.close(pstmt2,null);
        }
    }
}

好了,这样就成功了!

 

 


这一篇就先写到这里了,我们把连接池等内容放到下一篇来介绍。

 

 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

发表评论