JDBC基础详解

一、JDBC概念

JDBC(Java数据库连接)就是使用Java语言操作关系型数据库的一套API,定义了一套操作所有的关系型数据库的规则,也就是一套标准接口,而不同的数据库对于JDBC有不同的实现类(驱动),真正执行的代码是驱动jar包中的实现类。

二、JDBC快速入门

1、创建工程,导入驱动jar包
2、注册驱动
3、获取连接
4、定义SQL语句
5、获取执行SQL对象
6、执行SQL
7、处理返回结果
8、释放资源

三、具体过程

1、创建一个project
intellij IDEA——File——New——Project——Empty Project——Next——起一个名字
2、配置
File——Project Structure——Project——Project SDK中改为1.8版本,下面选8
3、创建模块
File——Project Structure——Modules——点+号——New Module——Java——起个名
4、导入驱动jar包
创建一个新的文件夹名为lib——将mysql的jar包复制粘贴到lib下
需要让当前工程识别jar包:右键——Add as Library——选择当前jar包生效的范围(Global Library:全局有效 Project Library:当前工程有效 Module Library:模块有效),选择模块有效即可
5、创建类
src下创建java class,包名.类名
6、代码
(psvm主方法快捷)
(sout打印快捷键)

package com.itexam.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1、创建驱动,将driver类加载进内存
        Class.forName("com.mysql.cj.jdbc.Driver"); 

        //2、获取连接
        String url = "jdbc:mysql://localhost:3306/db1?serverTimezone=GMT";
        String username = "root";
        String password = "123456";
        Connection conn = DriverManager.getConnection(url,username,password);

        //3、定义sql
        String sql = "UPDATE account set money = 2000 where id = 1";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        //5、执行sql
        int count = stmt.executeUpdate(sql);

        //6、处理结果
        System.out.println(count);

        //7、释放资源
        stmt.close();
        conn.close();
    }
}

四、JDBC API详解

(一)DriverManager(驱动管理类)

1、注册驱动
Class.forName(“com.mysql.cj.jdbc.Driver”);
MYSQL5之后的驱动包可以省略注册驱动的步骤,因为会自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类

2、获取数据库连接
Connection conn = DriverManager.getConnection(url,username,password);
(1)url:连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
注意:
如果连接的是本机mysql服务器,并且mysql服务器默认端口是3306,则url可以简写为jdbc:mysql:///数据库名称?参数键值对1&参数键值对2…
配置useSSL=false 参数,禁用安全连接方式,解决警告提示。
(2)user:用户名
(3)password:密码

(二)Connection

1、获取执行SQL的对象
(1)普通执行SQL对象
Statement stmt = conn.createStatement();
(2)预编译SQL的执行SQL对象,防止SQL注入
PreparedStatement preparedStatement(sql)
(3)执行存储过程的对象(不常用)
CallableStatement prepareCall(sql)

2、事务管理

(1)MYSQL事务管理
开启事务:BEGIN; / START TRANSACTION;
提交事务:COMMIT;
回滚事务:ROLLBACK;
注:MySQL默认自动提交事务

(2)JDBC事务管理:Connection接口中定义了3个对应的方法
开启事务:setAutoCommit(boolean autoCommit); true为自动提交事务;false为手动提交事务,一般设置为false。
提交事务:commit()
回滚事务:rollback()
成功就全成功,失败就全失败

【测试】
进行事务处理
这里对mysql有两条操作语句,在事务中,两个操作要么全成功,要么全失败。

全成功情况

  //3、定义sql
        String sql1 = "UPDATE account set money = 2000 where id = 1";
        String sql2 = "UPDATE account set money = 3000 where id = 2";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        /*在执行SQL之前开事务,在这个事务中(从开启到提交),如果出现异常就应该回滚事务,用java异常处理机制try-catch (快捷键ctrl+alt+t)*/
        try {
            /*开启事务*/
            conn.setAutoCommit(false);

            /*第一个任务*/
            //5、执行sql
            int count1 = stmt.executeUpdate(sql1);

            //6、处理结果
            System.out.println(count1);

            /*第二个任务*/
            //5、执行sql
            int count2 = stmt.executeUpdate(sql2);

            //6、处理结果
            System.out.println(count2);

            /*执行成功后提交事务*/
            conn.commit();
        } catch (Exception throwables) { //抓大异常,不仅是SQLException
            //出现异常回滚事务
            conn.rollback();
            throwables.printStackTrace();
        }

        /*执行成功后提交事务,可放进try-catch块里*/

原表:
在这里插入图片描述
IDEA执行结果:
在这里插入图片描述
MYSQL修改结果:
在这里插入图片描述
两条语句全部执行成功。

全失败情况:
手动造一个异常,第一个操作执行成功后,到这里异常,第二个操作会执行失败,进入catch会回滚到事务开启之前的状态,所以第一个操作执行成功后又恢复原样,第二个操作执行失败,表面上两个操作都未执行

//3、定义sql
        String sql1 = "UPDATE account set money = 2000 where id = 1";
        String sql2 = "UPDATE account set money = 3000 where id = 2";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        /*在执行SQL之前开事务,在这个事务中(从开启到提交),如果出现异常就应该回滚事务,用java异常处理机制try-catch (快捷键ctrl+alt+t)*/
        try {
            /*开启事务*/
            conn.setAutoCommit(false);

            /*第一个任务*/
            //5、执行sql
            int count1 = stmt.executeUpdate(sql1);

            //6、处理结果
            System.out.println(count1);

            /*手动造一个异常,第一个操作执行成功后,到这里异常,第二个操作会执行失败,进入catch会回滚到事务开启之前的状态,
            所以第一个操作执行成功后又恢复原样,第二个操作执行失败,表面上两个操作都未执行*/
            int i = 3/0;

            /*第二个任务*/
            //5、执行sql
            int count2 = stmt.executeUpdate(sql2);

            //6、处理结果
            System.out.println(count2);

            /*执行成功后提交事务*/
            conn.commit();
        } catch (Exception throwables) { //抓大异常,不仅是SQLException
            //出现异常回滚事务
            conn.rollback();
            throwables.printStackTrace();
        }

        /*执行成功后提交事务,可放进try-catch块里*/

原表:
在这里插入图片描述
IDEA执行结果:
在这里插入图片描述
原表未被修改:
在这里插入图片描述
不进行事务处理
把事务处理的代码都注释掉,保留两个操作中间的异常,那么第一个操作会成功,第二个操作会失败

 try {
            /*开启事务*/
            //conn.setAutoCommit(false);

            /*第一个任务*/
            //5、执行sql
            int count1 = stmt.executeUpdate(sql1);

            //6、处理结果
            System.out.println(count1);

            /*手动造一个异常,第一个操作执行成功后,到这里异常,第二个操作会执行失败,进入catch会回滚到事务开启之前的状态,
            所以第一个操作执行成功后又恢复原样,第二个操作执行失败,表面上两个操作都未执行*/
            int i = 3/0;

            /*第二个任务*/
            //5、执行sql
            int count2 = stmt.executeUpdate(sql2);

            //6、处理结果
            System.out.println(count2);

            /*执行成功后提交事务*/
            //conn.commit();
        } catch (Exception throwables) { //抓大异常,不仅是SQLException
            //出现异常回滚事务
            //conn.rollback();
            throwables.printStackTrace();
        }

原表:
在这里插入图片描述

IDEA执行结果:
在这里插入图片描述
代码执行后的表中,第一条从1000改为了2000,第二条未发生变化:
在这里插入图片描述

(三)Statement

1、执行SQL语句

(1)int executeUpdate(sql):执行DML(对数据增删改)、DDL(对表和库的增删改查)语句

返回值:A.DML语句影响的行数

//3、定义sql
        String sql1 = "UPDATE account set money = 2000 where id = 1";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        //5、执行sql
        int count1 = stmt.executeUpdate(sql1);

        //6、处理结果
        System.out.println(count1);

在上述代码中,sql1就是一条DML语句,count1就是执行后所影响的行数,运行结果为1。如果将DML语句改为UPDATE account set money = 2000 where id = 10,而原表中并没有id=10的记录,运行结果会返回0

但用户想要看到的结果是“成功”或者是“失败”,而不是输出0或1,可以通过影响的行数判断是否执行成功,使用if-else语句,如果大于0输出“执行成功”,否则输出“执行失败”,代码如下:

//3、定义sql
        String sql1 = "UPDATE account set money = 2000 where id = 1";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        //5、执行sql
        int count1 = stmt.executeUpdate(sql1);

        //6、处理结果
        //System.out.println(count1);
        if(count1>0){
            System.out.println("执行成功");
        }else {
            System.out.println("执行失败");
        }

运行结果:
在这里插入图片描述

B.DDL语句执行后,执行成功也可能返回0
下面代码是创建一个数据库db2,执行成功,输出结果为1,在数据库内也会成功创建db2数据库。

//3、定义sql
        String sql1 = "create database db2";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        //5、执行sql
        int count1 = stmt.executeUpdate(sql1);

        //6、处理结果
        System.out.println(count1);

下面代码是删除一个数据库db2,IDEA输出结果为0,但数据库db2成功被删除了,说明此DDL语句执行成功也可能会返回0:

//3、定义sql
        String sql1 = "drop database db2";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        //5、执行sql
        int count1 = stmt.executeUpdate(sql1);

        //6、处理结果
        System.out.println(count1);

执行结果:
在这里插入图片描述
因此,执行DDL语句时,不能通过影响行数的结果判断是否执行成功。

(2)ResultSet executrQuery(sql): 执行DQL语句(查询语句)(比较常用)

(四)ResultSet

返回值:ResultSet 结果集对象(封装了DQL查询语句的结果)

【获取查询结果】

boolean next():
1)将光标从当前位置向下移动一行,开始默认在属性行;
2)判断当前行是否为有效行
返回值:

  • true:有效行,当前行有数据;
  • false:无效行,当前行没有数据。

xxx getXxx(参数):获取数据
xxx:数据类型,如:int getInt(参数),String getString(参数)
参数:int:列的编号,从1开始 ; String:列的名称

获取查询结果过程:
在这里插入图片描述
最开始光标指在id-name-money一行,从当前位置往下一行,指在id=1的行,判断这一行是否有效,当前行有效,返回true,获取数据;从当前位置往下一行,指在id=2的行,判断这一行是否有效,当前行有效,返回true,获取数据;从当前位置往下一行,判断这一行是否有效,当前行无效,没有数据,返回false,执行结束;

这个过程是一个重复执行的过程,可以用一个循环:

//循环判断游标是否是最后一行末尾
while(rs.next()){
    //获取数据
    rs.getXxx(参数);
}

以下两种方法都可以实现,第一种使用getXxx(int(列的编号,从1开始)),另一种是getXxx(String(列的名称)),记得加引号!

//3、定义sql
        String sql1 = "select * from account";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        //5、执行sql
        ResultSet rs = stmt.executeQuery(sql1);

        //6、处理结果,遍历rs中的数据
           //6.1光标向下移动一行,并判断当前行是否有数据
//        while(rs.next()){
//            //6.2 获取数据 getXxx()
//            int id = rs.getInt(1);
//            String name = rs.getString(2);
//            double money = rs.getDouble(3);
//
//            System.out.println(id);
//            System.out.println(name);
//            System.out.println(money);
//
//            System.out.println("-----------------------");
//        }

        while(rs.next()){
            //6.2 获取数据 getXxx()
            int id = rs.getInt("id");
            String name = rs.getString("name");
            double money = rs.getDouble("money");

            System.out.println(id);
            System.out.println(name);
            System.out.println(money);

            System.out.println("-----------------------");
        }

        //7、释放资源
        stmt.close();
        conn.close();

两种方式运行结果相同:
在这里插入图片描述
【ResultSet案例】

我们已经从上述代码中获取了数据库中的数据,每一行就是一个java对象,形成很多个java对象。在实际应用中,我们希望以一个表的形式来展现,就需要对这些java对象进一步处理,放到一个容器内,而集合就是一个专门装对象的容器,然后把这个容器给页面,页面就可以展示这些数据。

需求:查询account账户表数据,封装为Account对象中,并存储到ArrayList集合中。

第一步:定义实体类Account

新建一个包pojo(这个名称一般代表存放一些实体类)
1、成员变量的名称要与数据库里字段的名称和类型一样。
2、提供setter和getter方法,快捷键alt+insert。
3、为了方便查看,使用toString()方法,同样alt+insert

注意2和3都要全选
在这里插入图片描述

public class Account {

    //成员变量的名称要与数据库里字段的名称和类型一样
    private int id;
    private String name;
    private double money;

    //提供setter和getter方法,快捷键alt+insert

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    //为了方便查看,使用toString()方法,同样alt+insert
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

第二步:创建对象,并将获取的数据封装到对象中
使用@Test代替主函数去运行测试

//6、处理结果,遍历rs中的数据
        //6.1光标向下移动一行,并判断当前行是否有数据
        while(rs.next()){

            //创建对象
            Account account = new Account();

            //6.2 获取数据 getXxx()
            int id = rs.getInt("id");
            String name = rs.getString("name");
            double money = rs.getDouble("money");

           //将获取的数据赋值给对象
            account.setId(id);
            account.setName(name);
            account.setMoney(money);
        }

第三步:把对象存到集合内

如果在while循环里创建集合,那么每一次循环都会有一个新的集合,我们只需要将所有对象存到一个集合内,所以创建集合应在while循环体外。

    //创建集合
        List<Account> list = new ArrayList<>();

        //6、处理结果,遍历rs中的数据
        //6.1光标向下移动一行,并判断当前行是否有数据
        while(rs.next()){

            //创建对象
            Account account = new Account();

            //6.2 获取数据 getXxx()
            int id = rs.getInt("id");
            String name = rs.getString("name");
            double money = rs.getDouble("money");

           //将获取的数据赋值给对象
            account.setId(id);
            account.setName(name);
            account.setMoney(money);

            //存入集合
            list.add(account);
        }

        System.out.println(list);

运行结果:
在这里插入图片描述

(五)PreparedStatement

PreparedStatement是一个接口,继承自Statement,表示预编译SQL语句的对象。

作用:
1.预编译SQL语句,性能更高;
2.预防SQL注入问题,将敏感字符进行转义

  • SQL注入:SQL注入是通过操作输入来修改事先定义好的SQL语句。用以达到执行代码对服务器进行攻击的方法。

以下是一个简单的登录代码:

//接受用户输入的用户名和密码
        String input_name = "Mike";
        String input_pwd = "123";

        //3、定义sql
        String sql = "select * from login_user where Login_name = '"+input_name+"' and Login_pwd = '"+input_pwd+"'";

        //4、获取执行sql的对象Statement
        Statement stmt = conn.createStatement();

        //5、执行sql
        ResultSet rs = stmt.executeQuery(sql);

        //6、处理结果,判断登录是否成功,只要rs里面有数据,就说明登录成功
        if(rs.next()){
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }

因为在数据库的表中能查到用户输入的登录名和密码,所以运行显示登录成功。

如果用户名随便写,密码也随便写,如下:
String input_name = “1vshupasse”;
String input_pwd = “’ or ‘1’='1”;
仍然会显示执行成功。打印一下sql,select * from login_user where Login_name = ‘1vshupasse’ and Login_pwd = ’ ’ or ‘1’=‘1’,修改了SQL原来的含义,or前面返回false,但or后面是恒等式,所以where后面的语句就没有起到作用,就会把所有的数据全部查出来。这就是SQL注入。

为了解决SQL注入问题,可以使用PreparedStatement对象解决,有以下步骤:
(1)获取PreparedStatement对象

  • //定义sql,SQL语句中的参数值,使用?占位符替代,避免了拼字符串:String sql = “select * from login_user where Login_name = ? and Login_pwd = ?”;
  • //通过Connection获取pstmt对象,并传入对应的sql语句:
    PreparedStatement pstmt = conn.prepareStatement(sql);

(2)设置参数值
PreparedStatement对象:setXxx(参数1,参数2),给占位符?赋值

  • Xxx:数据类型,如setInt(参数1,参数2),要跟表中属性的数据类型对应
  • 参数:参数1:?的位置编号,从1开始;
    参数2:?的值

例如:

 //设置?的值
    pstmt.setString(1,input_name);
    pstmt.setString(2,input_pwd);

(3)执行SQL
executeUpdate(); 或者executeQuery(); 此时括号内不需要再传递sql。

以下是完整代码:

@Test
    public void testPreparedStatement() throws Exception {
        //1、创建驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2、获取连接
        String url = "jdbc:mysql://localhost:3306/db1?serverTimezone=GMT";
        String uname = "root";
        String pwd = "123456";
        Connection conn = DriverManager.getConnection(url,uname,pwd);

        //接受用户输入的用户名和密码
        String input_name = "Mike";
        String input_pwd = "1234";

        //3、定义sql,SQL语句中的参数值,使用?占位符替代,避免了拼字符串
        String sql = "select * from login_user where Login_name = ? and Login_pwd = ?";

        //通过Connection获取pstmt对象,并传入对应的sql语句
        PreparedStatement pstmt = conn.prepareStatement(sql);

        //设置?的值
        pstmt.setString(1,input_name);
        pstmt.setString(2,input_pwd);

        //5、执行sql
        ResultSet rs = pstmt.executeQuery();

        //6、处理结果,判断登录是否成功,只要rs里面有数据,就说明登录成功
        if(rs.next()){
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }



        //7、释放资源
        rs.close();
        pstmt.close();
        conn.close();
    }

PreparedStatement原理

(1)PreparedStatement预编译功能默认是关闭的,开启: &useServerPrepStmts=true
(2)配置MySQL执行日志(重启mysql服务后生效)

log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
long_query_time=2
  • 将上述代码复制到mysql安装路径的my.ini文件下,重启mysql,在IDEA中开启预编译功能,执行后,可在log文件中看到整个执行的过程

(3)原理

  1. 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查编译
  2. 执行时就不用在进行以上步骤,速度更快
  3. 如果sql模板一样,则只需要进行一次检查、编译。

五、数据库连接池

(一)数据库连接池简介

  • 数据库连接池是个容器,负责分配、管理数据库连接
  • 它允许应用程序重复使用一个现有的数据库连接,而不是在重新建立一个;
  • 当容器内的连接都用完了,又来了一个新的用户,这是会自动检查分配出去的连接在干什么,会强制收回不使用的连接,放回到连接池,再分配给新的用户。
  • 好处:(1)资源复用;(2)提升系统响应速度;(3)避免数据库连接遗漏

在系统启动之前,初始化一个容器,在这个容器里提前申请很多个数据库连接,用户使用时,会分配给用户一个数据库连接,用户使用完毕,这个连接不会被释放,而是重新回到这个容器内,实现资源复用。

(二)数据库连接池实现

1、标准接口:DataSource

  • 官方(SUN)提供的数摇库连接池标准接口,由第三方组织实现此接口。
  • 功能:获取连接 Connection getConnection()

2、常见的数据库连接池:
DBCP
C3Po.
Druid.

(三)Druid(德鲁伊)
Druid连接池是阿里巴巴开源的数据库连接池项目
功能强大,性能优秀,是Java语言最好的数据库连接池之一;

Druid使用步骤
1、导入jar包,druid-1.1.12.jar
2、定义配置文件
3、加载配置文件
4、获取数据库连接池对象
5、获取连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值