阶段三-Day05-JDBC

2023-12-24 05:21:29

一、JDBC概述

1. 操作数据库的方式

方式1: 通过命令行的形式

方式2: 使用图形化界面的客户端 如: Navicat

方式3: 通过java程序连接MySQL数据库,也就是使用JDBC技术。

2. 什么是JDBC

  • JDBC(Java Data Base Connectivity),是 Java连接数据库的技术

  • 是一种执行SQL的API, 可以为多种关系型数据库提供统一的访问

  • 它是一组用java语言编写的类和接口组成, 是Java访问数据库的标准规范

3. JDBC的原理

什么是数据库驱动程序

  • 数据库驱动就是直接操作数据库的一个程序

  • 不同数据产品的数据库驱动名字有差异

  • 不同数据产品操作大同小异

二、JDBC实现查询

1.准备数据库数据

2.JAVA实现

1.引入数据库驱动包(导入jar包,jar包中保存了需要的.class文件,减少编译次数,提高效率)

将MySQL的驱动jar包添加到项目中的lib文件夹, 并将jar添加到libraries。

将jar包粘贴在lib文件夹中

右键jar包

添加依赖

2.代码实现

1.加载驱动

类加载后执行静态代码块的内容

可以使用DriverManager

注意: JDBC4.0之后, 在每个驱动jar包中META-INF/services目录下提供了一个名为java.sql.Driver的文件。可以不写类加载语句

2. 获取连接

JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔

第一部分是协议 jdbc,这是固定的

第二部分是子协议,就是数据库厂商名称,连接mysql数据库

第三部分是由数据库厂商规定的,我们需要了解每个数据库厂商的要求,mysql的第三部分分别由数据库服务器的IP地址(localhost)、端口号(3306),以及要使用的数据库名称组成

第四部分是useSSl=false 是否使用证书,一种规范,我们不使用,填false

第五部分是useUnicode=true 是否使用万国码,我们使用

第六部分是characterEncoding=utf-8,字符集使用utf-8

第七部分是serverTimezone=Asia/Shanghai,设置时区亚洲上海;

3.获取SQL发送对象

4.执行SQL

注意:是把SQL发送给数据库,由数据库进行执行SQL语句

5.遍历结果集?

?

ResultSet接口方法说明
boolean next()1.游标向下一行 2.返回boolean类型,如果有下一条记录,返回true,否则返回false
xxx getXxx(String or int)1.通过列名获取数据 2.通过列索引获取数据,索引从1开始

6.资源释放

  • ResultSet结果集: 当它的 Statement 关闭、重新执行或用于从多结果序列中获取下一个结果时,该ResultSet将被自动关闭

  • 释放原则: 先开的后关, 后开的先关 Statement ==> Connection

三、JDBC实现增删改

增删改与查询差不多,比查询少了一步,查询要对返回的结果进行遍历,增删改操作完就可以释放资源了

public class Test {
    public static void main(String[] args) throws Exception{
        //1.加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接
        String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "root";
        Connection connection = DriverManager.getConnection(url,username,password);
        //3.创建SQL发送器
        Statement statement = connection.createStatement();
        //4.发送SQL到数据库,数据库执行
        //增删改使用的是.executeUpdate()方法
        //返回值是影响的行数
        //增加 这里如果要使用双引号的话要转义,否则按照字符串来识别了
        String sql1 = "insert into jdbc_user values(default,'zl','456',19)";
        //删除
        String sql2 = "delete from jdbc_user where id = 4";
        //修改
        String sql3 = "update jdbc_user set password = 147 where id = 3";
        int i = statement.executeUpdate(sql2);
        if (i == 1) System.out.println("操作成功");
        //5.释放资源
        if (statement!=null){
            statement.close();
        }
        if (connection!=null){
            connection.close();
        }
    }
}

四、ORM编程思想

1. ORM简介

对象关系映射(Object Relational Mapping,简称ORM,或O/R mapping)是一种为了解决面向对象语言与关系数据库存在的互不匹配的现象。

就是我们在创建实体类时类中的属性与表中的字段相互对应

实体类

实体类就是一个定义了属性,拥有getter、setter、无参构造方法(基本必备)的一个类。实体类可以在数据传输过程中对数据进行封装,相当于一个“工具”、“容器”、“载体”,能存储、传输数据,能管理数据。

五、用户登录功能

  • 需求 用户在控制台上输入用户名和密码, 显示 欢迎xxx登陆成功 或 登录失败

  • 实现思路

    1. 获取用户从控制台上输入的用户名和密码来查询数据库

    2. JDBC查询操作的6步

    3. 根据用户名和密码查询

  • 查询到记录(该用户存在), 登陆成功

  • 查询不到记录(该用户不存在), 登陆失败

dao层:数据库相关操作

pojo层:实体类层

service层:服务相关

test:测试相关

util层:工具类相关

创建UserDao

public interface UserDao {
    User selectLogin(String name, String passwword);
}

创建UserDaoImp

//dao文件夹中都是和数据库的相关操作
//实际开放中不要把异常声明出去,应使用try-catch处理
public class UserDaoImp implements UserDao{
    Connection connection = null;
    Statement statement = null;
    User user = null;
    @Override
    public User selectLogin(String name, String password) {
        try {
            //1.加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                    "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
            connection = DriverManager.getConnection(url, "root", "root");
            //3.创建SQL发送器
            statement = connection.createStatement();
            //4.发送SQL语句
            String sql = "select * from jdbc_user where username='" + name + "' and password='" + password + "'";
            ResultSet rs = statement.executeQuery(sql);
            if (rs.next()){
                System.out.println("登录成功");
            }else {
                System.out.println("登录失败");
            }
            //5.处理结果集
            while (rs.next()) {
                int id = rs.getInt(1);
                String uname = rs.getString(2);
                String pwd = rs.getString(3);
                //ORM对象关系映射
                user = new User(id, uname, pwd);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (statement!=null){
                    statement.close();
                }
                if (connection!=null){
                    connection.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return user;
    }
}

创建pojo中User类

//实体类实现序列化流
public class User implements Serializable {
    private int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

}

创建测试类Test

public class Test {
    public static void main(String[] args) {
        UserDaoImp userDaoImp = new UserDaoImp();
        userDaoImp.selectLogin("zs","123");
    }
}

弊端分析:

如果我们将or'1=1'传递进去就会使sql语句变成一个衡成立的式子

  1. 存在的问题?

    我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,但是改变了原有 SQL真正的意义,以上问题称为SQL注入。

    SQL注入: 输入的内容作为sql执行一部分, 改变了原有sql的真正含义。

  2. 如何解决SQL注入的风险?

    要解决SQL注入就不能让用户输入的密码和SQL 语句进行简单的手动字符串拼接

六、PreparedStatement预处理对象

1. PreparedStatement接口介绍

  • PreparedStatement 是Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句对象。

  • 预编译:是指SQL 语句被预编译, 并存储在PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。

2. PreparedStatement特点

  • 因为有预先编译的功能,提高SQL的执行效率

  • 可以有效的防止SQL 注入的问题,安全性更高

3. 如何使用PreparedStatement

//dao文件夹中都是和数据库的相关操作
//实际开放中不要把异常声明出去,应使用try-catch处理
public class UserDaoImp implements UserDao{
    Connection connection = null;
    Statement statement = null;
    User user = null;
    @Override
    public User selectLogin(String name, String password) {
        try {
            //1.加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                    "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
            connection = DriverManager.getConnection(url, "root", "root");
            //3.创建SQL发送器
            statement = connection.createStatement();
            //4.发送SQL语句
            String sql = "select * from jdbc_user where username='" + name + "' and password='" + password + "'";
            System.out.println(sql);
            ResultSet rs = statement.executeQuery(sql);
            if (rs.next()){
                System.out.println("登录成功");
            }else {
                System.out.println("登录失败");
            }
            //5.处理结果集
            while (rs.next()) {
                int id = rs.getInt(1);
                String uname = rs.getString(2);
                String pwd = rs.getString(3);
                //ORM对象关系映射
                user = new User(id, uname, pwd);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (statement!=null){
                    statement.close();
                }
                if (connection!=null){
                    connection.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return user;
    }
}

7. PreparedStatement批量添加

public class Test {
    public static void main(String[] args) throws Exception {
        //1.加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接
        String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "root";
        Connection connection = DriverManager.getConnection(url,username,password);
        //3.创建SQL预处理发送器
        String sql = "insert into jdbc_user values(null,?,?)";
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置参数
        ps.setString(1,"zl");
        ps.setString(2,"456");
        //添加到批处理操作
        ps.addBatch();

        ps.setString(1,"tom");
        ps.setString(2,"456");
        //添加到批处理操作
        ps.addBatch();

        //执行批处理操作,返回的是每一次影响的行数,所以是一个int集合
        int[] ints = ps.executeBatch();
        System.out.println(Arrays.toString(ints));

        //释放资源
        ps.close();
        connection.close();
    }
}

8. PreparedStatement获取自增主键值

//获取自增主键值
public class Test {
    public static void main(String[] args) throws Exception{
        //1.加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接
        String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "root";
        Connection connection = DriverManager.getConnection(url,username,password);
        //3.创建SQL预处理发送器
        String sql = "insert into jdbc_user values(default,?,?)";
        //要想设置返回自增主键,这里要添加另一个参数
        PreparedStatement ps = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
        //设置参数
        ps.setString(1,"jerry");
        ps.setString(2,"456");
        //4.获取结果
        int i = ps.executeUpdate();

        //获取自增主键
        ResultSet resultSet = ps.getGeneratedKeys();
        if (resultSet.next()){
            System.out.println("主键id:"+resultSet.getInt(1));
        }

        //5.释放资源
        ps.close();
        connection.close();
    }
}

9. PreparedStatement执行原理

六、JDBC事务控制

1. 事务回顾

事务(ACID): 原子性、一致性、隔离性、持久性。 ? 事务的隔离级别:

1. 读未提交: 脏读 不可重复读 幻读

2. 读已提交: 不可重复读 幻读

3. 可重复读: 幻读

4. 串行化: 完全解决,整张表加锁, 同时只能有一个客户端操作。

事务是一个整体, 由一条或者多条SQL语句组成, 这些SQL语句要么都执行成功, 要么就失败, 只要有一条SQL出现异常, 整个操作就会回滚。

回滚: 就是事务运行的过程中发生了某种故障, 或者SQL出现了异常, 事务不能继续执行, 系统将事务中对数据库的所有已完成的操作全部取消, 回滚到事务开始时的状态。

宕机等情况自动回滚, 代码的bug手动回滚。

2.JDBC中事务的使用

准备一个账户表

代码实现:

public class Test {
    public static void main(String[] args){
        PreparedStatement ps = null;
        Connection connection = null;
        try {
            //1.加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                    "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
            String username = "root";
            String password = "root";
            connection = DriverManager.getConnection(url,username,password);
            //3.设置手动提交事务
            //设置自动提交为false,就是手动提交
            connection.setAutoCommit(false);
            String sql ="update account set money = money + ? where name = ?";
            ps = connection.prepareStatement(sql);
            //给占位符赋值
            ps.setInt(1,-500);
            ps.setString(2,"tom");
            //执行
            ps.executeUpdate();

            //模拟故障
            /*int i = 1/0;*/
            //给占位符赋值
            ps.setInt(1,500);
            ps.setString(2,"jack");
            //执行
            ps.executeUpdate();

            //提交事务
            connection.commit();
        } catch (Exception e) {
            try {
                //报错时回滚
                connection.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }finally {
            //释放资源放在finally中
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(connection != null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

七、连接池

频繁的建立、关闭数据库连接,会极大的降低系统的性能,这就需要连接池的帮助

1. 连接池原理

数据库连接池的基本原理是连接池对象中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法

外部使用者可通过getConnection 方法获取连接,使用完毕后再通closeConnection方法将连接返回,注意此时连接并没有关闭,而是放回连接池,并为下一次使用做好准备。

2.模拟连接池

public class MyConnectionPool {
    //定义静态,让该集合也在类加载后初始化,使静态代码块可以使用
    private static LinkedList<Connection> list = new LinkedList<>();
    //放在静态代码块中,类加载后只执行一次
    static {
        try {
            //加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                    "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
            //获取30个连接使用
            for (int i = 0; i < 30; i++) {
                Connection connection = DriverManager.getConnection(url, "root", "root");
                //使用集合保存连接
                list.add(connection);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
    //从连接池中获取连接,因为有多个线程使用,要加锁
    //锁方法的锁对象是类对象
    public synchronized static Connection getConnection(){
        //当连接池中的连接都被占用时,获取连接的线程需要等待
        if (list.size() == 0){
            try {
                MyConnectionPool.class.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //移除链表中头部的元素,返回值就是这个元素
        Connection connection = list.remove(0);
        return connection;
    }
    //将连接放回连接池
    public synchronized static void closeConnection(Connection connection) {
        list.add(connection);
        //唤醒等待的线程
        MyConnectionPool.class.notifyAll();
    }
}

测试:

public class TestCon {
    public static void main(String[] args) {
        //测试连接池的效率, 测试对 mysql 5000 次操作
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = MyConnectionPool.getConnection();
            MyConnectionPool.closeConnection(connection);
        }
        long end = System.currentTimeMillis();
        System.out.println("5000 连接 mysql 耗时=" + (end - start));
    }
}

3. 连接池的工具类

3.1 分类
  1. C3P0数据库连接池,速度相对较慢,稳定性不错。(hibernate,spring)

  2. DBCP数据库连接池,速度相比C3P0快,但不稳定。

  3. Druid(德鲁伊)数据库连接池,是由阿里提供的连接池,及DBCP,C3P0,...优点于一身的数据库连接池。

3.2 C3P0使用

使用前要导入jar包

public class Test {
    public static void main(String[] args) throws Exception {
        //1.创建数据源对象
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        //2.设置使用驱动,url,用户名,密码
        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "root";
        cpds.setDriverClass(driver);
        cpds.setJdbcUrl(url);
        cpds.setUser(username);
        cpds.setPassword(password);
        //设置初始化连接数
        cpds.setInitialPoolSize(10);
        //设置最大连接数
        cpds.setMaxPoolSize(30);
        //测试数据
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = cpds.getConnection();
            //归还连接
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时=" + (end - start));
    }
}
3.3 Druid使用
public class Test {
    public static void main(String[] args) throws Exception {
        //1.获取数据源
        DruidDataSource dbs = new DruidDataSource();
        //设置驱动,url,用户名,密码
        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/lsh04?useSSl=false&useUnicode=true" +
                "&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "root";
        dbs.setDriverClassName(driver);
        dbs.setUrl(url);
        dbs.setUsername(username);
        dbs.setPassword(password);
        //测试数据
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = dbs.getConnection();
            //归还连接
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时=" + (end - start));
    }
}

文章来源:https://blog.csdn.net/m0_59163770/article/details/132768032
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。