JDBC学习

一. JDBC本质

  • JDBC是SUN公司制定的一套接口interface
    接口都是调用者和实现者
    面向接口调用、面向接口写实现类,这都属于面向接口编程。
  • 为什么要面向 接口编程
    解耦合 :降低程序的耦合度,提高程序的扩展力。
    接口为什么能够降低耦合度
  • 多态机制就是非常典型的:面向抽象编程(不要面向具体编程)
    多态能降低耦合度

多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,就是同一种事物表现出的多种形态。编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。

比如有一个函数是叫某个人来吃饭,函数要求传递的参数是人的对象,可是来了一个美国人,你看到的可能是用刀和叉子在吃饭,而来了一个中国人你看到的可能是用筷子在吃饭,这就体现出了同样是一个方法,可以却产生了不同的形态,这就是多态!

如果父类与子类有同名的属性---------执行父类的属性
如果父类与子类有同名的方法(重写)----执行子类重写之后的方法
若想要调用子类中独有的成员
(强制类型转化) 造型 铸型 (向上/向下转型)

建议:

Animal a = new Cat();
Animal a = new Dog();
//当你定义其他函数,比如feed函数:可以定义feed(Animal a){}
//此时,只要你传入的参数是Animal以及子类都可以,
//传入参数Cat,就调用Cat的对象的方法属性
//传入参数Dog,就调用Dog的对象的方法属性
//这就是面向抽象编程,面向一个抽象的Animal,而不是面向具体的Dog或者Cat,
//这样,当有一天Dog和Cat删除了,也不影响我们的eat函数,因为Animal父类还在
public void feed(Animal a){
//面向父类类型编程
}

不建议:

Dog d = new Dog();
Cat c = new Cat();
  • 为什么SUN制定一套JDBC接口呢?
    因为每一个数据的底层实现原理都不一样。Oracle数据库有自己的原理。MySQL数据库也有自己的原理。每一个数据库产品都有自己独特的实现原理。程序员不可能挨个调用使用,所以设计JDBC接口。
    在这里插入图片描述

这样以后程序员面向JDBC接口写代码,调用JDBC提供的几个类。
JDBC其实就是一堆的class类文件。
这些接口的实现由各大数据库厂家自己进行编写。去实现接口中的各个类class。

二. 模拟JDBC的使用

Sun公司:制定接口
数据库厂家:实现接口,子类数据库厂家 继承 Sun公司的接口
程序员:通过多态,调用父类----接口中方法,实际调用 子类----数据库厂家 中 重写 接口的方法

Sun公司:

/*
SUN公司负责制定这套SUN接口
 */
public interface JDBC{
    //数据库连接方法
    void getConnection();
}

数据库厂家:

/*
Oracle的数据库厂家负责编写JDBC接口的实现方法
 */
public class Oracle implements JDBC{
    //数据库连接方法
    void getConnection(){
        System.out.println("连接Oracle数据库成功");
    }
}

/*
SqlServer的数据库厂家负责编写JDBC接口的实现方法
 */
public class SqlServer implements JDBC{
    //数据库连接方法
    void getConnection(){
        System.out.println("连接SqlServer数据库成功");
    }
}

程序员:new方法创建对象

/*
Java程序员角色:
不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码
面向接口编程,面向抽象编程,不要面向具体编程。
 */
public class JavaProgrammer{
    public static void main(String[] args){
        JDBC jdbc = new SqlServer(); //使用哪一个数据库,就new哪一个数据库
        //JDBC类型,调用接口中的方法就可以了
        jdbc.getConnection();  //多态:调用父类的方法即可
        // 子类重写了父类方法,默认调用子类重写的方法。
        // 不需要关注继承接口的具体数据库类是如何实现的,不用程序员自己去实现,
        // 数据库厂家自己进行实现

        //最终,我们调用接口中的抽象方法,但是却可以调用各个子类的堆抽象方法的实现。
    }
}

通过多态:JDBC jdbc = new SqlServer();,可以实现调用父类的方法名字,实际上调用的是子类重写父类的方法。
这样以后想要换一家数据库也只需要,修改一个代码JDBC jdbc = new Oracle();即可,后面的代码调用方法也都是调用父类的JDBC的方法(虽然实际调用的是子类的方法),不需要修改其他。

但是如果你就面向具体编程了,比如SqlServer jdbc = new SqlServer();那么不同厂家有不同的函数名,就比如连接这个函数,SqlServer厂家的方法是conn,Oracle厂家的方法是connection。
之前数据库使用SqlServer ,调用连接方法是jdbc.conn,
那么当你把数据库变换Oracle以后,首先类一定要修改Oracle jdbc = new Oracle();,那么调用方法就该为jdbc.connection。
修改的地方太多了,这样对程序员来说太难了,所以规定了一个JDBC接口,制定规则,各大厂家去实现这个接口,程序员只需要调用父类JDBC中的方法,就可以通过多态实际上调用的是子类重写的方法。
程序员:反射方法创建对象

public class JavaProgrammer{
    public static void main(String[] args){
        //除了new的方法创建对象
        //还可以通过反射的方法创建对象
        Class c = Class.forName("Oracle");
        JDBC jdbc = (JDBC)c.newInstance();
        jdbc.getConnection();    
    }
}

三. 配置文件

如果让客户在程序中修改使用数据库类型,不大现实,所以经常使用配置文件,来帮助用户随时修改使用哪种数据库。
首先定义一个配置文件:jdbc.properties文件:里面只有一行:
className = Oracle
然后程序员的代码变为:

public class JavaProgrammer{
    public static void main(String[] args) throws Exception {
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String className = bundle.getString("className");//里面输入key值,即等号左边
        Class c = Class.forName(className);
        JDBC jdbc = (JDBC)c.newInstance();
        jdbc.getConnection();
    }
}
//此时,如果想要修改使用的数据库类型,只需要在配置文件中修改就可以了。
//改配置文件,就可以,以后都不用改代码了
所以,如果需要使用数据库的时候,需要自己下载对应数据库的jar包,然后配置到环境中,jar包实际上就是各种实现接口的类,即是一堆class文件。

四. JDBC编程六步走

  1. 注册驱动(作用:告诉JAVA程序员,即将要连接的是哪个品牌的数据库)
  2. 获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用之后一定要关闭)
  3. 获取数据库操作对象(专门执行sql语句的对象)
  4. 执行SQL语句(DQL DML…)
  5. 处理查询结果集(只有当第四步执行的是delect语句的时候,才有这第五步处理查询结果集)
  6. 释放资源(使用资源之后一定要关闭资源。Java和数据库属于进程之间的通信,重量级的,使用之后一定要关闭)
import java.sql.*;
public class JDBCTest01 {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        //由于要关闭的内容都在try中,不能关闭,所以讲try中的几个变量移到外面去:
        try{
            // 1、注册驱动
            Driver driver = new com.mysql.jdbc.Driver();	//多态,父类型引用指向子类型对象
            DriverManager.registerDriver(driver);
            //第1步也可以合为一句:
            //DriverManager.registerDriver(new com.mysql.jdbc.Driver());

             //注册驱动无非就是加载数据库的jar包,所以还可以通过反射机制
            //1、注册驱动另一种写法
            Class.forName("com.mysql.jdbc.Driver()");
			//反射会导致后面的类的加载,类加载就会自动执行静态代码块
			//直接完成了驱动注册,也不需要接收返回值

            // 2、获取连接
            String url = "jdbc:mysql://127.0.0.1:3306/mydatabase"; //url: 协议;IP;端口;资源名
            String user = "root";  // 数据库使用者
            String password = "146";  //密码
            conn = DriverManager.getConnection(url,user,password);

            // 3、获取数据库操作对象
            stmt = conn.createStatement();

            // 4、执行sql语句
            // int executeUpdate(String sql)  返回值是“影响数据库中的记录条数”
            int count = stmt.executeUpdate("update dept set dname = '销售部',loc = '合肥' where deptno = 20;");

            // 5、处理查询结果集  由于不是select,所以不用处理
        } catch(SQLException e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            // 从小到大依次关闭,每个资源都需要捕获异常
            //为保证资源一定释放,在finally语句块中关闭资源,分别对其try...catch...
            if(stmt != null) {
                try	{
                    stmt.close();
                }
                catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null) {
                try	{
                    conn.close();
                }
                catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

五. 数据库中所有字符串写到配置文件中

实际开发中不建议把连接数据库的信息写死到java程序中,所以考虑写到配置文件中。
在这里插入图片描述
在这里插入图片描述

六. 查select

rs = stmt.executeQuery("select empno,ename,sal from emp");

每次调用next方法,向下一行,有数据就返回true
在这里插入图片描述

主要如何处理查询结果:
方式一:

//通过下标获取查询结果,1就是第一列结果,2是第二列结果
String empno = rs.getString(1);
String ename = rs.getString(2);
String sal = rs.getString(3);
System.out.println(empno + "," + ename + "," + sal);

方式二:

// 按下标取出,程序不健壮
String empno = rs.getString("empno");
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno + "," + ename + "," + sal);

方式三:

// 以指定的格式取出
int empno = rs.getInt(1);
String ename = rs.getString(2);
double sal = rs.getDouble(3);
System.out.println(empno + "," + ename + "," + (sal + 100));

整体查询代码:

import java.sql.*;
import java.util.*;
public class JDBCTest05 {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try{
            ResourceBundle rb = ResourceBundle.getBundle("jdbc");
            String driver = rb.getString("driver");
            String url = rb.getString("url");
            String user = rb.getString("user");
            String password = rb.getString("password");

            // 1、注册驱动
            Class.forName(driver);
            // 2、建立连接
            conn = DriverManager.getConnection(url,user,password);
            // 3、获取数据库操作对象
            stmt = conn.createStatement();
            // 4、执行sql语句
            rs = stmt.executeQuery("select empno,ename,sal from emp");
            // 5、获取查询结果集
            while(rs.next()){
				/*
				String empno = rs.getString(1);
				String ename = rs.getString(2);
				String sal = rs.getString(3);
				System.out.println(empno + "," + ename + "," + sal);
				*/

				/*
				// 按下标取出,程序不健壮
				String empno = rs.getString("empno");
				String ename = rs.getString("ename");
				String sal = rs.getString("sal");
				System.out.println(empno + "," + ename + "," + sal);
				*/

				/*
				// 以指定的格式取出
				int empno = rs.getInt(1);
				String ename = rs.getString(2);
				double sal = rs.getDouble(3);
				System.out.println(empno + "," + ename + "," + (sal + 100));
				*/

                int empno = rs.getInt("empno");
                String ename = rs.getString("ename");
                double sal = rs.getDouble("sal");
                System.out.println(empno + "," + ename + "," + (sal + 200));
            }

        } catch(Exception e){
            e.printStackTrace();
        }finally{
            // 6、释放资源
            if(rs != null){
                try{
                    rs.close();
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
            if(stmt != null){
                try{
                    stmt.close();
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

七. 使用Statement可能导致SQL注入问题

实现功能:

  1. 需求:
    模拟用户登录功能的实现
  2. 业务描述:
    程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码。
    用户输入用户名和密码以后,提交信息,java程序收集到用户信息。
    java程序连接数据库验证用户和密码是否合法。
    合法:显示登录成功。
    不合法:显示登陆失败。

所以,代码实现:

String sql = "select * from t_user where loginName = '"+loginName + "'and loginPwd = '" + loginPwd + "'";
rs = stmt.executeQuery(sql);
    if(rs.next()){
        loginSucess = true;
    }

此时如果输入:
用户名:fdsa
密码:fdsa’ or ‘1’='1

‘1’ = '1’是对的,所以or后面是对的,就会导致整个表都被输出。所以rs.next()一定会输出。

问题出在用户输入的内容有sql的关键字导致SQL注入
导致SQL注入的根本原因是什么:用户输入信息中含有sql语句的关键字,而这些关键字参与sql语句的编译过程。导致sql语句的原意被扭曲,进而达到sql注入。

八. PreparedStatement解决SQL注入问题

只要不让他参加编译就可以了啊,但是上面的代码不行,正好完成了sql语句的拼接编译进去了。

//Statement stmt = null;  变为下面那句
PreparedStatement ps = null;

//然后后面的3变化
// 3、获取预编译的数据库操作
String sql = "select * from t_user where loginName = ? and loginPwd= ? ";
ps = conn.prepareStatement(sql);
//给占位符?传值(第一个问号下标是1,第二个问号下标是2)
ps.setString(1,loginNamme);
ps.setString(2,loginPwd);
// 4、执行sql语句
rs = ps.executeQuery();
if(rs.next()){
   loginSucess = true;
}

Statement和PreparedStatement的区别

  • Statement存在sql注入问题,PreparedStatement解决sql注入问题。
  • Statement是编译一次执行一次,PreparedStatement效率较高一些。
  • PreparedStatement会在编译阶段做类型的安全检查。

九. 业务需要SQL注入

  • 但有些系统需要SQL注入,这时使用statement。
    比如:京东升序降序。必须SQL注入,order。
  • 需要进行SQL拼接的时候需要SQL注入,如果只是传值,使用preparedstatement。
  • 综上:preparedstatement使用较多。只有极少情况下需要使用statement。

十. preparedstatement实现插入和删除

// 3、获取预编译的数据库操作
String sql = "insert into dept (deptno , dname, loc) values (?,?,?)";
ps = conn.prepareStatement(sql);
//给占位符?传值(第一个问号下标是1,第二个问号下标是2)
ps.setInt(1,60);
ps.setString(2,"销售部");
ps.setString(3,"上海");
// 4、执行sql语句
int count = ps.executeUpdate();

十一. JDBC事务

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

假设一个银行转账系统,数据库中有用户名actno int类型和银行余额balance double类型。
账户111:余额20000
账户222:余额0
现在手写代码,实现账户111给账户222转账10000元。按理来说,转账成功后:
账户111:余额10000
账户222:余额10000
正常情况下代码:

			//设置账户1:
            // 3、获取预编译的数据库操作
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);
            //给占位符?传值(第一个问号下标是1,第二个问号下标是2)
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            // 4、执行sql语句
            int count = ps.executeUpdate();
            
            //设置账户2:
            ps.setDouble(1,10000);
            ps.setInt(2,222);
            // 4、执行sql语句
            count += ps.executeUpdate();
            System.out.println(count == 2?"转账成功":"转账失败");

//正常情况下:输出:转账成功
//账户111余额为10000
//账户222余额10000

制造异常:
验证JDBC事务是否是默认提交的。

//设置账户1:
            // 3、获取预编译的数据库操作
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);
            //给占位符?传值(第一个问号下标是1,第二个问号下标是2)
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            // 4、执行sql语句
            int count = ps.executeUpdate();

            //##############制造异常###########
            String s = null;
            s.toString();
            //异常会导致程序从这里直接退出,导致账户2未收到转账信息

            //设置账户2:
            ps.setDouble(1,10000);
            ps.setInt(2,222);
            // 4、执行sql语句
            count += ps.executeUpdate();
            System.out.println(count == 2?"转账成功":"转账失败");
//账户111余额为10000
//账户222余额0
//因为异常导致,直接不执行后面的语句,直接执行finally了。
//账户2没能执行到,凭空丢了10000

在这里插入图片描述

修改默认提交:
conn.setAutoCommit(false); 在第2步,链接后面,加上,将自动提交机制修改为手动提交
conn.commit();当多个数据库语句成功执行完毕,说明没有异常,事务结束,手动提交。

if(conn != null){
                conn.rollback();
            }

如果有异常,判断conn是否为空,不为null,则应当进行回滚操作。

import java.sql.*;
public class JDBCTest01 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try{
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver()");

            // 2、获取连接
            String url = "jdbc:mysql://127.0.0.1:3306/mydatabase"; //url: 协议;IP;端口;资源名
            String user = "root";  // 数据库使用者
            String password = "146";  //密码
            conn = DriverManager.getConnection(url,user,password);
            //**************将自动提交机制修改为手动提交**************
            conn.setAutoCommit(false);

            //设置账户1:
            // 3、获取预编译的数据库操作
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);
            //给占位符?传值(第一个问号下标是1,第二个问号下标是2)
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            // 4、执行sql语句
            int count = ps.executeUpdate();

            //##############制造异常###########
            String s = null;
            s.toString();
            //如果有异常,就进行回滚操作,没有异常就正常手动提交。

            //设置账户2:
            ps.setDouble(1,10000);
            ps.setInt(2,222);
            // 4、执行sql语句
            count += ps.executeUpdate();
            System.out.println(count == 2?"转账成功":"转账失败");
            //能够执行到这,说明程序没有异常,事务结束,手动提交
            conn.commit();

            // 5、处理查询结果集  由于不是select,所以不用处理
        } catch(SQLException e) {
            if(conn != null){
                conn.rollback();
            }
            e.printStackTrace();
        } finally {
            // 6、释放资源
            // 从小到大依次关闭,每个资源都需要捕获异常
            if(ps != null) {
                try	{
                    ps.close();
                }
                catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null) {
                try	{
                    conn.close();
                }
                catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

十二. 封装成工具类

将刚才的代码封装成工具类,防止每天写重复的代码。

import java.sql.*;

public class DBUtil {
    /*
    工具类中的构造方法都是私有的:https://blog.youkuaiyun.com/static_void_james/article/details/78254465
    因为工具类当中方法都是静态的,不需要new对象,直接采样类名调用。
    和类绑定,这样每次调用就不需要new对象。
     */
    private DBUtil(){};

    //静态块,加载类的时候自动执行,使用数据库一定要先注册驱动,所以讲这一部分写在静态块中
    static {
        try {
            Class.forName("jdbc:mysql://127.0.0.1:3306/mydatabase");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
                "jdbc:mysql://127.0.0.1:3306/mydatabase",
                "root",
                "146");
    }

    public static void close(Connection conn,Statement ps, ResultSet rs){//面向父类编程,Statement p而不是PreparesStatement,更具有一般性。
        if(ps != null) {
            try	{
                ps.close();
            }
            catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn != null) {
            try	{
                conn.close();
            }
            catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

使用工具类进行模糊查询:

public class JDBCTest{
    public static void main(String[] args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "select ename from emp where ename like ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"_A%");
            rs = ps.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(conn, ps, rs);
        }
    }
}

十三. 行级锁;悲观锁;乐观锁

  • 行级锁
    select ename, job, sal from emp where job='MANAGER' for update;
    数据库查询的时候有了for update表示行级锁。
    加一个for update就是行级锁,工作岗位是MANAGER中的数据被锁住,其他线程不能够操作。
    当前事务还没有结束的时候,这几行数据被锁住。
  • 悲观锁:
    事务必须排队执行,数据锁住,不允许并发。
  • 乐观锁:
    意思是任何人都可以修改,但是数据库中后面有一个版本号,比如我当时取走数据的时候版本号1.0,另一个线程修改了数据库版本号变为2.0,我提交自己数据的时候发现版本号和刚才的不一样了,所以会进行回滚,不修改数据。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值