java进阶-第9章-JDBC

一、引言


数据库访问层

该层所做事务直接操作数据库,针对数据的增添、删除、修改、更新、查找等。
简单的说法就是实现对数据表的Select,Insert,Update,Delete的操作。如果要加入ORM的元素,那么就会包括对象和数据表之间的mapping,以及对象实体的持久化。

优点

1、开发人员可以只关注整个结构中的其中某一层;
2、可以很容易的用新的实现来替换原有层次的实现;
3、可以降低层与层之间的依赖;
4、有利于标准化;
5、利于各层逻辑的复用

缺点

1、降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
2、有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。

1.1 如何操作数据库

使用客户端工具访问数据库,需要手工建立连接,输入用户名和密码登录,编写 SQL 语句,点击执行,查看操作结果(结果集或受影响行数)。

java进阶-第9章-JDBC_第1张图片
1.2 实际开发中,会采用客户端操作数据库吗?

在实际开发中,当用户的数据发生改变时,不可能通过客户端操作执行 SQL 语句,因为操作量过大,无法保证效率和正确性。

二、JDBC(Java Database Connectivity)

2.1 什么是 JDBC?

JDBC(Java Database Connectivity) Java 连接数据库的规范(标准),可以使用 Java 语言连接数据库完成 CRUD (CREATE READ UPDATE DELETE)操作。

2.2 JDBC 核心思想

Java 中定义了访问数据库的接口,可以为多种关系型数据库提供统一的访问方式。由数据库厂商提供驱动实现类(Driver 数据库驱动)。

java进阶-第9章-JDBC_第2张图片

2.2.1 MySQL 数据库驱动
  • mysql-connector-java-5.1.X  适用于 5.X 版本
  • mysql-connector-java-8.0.X  适用于 8.X版本
2.2.2 JDBC API

JDBC 是由多个接口和类进行功能实现。

类型 权限定名 简介
class java.sql.DriverManager 管理多个数据库驱动类,提供了获取数据库连接的方法
interface java.sql.Connection 代表一个数据库连接(当connection不是null时,表示已连接数据库)
interface java.sql.Statement 发送SQL语句到数据库工具
interface java.sql.ResultSet 保存SQL查询语句的结果数据(结果集)
class java.sql.SQLException 处理数据库应用程序时所发生的异常
2.3 环境搭建
  • 在项目下新建 lib 文件夹,用于存放 jar 文件。
  • 将 mysql 驱动mysql-connector-java-5.1.X复制到项目的 lib 文件夹中。
  • 选中 lib 文件夹右键 Add as Libraay,点击 OK。

三、JDBC 开发步骤【重点


3.1 注册驱动

使用 Class.forName(“com.mysql.jdbc.Driver”);手动加载字节码文件到 JVM 中。

Class.forName("com.mysql.jdbc.Driver");//加载驱动
3.2 连接数据库
  • 通过 DriverManager.getConnection(url,user,password) 获取数据库连接对象
    • URL:jdbc:mysql://localhost:3306/database
    • username:root
    • password:1234
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/tuling?useUnicode=true&characterEncoding=utf8", "root","1234");
  • URL(Uniform Resource Locator) 统一资源定位符:由协议、IP、端口、SID(程序实例名称)组成
3.3 获取发送 SQL 的对象

通过 Connection 对象获得 Statement 对象,用于对数据库进行通用访问。

Statement statement = conn.createStatement();
3.4 执行SQL 语句

执行 SQL 语句并接收执行结果。

String sql ="INSERT INTO t_jobs(JOB_ID,JOB_TITLE,MIN_SALARY,MAX_SALARY) VALUES('JAVA_Le','JAVA_Lecturer',4000,10000);";

int result = statement.executeUpdate(sql);//执行SQL语句并接收结果
  • 注意:在编写 DML 语句时,一定要注意字符串参数的符号是单引号 ‘值’
  • DML 语句:增删改时,返回受影响行数(int 类型)。
  • DQL 语句:查询时,返回结果数据(ResultSet 结果集)。
3.5 处理结果

接受处理操作结果。

if(result == 1){
	System.out.println("Success");
}
  • 受影响行数:逻辑判断、方法返回。
  • 查询结果集:迭代、依次获取。
3.6 释放资源

遵循先开后关原则,释放所使用到的资源对象。

statement.close();
conn.close();
3.7 综合案例

整合以上核心六步,实现向数据库表中插入一条数据。


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class DeleteJdbc {
    public static void main(String[] args) throws Exception{
        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获得连接对象
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb","root","1234");
        //3.获得执行SQL的对象
        Statement statement = connection.createStatement();
        //4.执行SQL语句,并接收结果
        int result = statement.executeUpdate("delete from t_jobs where job_id = 'H5_mgr';");
        //5.处理结果
        if(result==1){
            System.out.println("删除成功!");
        }else{
            System.out.println("删除失败!");
        }
        //6.释放资源
        statement.close();
        connection.close();
    }
}

四、ResultSet(结果集)

在执行查询 SQL 后,存放查询到的结果集数据。

4.1 接收结果集

ResultSet rs = statement.executeQuery(sql);

ResultSet rs= statement.executeQuery("SELECT * FROM t_employees;");
4.2 遍历 ResultSet 中的数据

ResultSet 以表(table)结构进行临时结果的存储,需要通过 JDBC API 将其中数据进行依次获取。

  • 数据行指针:初始位置在第一行数据前,每调用一次 boolean next()方法ResultSet 的指针向下移动一行,结果为 true,表示当前行有数据。
  • rs.getXxx(整数);代表根据列的编号顺序获得,从 1 开始。
  • rs.getXxx(“列名”);代表根据列名获得。
boolean next() throws SQLException  //判断 rs 结果集中下一行是否存在数据
4.2.1 遍历方法
int getInt(int columnIndex) throws SQLException //获得当前行第N列的int值
int getInt(String columnLabel) throws SQLException //获得当前行columnLabel列的int值

double getDouble(int columnIndex) throws SQLException //获得当前行第N列的double值
double getDouble(String columnLabel) throws SQLException //获得当前行columnLabel列的double值

String getString(int columnIndex) throws SQLException //获得当前行第N列的String值
String getString(String columnLabel) throws SQLException //获得当前行columnLabel列的String值

......
  • 注意:列的编号从 1 开始。
4.3 综合案例

对 t_jobs 表中的所有数据进行遍历。

4.3.1 根据列的名称获取
import java.sql.*;

public class JobsQuery {
    public static void main(String[] args) {
            //1.加载驱动
            Class.forName("com.mysql.jdbc.Driver");

            //2.获取数据库连接对象
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/product", "root", "1234");
      
            //3.获取发送 sql 语句对象
            Statement statement = connection.createStatement();

            //4.执行 SQL 语句并接收结果集
            ResultSet resultSet = statement.executeQuery("select  * from t_jobs");
            //5 处理结果集
            while(resultSet.next()){
                //5.1有数据,依据列名获取数据
                String job_id = resultSet.getString("job_id");
                String job_title = resultSet.getString("job_title");
                int min_salary = resultSet.getInt("min_salary");
                int max_salary = resultSet.getInt("max_salary");
                System.out.println(job_id+"\t"+job_title+"\t"+min_salary+"\t"+max_salary);
            }
       
                //6.释放资源
               rs.close();
      				 statement.close();
      				 connection.close();
        }
    }
    }
}
4.3.2 根据列的编号获取数据
						//。。。。与上无异
            while(resultSet.next()){
                //5.2有数据,依据列名获取数据
                String job_id = resultSet.getString(1);
                String job_title = resultSet.getString(2);
                int min_salary = resultSet.getInt(3);
                int max_salary = resultSet.getInt(4);
                System.out.println(job_id+"\t"+job_title+"\t"+min_salary+"\t"+max_salary);
            }
						//释放资源

五、 常见错误

  • java.lang.ClassNotFoundException:找不到类(类名书写错误、没有导入jar包)
  • java.sql.SQLException:与sql语句相关的错误 (约束错误、表名列名书写错误) 建议:在客户端工具中测试SQL语句之后再粘贴在代码中
  • com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column   原因:列值Sting类型没有加单引号
  • Duplicate entry ‘1’ for key ‘PRIMARY’    原因,主键值已存在或混乱,更改主键值或清空表
  • com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column ‘password’ in
    • 原因:可能输入的值的类型不对,确定是否插入的元素时对应的值的类型正确

六、 综合案例【登录】

6.1 创建表
  • 创建一张用户表 User
    • id ,主键、自动增长。
    • 用户名,字符串类型,唯一、非空
    • 密码,字符串类型,非空
    • 手机号码,字符串类型
  • 插入 2 条测试语句
6.2 实现登录
  • 通过控制台用户输入用户名和密码。
  • 用户输入的用户名和密码作为条件,编写查询 SQL 语句。
  • 如果该用户存在,提示登录成功,反之提示失败。

七、SQL注入问题

7.1 什么是 SQL 注入

用户输入的数据中有 SQL 关键字或语法并且参与了 SQL 语句的编译,导致 SQL 语句编译后的条件含义为 true,一直得到正确的结果。这种现象称为 SQL 注入。

需求:实现登录功能,需要通过传入的登录名和密码查询数据库表中的用户是否存在。
如果使用的是Statement语句来执行sql语句。

String sql="select * from account where username='"+loginName+"' and password='"+loginPwd+"'";

如果应用中传入:

》请输入用户名:
test1
》请输入密码:
abc' or '1'='1

username="test1"
password="abc' or '1'='1"

那么当上面的sql拼接变量值之后的sql语句为

select * from account where username='test1' and password='abc' or '1'='1'

那么将登录成功。

7.2 如何避免 SQL 注入

由于编写的 SQL 语句是在用户输入数据,整合后再进行编译。所以为了避免 SQL 注入的问题,我们要使 SQL 语句在用户输入数据前就已进行编译成完整的 SQL 语句,再进行填充数据。

八、PreparedStatement【重点


PreparedStatement 继承了 Statement 接口,执行 SQL 语句的方法无异。

8.1 PreparedStatement的应用

作用:

  • 预编译SQL 语句,效率高。
    PS:什么是预编译?
    预编译语句PreparedStatement是java.sql中的一个接口,它是Statement的子接口。通过Statement对象执行sql语句时,需要将sql语句发送给DBMS,由DBMS首先进行编译再执行(在创建通道的时候并不进行sql的编译工作,事实上也无法进行编译)。而通过PreparedStatement不同,在创建PreparedStatement对象时就指定了sql语句,该语句立即发送给DBMS进行编译,当该语句被执行时,DBMS直接运行编译后的sql语句,而不需要像其他sql语句那样首先将被编译。
    一般在考虑反复使用一个sql语句时才使用预编译,预编译语句常常放在一个循环中使用(在这种情况下预编译的优势就很明显了),通过反复设置参数从而达到多次使用该语句;
  • 安全,避免SQL注入 。
  • 可以动态的填充数据,执行多个同构的 SQL 语句。
8.1.1 参数标记
//1.预编译 SQL 语句
PreparedStatement pstmt = conn.prepareStatement("select * from user where username=? and password=?");
  • 注意:JDBC中的所有参数都由 ?符号占位,这被称为参数标记。在执行SQL语句之前,必须为每个参数提供值。
  • 简化SQL语句的编写,提高SQL语句执行效率,提高安全性。
8.1.2 动态参数绑定

pstmt.setXxx(下标,值) 参数下标从 1 开始,为指定参数下标绑定值

//1.预编译 SQL 语句
PreparedStatement pstmt = conn.prepareStatement("select * from user where username=? and password=?");	 
//2.为参数下标赋值
pstmt.setString(1,username);
pstmt.setString(2,password);

九、封装工具类

  • 在实际JDBC的使用中,存在着大量的重复代码:例如连接数据库、关闭数据库等这些操作!
  • 我们需要把传统的JDBC代码进行重构,抽取出通用的JDBC工具类!以后连接任何数据库、释放资源都可以使用这个工具类。
9.1 重用性方案
  • 封装获取连接、释放资源两个方法。
    • 提供public static Connection getConnection(){}方法。
    • 提供public static void closeAll(Connection conn , Statement sm , ResultSet rs){}方法。
9.1.1 重用工具类实现
import java.sql.*;

/**
 * 重用性方案
 * 获取连接
 * 释放资源
 */
public class DBUtils {

    static {//类加载,执行一次!
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    //1.获取连接
    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb", "root", "1234");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    //2.释放资源
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
9.2 跨平台方案
  • 定义public static final Properties prop = new Properties(); //读取配置文件的Map
  • 定义static{
    • //首次使用工具类时,加载驱动
      InputStream is = JDBCUtil.class.getResourceAsStream(“路径”); //通过复用本类自带流,读取jdbc.properties配置文件。classPath = bin
    • prop.load(is); //通过prop对象将流中的配置信息分割成键值对
    • String driverName = prop.getProperty(“driver”); //通过driverName的键获取对应的值(com.mysql.jdbc.Driver)
    • Class.forName(driverName); //加载驱动
      }
9.2.1 跨平台工具类实现

在src 目录下新建 db.properties 文件。

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb3
user=root
password=1234

工具类的封装。


import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class DBUtils {
    private static final Properties PROPERTIES = new Properties();//存储配置文件的map

    static {

        InputStream is = DBUtils.class.getResourceAsStream("/db.properties");

        try {
            PROPERTIES.load(is);//通过流,将配置文件内容加载到properties集合
            Class.forName(PROPERTIES.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(java,mysql,java,数据库,oracle)