JDBC

104 阅读4分钟

JDBC 是什么

Java DataBase Connectivity(Java语言连接数据库)

JDBC的本质是什么? JDBC是SUN公司制定的一套接口(interface) 接口都有调用者和实现者。 面向接口调用、面向接口写实现类,这都属于面向接口编程。

为什么要面向接口编程? 解耦合:降低程序的耦合度,提高程序的扩展力. 多态机制就是非常典型的面向抽象编程(不要面向具体编程)

建议: Animal a = new Cat(); Animal b = new Dog();

// 喂养的方法 public void feed(Animal a){//面向父类编程}

不建议: Dog d=new Dog(); Cat c=new Cat();

JDBC编程六步

    • 注册驱动(作用:告诉java程序,即将要连接的是哪个品牌的数据库)
    • 获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完一定要释放)
    • 获取数据库操作对象
    • 执行SQL(DQL ,DML……)
    • 处理查询结果集(只有当第4步执行的是select的时候才有第五步处理查询结果集)
    • 释放资源(使用完资源后一定要关闭资源,Java和数据库之间属于进程间通信,使用后一定要关闭)

登录案例(使用Statement的存在SQL注入问题,使用PreparedStatement不存在该问题)

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;

public class Utils {

    public static Map<String, String> initUI() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入用户名");
        String name = scanner.nextLine();
        System.out.print("请输入密码");
        String password = scanner.nextLine();
        Map<String, String> userInfoMap = new HashMap<>();
        userInfoMap.put("name", name);
        userInfoMap.put("password", password);

        return userInfoMap;
    }
    public static void loginByStatement(Map<String, String> userInfoMap) {
        /**
         * JDBC连接数据库6步骤
         * 1 加载驱动
         * 2 获取连接对象
         * 3 创建数据库操作对象
         * 4 执行sql
         * 5 处理resultSet
         * 6 释放资源
         * */
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        String loginName = userInfoMap.get("name");
        String loginPassword = userInfoMap.get("password");
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
            String url = resourceBundle.getString("url");
            String user = resourceBundle.getString("user");
            String password = resourceBundle.getString("password");
            connection = DriverManager.getConnection(url, user, password);
            statement = connection.createStatement();
            String sql = "select * from t_user where name='" + loginName + "' and password='" + loginPassword + "'";
            resultSet = statement.executeQuery(sql);
            if (resultSet.next()) {
                System.out.println("登录成功");
            } else {
                System.out.println("登录失败");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void loginByPreparedStatement(Map<String, String> userInfoMap) {
        /**
         * JDBC连接数据库6步骤
         * 1 加载驱动
         * 2 获取连接对象
         * 3 创建数据库操作对象
         * 4 执行sql
         * 5 处理resultSet
         * 6 释放资源
         * */
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        String loginName = userInfoMap.get("name");
        String loginPassword = userInfoMap.get("password");
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
            String url = resourceBundle.getString("url");
            String user = resourceBundle.getString("user");
            String password = resourceBundle.getString("password");
            connection = DriverManager.getConnection(url, user, password);
            String sql = "select * from t_user where name=? where password=?";
            statement = connection.prepareStatement(sql);
            statement.setString(1,loginName);
            statement.setString(2,loginPassword);
            resultSet = statement.executeQuery();
            if (resultSet.next()) {
                System.out.println("登录成功");
            } else {
                System.out.println("登录失败");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (resultSet != null) {
                    resultS et.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

解决sql注入的关键

用户提供的信息中即使含有sql语句的关键字,但是这些关键字不能参与编译,就可以法解决

对比Statement和PreparedStatement

  • statement存在sql注入问题,PreparedStatement不存在
  • statement是编译一次执行一次,PreparedStatement是编译一次执行多次,PreparedStatement 效率高一些
  • PreparedStatement 会在类型编译阶段做类型安全检查
  • 当需要用到sql注入的时候必须使用Statement,不需要的时候使用PreparedStatement

JDBC事务

1 JDBC中的事务是自动提交的,什么是自动提交? 只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。但是在实际的业务当中,通常都是 N条SQL语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者失败。

2 JDBC转账实例

/**
 * 转账方法
 */
public static void transferAccount() {
    /**
     * JDBC连接数据库6步骤
     * 1 加载驱动
     * 2 获取连接对象
     * 3 创建数据库操作对象
     * 4 执行sql
     * 5 处理resultSet
     * 6 释放资源
     * */
    Connection connection = null;
    PreparedStatement preparedStatement = null;

    try {
        Class.forName("com.mysql.cj.jdbc.Driver");
        ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
        String url = resourceBundle.getString("url");
        String user = resourceBundle.getString("user");
        String password = resourceBundle.getString("password");
        connection = DriverManager.getConnection(url, user, password);
        // JDBC 开启事务
        connection.setAutoCommit(false);
        String sql = "update t_act set balance=? where actno=?";
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setDouble(1, 1000);
        preparedStatement.setInt(2, 111);
        int count = preparedStatement.executeUpdate();
        preparedStatement.setDouble(1, 19000);
        preparedStatement.setInt(2, 222);
        count += preparedStatement.executeUpdate();
        System.out.println(count == 2 ? "转账成功" : "转账失败");
        connection.commit();

    } catch (Exception e) {
        try {
            if (connection != null) {
                connection.rollback();
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
        e.printStackTrace();
    } finally {
        try {
            if (connection != null) {
                connection.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

当需要执行多个sal来完成一个业务的时候,为了保证业务数据的正常,需要保证每个sql都执行完成才能确定成功,可以通过connection.setAutoCommit(false) 方法开启事务,当DML语句都成功时,执行connection.commit()方法提交事务。当catch到异常时候,执行connection.rollback()将事务进行回滚,以此来保证业务的完整性和正确性。

悲观锁(行级锁)和乐观锁

实例: select * from emp where job ='MANAGER' for update;

悲观锁关键词: for update 悲观锁不允许并发,一个事务锁定后,其他事务只能等待. 乐观锁允许并发操作,但是有一个版本号,如果有两个事务同时读一条数据,其中一个事务进行了数据修改,并进行了提交,第二个进行修改的事务则会发现版本号不一致,将修改进行回滚