1. JDBC介绍

  java语言连接数据库,JDBC通过载入不同的数据库驱动程序进而于不同的数据库进行连接。

 

2. 数据库驱动载入

  使用文本编辑器需要配置classpath环境变量,环境变量路径配置驱动地址;使用IDEA在选择需要加载驱动的 模块->打开模块设置(Open Module Settings)->库(Libraries)-> 加载数据库驱动(+号)。

 

3. 数据库表的设计可以使用PowerDesign工具

 

4. JDBC编程六步

  (1)注册数据库驱动;(2)连接数据库;(3)创建执行SQL对象;(4)执行SQL语句;(5)查询语句处理查询结果集;(6)释放资源;

 

5. jdbc实例,执行insert,delete,update,select语句

  处理插入语句

public class JDBCTest{
    public static void main(String[] args){
        Connection conn = null;
        Statement stat = null;
        try{
            Driver driver = new com.mysql.cj.jdbc.Driver();   //多态,父类型引用指向子类型对象
            //1. 注册数据库驱动
            DriverManager.registerDriver(driver);
            String url = "jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false";
            String user = "root";
            String password = "123456";
            //2. 连接数据库
            conn = DriverManager.getConnection(url,user,password);
            System.out.println(conn);
            //3. 获取执行SQL对象
            stat = conn.createStatement();
            //4. 执行SQL语句
            String sql = "insert into dept_bak(deptno,dname,loc) value(60,'销售部','北京')";
            int result = stat.executeUpdate(sql);
            System.out.println(result == 1 ? "执行成功":"执行失败");
        }catch(SQLException e){
            e.printStackTrace();
        }finally{
            //6. 释放资源
            //释放资源放在finally中,不管前面是否执行成功,都释放资源
            //释放资源依次从下往上释放(stat,conn),释放资源也需要处理异常
            if(stat != null){
                try{
                    stat.close();
                
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            
        }    
    }
}

  注册数据库驱动使用registerDriver()方法,但是这种方法已经不常用,后面使用反射会更加常用。

 

  处理更新,删除语句

public class jdbcTest02{
    public static void main(String[] args){
        Connection conn = null;
        Statement stat = null;
        try{
            //1. 注册驱动
            DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
            //2. 连数据库
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            //3. 获取执行SQL对象
            stat = conn.createStatement();
            //4. 执行SQL语句
            //String sql = "delete from dept_bak where deptno = 50";
            String sql = "update dept_bak set dname = '研发部' where deptno = 60";
            int result = stat.executeUpdate(sql);
            System.out.println(result == 1 ? "执行成功":"执行失败");
            
        }catch(SQLException e){
            e.printStackTrace();
        }finally{
            //6. 释放资源
            if(stat != null){
                try{
                    stat.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

  其中insert,update,delete都不需要对查询结果集处理,只需根据返回结果(SQL语句执行后数据改变的条数对应返回结果数)来判断SQL语句是否执行成功。

 

  使用反射,并且把数据库等字符串信息写入.properties文件中,使用资源绑定器读取配置文件。

public class jdbcTest04{
    public static void main(String[] args){
        ResourceBundle rb = ResourceBundle.getBundle("db");
        String dbdriver = rb.getString("driver");
        String url = rb.getString("url");
        String user = rb.getString("user");
        String password = rb.getString("password");
        Connection conn = null;
        Statement stat = null;
        try{
            Class.forName(dbdriver);
            conn = DriverManager.getConnection(url,user,password);
            stat = conn.createStatement();
            String sql = "update dept_bak set dname = '研发部2',loc = '天津' where deptno = 60";
            int result = stat.executeUpdate(sql);
            System.out.println(result == 1? "执行成功":"执行失败");
            
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(stat != null){
                try{
                    stat.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }
        
        
        
    }
}
db.properties

driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false user=root password=123456

  使用资源绑定器对代码部分修改较少

 

  执行select语句,处理查询结果集

public class jdbcTest05{
    public static void main(String[] args){
        Connection conn = null;
        Statement stat = null;
        ResultSet rs = null;
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            stat = conn.createStatement();
            String sql = "select empno as a,ename,sal from emp";
            rs = stat.executeQuery(sql);   //执行select语句
            
            //1. 使用getString(数据库字段名) 遍历
/*            while(rs.next()){    //next()遍历查询结果集
                String empno = rs.getString("empno");
                String ename = rs.getString("ename");
                String sal = rs.getString("sal");
                System.out.println(empno + " " + ename + " " + sal);
            }
*/        
            //2. 使用getString(第几个字段)遍历
/*            while(rs.next()){
                String empno = rs.getString(1);
                String ename = rs.getString(2);
                String sal = rs.getString(3);
                System.out.println(empno + " " + ename + " " + sal);
            }
*/            
            //3. 遍历具体字段getInt(),getString(),getDouble()
/*            while(rs.next()){
                int empno = rs.getInt("empno");
                String ename = rs.getString("ename");
                double sal = rs.getDouble("sal");
                System.out.println(empno + " " + ename + " " + (sal + 100));  //可以进行数据运算
            }
*/
            //4. 可以遍历别名字段
            while(rs.next()){
                String empno = rs.getString("a");  //使用别名,遍历查询结果集
                String ename = rs.getString("ename");
                String sal = rs.getString("sal");
                System.out.println(empno + " " + ename + " " + sal);
            }
            
            
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(rs != null){
                try{
                    rs.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(stat != null){
                try{
                    stat.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

  与update,delete,insert语句不同的是,select语句需要处理查询结果集,处理查询结果集通常有4种方式来获取字段的值,同时在最后需要关闭查询结果集的异常。

 

6. SQL注入问题

  SQL注入:用户输入的信息中可能含有SQL语句的关键字,并且这些关键字参与SQL语句的编译过程,导致SQL语句的查询原意扭曲,进而达到SQL注入。

  使用用户登录模拟SQL注入问题

public class jdbcTest06{
    public static void main(String[] args){
        //初始化用户界面
        Map<String,String> loginInfo = initUI();
        //用户登录
        boolean loginFlag = login(loginInfo);
        
        System.out.println(loginFlag? "登录成功":"登录失败");
    }
    
    public static Map<String,String> initUI(){
        System.out.println("请输入用户名:");
        Scanner s = new Scanner(System.in);
        String username = s.nextLine();
        System.out.println("请输入密码:");
        String password = s.nextLine();
        Map<String,String> map = new HashMap();
        map.put("username",username);
        map.put("password",password);
        return map;
    }
    
    public static boolean login(Map<String,String> userloginInfo){
        boolean flag = false;
        String getusername = userloginInfo.get("username");
        String getpassword = userloginInfo.get("password");
        Connection conn = null;
        Statement stat = null;
        ResultSet rs = null;
        
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            stat = conn.createStatement();
            String sql = "select * from t_user where loginname='" + getusername + "' and loginpwd = '"+getpassword+"'";
            rs = stat.executeQuery(sql);
            if(rs.next()){
                flag = true;
            }
            
            
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(rs != null){
                try{
                    rs.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(stat != null){
                try{
                    stat.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }    
        return flag;
    }
}

  在t_user表有两条数据{“zhangsan”:"123","jack","123"},当使用数据库中的用户名和密码匹配时可以正常登录,使用数据库中没有的数据也可以登录失败,但是当使用用户名:fdsa,密码:fdsa' or '1'='1,也可以登录成功,这是因为用户名和密码正好完成了SQL语句的拼接(select * from t_user where loginname = 'fdsa' and loginpwd = 'fdsa' or '1'='1' or  '1' = '1';),执行了非法的SQL编译。

 

7. 解决SQL注入问题

  使用prepareStatement创建预编译数据库对象

public class jdbcTest07{
    public static void main(String[] args){
        
        Map<String,String> userloginInfo = InitUI();
        
        boolean result = useLogin(userloginInfo);
        
        System.out.println(result? "登录成功":"登录失败");
    }
    
    public static Map<String,String> InitUI(){
        System.out.println("请输入用户名:");
        Scanner s = new Scanner(System.in);
        String username = s.nextLine();
        System.out.println("请输入密码:");
        String password = s.nextLine();
        Map<String,String> map = new HashMap<>();
        map.put("username",username);
        map.put("password",password);
        return map;
    }
    
    public static boolean useLogin(Map<String,String> map){
        boolean flag = false;
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        String username = map.get("username");
        String password = map.get("password");
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            String sql = "select * from t_user where loginname = ? and loginpwd = ?";   //?表示占位符,用来接收值
            ps = conn.prepareStatement(sql);   //创建预编译数据库操作对象
            ps.setString(1,username);    //对占位符1传入值
            ps.setString(2,password);    //对占位符2传入password,loginname和loginpwd都是字符串,所以使用setString(),如果是Int,则使用setInt()
            rs = ps.executeQuery();
            if(rs.next()){
                flag = true;
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(rs != null){
                try{
                    rs.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(ps != null){
                try{
                    ps.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }
        return flag;
    }
}

  使用prepareStatement解决SQL注入问题原理:先编译SQL语句框架,然后填入变量值,就算有SQL关键字,也不会使关键字参与SQL编译。

 

  使用prepareStatement执行insert,update,delete语句

public class jdbcTest08{
    public static void main(String[] args){
        Connection conn = null;
        PreparedStatement ps= null;
        
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            
        /*    String sql = "insert into dept_bak(deptno,dname,loc) values(?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,70);
            ps.setString(2,"研发二部");
            ps.setString(3,"北京");   */
            
        /*    String sql = "update dept_bak set dname = ?,loc = ? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"研发三部");
            ps.setString(2,"上海");
            ps.setInt(3,70);    */
            
            String sql = "delete from dept_bak where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,70);
            
            int result = ps.executeUpdate();
            System.out.println(result);
            
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(ps != null){
                try{
                    ps.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

 

8. createStatement和prepareStatement比较

  在一些场景中可能需要使用createStatement来实现某些功能,例如排序等场景

public class jdbcTest02 {
    public static void main(String[] args) {
        //1. 使用PreparedStatement不能满足排序场景
        /*System.out.println("请输入asc或desc进行升序或降序:");
        Scanner scanner = new Scanner(System.in);
        String str = scanner.nextLine();
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            String sql = "select * from emp order by ename ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,str);
            rs = ps.executeQuery();
            if(rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(rs != null){
                try{
                    rs.close();
                }catch (SQLException e){
                    e.printStackTrace();
                }
            }
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }*/
        //2. 使用Statement满足升序排序场景
        System.out.println("请输入asc或desc进行升序或降序:");
        Scanner scanner = new Scanner(System.in);
        String str = scanner.nextLine();
        Connection conn = null;
        Statement stat = null;
        ResultSet rs = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            stat = conn.createStatement();
            String sql = "select * from emp order by ename " + str;
            rs = stat.executeQuery(sql);
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(rs != null){
                try{
                    rs.close();
                }catch (SQLException e){
                    e.printStackTrace();
                }
            }
            if(stat != null){
                try {
                    stat.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  在排序场景中我们需要asc或desc来参与SQL语句的编译,如果使用prepareStatement那么把排序当作字符串处理'asc'或'desc',这样SQL语句是不能执行的,所以此时可以使用createStatement来满足业务需求,所以并不是说所有情况下都需使用prepareStatement,针对不同场景选择不同的数据库执行对象。

  createStatement和prepareStatement对比:

  (1)prepareStatement可解决SQL注入问题;

  (2)createStatement是编译一次执行一次,preparedStatement编译一次,可执行多次,效率更高;

  (3)preparedStatement会在编译阶段对类型的进行安全检查;

  当业务层面需要SQL注入的情况(需要SQL语句拼接)使用createStatement,其他情况基本使用prepareStatement。

 

9. jdbc事务

  jdbc事务是自动提交的,执行一次,提交一次

public class jdbcTest03 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            String sql = "update dept_bak set dname = ? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"研发5部");
            ps.setInt(2,60);
            int result = ps.executeUpdate();    //在此处打断点,当执行完这步,数据库中数据已经更新

            ps.setString(1,"研发6部");
            ps.setInt(2,60);
            result = ps.executeUpdate();

            System.out.println( result == 1 ? "执行成功":"执行失败");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  如果在调用executeUpdate()处打断点,当执行完语句,数据库中值已经更新,可以看出事务是自动提交的,这显然不符合业务需求,如果是转账的操作那么账户1已经减去了一部分钱,而账户2还没有收到。

 

  模拟转账场景

public class jdbcTest04 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);
            conn.setAutoCommit(false);    //开启事务,关闭自动提交

            ps.setDouble(1,10000);
            ps.setInt(2,111);
            int result = ps.executeUpdate();

    //        String str = null;    //模拟异常,由于jdbc是自动提交事务的,如果转账过程中发生异常,那么此时111账户余额已经为10000,但是222账户还没有增加
    //        str.toString();

            ps.setDouble(1,10000);
            ps.setInt(2,222);
            result += ps.executeUpdate();
            conn.commit();     //当两边都执行成功,转账成功后,提交事务

            System.out.println(result == 2? "转账成功":"转账失败");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            if(conn != null){
                try {
                    conn.rollback();     // 当执行异常时,回滚
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

  在转账场景中,如果转账过程中发生异常,那么账户1已经减去了10000块,而账户2并没有增加。

  所有我们要在建立连接后开启事务,关闭自动提交(conn.setAutoCommit(false)),当我们的SQL语句都执行成功后才提交(conn.commit()),如果当中发生异常那么必须回滚(conn.rollback())。

 

10. jdbc中常用方法的封装

  对jdbc中一些常用的方法我们可以封装到一个类中,这样我们在其他类中可以更方便的调用

public class DBUtil {
    //可以直接使用类名调用方法,不需要new对象,所以构造方法私有化
    private DBUtil(){}

    static{
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");    //可能会多次连接数据库,但是注册驱动只注册一次,静态代码块在类加载时执行,且只执行一次
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }

    }

    //获取数据库连接对象
    public static Connection getConnection() throws SQLException{     //使用抛出异常,不用捕获异常,因为在主方法中已经进行捕获
        return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false","root","123456");
    }

    //关闭资源
    public static void close(Connection conn,Statement stat,ResultSet rs){
        if(rs != null){
            try{
                rs.close();
            }catch(SQLException e){
                e.printStackTrace();
            }
        }
        if(stat != null){
            try{
                stat.close();
            }catch(SQLException e){
                e.printStackTrace();
            }
        }
        if(conn != null){
            try{
                conn.close();
            }catch(SQLException e){
                e.printStackTrace();
            }
        }
    }
}

  使用DBUtil工具类实现模糊查询

public class jdbcTest05 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "select * from emp where ename like ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"_A%");    //模糊查询,ename中第二个字母是'A'
            rs = ps.executeQuery();
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtil.close(conn,ps,rs);
        }
    }
}

 

11. 行级锁

  select ename,job,sal from emp where job = 'manager' for update;

  当在select语句之后添加for update之后就表示给查询处理的整行完整数据添加了行级锁,当添加了行级锁之后,事务2就不能对数据进行修改了,必须等待事务1提交之后才能进行修改数据

  使用两个事务1,2模拟行级锁

/**
 * 演示行级锁
 * 事务1
 */
public class jdbcTest06 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try{
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);
            String sql = "select ename,job,sal from emp_bak where job = ? for update";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");
            rs = ps.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("ename") + " "+ rs.getString("job") + " "+ rs.getDouble("sal"));
            }
            conn.commit();  //添加行级锁后,在此处打断点,模拟事务1还没执行完查询,那么事务2(jdbcTest07)就不能进行更新,没有返回值,当事务1执行完成后,事务2才执行更新
        }catch (Exception e){
            if(conn != null){
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally {
            DBUtil.close(conn,ps,rs);
        }
    }
}
/**
 * 演示行级锁
 * 事务2
 */
public class jdbcTest07 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);
            String sql = "update emp_bak set sal = sal * 1.1 where job = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");
            int result = ps.executeUpdate();
            System.out.println(result);
            conn.commit();
        } catch (Exception e) {
            if(conn != null){
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally {
            DBUtil.close(conn,ps,null);
        }
    }
}

 

posted on 2021-08-02 22:06  homle  阅读(55)  评论(0编辑  收藏  举报