Java JDBC基础 连接数据库 操作数据库

1、JDBC概述

1.1 数据持久化

     持久化(persistence) :把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用数据持久化意味着将内存中的数据保存到硬盘上加以"固化",而持久化的实现过程大多通过各种关系数据库来"完成

    持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

1.2 Java中的数据库存储技术

在Java中,数据库存取技术可分为如下几类:

  • JDBC直接访问数据库
  •  JDO (Java Data Object)技术
  • 第三方O/R工具,如Hibernate, Mybatis等

JDBC是java访问数据库的基石,JDO, Hibernate,MyBatis等只是更好的封装了JDBC.

1.3 JDBC介绍 Java Datebase Connectivity

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API ) ,定义了用来访问数据库的标准]ava类库, (java.sql.javax.sql )使用这些类库可以以一种标准的方法、方便地访问数据库资源。
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
  • JDBC的目标是使java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

1.4 JDBC体系结构

  • JDBC接口(API )包括两个层次:。

面向应用的API: Java API ,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)

面向数据库的API : Java Driver API ,供开发商开发数据库驱动程序用。

面向接口编程概念:JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。

说明:面向接口编程,要求Java程序员编程的代码里面不要出现任何第三方的API,以确保代码的通用性和可移植性

1.5 JDBC程序编写步骤

2、获取数据库连接 (以下方法逐步完善,最后一个就完美了)

2.1 Driver接口实现类

(1)java.sql.Driver接口是所有JDBC驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。

(2)在程序中不需要直接去访问实现了Driver接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。

Oracle的驱动: oracle.jdbc.driver.OracleDrivero

mysql的驱动: com.mysql.jdbc.Driver

使用Driver实现数据库连接步骤:
A: 创建一个 Driver 实现类的对象
B: 准备连接数据库的基本信息: url, user, password
C: 调用 Driver 接口的 connect(url, info) 获取数据库连接

说明:下载mysql驱动的jar包,驱动,mysql驱动,就是实现类(也可通过maven直接获取到mysql驱动)

2.2 URL

  • JDBC URL用于标识一个被注册的驱动程序,驱动程序管理器通过这个URL选择正确的驱动程序,从而建立到数据库的连接。
  • JDBC URL的标准由三部分组成,各部分间用号分隔

语法:   

         jdbc:子协议:子名称

说明:

 ① 协议: JDBC URL中的协议总是jdbc。

 ② 子协议:子协议用于标识一个数据库驱动程序。

 ③ 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名

exp:

2.3、连接数据库(最终获取Connection对象)

 2.3.1 获取连接Connection对象的方式一:使用Driver连接数据库

使用Driver实现数据库连接步骤:
A: 创建一个 Driver 实现类的对象
B: 准备连接数据库的基本信息: url, user, password
C: 调用 Driver 接口的 connect(url, info) 获取数据库连接

//JDBCjava本来就有,只需通过maven导入mysql驱动(实现类)
public class ConnectionTest{
  public void testConnection1() throws SQLException{
    Driver driver = new com.mysql.jdbc.Driver(); //用JDBC接口 获取 mysql驱动实例化对象
              //协议:子协议:本机或别人的ip地址:端口号/数据库
    String url ="jdbc:mysql://localhost:3306/test";
    //用Map子类集合Propertis 来装账号和密码
    Properties info = new Properties();
    info.setProperty("user","root"); //向集合添加用户名
    info.setProperty("password","abc");//向集合添加用户密码
    Connection conn = driver.connect(url,info); //注册驱动连接好数据库并将对象coon返回
    System.out.println(conn);
  }
}

此方法有弊端,第一句代码直接用第三方厂家的实现类来获取了Driver对象,这样会影响可移植性(不方便切换其他数据库)。因此需要改进

2.3.2 获取连接Connection对象的方式二:对方式一进行改进   通过反射来动态获取Driver对象.差别就在第一步

方式二在如下过程中不出现第三方的api,使得程序具有更好的可移植性
public class ConnectionTest{
  public void testConnection2() throws Exception{
    //使用反射获取driver对象
    Class clazz = Class.forName("com.mysql.jdbc.Driver");//此方法要求该对象必须给权限和提供了空参构造器
    Driver driver = (Driver) clazz.newInstance(); //用JDBC接口 获取 mysql驱动实例化对象
              //协议:子协议:本机或别人的ip地址:端口号/数据库
    String url ="jdbc:mysql://localhost:3306/test";
    //用Map子类集合Propertis 来装账号和密码
    Properties info = new Properties();
    info.setProperty("user","root"); //向集合添加用户名
    info.setProperty("password","abc");//向集合添加用户密码
    Connection conn = driver.connect(url,info); //注册驱动并连接好数据库并将对象coon返回
    System.out.println(conn);
  }
}

2.3.3 获取连接Connection对象的方式三:使用DriverManager类

说明:使用驱动管理获取需要注册驱动

public class ConnectionTest{
  public void testConnection3() throws Exception{
    //获取3个连接基本信息 url,用户名,密码
    String url ="jdbc:mysql://localhost:3306/test";
    String user = "root";
    String password = "abc123";
    //使用反射获取Driver实现类对象
    Class clazz = Class.forName("com.mysql.jdbc.Driver");//此方法要求该对象必须给权限和提供了空参构造器
    Driver driver = (Driver) clazz.newInstance(); //用JDBC接口 获取 mysql驱动实例化对象
              //协议:子协议:本机或别人的ip地址:端口号/数据库  
    //注册驱动
    DriverManager.registerDriver(driver);//使用驱动管理,需要注册驱动
    //获取连接
    Connection conn = DriverManager.getConnection(url,user,passord); //连接好数据库并将对象coon返回
    System.out.println(conn);
  }
}

2.3.4 获取连接Connection对象的方式四:使用DriverManager  (优化代码方法)只是加载驱动,不用显式注册驱动了

public class ConnectionTest{
  public void testConnection4() throws Exception{
    //获取3个连接基本信息 url,用户名,密码
    String url ="jdbc:mysql://localhost:3306/test";
    String user = "root";
    String password = "abc123";
    //加载Driver实现类并注册
    Class.forName("com.mysql.jdbc.Driver");
    //获取连接
    Connection conn = DriverManager.getConnection(url,user,passord); //连接好数据库并将对象coon返回
    System.out.println(conn);
  }
}

特别说明:优化代码的核心在于

Class.forName(""com.mysql.jdbc.Driver);//反射  Class类的forName方法,加载驱动的Driver类

加载驱动的Driver类的时候,查看Driver类源码,有一段随类一起加载的静态代码块

static{

    try{

        java.sql.DriverManager.registerDriver(new Driver()); //加载驱动的时候,就创建并注册Driver匿名对象

    }  catch (SQLException E)){

        throw new RuntimeException("Can't register driver");

    }

}

2.3.5 通过加载配置文件的方式获取Connection对象

   一般配置信息url,password,user等,最好不要显式的写在代码里面。写到专门的配置文件里面会更安全

根据maven规则。配置文件一般都放在src/main/resources 目录下。一般用map接口实现类properties集合封装的配置信息,都用.propertis文件配置

#Step1 编写配置文件 JDBC.properties  注意properties配置文件,“=”两边不能有空格,会有歧义

user=root

password=abc123

url=jdbc:mysql://localhost:3306/test

driverClass=com.mysql.jdbc.Driver

public class ConnectionTest{
  public void testConnection5() throws Exception{
    //读取配置文件中的4个基本信息,返回一个流
    InputStream is=ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
    Properties pros=new Properties();//创建一个集合来读取配置文件
    pros.load(is);
  
    //通过key读取出相应的基本信息的值
    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    String url = pros.getProperty("url");
    String driverClass = pros.getProperty("driverClass");


    //加载Driver实现类并注册
    Class.forName(driverClass);
    //获取连接
    Connection conn = DriverManager.getConnection(url,user,passord); //连接好数据库并将对象coon返回
    System.out.println(conn);
  }
}

说明:类加载器的Class.getClassLoader.getResourceAsStream(String path) :

默认则是从ClassPath根目录下获取文件资源,path不能以'/'开头,最终是由ClassLoader获取资源。并返回一个输入流。

  • 最终方法的好处?

      实现了数据与代码分离,实现了解耦。(只用修改配置文件,不改代码,就可以换一个数据库连接)

说明:如果修改代码,哪怕只修改一句话,就需要重新编译。如果是大项目,很费时间。但是配置文件和代码分离,修改配置文件,不需要重新编译代码,就会有很好的效率。

3、使用PreparedStatement实现CRUD操作

3.1 操作和访问数据库

    数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。

    在java.sql包中有3个接口分别定义了对数据库的调用的不同方式:

① Statement: 用于执行静态SQL语句并返回它所生成结果的对象。(有缺陷,一般不用)

② PrepatedStatement: SQL语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。

③ CallableStatement: 用于执行SQL存储过程

PreparedStatement Vs Statement

① PreparedStatement能最大可能提高性能:

② DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。

③ 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配没有缓存语句的意义,事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次(语法检查,语义检查,翻译成二进制命令,缓存)

3.2 Statement缺陷

遇到变量,就需要用" + "拼写语句,麻烦

SQL语句 SELECT USER,PASSWORD FROM user_table WHERE USER = 'AA' AND PASSWORD = '123456'

String sql = "SELECT user, password FROM user_table WHERE user='"+user+"'AND password='"+password+'";

黑客可以利用“+”拼写规则  SQL 恶意注入,不能容忍

String sql = "SELECT user,password FROM user_table WHERE user ='1' or 'AND password' = 1 or'1' = '1'";

该语句会导致,用户名和密码不匹配也登录成功。利用拼写规则,恶意写出 or 1=1 导致永远为真

 

3.3 使用PreparedStatement实现CRU(增删改)操作

   增删改  共性:只需要对数据默默动手操作,不需要返回值

   查询  : 必须要返回值给大家看 。由于要处理查询到的数据表,所以很麻烦,单据讲

(1)操作步骤

如何使用Connection获取PrepatedStatement对象:

https://docs.oracle.com/en/java/javase/15/docs/api/java.sql/java/sql/Connection.html

PreparedStatement对象如何设置sql语句中的占位符

https://docs.oracle.com/en/java/javase/15/docs/api/java.sql/java/sql/PreparedStatement.html

(2)添加操作

public class ConnectionTest {
  public void testInsert() throws Exception{
    //1、读取配置文件中的4个基本信息,返回一个流
    InputStream is=ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
    Properties pros=new Properties();//创建一个集合来读取配置文件
    pros.load(is);
  
    //2、通过key从配置文件读取出相应的基本信息的值
    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    String url = pros.getProperty("url");
    String driverClass = pros.getProperty("driverClass");

    //3、加载Driver实现类并注册
    Class.forName(driverClass);
    //4、获取连接
    Connection conn = DriverManager.getConnection(url,user,passord); //连接好数据库并将对象conn返回
    System.out.println(conn);
    //5、预编译sql语句,返回PrepareStatement实例
    String sql="insert into customers(name,email,birch) values (?,?,?)";//这里?是占位符,不是通配符。就是通过这个占位符来解决Statement的弊端
    PrepareStatement ps = coon.prepareStatement(sql); //获取到PrepareStatement实例
    //6、填充占位符
    ps.setString(1,"霸王丸");//设置第1个占位符 String类型的霸王丸
    ps.setString(2,"nezha@gmail.com");//设置第2个占位符 String类型的邮箱
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//设置日期格式,为常见的格式
    java.util.Date date = sdf.parse("1000-01-01"); //按照指定的日期格式将字符串解析成日期
    ps.setDate(3,new Date(date.getTime()));//设置第3个占位符 日期类型的生日
    //7、执行操作
    ps.execute();
    //8、关闭资源
    ps.close();
    conn.close();
  }
}

说明:我这里只是为了省事,带有流关闭的异常,都需要自行try-catch-finally解决调,不要往上抛

扩展:由于每次建立数据库,都必然会发生获取数据库连接,关闭资源这些事,代码量也不少。因此可以专门为这些事情写一个工具类JDBCUtils

(3)用工具类JDBCUtils封装好连接和关闭代码

public class JDBCUtils{
  public static Connection getConnection() throws Exception{  //获取数据库的连接
    //1、读取配置文件中的4个基本信息,返回一个流 类加载器换成了加载本类的加载器,这样就不管类叫什么名字了
    InputStream is=ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
    Properties pros=new Properties();//创建一个集合来读取配置文件
    pros.load(is);
  
    //2、通过key从配置文件读取出相应的基本信息的值
    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    String url = pros.getProperty("url");
    String driverClass = pros.getProperty("driverClass");

    //3、加载Driver实现类并注册
    Class.forName(driverClass);
    //4、获取连接
    Connection conn = DriverManager.getConnection(url,user,passord); //连接好数据库并将对象conn返回
    return conn; //返回连接
  }
  public void closeResource(Connection conn,Statement ps){ //注意IDEA会让你选择,工具里面形参写接口,不要写实现类。Statement父接口也可以指向P实现类,会更安全
    try{
      if(ps!=null)
        ps.close();
    } catch (SQLException e){
      e.printStackTrace();
    }
    try{
      if(conn!=null)
        conn.close();
    } catch (SQLException e){
      e.printStackTrace();
    }
  }
   public void closeResource(Connection conn,Statement ps,ResultSet rs){ //再写一个关闭资源方法,可以构成方法重载。因为后面可能遇到关闭多个流的时候
    try{
      if(ps!=null)
        ps.close();
    } catch (SQLException e){
      e.printStackTrace();
    }
    try{
      if(conn!=null)
        coon.close();
    } catch (SQLException e){
      e.printStackTrace();
    }
    try{
      if(rs!=null)
        rs.close();
    } catch (SQLException e){
      e.printStackTrace();
    }
  }
}

(4)使用PreparedStatement修改表的一条记录

操作步骤:

① 获取数据库的连接

② 预编译sql语句,返回PreparedStatement实例

③ 填充占位符

④ 执行

⑤ 关闭资源

public class PreparedStatementUpdateTest{
  //修改customers表的一条记录
  public void testUpdate() throws Exception{
    //1.获取数据库连接  用上面写的工具类方法
    Connection conn = JDBCUtils.getConnection();
    //2.预编译sql语句,返回PreparedStatement实例
    String sql = "update customers set name = ? where id =?";
    PreparedStatement ps = conn.prepareStatement(sql);
    //3.填充占位符
    ps.setObject(1,"莫扎特");//虽然知道是String,但是用Object可以更通用
    ps.setObject(2,18);//虽然知道是int,但是Object更通用
    //4.执行sql语句
    ps.execute();
    //5.资源关闭 用我们自己的工具类的方法
    JDBCUtils.closeResource(conn,ps);
  }
}

扩展:通用 增删改写法

分析:增删改不同的地方是 sql语句本身 和 sql语句的占位符数量。sql语句就是一个String,占位符数量不可预测,因此可使用可变形参

public class PreparedStatementUpdateTest{
  //通用的增删改操作
  public void update(String sql,Object ...args) throws Exception{//sql中的占位符个数与可变形参一致
    //1.获取数据库连接
    Connection conn=JDBCUtils.getConnection();
    //2.预编译sql语句,返回PreparedStatement实例
    PreparedStatement ps = conn.prepareStatement(sql);
    //3.填充占位符
    for(int i=0;i<args.length;i++){
      ps.setObject(i+1,args[i]);//特别小心sql是从1开始,但数组是从0开始
    }
    //4.执行sql语句
    ps.execute();
    //5.资源关闭 用我们自己的工具类的方法
    JDBCUtils.closeResource(conn,ps);
  }
}

 特别注意:

    java中写sql语句,由于都是字符串表达,转到sql里面的时候,去掉双引号,还原sql本身语句的时候,容易有歧义。有时sql就认不到了,因此类似数据表名,列名等字段名要加``。防止出错

3.4 查询操作

3.4.1 查询Customer表

难点:比起增删改,查询需要返回一个数据表,将这个数据表封装到一个对象中,给java处理

SQL语句查询到的数据表:SELECT id,NAME,email,birth FROM customers WHERE id =1

public class CustomerForQuery{
  public void testQuery1() throws Exception{
    Connection conn = JDBCUtils.getConnection();
    String sql = "select id,name,email,birth from customers where id=?";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setObject(1,1);//填充占位符,第一个?位置,然后id=1
    //执行,并返回结果集
    ResultSet resultset = ps.executeQuery();
    //难点,处理结果集
    if(resultSet.next()){ //next()作用:判断结果集的下一条是否有数据,如果有数据返回true,指针下移。如果返回false,指针不会下移。这里明显第一条有数据。现在就处理查询到的第一条数据
    //获取这条数据的各个字段值
    int id = resultSet.getInt(1);//获取第一个字段的值
    String name = resultSet.getString(2);//获取第二个字段的值
    String email = resultSet.getString(3);//获取第三个字段的值
    Date birth = resultSet.getDate(4);//获取第四个字段的值
    //用自己编写的Customer类的对象,来接收查询数据
    Customer customer = new Customer(id,name,email,birth);
    System.out.println(customer);
    }
    //关闭资源
    JDBCUtils.closeResource(conn,ps,rs);
  }
}


//编写一个java类来表示接收到的这个数据表,按照javabean的规则来写
public class Customer{ //这个类表示Customer数据表
  //根据下面的对应方式,造好每个字段的属性
  private int id;
  private String name;
  private String email;
  private Date birith;

  //用Idea自行生成空参构造器,带参构造器,get,set方法,以及toString方法
  public Customer(){  //空参构造器
    super();
  }
  public Customer(int id,String name, String email,Date birth){
    super();
    this.id=id;
    this.name=name;
    this.email=email;
    this.birth=birth;
  }
  public int getId(){
    return id;
  }
  //等等方法
}
  • ORM编程思想(Object relational mapping)

① 一个数据表对应一个java类

② 表中的一条记录对应java类的一个对象

③ 表中的一个字段对应java类的一个属性

  • Java 与 SQL 对应数据类型转换表
Java类型SQL类型
booleanBIT
byteTINYINT
shortSMALLINT
intINTEGER
longBIGINT
StringCHAR,VARCHAR,LONGVARCHAR
byte arrayBINARY,VAR BINARY
java.sql.DateDATE
java.sql.TimeTIME
java.sql.TimestampTIMESTAMP

 扩展:写一个通用查询方法   查询10周坏蛋

public class CustomerForQuery{
  public Customer queryForCustomers(String sql,Object...args) throws Exception{
    //获取连接
    Connection conn = JDBCUtils.getConnection();
    PreparedStatement ps = conn.prepareStatement(sql);
    //填充sql中的占位符
    for(int i=0;i<args.length;i++){
      ps.setObject(i+1,args[i]);
    }
    //执行,并返回结果集
    ResultSet rs = ps.executeQuery();
    //获取结果集的元数据:ResultSetMetaDate
    ResuleSetMetaDate rsmd = rs.getMetaData();
    //通过ResultSetMetaDate获取结果集中的列数
    int columnCount = rsmd.getColumCount();

    //难点,处理结果集
    if(rs.next()){ //next()作用:判断结果集的下一条是否有数据,如果有数据返回true,指针下移。如果返回false,指针不会下移
      Customer cust = new Customer();//用自己写的Customer类的对象来接收查询的数据表数据
      //处理结果集一行数据中的每一个列
      for(int i=0;i<columnCount;i++){
        Object columnValue = rs.getObject(i+1);//获取第i+1行,第1列的值,Object可以是String,Date,Integer等任意类型
        //获取每个列的列名
        String columnName = rsmd.getColumnName(i+1);//获取数据表的列名
        //给cust对象指定的columnName属性,赋值为columValue,通过反射
        Field field = Customer.class.getDeclaredField(columnName);
        field.setAccessible(true); //就是因为给private赋值,所以才不得不用反射,要不直接cust.属性  就赋值了
        field.set(cust,columValue);//给cust对象的相关属性赋值
      }
      return cust;
    }
    //关闭资源
    JDBCUtils.closeResource(conn,ps,rs);
    return null;
  }
  //测试一下上面写的通用方法
  @Test
  public void testQueryForCustomers(){
    String sql = "select `name`,`email` from customers where name =?";
    Customer customer = queryForCustomers(sql,"周杰伦"); //用自己写的查询方法,查询数据库中周杰伦的信息
    System.out.println(customer);
  }
}

说明:获取的结果集ResultSet里面有查询到的所有数据的值。但是结果集的元数据ResultSetMetaDate,有数据表头的信息(比如列名和列数)

ResultSetMetaDate:An object that can be used to get information about the types and properties of the columns in a ResultSet object. The following code fragment creates the ResultSet object rs, creates the ResultSetMetaData object rsmd, and uses rsmd to find out how many columns rs has and whether the first column in rs can be used in a WHERE clause.

https://docs.oracle.com/en/java/javase/15/docs/api/java.sql/java/sql/ResultSetMetaData.html

3.4.2 查询Order表  通用查询操作

java中的小驼峰命名规则没得下划线,因此java中这个表Order类的属性名就不准备和mysql里面的一致啦,就用小驼峰。但是这样两边列名不一致会导致一种异常,因此这样做代码,需要做某种改进

步骤:

step1.查询语句中,给每个列取别名,别名需要和Order类的属性名一致

step2.使用ResultSetMeteData中获取别名的方法getColumnLabel()来替换原来的getColumnName()

//用javaBean规则写一个Order类,代表Order数据表
public class Order{
  private int orderId;
  private String orderName;
  private Date orderDate;
  //让Idea自动生成构造器,空参的,带参的,get,set以及toString方法
  public Order(){
    super();
  }
  //等等等等
}
public class OrderForQuery{
  public Order orderForQuery(String sql,Object...args){
    Connection conn=JDBCUtils.getConnection(); //获取连接
    PreparedStatement ps = conn.prepareStatement(sql); //预处理sql语句
    for(int i = 0;i<args.length;i++){  //填充占位符
      ps.setObject(i+1,args[i]);
    }
    //执行,获取结果集
    ResultSet rs = ps.executeQuery();
    //获取结果集的元数据
    ResultSetMetaData rsmd=rs.getMetaData();
    //获取列数
    int columnCount = rsmd.getColumnCount();
    if(rs.next()){
      Order order = new Order();
      for(int i=0; i< columnCount;i++){
        //获取每个列的列值,通过ResultSet
        Object columnValue = rs.getObject(i+1);
        //获取每个列的列名,通过ResultSetMetaData
        String columnLabel = rsmd.getColumnLabel(i+1);//getColumnLabel()获取列的别名
        //通过反射,将对象指定名columnName的属性赋值为指定的值的columnValue
        Field field = Order.class.getDeclaredField(columnLabel);
        field.setAccessible(true);
        field.set(order,columnValue);
      }
      return order;
    }
    JDBCUtils.closeResource(conn,ps,rs);
    return null;
  }
  @Test
  public void testOrderForQuery(){
    //特别注意:针对于表的字段名和类的属性名不一致的情况
    //必须声明sql时,使用类的属性名来命名字段的别名
    String sql ="select order_id orderId,order_name orderName,order_data orderDate from `order` WHERE order_id=1";
    Order order = orderForQuery(sql,1);
    System.out.println(order);
  }
}

总结:代码逻辑中用getColumnLabel() 比 getColumnName()更好,getColumnName()只能读取列名。getColumnLabel()可以读取别名,而且再没有别名的情况下,会自动读取列名

  • 查询操作流程

3.4.3 再进一步  针对不同表的 通用查询

要点:由于一个类代表一张数据表,现在要查不同表,因此数据表(类)要作为形参传入,用Object不专业,因此用泛型

public class PreparedStatementQueryTest{
  //对接收的类(数据表),和方法一起使用泛型
  public <T> T getInstance(Class<T> clazz,String sql,Object...args){
    Connection conn = JDBCUtils.getConnection();
    PreparedStatement ps = conn.prepareStatement(sql);
    for(int i=0; i<args.length;i++){
      ps.setObject(i+1,args[i]);
    }
    ResultSet rs = ps.executeQuery();//获取结果集
    ResultSetMetaData rsmd = rs.getMetaData();//获取元数据
    if(rs.next()){
      T t = clazz.newInstance(); //通过反射造T类型的对象(造出某个数据表)
      //处理结果集 一行数据中的 每一个列
      for(int i=0;i<columnCount;i++){
        //获取列值
        Object columValue = rs.getObject(i+1);
        //获取列名
        String columnLabel = rsmd.getColumnLabel(i+1);
        //给cust对象指定的columnName属性,赋值为columValue
        Field field = clazz.class.getDeclaredField(columnLabel);
        field.setAccessible(true);
        field.set(t,columValue);
      } 
      
    }
    return t;
    JDBCtils.closeResource(conn,ps,rs);
  }
  @Test  //测试Customer表
  public void testGetInstance(){
    String sql1 = "select id,name,email from customers where id =?";
    Customer customer = getInstance(Customer.class,sql1,12);
    System.out.println(customer);
    //测试Order表
    String sql2 ="select order_id orderId,order_name orderName from `order` where order_id=?"; //一定记得取别名
    Order order = getInstance(Order.class,sql1,1);
    System.out.println(order);
  }
}

继续深入,通用获取某个表的多条记录。

要点:需要获取多条记录,就意味着有多个对象,因此需要用一个集合去收集这些对象

public class PreparedStatementQueryTest{
  //对接收的类(数据表),和方法一起使用泛型,用一个集合收集对象
  public <T> T List<T> getForList(Class<T> clazz,String sql,Object...args){
    Connection conn = JDBCUtils.getConnection();
    PreparedStatement ps = conn.prepareStatement(sql);
    for(int i=0; i<args.length;i++){
      ps.setObject(i+1,args[i]);
    }
    ResultSet rs = ps.executeQuery();//获取结果集
    ResultSetMetaData rsmd = rs.getMetaData();//获取元数据
    ArrayList<T> list = new ArrayList<T>();//造一个集合,收集数据表(对象)
    while(rs.next()){ //一行一行的收集数据,并将每一行的数据给一个对象,最后将所有对象加入集合
      T t = clazz.newInstance(); //通过反射造T类型的对象(造出某个数据表)
      //处理结果集 一行数据中的 每一个列
      for(int i=0;i<columnCount;i++){
        //获取列值
        Object columValue = rs.getObject(i+1);
        //获取列名
        String columnLabel = rsmd.getColumnLabel(i+1);
        //给cust对象指定的columnName属性,赋值为columValue
        Field field = clazz.class.getDeclaredField(columnLabel);
        field.setAccessible(true);
        field.set(t,columValue);
      } 
      list.add(t);//将这行数据(t对象),加入list集合
    }
    return list; //返回收集到的所有行
    JDBCtils.closeResource(conn,ps,rs);
  }
  @Test  //测试查询数据表里的多条数据
  public void testGetInstance(){
    String sql = "select id,name,email from customers where id <?";
    List<Customer> list = getForList(Customer.class,sql,12);
    for(Customer customerTemt:list){  //打印数组中收集到的对象(数据表的列)
      System.out.println(customerTemt);
    }
  }
}

 


版权声明:本文为chengqingshihuishui原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。