JDBC

JDBC(Java DataBase Connectivity,Java语言连接数据库)是SUN公司制定的一套接口(interface)

接口都具有调用者和实现者,面向接口调用、面向接口编写实现类都属于面向接口编程。为什么要面向接口编程呢?解耦,降低程序的耦合度提供程序的扩展性。多态机制就是典型的面向抽象编程(不要面向具体编程)。

为什么SUN会制定一套JDBC接口呢?因为每种数据库产品的厂商的底层实现原理都不一样

JDBC

例如:模拟Java程序员、SUN公司、数据库厂商对JDBC操作

SUN公司制定JDBC接口标准

$ vim JDBC.java
public interface JDBC {
  void getConnection();
}

MySQL厂商实现JDBC接口

$ vim MySql.java
public class MySQL implements JDBC {
  public void getConnection(){
    System.out.println("MySQL");
  }
}

Oracle厂商实现JDBC接口

$ vim Oracle.java
public class Oracle implements JDBC {
  public void getConnection(){
    System.out.println("Oracle");
  }
}

SqlServer厂商实现JDBC接口

$ vim SqlServer.java
public class SqlServer implements JDBC {
  public void getConnection(){
    System.out.println("SqlServer");
  }
}

Java程序面向接口编程

$ vim jdbc.properties
driverName=MySQL

读取配置创建对象

$ vim Test.java
import java.util.*;

public class Test {
  public static void main(String[] args) throws Exception {
    ResourceBundle rb = ResourceBundle.getBundle("jdbc");
    String driverName = rb.getString("driverName");

    Class c = Class.forName(driverName);
    JDBC jdbc = (JDBC)c.newInstance();

    jdbc.getConnection();
  }
}

配置JDBC驱动类型

JDBC开发准备工作,需要先从数据库厂商官网下载对应的驱动jar包,然后将其配置到环境变量classpath中。

例如:MySQL的JDBC驱动

oracle
配置classpath
$ echo %classpath%
.;D:\tomcat\apache-tomcat-8.5.82\lib\servlet-api.jar;E:\java\jdbc\mysql-connector-java-8.0.30.jar;

JDBC编程

JDBC
  1. 注册驱动:告知JVM即将连接的是那个厂商的数据库
  2. 获取连接:打开JVM进程与数据库进程之间的通道,进程之间通信完毕后需关闭
  3. 获取数据库操作对象(专门执行SQL语句)。
  4. 执行SQL语句:DQL、DML...
  5. 处理查询结果集:SELECT语句查询结果才返回结果集
  6. 释放资源:Java和数据库属于进程间的通信开启后一定要关闭
$ vim Test.java
import java.sql.*;

public class Test{
    public static void main(String[] args){
        Connection conn = null;
        Statement stmt = null;
        try{
            Driver d = new com.mysql.cj.jdbc.Driver();
            DriverManager.registerDriver(d);

            String url = "jdbc:mysql://127.0.0.1:3306/fw?serverTimeZone=UTC";
            String user = "root";
            String password = "q7tkI4QvegrjUTCm";
            conn = DriverManager.getConnection(url, user, password);
            //System.out.println(conn);//com.mysql.cj.jdbc.ConnectionImpl@7f416310
            
            stmt = conn.createStatement();

            String sql;
            sql = "INSERT INTO `sys_user`(`username`, `password`) VALUES('admin', MD5('admin'))";
            int n = stmt.executeUpdate(sql);
            System.out.println(n);//1

            sql = "SELECT * FROM `sys_user` WHERE 1=1";
            ResultSet rs = stmt.executeQuery(sql);
            System.out.println(rs);//com.mysql.cj.jdbc.result.ResultSetImpl@31206beb

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

注册驱动

Driver d = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(d);

简化写法(常用)

Class.forName("com.mysql.cj.jdbc.Driver");

为什么可以这样写呢?利用反射加载类时,目标类中的静态代码块会自动运行。

源码分析

这样写的好处是由于参数是一个字符串,因此可写入配置文件xxx.properties

import java.sql.*;

public class Test{
    public static void main(String[] args){
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
        }catch(SQLException e){
            e.printStackTrace();
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }finally{
        }
    }
}

properties

将配置信息抽取形成配置文件,通过读取配置文件完成数据库连接。

  • *.properties资源属性文件必须存放在项目下src目录下才能被正常识别

创建属性配置文件

$ vim jdbc.properties
className=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/fw?serverTimeZone=UTC
user=root
password=q7tkI4QvegrjUTCm

使用资源绑定器绑定资源配置文件

ResourceBundle rb = ResourceBundle.getBundle("jdbc");
String className = rb.getString("className");
String url = rb.getString("url");
String user = rb.getString("user");
String password = rb.getString("password");

完整代码

$ vim Test.java
import java.sql.*;
import java.util.ResourceBundle;
import java.util.*;

public class Test{
    public static void main(String[] args){
        ResourceBundle rb = ResourceBundle.getBundle("jdbc");
        String className = rb.getString("className");
        String url = rb.getString("url");
        String user = rb.getString("user");
        String password = rb.getString("password");

        Connection conn = null;
        Statement stmt = null;
        try{
            Class.forName(className);
            conn = DriverManager.getConnection(url, user, password);
            //System.out.println(conn);//com.mysql.cj.jdbc.ConnectionImpl@7f416310
            
            stmt = conn.createStatement();

            String sql;
            sql = "INSERT INTO `sys_user`(`username`, `password`) VALUES('admin', MD5('admin'))";
            int n = stmt.executeUpdate(sql);
            System.out.println(n);//1

            sql = "SELECT * FROM `sys_user` WHERE 1=1";
            ResultSet rs = stmt.executeQuery(sql);
            System.out.println(rs);//com.mysql.cj.jdbc.result.ResultSetImpl@31206beb

        }catch(Exception e){
            e.printStackTrace();
        }finally{
            try{
                if(stmt != null){
                    stmt.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }

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

ResultSet

$ vim Test.java
import java.sql.*;
import java.util.*;

public class Test{
    public static void main(String[] args){
        ResourceBundle rb = ResourceBundle.getBundle("jdbc");
        String className = rb.getString("className");
        String url = rb.getString("url");
        String user = rb.getString("user");
        String password = rb.getString("password");

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try{
            Class.forName(className);
            conn = DriverManager.getConnection(url, user, password);
            stmt = conn.createStatement();
            
            String sql = "SELECT * FROM `sys_user` WHERE 1=1";
            rs = stmt.executeQuery(sql);
            while(rs.next()){
                System.out.println("id=" + rs.getString(1) + ", username=" + rs.getString("username"));
            }

        }catch(Exception e){
            e.printStackTrace();
        }finally{
            try{
                if(rs != null){
                    rs.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }
            try{
                if(stmt != null){
                    stmt.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }
            try{
                if(conn != null){
                    conn.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }
        }
    }
}

IDEA

创建空项目

创建空项目

项目内新建Java模块

Java模块

为模块配置JDBC驱动

添加类库
外部库

SQL注入

使用Statement执行SQL语句时存在SQL注入的运行风险,解决方案是采用预编译SQL语句框架后在传值。预编译的SQL语句中是用?作为占位符用于接收值,进而生成SQL语句框架。

String sql = "SELECT * FROM `sys_user` WHERE 1=1 AND `username` = ? AND `password` = MD5(?)";
ps = conn.prepareStatement(sql);
ps.setString(1, username.trim());
ps.setString(2, password.trim());
rs = ps.executeQuery();
if(rs.next()){
    flag = true;
}

预处理机制为什么可以预防SQL注入呢?因为SQL语句编译阶段会进行词法分析、语法分析、语义分析等过程,简单来说编译过程会识别SQL关键字、执行逻辑等,编译一旦结束SQL语句什么也就确定了。预处理也就是在SQL语句编译之后再赋值,此时拼接出来的SQL语句已经没有办法再改变原来的执行逻辑,占位符部分就只是相当于输入字符串被处理,其中的关键字也就没有实际的意义。所以说SQL注入只对编译过程有破坏作用,执行阶段如果只是把占位符的输入串作为数据处理,就不需要再对SQL语句进行解析,因此解决了注入问题。

  • ?禁止使用引号包裹,包裹后预处理将失效。
  • 多个?占位符在JDBC中下标从1开始递增
package com.jc.jdbc;

import java.sql.*;
import java.util.*;

public class Test {
    public static boolean login(Map<String,String> m){
        boolean flag = false;

        String username = m.get("username");
        String password = m.get("password");

        ResourceBundle rb = ResourceBundle.getBundle("jdbc");

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Class.forName(rb.getString("className"));
            conn = DriverManager.getConnection(rb.getString("url"), rb.getString("user"), rb.getString("password"));

            String sql = "SELECT * FROM `sys_user` WHERE 1=1 AND `username` = ? AND `password` = MD5(?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, username.trim());
            ps.setString(2, password.trim());
            rs = ps.executeQuery();
            if(rs.next()){
                flag = true;
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(ps != null){
                    ps.close();
                }
            }catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(conn != null){
                    conn.close();
                }
            }catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return flag;
    }
    public static Map<String, String> initUI(){
        Scanner s = new Scanner(System.in);
        System.out.print("usename:");
        String username = s.nextLine();

        System.out.print("password:");
        String password = s.nextLine();

        Map<String,String> m = new HashMap<>();
        m.put("username", username);
        m.put("password", password);

        return m;
    }

    public static void main(String[] args) {
        boolean b = login(initUI());
        System.out.println(b?"SUCCESS":"ERROR");
    }
}

Statement与PreparedStatement二者之间的执行效率那个会更高一些呢?很明显PreparedState由于是先编译后赋值,因此多次执行就无需重复编译。相比Statement,每次都需要经过编译和执行两个阶段,效率也更高。

另外PreparedStatment预编译相比Statement会在编译期间做类型的安全检查,相比较Statement更为优越。

事务控制

JDBC中事务默认是自动提交的,也就是说只要执行一条DML语句就会自动自动提交一次。而实际开发中通常是多条DML语句共同联合才能完成,必须保证多个DML语句在同一个事务中同时成功或同时失败。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值