欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java入门(八)JDBC

程序员文章站 2022-06-23 12:50:34
...

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

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

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

JDBC概述

Java入门(八)JDBC

JDBC是什么

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

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

为什么使用JDBC

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

怎么使用JDBC

Java入门(八)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 游标

Java入门(八)JDBC

  • 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. 保护数据库连接数量,避免连接过载;

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

Java入门(八)JDBC

课堂练习

Java入门(八)JDBC

   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();
       }
   }

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

Java入门(八)JDBC

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

Java入门(八)JDBC

数据库执行计划

  • 目的:优化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的时候,就需要进行多次连接通讯,效率低。

Java入门(八)JDBC

Statement 解决方案:

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

Java入门(八)JDBC

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);
        }
    }
    

Java入门(八)JDBC

结果集元数据

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

Java入门(八)JDBC

  • 举个栗子:

    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;

Java入门(八)JDBC

课堂练习

  • 借钱的业务

    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语句在恢复自动提交的时候也会执行。

返回自增主键

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

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

Java入门(八)JDBC

Java入门(八)JDBC

解决方案:

  • 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);
    }
}

Java入门(八)JDBC

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

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

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

相关标签: Java入门 jdbc