JDBC学习笔记

JDBC学习

注:本文资料是对尚硅谷JDBC视频的详细学习笔记。

一:什么是JDBC:

在这里插入图片描述

JDBC(Java Database Connectivity)是Java编程语言的一种API,用于连接和操作数据库。它允许Java应用程序与各种关系型数据库进行通信,以便进行数据的读取、写入和更新操作。通过JDBC,开发人员可以使用标准的SQL语句来访问数据库,并且可以实现跨平台的数据库访问。JDBC提供了一组接口和类,开发人员可以使用这些接口和类来编写数据库操作的代码。

二:两种实现方法:

在这里插入图片描述

Statement和PreparedStatement都是用来执行SQL语句的接口,但它们之间有一些重要的区别。
  1. Statement是一种静态的SQL语句,它在执行之前已经被编译好了,每次执行SQL语句时都会重新编译,因此可能会导致性能上的损失。而PreparedStatement是一种预编译的SQL语句,它在执行之前已经被编译好了,可以多次执行同一个SQL语句而不需要重新编译,从而提高了性能。
  2. Statement不支持参数化查询,即在执行SQL语句时不能动态地传入参数,而PreparedStatement支持参数化查询,可以在执行SQL语句时动态地传入参数,这样可以防止SQL注入攻击。
  3. PreparedStatement通常比Statement更安全,因为它可以预编译SQL语句并使用参数化查询,从而减少了SQL注入攻击的风险。
总的来说,PreparedStatement比Statement更高效、更安全,因此在实际开发中更推荐使用PreparedStatement来执行SQL语句。

我们先来看看它们共有的代码部分与属性:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

也就是说,它们都要有以下的代码:

//DriverManager.registerDriver(new Driver());//两次驱动,只触发代码块
        //new Driver();不推荐,在Oracle中可能无法成功使用
        Class.forName("com.mysql.cj.jdbc.Driver");//调用类的加载器
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB","root","zheshimima");
//........
ResultSet resultSet = stmt.executeQuery();//Statement还要传入参数sql
//资源的释放
......
在Java中,使用Class.forName()方法加载驱动是因为数据库驱动程序通常在静态代码块中注册自己,而Class.forName()方法会触发静态代码块的执行,从而注册数据库驱动程序。通过加载驱动,可以建立与数据库的连接,并执行相应的数据库操作。因此,使用Class.forName()方法加载驱动是连接数据库的必要步骤。

我们再来看看Statement的实现交互:

public class StatementQueryPart {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB","root","zheshimima");
        Statement stmt = conn.createStatement();
        String sql = "SELECT * FROM Users";
        ResultSet resultSet = stmt.executeQuery(sql);
        while(resultSet.next()){
            int id = resultSet.getInt(1);
            String name = resultSet.getString("name");
            System.out.println("id = "+id+' '+"name = "+name);
        }
        resultSet.close();
        stmt.close();
        conn.close();
    }
它使用resultSet.next()来检查是否还有更多的结果集记录。resultSet.next()方法会移动指针到结果集的下一行,并返回一个布尔值,如果当前位置之后还有更多的记录,则返回true,否则返回false
注意,无论是executeUpdate还是executeQuery,Statement与Preparedstatement都可以调用该静态方法,不过Preparedstatement不要传入参数,而对于选择,我们有:

在这里插入图片描述

我们再来看看Preparedstatement:

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
        String sql = "SELECT * FROM Users WHERE id = ? AND name = ? ;";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setObject(1, "101");
        preparedStatement.setObject(2, "Alice");
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            int id = resultSet.getInt(1);
            String name = resultSet.getString("name");
            System.out.println("id = " + id + ' ' + "name = " + name);
        }
        resultSet.close();
        preparedStatement.close();
        conn.close();
    }
这是流程介绍:

在这里插入图片描述

我们在实际中,更多的使用的是这种方法,除了上面的查询,我们还可以有增加,删除,更新等操作:
 @Test
    public void add_data() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
        String sql = "INSERT INTO Users(id,name,age) VALUES(?,?,?);";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setObject(1, 103);
        preparedStatement.setObject(2, "Shelly");
        preparedStatement.setObject(3, 56);
        int rows = preparedStatement.executeUpdate();
        if (rows > 0) {
            System.out.println("OK");
        } else {
            System.out.println("NO");
        }
        preparedStatement.close();
        conn.close();
    }

    @Test
    public void update_data() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
        String sql = "UPDATE Users SET name=?, age=? WHERE id=?;";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setObject(1, "Shelly");
        preparedStatement.setObject(2, 57);
        preparedStatement.setObject(3, 103);
        int rows = preparedStatement.executeUpdate();
        if (rows > 0) {
            System.out.println("OK");
        } else {
            System.out.println("NO");
        }
        preparedStatement.close();
        conn.close();
    }

    @Test
    public void delete_data() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
        String sql = "DELETE FROM Users WHERE id=?;";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setObject(1, 103);
        int rows = preparedStatement.executeUpdate();
        if (rows > 0) {
            System.out.println("OK");
        } else {
            System.out.println("NO");
        }
        preparedStatement.close();
        conn.close();
    }
当然,我们可以把结果封装到集合框架中,并且这种方法,可以不用手动,泛用性更高:
@Test
    public void show_all_data() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
        String sql = "SELECT * FROM Users;";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        List<Map> resultList = new ArrayList<>();
        ResultSet rs = preparedStatement.executeQuery();
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        while (rs.next()) {
            Map<String, Object> row = new HashMap<>();
            for (int i = 1; i <= columnCount; i++) {
                String columnName = metaData.getColumnLabel(i);//获取列的别名
                Object columnValue = rs.getObject(i);
                row.put(columnName, columnValue);
            }
            resultList.add(row);
        }
        System.out.println(resultList);
        rs.close();
        preparedStatement.close();
        conn.close();
    }

这段代码是一个Java测试方法,它连接到名为"BasicInfoDB"的MySQL数据库,并从"Users"表中选择所有数据。代码中首先加载MySQL驱动程序,然后建立与数据库的连接。接着执行一个SELECT查询,将结果存储在一个ResultSet对象中。然后,通过ResultSetMetaData获取结果集的元数据,包括列数和列名等信息。在一个循环中,遍历结果集的每一行,并将每一行的数据存储在一个Map对象中,然后将该Map对象添加到一个List中。最后,输出这个List,即包含所有数据的结果集。最后,关闭ResultSet、PreparedStatement和Connection对象,释放资源。

在这段代码中,有几个重要的方法和对象需要重点关注:
  1. DriverManager.getConnection(url, username, password):这是Java中用于建立数据库连接的方法。它接受三个参数,分别是数据库的URL、用户名和密码。在这里,我们使用这个方法连接到名为"BasicInfoDB"的MySQL数据库。
  2. PreparedStatement:这是用于执行SQL语句的对象。在这段代码中,我们使用PreparedStatement对象来执行SELECT查询语句,可以使用PreparedStatement的setXXX()方法设置参数,防止SQL注入攻击。
  3. ResultSet:这是用于存储查询结果的对象。在这段代码中,我们通过执行executeQuery()方法获取查询结果集,然后通过ResultSetMetaData获取结果集的元数据,包括列数和列名等信息。
  4. ResultSetMetaData:这是用于获取ResultSet元数据的对象。在这段代码中,我们使用ResultSetMetaData对象获取结果集的元数据,如列数和列名等信息。
  5. Map和List:这是Java中用于存储数据的集合类。在这段代码中,我们使用Map来存储每一行的数据,使用List来存储所有行的数据,最终输出整个结果集。
  6. 关闭资源:在代码的最后,我们通过调用close()方法关闭ResultSet、PreparedStatement和Connection对象,释放资源,避免资源泄漏和性能问题。
这些方法和对象是这段代码中的重点,通过它们可以实现与数据库的连接、查询数据、获取元数据和存储数据等功能。
注意,metaData.getColumnLabel方法是显示列的别名,而metaData.getColumnName是不显示列别名,使用在某些时候,并不符合我们开发的需要。

三:主键回显与主键值的获取:

@Test
    public void Obtain() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
        String sql = "INSERT INTO Users(id,name,age) VALUES(?,?,?);";
        PreparedStatement preparedStatement = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
        preparedStatement.setObject(1, 105);
        preparedStatement.setObject(2, "Jack");
        preparedStatement.setObject(3, 57);
        int rows = preparedStatement.executeUpdate();
        if (rows > 0) {
            System.out.println("OK");
            ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
            generatedKeys.next();
            int id = generatedKeys.getInt(1);
            System.out.println(id);
        } else {
            System.out.println("NO");
        }
        preparedStatement.close();
        conn.close();
    }
主键回显是在插入数据库记录后,获取该记录的主键值。在这段代码中,主键回显的操作如下:
  1. 执行插入操作:int rows = preparedStatement.executeUpdate(); 这行代码执行了插入操作,将新的用户信息插入到 Users 表中。
  2. 检查插入是否成功:if (rows > 0) {...} 这行代码检查插入操作是否成功。如果 executeUpdate() 方法返回的值大于 0,说明插入操作成功。
  3. 获取生成的键:ResultSet generatedKeys = preparedStatement.getGeneratedKeys(); 这行代码获取了插入操作生成的键。在这里,生成的键就是新插入记录的主键。
  4. 读取主键值:generatedKeys.next(); int id = generatedKeys.getInt(1); 这两行代码读取了生成的主键值。next() 方法将结果集的光标移动到下一行,getInt(1) 方法获取第一列的值,也就是主键的值。
  5. 打印主键值:System.out.println(id); 这行代码打印了主键值。
  6. conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);,注意,常量参数的传入。
这就是主键回显的操作。在实际应用中,主键回显常常用于插入记录后,需要使用新记录的主键进行其他操作的场景。例如,插入一个订单记录后,可能需要使用订单的主键作为外键,插入订单项记录。这时,就需要在插入订单记录后,立即获取订单的主键。主键回显就可以满足这种需求。

四:批量添加:

在这里插入图片描述

public void batchInsert(List<User> users) throws SQLException, ClassNotFoundException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    // 在连接字符串中启用rewriteBatchedStatements,以允许批量插入
    Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB?rewriteBatchedStatements=true", "root", "password");
    String sql = "INSERT INTO Users(id,name,age) VALUE(?,?,?)";
    PreparedStatement preparedStatement = conn.prepareStatement(sql);

    for (User user : users) {
        preparedStatement.setInt(1, user.getId());
        preparedStatement.setString(2, user.getName());
        preparedStatement.setInt(3, user.getAge());
        preparedStatement.addBatch();
    }

    int[] rows = preparedStatement.executeBatch();

    for (int i = 0; i < rows.length; i++) {
        if (rows[i] > 0) {
            System.out.println("第 " + (i + 1) + " 条记录插入成功");
        } else {
            System.out.println("第 " + (i + 1) + " 条记录插入失败");
        }
    }

    preparedStatement.close();
    conn.close();
}
在这个示例中,我们首先创建了一个 PreparedStatement,然后对于 users 列表中的每个用户,我们都将用户的信息添加到批处理中。然后,我们执行批处理,得到一个表示每条记录是否插入成功的数组。最后,我们遍历这个数组,打印出每条记录是否插入成功。

五:事务类设计:

在这里插入图片描述

这里是一个简单的示例,使用 JDBC 模拟转账操作,体现了事务的特性。我们设计了两个类,一个是业务类 TransferService,另一个是加减钱类 AccountDao
// 加减钱类
public class AccountDao {
    public void decreaseMoney(Connection conn, int id, double money) throws SQLException {
        String sql = "UPDATE Account SET balance = balance - ? WHERE id = ?";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setDouble(1, money);
        preparedStatement.setInt(2, id);
        preparedStatement.executeUpdate();
    }

    public void increaseMoney(Connection conn, int id, double money) throws SQLException {
        String sql = "UPDATE Account SET balance = balance + ? WHERE id = ?";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setDouble(1, money);
        preparedStatement.setInt(2, id);
        preparedStatement.executeUpdate();
    }
}

// 业务类
public class TransferService {
    private AccountDao accountDao = new AccountDao();

    public void transfer(int fromId, int toId, double money) throws SQLException, ClassNotFoundException {
        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql:///BankDB", "root", "password");
            // 开启事务
            conn.setAutoCommit(false);

            // 执行转账操作
            accountDao.decreaseMoney(conn, fromId, money);
            accountDao.increaseMoney(conn, toId, money);

            // 提交事务
            conn.commit();
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                conn.rollback();
            }
            throw e;
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }
}
在这个示例中,TransferService 类是业务类,它负责处理转账业务。AccountDao 类是加减钱类,它负责更新账户余额。在 transfer 方法中,我们首先开启了一个事务,然后执行转账操作,最后提交事务。如果在执行转账操作过程中发生了异常,我们会回滚事务,以保证数据的一致性。
DAO(Data Access Object)是一种设计模式,用于将应用程序的业务逻辑与数据访问逻辑分离。在使用DAO模式时,数据访问逻辑被封装在一个独立的类中,这个类通常包含对数据库的CRUD(创建、读取、更新、删除)操作。通过使用DAO模式,可以使应用程序的不同部分相互独立,提高了代码的可维护性和可扩展性。DAO模式通常用于将数据库操作与业务逻辑分离,使得应用程序更易于管理和测试。
注意:关键是把分开的Connection合为一个,统一的关闭和操作,避免了负数转帐的异常,我们要关闭自动提交,这样,遇到异常时,才可以及时回滚。

六:连接池:

连接池是一种数据库连接管理技术,用于提高数据库访问性能和资源利用率。连接池会在应用程序启动时创建一定数量的数据库连接,并将这些连接保存在一个池中。当应用程序需要访问数据库时,它会从连接池中获取一个空闲的连接,使用完毕后再将连接放回池中,而不是每次都重新建立连接。这样可以减少数据库连接的建立和关闭次数,提高数据库访问效率,同时也能减轻数据库的负担,提高系统的稳定性和性能。
我们先来看看参数的设置:

在这里插入图片描述

硬编码:

Druid的硬编码是指在代码中直接指定Druid数据源的连接信息和配置参数,而不是通过外部配置文件或者动态配置来管理。硬编码通常是在代码中直接创建Druid数据源对象,并设置连接URL、用户名、密码等信息,然后通过该数据源对象来获取数据库连接。
下面是一个简单的Druid硬编码示例:
import com.alibaba.druid.pool.DruidDataSource;

public class DruidExample {
    public static void main(String[] args) {
        // 创建Druid数据源
        DruidDataSource dataSource = new DruidDataSource();
        
        // 设置连接URL
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydatabase");
        
        // 设置用户名和密码
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        
        // 设置其他配置参数
        dataSource.setInitialSize(5);
        dataSource.setMaxActive(20);
        
        // 获取数据库连接
        try {
            Connection conn = dataSource.getConnection();
            // do something with the connection
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭数据源
            conn.close();
        }
    }
}
在上面的示例中,我们直接在代码中创建了一个Druid数据源对象,并设置了连接URL、用户名、密码等信息,然后通过该数据源对象获取数据库连接。这种硬编码的方式虽然简单直接,但是不够灵活,如果需要修改连接信息或者配置参数,就需要修改代码并重新编译。因此,通常建议使用外部配置文件或者动态配置来管理Druid数据源的连接信息和配置参数。

软编码:

软编码是指将应用程序中的配置信息、连接信息、参数等硬编码到外部配置文件中,而不是直接写在代码中。这样可以提高代码的灵活性和可维护性,因为配置信息可以在不修改代码的情况下进行调整。
下面是一个简单的软编码示例,使用外部配置文件来配置Druid数据源:
在resources目录下创建一个名为application.properties的配置文件,添加如下内容:
jdbc.url=jdbc:mysql://localhost:3306/mydatabase
jdbc.username=root
jdbc.password=password
注意,后缀必须是properties。
然后在代码中读取配置文件中的信息来创建Druid数据源:
import com.alibaba.druid.pool.DruidDataSource;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class DruidExample {
    public static void main(String[] args) {
        Properties properties = new Properties();
        try (InputStream input = DruidExample.class.getClassLoader().getResourceAsStream("application.properties")) {
            properties.load(input);
        } catch (IOException e) {
            e.printStackTrace();
        }
		//工程模式会在方法内部自动配置好参数并且返回一个实例
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        // 获取数据库连接
        try {
            Connection conn = dataSource.getConnection();
            // do something with the connection
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭数据源
            conn.close();
        }
    }
}
在这个示例中,我们通过读取外部配置文件application.properties来获取数据库连接信息,而不是直接在代码中硬编码。这样可以方便地修改连接信息而不需要修改代码。软编码可以提高代码的可维护性和灵活性,推荐在实际项目中使用。

七:工具类的封装:

(1):连接池的封装
public class DBConnectionUtil {
    private static DataSource dataSource;

    static {
        Properties properties = new Properties();
        try {
            InputStream in = DBConnectionUtil.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(in);
            dataSource = DruidDataSourceFactory.createDataSource(properties)} catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    private DBConnectionUtil() {}

    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public void closeConnection(Connection conn) throws SQLException {
        if (conn != null && !conn.isClosed()) {
            conn.close();
        }
    }
}
在这个示例中,DBConnectionUtil 是一个单例类,它只有一个实例。我们在静态初始化块中创建了这个实例,并初始化了 DataSource。然后,我们提供了 getConnection()closeConnection() 方法来获取和释放数据库连接。
(2):在考虑事务和连接池下,怎么让一个线程的不同方法获取同一个连接:
注意,在上面的事务中,我们不能直接在两个类中获取(1)的实例,因为那是同一个连接池,而不是一个连接,一个连接池是许多连接的集合。我们现在就要引入ThreadLocal。
举例:

在这里插入图片描述

这样,就不用频繁传入Connection,更加方便。

在这里插入图片描述

使用 ThreadLocal 来管理数据库连接。这样可以确保每个线程都有自己的数据库连接,避免了多线程环境下的并发问题。以下是一个修改(1)后的示例:
public class DBConnectionUtil {
    private static DataSource dataSource;
    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
    
    static {
        Properties properties = new Properties();
        try {
            InputStream in = DBConnectionUtil.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(in);
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() throws SQLException {
        Connection conn = connectionHolder.get();
        if (conn == null) {
            conn = dataSource.getConnection();
            connectionHolder.set(conn);
        }
        return conn;
    }

    public static void closeConnection() throws SQLException {
        Connection conn = connectionHolder.get();
        if (conn != null) {
            conn.close();
            connectionHolder.remove();
        }
    }
}
在这个示例中,我们使用了 ThreadLocal 来存储每个线程的数据库连接。当调用 getConnection() 方法时,我们首先从 ThreadLocal 中获取连接,如果获取不到(也就是当前线程还没有连接),我们就创建一个新的连接,并存入 ThreadLocal。当调用 closeConnection() 方法时,我们关闭 ThreadLocal 中的连接,并从 ThreadLocal 中移除。这样,我们就可以确保每个线程都有自己的数据库连接,避免了多线程环境下的并发问题。
注意,如果是在事务中,结束时,我们要在closeConnection中关闭事务,即打开自动提交,即conn.setAutoCommit(true);,与此同时,事务也要改变:
// 加减钱类
public class AccountDao {
    public void decreaseMoney(int id, double money) throws SQLException {
        Connection conn = null;
        try {
            conn = DBConnectionUtil.getConnection();
            String sql = "UPDATE Account SET balance = balance - ? WHERE id = ?";
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            preparedStatement.setDouble(1, money);
            preparedStatement.setInt(2, id);
            preparedStatement.executeUpdate();
        } finally {
            DBConnectionUtil.closeConnection(conn);
        }
    }

    public void increaseMoney(int id, double money) throws SQLException {
        Connection conn = null;
        try {
            conn = DBConnectionUtil.getConnection();
            String sql = "UPDATE Account SET balance = balance + ? WHERE id = ?";
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            preparedStatement.setDouble(1, money);
            preparedStatement.setInt(2, id);
            preparedStatement.executeUpdate();
        } finally {
            DBConnectionUtil.closeConnection(conn);
        }
    }
}

// 业务类
public class TransferService {
    private AccountDao accountDao = new AccountDao();

    public void transfer(int fromId, int toId, double money) throws SQLException {
        Connection conn = null;
        try {
            // 开启事务
            conn = DBConnectionUtil.getConnection();
            conn.setAutoCommit(false);

            // 执行转账操作
            accountDao.decreaseMoney(fromId, money);
            accountDao.increaseMoney(toId, money);

            // 提交事务
            conn.commit();
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                conn.rollback();
            }
            throw e;
        } finally {
            DBConnectionUtil.closeConnection(conn);
        }
    }
}
在这个示例中,我们修改了 AccountDao 类的 decreaseMoneyincreaseMoney 方法,使它们在方法内部获取和释放数据库连接。这样,我们就不需要在方法参数中传递 Connection 对象了。同时,我们也修改了 TransferService 类的 transfer 方法,使其在方法内部获取和释放数据库连接。

(3):BaseDao的封装:

DAO(Data Access Object)是一种设计模式,用于将应用程序的业务逻辑与数据访问逻辑分离。在使用DAO模式时,数据访问逻辑被封装在一个独立的类中,这个类通常包含对数据库的CRUD(创建、读取、更新、删除)操作。通过使用DAO模式,可以使应用程序的不同部分相互独立,提高了代码的可维护性和可扩展性。DAO模式通常用于将数据库操作与业务逻辑分离,使得应用程序更易于管理和测试。
注意,我们不难发现,DQL与非DQL之间调用方法的区别,所以我们分情况讨论:

3.1.非DQL封装:

这里是一个使用 ThreadLocalBaseDao 类的示例,以及一个继承自 BaseDaoAccountDao 类的示例:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public abstract class BaseDao {
    
    public int update(String sql, Object... params) throws SQLException {
        try {
           Connection conn = DBConnectionUtil.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        for (int i = 0; i < params.length; i++) {
            preparedStatement.setObject(i + 1, params[i]);
        }
        preparedStatement.close();
        //判断是不是为事务!
        if(conn.getAutoCommit()){
			DBConnectionUtil.closeConnection();
        }
        return preparedStatement.executeUpdate();
    }
}

public class AccountDao extends BaseDao {
    public void decreaseMoney(int id, double money) throws SQLException {
        String sql = "UPDATE Account SET balance = balance - ? WHERE id = ?";
        update(sql, money, id);
    }

    public void increaseMoney(int id, double money) throws SQLException {
        String sql = "UPDATE Account SET balance = balance + ? WHERE id = ?";
        update(sql, money, id);
    }
}
在这个示例中,BaseDao 类是一个抽象类,它封装了非查询(非 DQL)语句的执行。update 方法接受一个 SQL 语句和一组参数,然后执行 SQL 语句并返回影响的行数。
AccountDao 类继承自 BaseDao 类,它提供了 decreaseMoneyincreaseMoney 方法,这两个方法分别用于减少和增加账户的余额。

注意:

		preparedStatement.close();
        //判断是不是为事务!
        if(conn.getAutoCommit()){
			DBConnectionUtil.closeConnection();
        }
        return preparedStatement.executeUpdate();
要注意是否要修改业务类的代码和如何判断是否为业务。

3.2.DQL封装:

这里是一个使用反射和泛型集合封装 DQL(数据查询语言)的 BaseDao 类的示例:
 public abstract class BaseDao {  
	public <T> List<T> query(String sql, Class<T> clazz, Object... params) throws SQLException {
        try {
            Connection conn = DBConnectionUtil.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        for (int i = 0; i < params.length; i++) {
            preparedStatement.setObject(i + 1, params[i]);
        }
        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData metaData = resultSet.getMetaData();
        List<T> list = new ArrayList<>();
        while (resultSet.next()) {
            T instance = null;
            try {
                instance = clazz.newInstance();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    String columnName = metaData.getColumnLabel(i);
                    Object value = resultSet.getObject(i);
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(instance, value);//把instance的filed字段设置为value
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            list.add(instance);
        }
     
    }
    		resultSet.close();
        	preparedStatement.close();
    	if(conn.getAutoCommit()){
			DBConnectionUtil.closeConnection();
        }
     return list;
}
在这个示例中,BaseDao 类是一个抽象类,它封装了 DQL 语句的执行。query 方法接受一个 SQL 语句、一个类对象和一组参数,然后执行 SQL 语句并返回查询结果的列表。查询结果的类型是泛型 T,这样我们就可以返回任何类型的对象列表。
在这个BaseDao类的query方法中,instance是一个泛型类型的对象,它的类型是TT是一个占位符,代表任何类型,这是Java泛型的一部分。
在方法的执行过程中,当从数据库查询结果集ResultSet中取出每一行数据时,instance被用来创建一个新的对象,该对象的类型由clazz参数指定(clazz是一个Class<T>对象,代表要实例化的类的类型)。
具体来说,当遍历ResultSet时,对于每一行数据:
  1. 使用clazz.newInstance()创建一个新的T类型的实例,并将这个新实例赋值给instance。这一步假设T的类有一个无参数的构造函数。
  2. 然后,代码遍历ResultSet的每一列,并使用clazz.getDeclaredField(columnName)获取T类型对象中对应的字段。
  3. 使用field.setAccessible(true)使得可以访问私有字段,接着使用field.set(instance, value)resultSet中当前列的值设置到instance对象的对应字段中。
  4. field.set(instance, value);:这行代码将instance对象的field字段设置为value。如果字段是static的,instance可以是null
  5. 最后,将填充完数据的instance对象添加到list列表中。
注意,在JAVA中,Map是无法反射的,因此我们这里假设T为应用的类,与,数据库的表中列名相对于,所以类内部就有相应的字段。并且,每次通过反射获取一个对象实例,它们彼此之间是不一样的。
  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Xiao Ling.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值