JDBC开荒

docker 创建MySQL

一、简介

Java DataBase Connectivity ,是Java程序访问数据库的标准接口
Java访问DB的时候,并不是直接通过TCP连接的,而是通过JDBC接口,而JDBC接口又是通过JDBC驱动来访问的
JDBC是Java标准库自带的,具体的JDBC驱动是由数据库厂商提供的,所以JBDC借口都是统一的

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│  ┌───────────────┐  │
   │   Java App    │
│  └───────────────┘  │
           │
│          ▼          │
   ┌───────────────┐
│  │JDBC Interface │◀─┼─── JDK
   └───────────────┘
│          │          │
           ▼
│  ┌───────────────┐  │
   │ MySQL Driver  │◀───── Oracle
│  └───────────────┘  │
           │
└ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ┘
           ▼
   ┌───────────────┐
   │     MySQL     │
   └───────────────┘

一个MySQL的驱动就是一个jar包,本身也是纯java编写的,我们自己的代码只需要引用java.sql.*接口,接口再通过MySQL驱动的jar包访问DB

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
   ┌───────────────┐
│  │   App.class   │  │
   └───────────────┘
│          │          │
           ▼
│  ┌───────────────┐  │
   │  java.sql.*   │
│  └───────────────┘  │
           │
│          ▼          │
   ┌───────────────┐     TCP    ┌───────────────┐
│  │ mysql-xxx.jar │──┼────────▶│     MySQL     │
   └───────────────┘            └───────────────┘
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
          JVM

二、JDBC查询

2.1 驱动

JDBC是一套标准的规范接口,在 java.sql 下 , 具体的实现类在厂家提供的驱动中
maven 添加驱动

        <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.0.31</version>
        </dependency>

创造一些测试数据

-- 创建数据库learjdbc:
DROP DATABASE IF EXISTS learnjdbc;
CREATE DATABASE learnjdbc;

-- 创建登录用户learn/口令learnpassword
CREATE USER IF NOT EXISTS learn@'%' IDENTIFIED BY 'learnpassword';
GRANT ALL PRIVILEGES ON learnjdbc.* TO learn@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;

-- 创建表students:
USE learnjdbc;
CREATE TABLE students (
  id BIGINT AUTO_INCREMENT NOT NULL,
  name VARCHAR(50) NOT NULL,
  gender TINYINT(1) NOT NULL,
  grade INT NOT NULL,
  score INT NOT NULL,
  PRIMARY KEY(id)
) Engine=INNODB DEFAULT CHARSET=UTF8;

-- 插入初始数据:
INSERT INTO students (name, gender, grade, score) VALUES ('小明', 1, 1, 88);
INSERT INTO students (name, gender, grade, score) VALUES ('小红', 1, 1, 95);
INSERT INTO students (name, gender, grade, score) VALUES ('小军', 0, 1, 93);
INSERT INTO students (name, gender, grade, score) VALUES ('小白', 0, 1, 100);
INSERT INTO students (name, gender, grade, score) VALUES ('小牛', 1, 2, 96);
INSERT INTO students (name, gender, grade, score) VALUES ('小兵', 1, 2, 99);
INSERT INTO students (name, gender, grade, score) VALUES ('小强', 0, 2, 86);
INSERT INTO students (name, gender, grade, score) VALUES ('小乔', 0, 2, 79);
INSERT INTO students (name, gender, grade, score) VALUES ('小青', 1, 3, 85);
INSERT INTO students (name, gender, grade, score) VALUES ('小王', 1, 3, 90);
INSERT INTO students (name, gender, grade, score) VALUES ('小林', 0, 3, 91);
INSERT INTO students (name, gender, grade, score) VALUES ('小贝', 0, 3, 77);

2.2 连接

使用JDBC 需要首先了解什么是Connection:一个jdbc连接,相当于java程序到数据库的连接,链接DB需要:URL username pw 口令
url:jdbc:mysql://:/?key1=value1&key2=value2

jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8

useSSL=false&characterEncoding=utf8 不使用SSL加密,使用UTF-8作为字符编码

// JDBC连接的URL, 不同数据库有不同的格式:
String JDBC_URL = "jdbc:mysql://localhost:3306/test";
String JDBC_USER = "root";
String JDBC_PASSWORD = "password";
// 获取连接:
Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
// TODO: 访问数据库...
// 关闭连接:
conn.close();

核心代码是DriverManager 提供的静态方法getConnection()。Driver会自动扫描classpath,找到所有的JDBC驱动,然后根据URL挑选一个合适的驱动

因为JDBC连接是昂贵的资源,用后及时释放 用 try(resource)来自动释放JDBC是一个好方法

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
    ...
}

2.3 查询

package com.ifeng;

import java.sql.*;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args ) throws SQLException {

        // JDBC连接的URL, 不同数据库有不同的格式:
        String JDBC_URL = "jdbc:mysql://localhost:3306/learnjdbc";
        String JDBC_USER = "root";
        String JDBC_PASSWORD = "123456";

        Connection connection;
        Statement statement = null;
        ResultSet resultSet = null;
        //获取链接
        try {
             connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
             statement = connection.createStatement();
             resultSet = statement.executeQuery("select * from students");
            while (resultSet.next()){
                Long id = resultSet.getLong(1);
                String name = resultSet.getString(2);
                String gender = resultSet.getString(3);
                String grade = resultSet.getString(4);
                String score = resultSet.getString(5);
                System.out.println("id = " + id + " , name = " + name + " , gender = " + gender + " , grade = " + grade + " , score = " + score);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if(statement != null) statement.close();
            if(resultSet != null) resultSet.close();
        }

    }
}

statement & resultSet 都是稀缺资源,及时关闭
resultSet.next() 用于判断是否有下一行,如果有 自动移入下一行
ResultSet 获取列时,索引从1开始

2.4 SQL注入

使用statement 非常容易引发SQL注入的问题
例如:验证登陆的方法:

User login(String name, String pass) {
    ...
    stmt.executeQuery("SELECT * FROM user WHERE login='" + name + "' AND pass='" + pass + "'");
    ...
}

name & pass 都是从前端传过来的

name = "bob' OR pass=", pass = " OR pass='"

SELECT * FROM user WHERE login='bob' OR pass=' AND pass=' OR pass=''

避免SQL注入,针对所有的字符串进行转义,但很麻烦
还有一个方法就是PreparedStatement :Prepared 始终使用 ? 作为占位符,并且把数据 & SQL 本身传递给DB

User login(String name, String pass) {
    ...
    String sql = "SELECT * FROM user WHERE login=? AND pass=?";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setObject(1, name);
    ps.setObject(2, pass);
    ...
}

改造上面的查询

        try {
             connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
            PreparedStatement preparedStatement = connection.prepareStatement("select * from students WHERE gender=? AND grade=?");
            preparedStatement.setObject(1,"M"); // 设置占位符的值
            preparedStatement.setObject(2,3);
            resultSet1 = preparedStatement.executeQuery();// 最后仍然是ResultSet
            while (resultSet1.next()){
                Long id = resultSet1.getLong(1);
                String name = resultSet1.getString(2);
                String gender = resultSet1.getString(3);
                String grade = resultSet1.getString(4);
                String score = resultSet1.getString(5);
                System.out.println("id = " + id + " , name = " + name + " , gender = " + gender + " , grade = " + grade + " , score = " + score);
            }
        }

三、JDBC更新

DB操作总结起来就是增删改查,CRUD:Create Retrieve Update Delete
查询用上面的PreparedStatement

3.1 插入

insert ,本质上也是用PreparedStatement执行一条SQL,不过执行者不是executeQuery() 而是executeUpdate()

             connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
            PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO students (id, grade, name, gender, score) VALUES (?,?,?,?,?)");
            preparedStatement.setObject(1,10);
            preparedStatement.setObject(2,3);
            preparedStatement.setObject(3,"Bob");
            preparedStatement.setObject(4,2);
            preparedStatement.setObject(5,101);

            int i = preparedStatement.executeUpdate(); // 使用executeUpdate来更新
            System.out.println("executeUpdate = " + i);

设置参数和查询是一样的,有几个?占位符就设置几个对应参数,
要严格执行不能手动拼接SQL字符串的原则,避免安全漏洞

3.2 插入并获取主键

表设置了自增主键,insert后 数据库会自动分配主键,如何获取主键?
在创建PreparedStatement的时候,指定一个RETURN_GENERATED_KEYS标志位,表示JDBC驱动必须返回插入的自增主键

             connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
            PreparedStatement preparedStatement = connection.prepareStatement(
                    "INSERT INTO students (id, grade, name, gender, score) VALUES (?,?,?,?)",
                    Statement.RETURN_GENERATED_KEYS
            );
            preparedStatement.setObject(1,5);
            preparedStatement.setObject(2,"Jerry");
            preparedStatement.setObject(3,2);
            preparedStatement.setObject(4,101);

            int i = preparedStatement.executeUpdate(); // 使用executeUpdate来更新
            try(ResultSet rs = preparedStatement.getGeneratedKeys()){
                if (rs.next()) {
                    System.out.println(rs.getLong(1));
                }
            }

3.3 更新

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
    try (PreparedStatement ps = conn.prepareStatement("UPDATE students SET name=? WHERE id=?")) {
        ps.setObject(1, "Bob"); // 注意:索引从1开始
        ps.setObject(2, 999);
        int n = ps.executeUpdate(); // 返回更新的行数
    }
}

3.4 删除

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
    try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) {
        ps.setObject(1, 999); // 注意:索引从1开始
        int n = ps.executeUpdate(); // 删除的行数
    }
}

四、事务

DB事务(Transaction)由若干个SQL语句构成一个操作序列,类似Java的synchronized同步,一个SQL要么全部成功,或者全部不成功:ACID

  • Atomicity:原子性
  • Consistency:一致性
  • Isolation:隔离性
  • Durability:持久性

数据库事务可以并发执行,从效率角度,定义了不同的隔离级别
在这里插入图片描述

  • 脏读:A事务读到B事务更新但未提交的数据,B回滚了
  • 不重复读:A事务第一次读取数据后,B事务修改了数据,A又读取数据,两次数据不一致
  • 幻读:A事务查询记录,没有,然后更新这条记录,竟然成功了

在JDBC中执行事物,本质是把多条SQL包裹在一个数据库事务中执行

Connect conn = openConnection();
try{
	//关闭自动提交
	conn.setAutoCommit(false);
	//执行多条SQL
	insert();update();delete;
	//提交事物
	conn.commit();
}catch(SQLException e){
	// 回滚事物;
	conn.rollback();
}

conn.setAtuoCommit(false) 代表自动提交关闭
conn.commit() 手动提交

//可以设定隔离级别,默认REPEATABLE_READ
conn.setTrasactionIsolation(Connection.TRANSACTION_READ_COMMITTED)

五、Batch

批量操作,可通过循环来执行PreparedStatement来执行,但是性能很低。只有参数不同若干语句可以作为batch执行(批量),它有特别的优化,速度远远快于循环执行的每个SQL

public class JDBCTest5
{
    public static void main( String[] args ) throws SQLException {

        List<Student> students = List.of(
                new Student("小红", 20L,3L,48)
                ,new Student("小红2", 20L,3L,49)
                ,new Student("小红3", 20L,3L,50)
                ,new Student("小红4", 20L,3L,49)
                ,new Student("小红5", 20L,3L,55)
        );

        // JDBC连接的URL, 不同数据库有不同的格式:
        String JDBC_URL = "jdbc:mysql://localhost:3306/learnjdbc";
        String JDBC_USER = "root";
        String JDBC_PASSWORD = "123456";

        Connection connection;
        Statement statement = null;
        ResultSet resultSet1 = null;
        //获取链接
        try {
             connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
            PreparedStatement preparedStatement = connection.prepareStatement(
                    "INSERT INTO students (grade, name, gender, score) VALUES (?,?,?,?)"
            );
            //对于同一个PreparedStatement反复设置参数并调用addBatch()
            for(Student s : students){
                preparedStatement.setObject(1,s.grade);
                preparedStatement.setObject(2,s.name);
                preparedStatement.setObject(3,s.gender);
                preparedStatement.setObject(4,s.score);
                preparedStatement.addBatch();//添加到batch
            }
            //执行batch
            int[] executeBatch = preparedStatement.executeBatch();
            for(int n:executeBatch){
                System.out.println(n + " inserted.");
            }
            
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if(statement != null) statement.close();
            if(resultSet1 != null) resultSet1.close();
        }

    }
}

六、连接池

JDBC连接是一种俺贵的资源,创建线程也是一种昂贵的操作,频繁的创建销毁JDBC,会造成大量消耗

JDBC链接池有一个标准的接口 javax.sql.DataSource , 接口在标准库中。要使用JDBC连接池,必须用它的实现类:

  • HikariCP
  • C3P0
  • BoneCP
  • Druid
    使用最广泛的是HikariCP以此为例,先添加HikariCP的依赖
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>2.7.9</version>
</dependency>

配置连接池


        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl("jdbc:mysql://localhost:3306/learnjdbc");
        hikariConfig.setUsername("root");
        hikariConfig.setPassword("123456");
        hikariConfig.addDataSourceProperty("connectionTimeout","1000");//连接超时 1s
        hikariConfig.addDataSourceProperty("idleTimeout","6000");//空闲超时 6s
        hikariConfig.addDataSourceProperty("maximumPoolSize","10");//最大连接数
        HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);

使用连接池

 connection = hikariDataSource.getConnection();
...

connection.close();

get close 都不是真的创建销毁,而是从连接池中获取 放回

七、项目

7.1 第一版

在这里插入图片描述

-- 创建表t_fruit:
USE learnjdbc;
CREATE TABLE t_fruit (
  fid BIGINT AUTO_INCREMENT NOT NULL,
  fname VARCHAR(50) NOT NULL,
  price INT NOT NULL,
  fcount INT NOT NULL,
  remark VARCHAR(50) NOT NULL,
  PRIMARY KEY(fid)
) Engine=INNODB DEFAULT CHARSET=UTF8;

-- 插入初始数据:

INSERT INTO t_fruit (fname, price, fcount, remark) VALUES ('Apple', 10, 20, '苹果');
INSERT INTO t_fruit (fname, price, fcount, remark) VALUES ('Strawberry', 19, 20, '草莓');
INSERT INTO t_fruit (fname, price, fcount, remark) VALUES ('orange', 11, 20, '橙子');

目录结构
在这里插入图片描述

public class FruitDAOImpl implements FruitDAO {

    Connection conn ;
    PreparedStatement psmt ;
    ResultSet rs ;

    // JDBC连接的URL, 不同数据库有不同的格式:
    final String DRIVER = "com.mysql.jdbc.Driver" ;
    final String URL = "jdbc:mysql://localhost:3306/learnjdbc";
    final String USER = "root";
    final String PWD = "123456";

    @Override
    public List<Fruit> getFruitList() {
        List<Fruit> fruitList = new ArrayList<>();
        try {
            //1 加载驱动
            Class.forName(DRIVER);
            //2 通过驱动获取连接对象
            conn = DriverManager.getConnection(URL, USER, PWD);
            //3 编写SQL
            String sql = "select * from learnjdbc.t_fruit";
            //4 创建预处理命令对象
            PreparedStatement psmt = conn.prepareStatement(sql);
            //5 执行查询
            ResultSet rs = psmt.executeQuery(sql);

            System.out.println(rs);
            //6 解析rs
            while (rs.next()){
                String fid = rs.getString(1);
                String fname = rs.getString(2);
                String price = rs.getString(3);
                String fcount = rs.getString(4);
                String remark = rs.getString(5);

                Fruit fruit = new Fruit(fid, fname, price, fcount, remark);
                fruitList.add(fruit);
            }
        } catch (SQLException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if(psmt!=null){
                    psmt.close();
                }
                if(conn!=null && !conn.isClosed()){
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


        return fruitList;
    }

7.2 抽取获取资源释放资源

每一个方法都是
//1 加载驱动
//2 通过驱动获取连接对象
//3 编写SQL
//4 创建预处理命令对象
//5 执行查询
//finaly close

可以把1 2 合成为getConn方法

    private Connection getConn(){
        try {
            //1 加载驱动
            Class.forName(DRIVER);
            //2 通过驱动获取连接对象
            conn = DriverManager.getConnection(URL, USER, PWD);
        } catch (SQLException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return conn;
    }

可以把最终的close都提取出来

    private void close(ResultSet rs,PreparedStatement psmt,Connection conn){
        try {
            if (rs != null) {
                rs.close();
            }
            if(psmt!=null){
                psmt.close();
            }
            if(conn!=null && !conn.isClosed()){
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

上面的getFruitList就变成了

    @Override
    public List<Fruit> getFruitList() {
        List<Fruit> fruitList = new ArrayList<>();
        try {
            conn = getConn();
            //3 编写SQL
            String sql = "select * from learnjdbc.t_fruit";
            //4 创建预处理命令对象
            PreparedStatement psmt = conn.prepareStatement(sql);
            //5 执行查询
            ResultSet rs = psmt.executeQuery(sql);

            System.out.println(rs);
            //6 解析rs
            while (rs.next()){
                String fid = rs.getString(1);
                String fname = rs.getString(2);
                String price = rs.getString(3);
                String fcount = rs.getString(4);
                String remark = rs.getString(5);

                Fruit fruit = new Fruit(fid, fname, price, fcount, remark);
                fruitList.add(fruit);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            close(rs,psmt,conn);
        }
        return fruitList;
    }

7.3 抽取BaseDAO中通用方法

常量抽取

FruitDAOImpl 存在 DRIVER URL USER PWD 等,这些可以向上抽出来,和其它impl一起使用

public abstract class BaseDAO {

    // JDBC连接的URL, 不同数据库有不同的格式:
    protected final String DRIVER = "com.mysql.jdbc.Driver" ;
    protected final String URL = "jdbc:mysql://localhost:3306/learnjdbc";
    protected final String USER = "root";
    protected final String PWD = "123456";
    
}

BaseDAO 为 abstract 的,Impl使用直接extend BaseDAO 即可

资源方法抽取

之前抽取的getConn() & close() 也可以在这里

    protected Connection conn ;
    protected PreparedStatement psmt ;
    protected ResultSet rs ;

    protected Connection getConn(){
        try {
            //1.加载驱动
            Class.forName(DRIVER);
            //2.通过驱动管理器获取连接对象
            return DriverManager.getConnection(URL, USER, PWD);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null ;
    }

    protected void close(ResultSet rs , PreparedStatement psmt , Connection conn){
        try {
            if (rs != null) {
                rs.close();
            }
            if(psmt!=null){
                psmt.close();
            }
            if(conn!=null && !conn.isClosed()){
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
执行方法抽取
        //1 加载驱动
        //2 通过驱动获取连接对象
        //3 编写SQL
        //4 创建预处理命令对象
        //5 执行查询
        //finaly close

每一个任务也就SQL不一样 ,其它都已经
再次进行抽取

    //执行更新,返回影响行数
    protected int executeUpdate(String sql , Object... params){
        try {
            conn = getConn();
            psmt = conn.prepareStatement(sql);
            if(params!=null && params.length>0){
                for (int i = 0; i < params.length; i++) {
                    psmt.setObject(i+1,params[i]);
                }
            }
            return psmt.executeUpdate() ;
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            close(rs,psmt,conn);
        }
        return 0;
    }

由于传入的参数个数是不一样的,所以用 不定参数 Object… params

Impl重写

    @Override
    public boolean addFruit(Fruit fruit) {
        String sql = "insert into t_fruit values(0,?,?,?,?)";
        return super.executeUpdate(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark())>0;
    }

7.4 抽取通用的查询方法-获取entityClass

public List getFruitList() {

对于其它Class ,模式都是一样的,所以可以抽取出来executeUpdate()




    //给预处理命令对象设置参数
    private void setParams(PreparedStatement psmt , Object... params) throws SQLException {
        if(params!=null && params.length>0){
            for (int i = 0; i < params.length; i++) {
                psmt.setObject(i+1,params[i]);
            }
        }
    }

    //执行查询,返回List
    protected List<T> executeQuery(String sql , Object... params){
        List<T> list = new ArrayList<>();
        try {
            conn = getConn() ;
            psmt = conn.prepareStatement(sql);
            setParams(psmt,params);
            rs = psmt.executeQuery();

            //通过rs可以获取结果集的元数据
            //元数据:描述结果集数据的数据 , 简单讲,就是这个结果集有哪些列,什么类型等等

            ResultSetMetaData rsmd = rs.getMetaData();
            //获取结果集的列数
            int columnCount = rsmd.getColumnCount();
            //6.解析rs
            while(rs.next()){
                T entity = (T)entityClass.newInstance();

                for(int i = 0 ; i<columnCount;i++){
                    String columnName = rsmd.getColumnName(i+1);            //fid   fname   price
                    Object columnValue = rs.getObject(i+1);     //33    苹果      5
                    setValue(entity,columnName,columnValue);
                }
                list.add(entity);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            close(rs,psmt,conn);
        }
        return list ;
    }

T是后续impl指定的

public class FruitDAOImpl extends BaseDAO<Fruit> implements FruitDAO {

在BaseDAO中应该获取Impl 中的T

    public BaseDAO(){
        //getClass() 获取Class对象,当前我们执行的是new FruitDAOImpl() , 创建的是FruitDAOImpl的实例
        //那么子类构造方法内部首先会调用父类(BaseDAO)的无参构造方法
        //因此此处的getClass()会被执行,但是getClass获取的是FruitDAOImpl的Class
        //所以getGenericSuperclass()获取到的是BaseDAO的Class
        Type genericType = getClass().getGenericSuperclass();
        //ParameterizedType 参数化类型
        Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
        //获取到的<T>中的T的真实的类型
        Type actualType = actualTypeArguments[0];
        try {
            entityClass = Class.forName(actualType.getTypeName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

7.5 抽取通用的查询方法

之前add方法中,由于T 不能 new
现在可以通过反射来做


    //通过反射技术给obj对象的property属性赋propertyValue值
    private void setValue(Object obj ,  String property , Object propertyValue){
        Class clazz = obj.getClass();
        try {
            //获取property这个字符串对应的属性名 , 比如 "fid"  去找 obj对象中的 fid 属性
            Field field = clazz.getDeclaredField(property);
            if(field!=null){
                field.setAccessible(true);
                field.set(obj,propertyValue);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

if(rs.next()){
      T entity = (T)entityClass.newInstance();

                for(int i = 0 ; i<columnCount;i++){
                    String columnName = rsmd.getColumnName(i+1);            //fid   fname   price
                    Object columnValue = rs.getObject(i+1);     //33    苹果      5
                    setValue(entity,columnName,columnValue);
                }
                return entity ;
            }

impl中调用

    @Override
    public List<Fruit> getFruitList() {
        return super.executeQuery("select * from t_fruit");
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

oifengo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值