MySQL学习笔记 07 - JDBC查询数据

增删改都会使得数据库中的数据发生变化,这种变化称为数据更新,因此在执行insertupdatedelete语句时,Statement对象调用executeUpdate()方法。
但对数据进行查询时,数据库中的数据没有发生任何变化,因此查询数据时,Statement对象提供了executeQuery()方法用于执行查询,该方法返回查询结果集对象ResultSet
在对数据进行查询时,可以将ResultSet理解为一个指针该指针同一时刻只能执行查询结果集中的一条记录,默认该指针指向查询结果集的表头,调用next()将该指针下移一行,如果下移后的行有数据,返回true,否则返回false

一、ResultSetMetaData接口

在使用JDBC查询数据时,使用ResultSetMetaData接口可以获取表的元数据。

方法作用
int getColumnCount(int column)返回表中列的数量
String getColumnTypeName(int column)获取表中列的数据类型名称
String getColumnName(int column)获取表中列的名称
String getColumnLabel(int column)获取表中列的别名(标签)
boolean isAutoIncrement(int column)获取表中的列是否自动增长
int isNullable(int column)获取表中的列是否可空(非0即真)
import java.sql.*;

/**
 * 使用JDBC获取查询数据的元数据
 *
 * @author DingYi
 * @date 2020/4/19 15:12
 */
public class GetMetaDataDemo {
  public static void main(String[] args) {
    getMetaData();
  }

  private static void getMetaData () {
    Connection conn = null;
    Statement stat = null;
    ResultSet rs = null;
    try {
      // 1. 注册数据库驱动
      Class.forName("com.mysql.jdbc.Driver");

      // 2. 建立和数据库之间的连接
      conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&characterEncoding=utf8", "root", "root");

      // 3. 拼写sql语句
      String sql = "select id as 编号, username as 用户名, userpass as 密码 from users;";

      // 4. 向数据库发送并执行sql语句
      stat = conn.createStatement();
      rs = stat.executeQuery(sql);

      // 5. 处理执行结果(元数据)
      ResultSetMetaData rsm = rs.getMetaData();
      for (int i = 0; i < rsm.getColumnCount(); i ++) {
        System.out.println(rsm.getColumnTypeName(i + 1) + " " +
                rsm.getColumnName(i + 1) + " " +
                rsm.getColumnLabel(i + 1) + " " +
                rsm.isAutoIncrement(i + 1) + " " +
                rsm.isNullable(i + 1));
      }
    } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
    } finally {
      // 6. 关闭资源
      if(rs != null){
        try {
          rs.close();
          rs = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (stat != null) {
        try {
          stat.close();
          stat = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if(conn != null) {
        try {
          conn.close();
          conn = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

二、PreparedStatement接口

JDBC将SQL语句发送到数据库后,数据库要做3件事:

  1. 分析SQL语句是否合法
  2. 编译该SQL语句
  3. 执行SQL语句

1、PreparedStatement接口

PreparedStatement接口是Statement接口的子接口,也是用来将SQL语句发送到数据库中执行并获取返回结果
PreparedStatement具有的优点:

  • 高效率:如果用一条SQL语句每次都分析语法、编译、执行,显然效率低下。使用PreparedStatement接口可以将编译过的 SQL语句缓存,当再次执行缓存过的SQL语句时,会忽略分析语法和编译的过程,提高了运行效率。在开发中建议使用PreparedStatement接口执行SQL语句。
  • 安全PreparedStatement可以防止SQL注入。SQL注入是利用SQL语句的漏洞对数据库发送有潜在威胁的SQL语句。

Statement接口在执行SQL语句时,会将用户输入的数据作为SQL语句的一部分,导致被SQL注入,带来了安全隐患。PreparedStatement接口在执行SQL语句时,会将用户输入的数据作为SQL语句的参数值,而不是作为SQL语句的一部分,因此可以防止SQL注入。

import java.sql.*;

/**
 * @author DingYi
 * @date 2020/4/19 15:47
 */
public class LoginDemo2 {
  public static void main(String[] args) {
    login("1' or '1' = '1", "1' or '2' = '2");
  }

  /**
   * 登录的方法
   * @param username 用户名
   * @param userpass 用户密码
   */
  private static void login (String username, String userpass) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
      // 1. 注册数据库驱动
      Class.forName("com.mysql.jdbc.Driver");

      // 2. 建立和数据库之间的连接
      conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&characterEncoding=utf8", "root", "root");

      // 3. 拼写sql语句
      String sql = "select * from users where username = ? and userpass = ?;";

      // 4. 向数据库发送并执行sql语句
      ps = conn.prepareStatement(sql);
      ps.setObject(1, username);
      ps.setObject(2, userpass);
      rs = ps.executeQuery();

      // 5. 处理执行结果
      System.out.println("编号\t用户名\t密码");
      while (rs.next()) {
        System.out.print(rs.getInt("id"));
        System.out.print("\t\t");
        System.out.print(rs.getString("username"));
        System.out.print("\t");
        System.out.print(rs.getString("userpass"));
        System.out.println();
      }
    } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
    } finally {
      // 6. 关闭资源
      if(rs != null){
        try {
          rs.close();
          rs = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (ps != null) {
        try {
          ps.close();
          ps = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if(conn != null) {
        try {
          conn.close();
          conn = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

2、获取新增记录的自增长值

添加记录时,如果记录的主键是自增长列,那么自增长列的值是由数据库自动维护的,在记录添加成功后,可以获取到新增记录的主键值。

  • Statement.RETURN_GENERATED_KEYS表示获取生成的主键值。
  • PreparedStatement. getGeneratedKeys();表示获取生成主键值的结果集。

三、JDBC 事务

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务可以是一条SQL语句,或者多条SQL语句。事务的执行是由高级编程语言编写的程序来执行的。在Java中使用JDBC操作事务

1、事务的特性

事务具有4个属性:原子性一致性隔离性持久性。这四个属性通常称为ACID特性

  • 原子性Atomicity):一个事务是一个不可分割的工作单位,事务中包括的所有SQL语句,要么都执行,要么都不执行。原子性是事务最根本的特性
  • 一致性Consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的
  • 隔离性Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性Durability):持久性是指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @author DingYi
 * @date 2020/4/21 11:38
 */
public class TransationDemo1 {
  public static void main(String[] args) {
    transfer();
  }

  /**
   * 转账的方法
   */
  private static void transfer() {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
      // 1. 注册数据库驱动
      Class.forName("com.mysql.jdbc.Driver");

      // 2. 建立和数据库之间的连接
      conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bank?useSSL=false&characterEncoding=utf8","root","root");

      // 老兵转出100元
      // 3. 拼写SQL语句
      String sql1 = "update customer set balance = balance - ? where customerName = ?;";

      // 4. 向数据库发送并执行SQL语句
      ps = conn.prepareStatement(sql1);
      ps.setObject(1, 100);
      ps.setObject(2, "老兵");
      int rows1 = ps.executeUpdate();

      // 5. 处理执行结果
      System.out.println("数据库中有" + rows1 + "条数据被执行...,老兵转出成功!");

      if(true){
        throw new RuntimeException("转账期间发生异常!");
      }


      // 新兵转入100元
      // 3. 拼写SQL语句
      String sql2 = "update customer set balance = balance + ? where customerName = ?;";

      // 4. 向数据库发送并执行SQL语句
      ps = conn.prepareStatement(sql2);
      ps.setObject(1, 100);
      ps.setObject(2, "新兵");
      int rows2 = ps.executeUpdate();

      // 5. 处理执行结果
      System.out.println("数据库中有" + rows2 + "条数据被执行...,新兵转入成功!");

    } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
    } finally {
      // 6. 关闭资源
      if (ps != null) {
        try {
          ps.close();
          ps = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (conn != null) {
        try {
          conn.close();
          conn = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
  }

}

2、事务的类型

事务的执行分为三个步骤:

  1. 开始事务Connection对象调用setAutoCommit(false)实现手动开始事务,Connection对象调用setAutoCommit(true)实现自动开始事务,默认是自动开始事务。
  2. 执行事务Statement接口和PreparedStatement接口调用executeUpdate()方法时开始执行事务。
  3. 结束事务

而结束事务有两种情况:

  1. 提交事务Connection对象调用commit()方法提交事务。
  2. 回滚事务Connection对象调用rollback()方法提回滚务。

注意,同一个事务中的所有SQL语句,必须使用同一个Connection对象!
如果事务中的所有SQL语句都正确执行了,那么事务会提交,提交后事务结束。如果事务中有任意一条语句执行失败,就会回滚事务,回滚后事务结束。
事务的类型分为两种,分别是自动事务手动事务。自动事务是JDBC自动开始事务、执行事务、结束事务。JDBC默认使用自动事务,自动事务执行每一条语句时都是一个独立的事务。手动事务是开发人员编写程序来控制事务的开始、事务的执行、事务的结束。如果一个事务中包含两条或以上的SQL语句时,需要用手动事务。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @author DingYi
 * @date 2020/4/21 11:40
 */
public class TransationDemo2 {
  public static void main(String[] args) {
    transfer();
  }

  /**
   * 转账的方法
   */
  private static void transfer() {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
      // 1. 注册数据库驱动
      Class.forName("com.mysql.jdbc.Driver");

      // 2. 建立和数据库之间的连接
      conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bank?useSSL=false&characterEncoding=utf8","root","root");

      // 开始事务
      conn.setAutoCommit(false);

      // 老兵转出100元
      // 3. 拼写SQL语句
      String sql1 = "update customer set balance = balance - ? where customerName = ?;";

      // 4. 向数据库发送并执行SQL语句
      ps = conn.prepareStatement(sql1);
      ps.setObject(1, 100);
      ps.setObject(2, "老兵");
      int rows1 = ps.executeUpdate();

      // 5. 处理执行结果
      System.out.println("数据库中有" + rows1 + "条数据被执行...,老兵转出成功!");

      if(true){
        throw new RuntimeException("转账期间发生异常!");
      }


      // 新兵转入100元
      // 3. 拼写SQL语句
      String sql2 = "update customer set balance = balance + ? where customerName = ?;";

      // 4. 向数据库发送并执行SQL语句
      ps = conn.prepareStatement(sql2);
      ps.setObject(1, 100);
      ps.setObject(2, "新兵");
      int rows2 = ps.executeUpdate();

      // 5. 处理执行结果
      System.out.println("数据库中有" + rows2 + "条数据被执行...,新兵转入成功!");

      // 提交事务
      conn.commit();
    } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
      // 回滚事务
      try {
        conn.rollback();
      } catch (SQLException ex) {
        ex.printStackTrace();
      }
    } finally {
      // 6. 关闭资源
      if (ps != null) {
        try {
          ps.close();
          ps = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (conn != null) {
        try {
          conn.close();
          conn = null;
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值