Java入门(八)JDBC

📢:哈喽~☀️欢迎加入程序🐒大家庭,快点开始✒️自己的黑客帝国吧 ~🌊🌊

内容简述:JDBC概述、封装JDBCUtils工具类、Statement、PreparedStatement、批处理、事务、返回自增主键、JDBC操作分页。

一、JDBC概述、怎样使用JDBC、初步封装

JDBC概述

在这里插入图片描述

JDBC是什么

  1. JDBC是一套sun公司定义的接口,是一套标准,规定了统一的数据库访问方法

  2. 各个数据库厂商实现这套标准,让java程序能操作数据库

为什么使用JDBC

  • jdbc的好处:我们只学习一套接口的使用和操作,就可以访问任何数据库了。

怎么使用JDBC

在这里插入图片描述

  1. 下载驱动,mysql驱动,去Maven库搜索MySQL connector下载即可;

  2. 把驱动加载到java环境中,注册驱动;

    // 注册驱动
    Class.forName("com.mysql.jdbc.Driver");
    
  3. 获得连接对象 Connection

    public static void main(String[] args) throws Exception {
      //1.注册驱动
      Class.forName("com.mysql.jdbc.Driver");
    
      //2.获得Connection对象
      //url jdbc:mysql://数据库IP:端口号/库名
      //username 数据库用户名
      //password 数据库密码
      Connection conn=DriverManager.getConnection(
              "jdbc:mysql://localhost:3306/demo1", 
              "root", "root");
    
      //验证是否连接成功
      System.out.println(conn);
    
      //证明接口实现类来自驱动
      System.out.println(conn.getClass());
    }
    
  • 补充:
    • 如果使用mysql版本为6.0以上的jar包,url 中要添加时区指定。
    • “jdbc:mysql://localhost:3306/demo1?serverTimezone=Asia/Shanghai”-------地方时区;
    • “jdbc:mysql://localhost:3306/demo1?serverTimezone=UTC”---------国际的标准时区。
  1. 连接上数据库后,要执行sql语句,使用Statement对象执行sql语句。

    4.1. 执行DDL

    String ddl="create table demo_2(id int,name varchar(20))";
    
    // true 代表有返回结果集
    // false 代表没有返回结果集
    boolean flag=sta.execute(ddl);
    System.out.println(flag);
    

    4.2. 执行DML

    // 插入操作 DML
    String dml="insert into demo_2 values(1,'hanmeimei')";
    sta.executeUpdate(dml);
    

    4.3. 执行DQL,处理结果集

    //查询
    String dql="select 'hello' as a from dual";
    
    //使用execute(dql)执行查询语句
    ResultSet rs=sta.executeQuery(dql);
    while(rs.next()){
      String str=rs.getString("a");
      System.out.println(str);
    }
    
  2. 关闭连接对象

    conn.close();
    

ResultSet 游标

在这里插入图片描述

  • ResultSet对象,使用游标控制数据 ;
  • 当执行查询语句的时候,resultSet对象用于封装查询结果方法;
  • Boolean rs.next()
    1. 让结果集中的游标往下移动一行;
    2. 判断该行是否有数据!有的话,返回true,没有就返回false。
  • rs.getXXX() 有很多种,分别对应数据库中不同的数据类型;
    • 数据库类型 get方法
    • char/varchar getString()
    • int getInt()
    • bigint getLong()
    • float/double getFloat()/getDouble()
    • datetime/timestamp getDate()

初步封装

  • java面向对象的思想,让我们获得连接对象的代码可以重用;

  • 创建一个工具类 DBUtils,封装获取连接对象的静态方法:

    public class DBUtils {
    
    	/**
    	 * 1.创建静态方法getConnection,返回值为Connection对象
    	 */
    	public static Connection getConnection() throws Exception{
    		// 1.1把4个字符串拆分出来
    		String driver="com.mysql.jdbc.Driver";
    		String url="jdbc:mysql://localhost:3306/demo1";
    		String userName="root";
    		String password="root";
    
    		// 1.2注册驱动
    		Class.forName(driver);
    
    		// 1.3获取连接对象
    		Connection conn=DriverManager.getConnection(url, userName, password);
    		return conn;
        }
    
    	public static void main(String[] args) throws Exception {
            // 调用封装的getConnection方法获得连接对象
    		Connection conn=DBUtils.getConnection();
    		String sql="select * from demo_2";
    		// 获取statement对象
    		Statement sta=conn.createStatement();
    		ResultSet rs=sta.executeQuery(sql);
    		while(rs.next()){
    			int id=rs.getInt(1);
    			String str=rs.getString(2);
    			System.out.println(id);
    			System.out.println(str);
    		}
    		conn.close();
        }
    }
    

课堂练习

  • 完成DBUtils的封装,并且测试成功

  • 思考尝试一下,封装conn.close()

封装进阶

  • JDBC可以操作所有的数据库;

  • 如果我们现在想要更换数据,或者更换用户/密码/库,需要进入代码去更改;

  • 把4个字符串driver,url,username,password,放到一个配置文件中,以便后期维护。

课堂练习

  • 完全熟悉jdbc操作数据的库基本流程 (Connection对象,Statement对象,Properties对象);
  • 完成进阶封装的代码操作;
  • 对emp表,进行3次增删改查操作。

二、连接池技术、优化Statement对象(使用PreparedStatement)

连接池

  • DBCP database connection pool—apache
  • 缓存中存储了连接对象,可以重用连接对象的技术。

为什么使用连接池

  1. 重用了数据库连接对象,提高了连接效率:
  • 在缓存(内存)中保存了一些connection对象,使用的时候拿出来用,用完了归还给缓存

  • 从内存获取和归还connection对象的效率,要远远高于创建和销毁connection对象的效率

  1. 保护数据库连接数量,避免连接过载;

    使用一个数据库的管理员,管理员对数据库连接做管理,导入包,并直接使用。

在这里插入图片描述

课堂练习

在这里插入图片描述

   public static void main(String[] args) {
       // 声明四个必要参数
       String driver;
       String url;
       String username;
       String password;

       // 声明两个策略参数
       int initialSize;
       int maxActive;

       // 声明BasicDataSource对象,赋值为null
       BasicDataSource ds=null;

       // 静态块中完成读取六个参数的操作
       // 创建BasicDataSource对象的操作
       // 给BasicDataSource对象设置6个参数的操作
       try {
           Properties conf=new Properties();
           InputStream in = DBUtils1.class.getClassLoader()
                   .getResourceAsStream("db.properties");
           conf.load(in);
           driver=conf.getProperty("jdbc.driver");
           url=conf.getProperty("jdbc.url");
           username=conf.getProperty("jdbc.username");
           password=conf.getProperty("jdbc.password");
           initialSize=Integer.parseInt(conf.getProperty("initialSize"));
           maxActive=Integer.parseInt(conf.getProperty("maxActive"));

           //创建BasicDataSource对象
           ds=new BasicDataSource();

           //给BasicDataSource对象设置6个参数
           ds.setDriverClassName(driver);
           ds.setUrl(url);
           ds.setUsername(username);
           ds.setPassword(password);
           ds.setInitialSize(initialSize);
           ds.setMaxActive(maxActive);
           Connection conn=ds.getConnection();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

封装连接池,完成查询表内容

在这里插入图片描述

多线程演示连接池的等待效果

在这里插入图片描述

数据库执行计划

  • 目的:优化Statement对象,重用执行计划。

  • sql语句数据库是不认识的,需要进行编译,编译后的结果叫做执行计划

  • 执行一条sql语句,就产生一个执行计划。

  • 同样的sql语句,哪怕一丁点的不一样(比如数据不一样,大小写不一样,空格不一样等),就会重新产生一个执行计划。

  • 因此,我们要优化statement对象,重用执行计划:

    • 优化方法----->使用PreparedStatement
      • Statement:一般用于执行不发生改变的sql语句(比如DDL,DCL,TCL等)
      • PreparedStatement:一般用于执行发生改变的sql语句(比如DML,DQL等)

示例:

//?是占位符,把可变数据的位置占住
String sql="insert into t9 values(?,?,?)";

//将sql语句发送到数据库,并创建执行计划
//ps就代表执行计划
PreparedStatement ps=conn.prepareStatement(sql);

//替换执行计划中的参数
//按照顺序和数量发送给执行计划
ps.setInt(1, 4);
ps.setString(2, "ddd");
ps.setString(3, "444");

//运行执行计划
ps.executeUpdate();

PreparedStatement的另一个功能是:可以防止sql注入。

注意:PreparedStatement不需要单引号修饰问号,但是Statement需要。

三、批量处理、结果集元数据、事务、返回自增主键、JDBC操作分页

批量处理

问题:Connection是一次连接,一次jdbc和数据库的响应叫一次通讯。当执行多条SQL的时候,就需要进行多次连接通讯,效率低。

在这里插入图片描述

Statement 解决方案:

  • 在jdbc中设置一个缓存区用于积攒sql语句,积攒一定数量之后执行;
  • 因为sql语句是DDL语句,所以执行语句不能省,只是节省通讯次数。

在这里插入图片描述

PreparedStatement解决方案:

  • 使用批量处理
  1. 积攒DDL语句,使用Statement对象

    sta.addBatch(ddl);
    
    sta.executeBatch();
    sta.clearBatch();
    

    完整方法:

    public static void main(String[] args) {
        //准备一批sql语句
        String ddl1="create table log1(id int,msg varchar(20))";
        String ddl2="create table log3(id int,msg varchar(20))";
        String ddl3="create table log4(id int,msg varchar(20))";
        String ddl4="create table log5(id int,msg varchar(20))";
        String ddl5="create table log2(id int,msg varchar(20))";
        Connection conn=null;
        try {
            conn=DBUtils1.getConnection();
            Statement sta=conn.createStatement();
            //把一批sql语句添加到缓存中
            sta.addBatch(ddl1);
            sta.addBatch(ddl2);
            sta.addBatch(ddl3);
            sta.addBatch(ddl4);
            sta.addBatch(ddl5);
            //执行一批SQL语句
            int[] arr=sta.executeBatch();
            //返回值有3种
            //1.>=0 代表成功
            //2.代表成功-2
            //oracle对executeBatch()并不完全支持,返回-2
            //3.代表失败-3
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            DBUtils1.closeConnection(conn);
        }
    }
    
  2. 积攒参数,使用PreparedStatement,重用执行计划。

    ps.addBatch();
    ps.executeBatch();
    ps.clearBatch();
    

    完整方法:

    public static void main(String[] args) {
        // 准备dml语句
        String dml = "insert into log1 values(?,?)";
        Connection conn = null;
        try {
            conn=DBUtils1.getConnection();
            PreparedStatement ps = conn.prepareStatement(dml);
            // 把一批参数添加到ps的缓存中
            ps.setInt(1, 1);
            ps.setString(2, "1111");
            ps.addBatch();
            ps.setInt(1, 2);
            ps.setString(2, "2222");
            ps.addBatch();
            ps.setInt(1, 3);
            ps.setString(2, "3333");
            ps.addBatch();
            // 批量执行一批参数
            int[] arr = ps.executeBatch();
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            DBUtils1.closeConnection(conn);
        }
    }
    

在这里插入图片描述

结果集元数据

  • 就是结果集的相关信息。
  • 目的:为了获取结果集中其它的描述信息。

在这里插入图片描述

  • 举个栗子:

    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn=DBUtils1.getConnection();
            String sql="select * from emp";
            Statement sta=conn.createStatement();
            ResultSet rs=sta.executeQuery(sql);
            // 获取元数据
            ResultSetMetaData meta = rs.getMetaData();
            // 获取列的数量
            int n = meta.getColumnCount();
            System.out.println(n);
            // 获取列的名称
            String name1=meta.getColumnName(1);
            String name2=meta.getColumnName(2);
            System.out.println(name1+"~"+name2);
            for(int i = 1; i <= meta.getColumnCount(); i ++){
                System.out.println(meta.getColumnName(i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            DBUtils1.closeConnection(conn);
        }
    }
    

课堂练习

  1. 使用sta对象,批量执行DDL语句 5个

    log6 (id int,msg varchar(20));
    sta.addBatch(sql);
    sta.executeBatch()
    
  2. 使用PreparedStatement对象,批量更新20条数据。

    • 注意控制不要让内存溢出。

事务

sql> set autocommit=0; 
sql> start transaction;
sql> .... 
sql> commit/rollback;

在这里插入图片描述

课堂练习

  • 借钱的业务

    create table account1( id int, name varchar(20), money double(7,2) );
    
    insert into account1 values(1,'aaa',10000); 
    insert into account1 values(2,'bbb',100);
    
  1. 完成转账事务逻辑

  2. 提高,把这个逻辑封装成方法

    • pay(int from,int to,double money)
    • from—>减钱的账户ID
    • to----->加钱的账户id
    • money–>转了多少钱

注意:如果是抛异常一定要自己调用回滚,因为我们在关闭连接对象的时候需要恢复自动提交,如果不自己调用回滚的话,缓存中的sql语句在恢复自动提交的时候也会执行。

返回自增主键

问题:在我们的日常开发过程中,数据的主键我们一般是使用数据库自动生成的序列。

那么如何在新增数据库之后获取到该条数据自动生成的主键序列呢?

在这里插入图片描述

在这里插入图片描述

解决方案:

  • 1.在建表的时候指定主键序列为自增长,auto_increment
  create table post(
    id int  primary key auto_increment,
    content varchar(200),
    k_id int
  );

  create table keywords(
    id int primary key auto_increment,
    content varchar(20)
  );
  • 2.java语句:
/**
 * 归还连接池中连接对象的方法
 */
public static void closeConnection(Connection conn) {
	try {
		if(conn != null) {
			// 还原自动提交的默认设置
			conn.setAutoCommit(true);
			// 此处close是归还
			conn.close();
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

public static void main(String[] args) {
    Connection conn=null;
    String sql1="insert into keywords values(null,?)";
    try {
        conn=DBUtils1.**getConnection**();
        // 关闭自动提交事务--开启事务
        conn.setAutoCommit(false);
        // 自动生成序列号的列名
        // 一张表中可以存在多个自增长的列,
        // 所以这里使用数组保存
        String[] cols= {"id"};
        // 获取ps对象
        // 第一个参数,生成执行计划的sql语句
        // 第二个参数,执行时,返回得到的列值的列名
        PreparedStatement ps=conn.prepareStatement(sql1,cols);
        ps.setString(1, "雾霾");
        // ps一执行,自增的id值,
        // 就返回到ps对象中保存着
        ps.executeUpdate();
        // 获取自增长的值
        ResultSet rs=ps.getGeneratedKeys();
        while(rs.next()) {
            // 注意:这里的getXXX(index)
            // 只能写下标不能写列名
            int id=rs.getInt(1);
            System.out.println("id:"+id);
        }
        conn.commit();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        DBUtils1.**closeConnection**(conn);
    }
}

JDBC操作分页

  • MySQL大表查询必须使用分页
select id from keywords limit 1,5;

Java 代码

public static void main(String[] args) {
    Connection conn = null;
    try {
        conn=DBUtils1.getConnection();
        String sql = "select id from keywords limit ?,?";
        PreparedStatement ps = conn.prepareStatement(sql);
        // 从0开始,显示5条
        ps.setInt(1, 0);
        ps.setInt(2, 5);
        ResultSet rs=ps.executeQuery();
        while(rs.next()){
            System.out.println(rs.getInt(1));
        }
    } catch (Exception e) {
    }finally {
        DBUtils1.closeConnection(conn);
    }
}

在这里插入图片描述

为什么MySQL查询大表必须要分页?

  • 官方要求查询结果存在数据库的缓存中,直到rs.getXXX,数据才会传到服务器内存;
  • 但是MySQL根标准官方建议不一样,对JDBC查询实现的不好;
  • MySQL只要一执行查询,就把结果全部存到服务器内存中了;
  • MySQL只要查询大表,必须使用分页;
  • 所以mysql的分页操作非常简单,算是一种补救。

注意:查询尽量不要使用select *,查询字段冗余,而且如果表结构变更,系统报错不容易排查原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值