JDBC核心技术(下)

一. JDBC的特殊使用情况

1.1 获取数据库自增长的主键

java.sql.prepareStatement提供的一个包含预编译 SQL 语句的新 PreparedStatement 对象,该对象能够返回自动生成的键

PreparedStatement prepareStatement(String sql,int autoGeneratedKeys)throws SQLException

创建一个默认 PreparedStatement 对象,该对象能获取自动生成的键。给定常量告知驱动程序是否可以获取自动生成的键。如果 SQL 语句不是一条 INSERT 语句,或者 SQL 语句能够返回自动生成的键(这类语句的列表是特定于供应商的),则忽略此参数。 

autoGeneratedKeys - 指示是否应该返回自动生成的键的标志,它是 Statement.RETURN_GENERATED_KEYS 或 Statement.NO_GENERATED_KEYS 之一 

@Test
public void returnPrimaryKey() throws Exception {
    /**
     * t_user插入一条数据! 并且获取数据库自增长的主键!
     * TODO: 使用总结
     *     1.创建prepareStatement的时候,告知,携带回数据库自增长的主键 (sql,Statement,RETURNGENERATED_KEYS)
     *     2.获取携带主键值的结果集对象,一行一列,获取对应的数据即可 ResultSet resultSet = statement,getGeneratedKeys();
     */
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbcdemo", "root", "root");

    String sql = "insert into t_user (account, password, nickname) values (?, ?, ?)";

    PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    statement.setString(1, "zhangsan");
    statement.setString(2, "zhangsantest");
    statement.setString(3, "admin123");

    int i = statement.executeUpdate();

    if (i > 0) {
        System.out.println("插入数据成功...");
        // 开始会先获取主键数据
        ResultSet resultSet = statement.getGeneratedKeys();
        // 此处移动一下光标
        resultSet.next();
        // 打印输出获取到的主键
        System.out.println(resultSet.getInt(1));
    } else {
        System.out.println("插入数据失败...");
    }

    // 释放资源
    statement.close();
    connection.close();
}

1.2 批量执行SQL语句

当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。

JDBC的批量处理语句包括下面三个方法:

  • addBatch(String):添加需要批量处理的SQL语句或是参数;
  • executeBatch():执行批量处理语句;
  • clearBatch():清空缓存的数据。
@Test
public void testBatchInsert() throws Exception {

    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbcdemo?rewriteBatchedStatements=true", "root", "root");
    String sql = "insert into t_user (account, password, nickname) values (?, ?, ?)";
    PreparedStatement statement = connection.prepareStatement(sql);

    long start = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        statement.setObject(1, "ff" + i);
        statement.setObject(2, "ff" + i);
        statement.setObject(3, "ff" + i);
        statement.addBatch();
    }

    statement.executeBatch();
    long end = System.currentTimeMillis();
    System.out.println("批量插入数据完成,总共耗时: " + (end - start));
    statement.close();
    connection.close();
}

"jdbc:mysql:///jdbcdemo?rewriteBatchedStatements=true", "root", "root"

路径后面添加?rewriteBatchedStatements=true 允许批量插入

 

 二. 数据库连接池介绍

2.1 数据库连接池简介

JDBC中使用连接时都要创建一个Connection对象,使用完毕后将其销毁。这种重复创建、销毁的过程是特别耗费计算机性能以及计算时间,而数据库如果使用了数据库连接池,就能达到Connection对象的复用效果。

  • 数据库连接池是一个容器。
  • 功能:负责分配管理数据库的连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
  • 释放空闲时间超过最大空闲时间的数据库连接,避免因为没有释放数据库连接而引起的数据库连接遗漏。
  • 好处:
    • 资源复用
    • 提升系统响应速度
    • 避免数据库连接遗漏
  • 数据库连接池在一开始就创建好了一些连接对象存储起来。用户需要连接数据库时,不需要自己创建连接,只需要从连接池中获取一个进行使用就行了,使用完毕后将连接对象归还给连接池 ,即为资源重用,也节省了频繁创建连接销毁连接所花费的时间,从而提高系统响应的速度。

常见的数据库连接池:

  • DBCP
  • C3P0
  • Druid
  • ...

2.2 Druid的使用

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

如何使用:

1. 将jar包放到当前工程下的lib目录,并在驱动jar上右键-->Build Path-->Add to Build Path。

 

2. 此处通过外部配置文件创建durid数据池连接

配置文件:

druidClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///jdbcdemo

有一些可配置参数,如:

  • initialSize:初始化时建立物理连接的个数
  • maxActive:最大连接池数量
  • ...

此处略,可以根据实际需求自行配置

public class DruidUseDemo {

    /**
     * 通过软链接实例化数据库连接池对象
     */
    public void testUseDruidBySoft() throws Exception {
        // 读取外部配置文件properties
        Properties properties = new Properties();

        // src下的文件,可以通过类加载器的方式获取
        InputStream ips = DruidUseDemo.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(ips);
        // 使用连接池的工具类创建连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        Connection connection = dataSource.getConnection();

        // TODO:数据库CRUD

        // 回收链接
        connection.close();
    }

三. JDBC事务

3.1 数据库事务介绍

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
  • 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。 
     

3.2JDBC事务处理

  • 数据一旦提交,就不可回滚。
  • 数据什么时候意味着提交?
    • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
    • 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
  • JDBC程序中为了让多个 SQL 语句作为一个事务执行:
    • 调用 Connection 对象的 setAutoCommit(false):以取消自动提交事务
    • 在所有的 SQL 语句都成功执行后,调用 commit():方法提交事务
    • 在出现异常时,调用 rollback():方法回滚事务

若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

通过一个模拟转账案例实现JDBC的事务操作,直接上代码:

3.2.1 JDBCUtils工具类封装

public class JDBCUtilsV2 {

    private static DataSource dataSource = null;

    // 利用线程本地变量,存储连接信息! 确保一个线程的多个方法可以获取同一个connection!
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();

    //驱动加载在整个程序中只加载一次,所以放到静态语句块中
    static {
        //初始化连接对象
        Properties properties = new Properties();
        InputStream ips = JDBCUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
        try {
            properties.load(ips);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对外提供的连接的方法
     *
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        // 先判断线程本地变量是否存在
        Connection connection = tl.get();
        // 第一次没有,就从线程池获取
        if (connection == null) {
            connection = dataSource.getConnection();
            tl.set(connection);
        }
        return connection;
    }

    /**
     *  回收线程的方法
     * @throws SQLException
     */
    public static void freeConnection() throws SQLException {
        Connection connection = tl.get();
        if (connection != null) {
            tl.remove();// 清空线程变量本地数据
            connection.setAutoCommit(true);//事务状态回归
            connection.close();// 回收到连接池
        }
    }

}

3.2.2 转账模拟操作 

数据库操作方法相关的BankDao 

public class BankDao {

    /**
     * 加钱的数据库操作方法
     *
     * @param account 加钱的账号
     * @param money   加钱的金额
     */
    public void add(String account, int money) throws Exception {

        Connection connection = JDBCUtilsV2.getConnection();
        // 创建sql语句
        String sql = "update t_bank set money = money + ? where account = ? ;";
        // 创建statement
        PreparedStatement ps = connection.prepareStatement(sql);
        // 占位符赋值
        ps.setObject(1, money);
        ps.setObject(2, account);
        // 执行sql语句
        ps.executeUpdate();
        // 关闭链接
        ps.close();

        System.out.println("加钱成功!");

    }

    /**
     * 减钱的数据库操作方法
     *
     * @param account 减钱的账号
     * @param money   减钱的金额
     */
    public void sub(String account, int money) throws Exception {
        Connection connection = JDBCUtilsV2.getConnection();
        // 创建sql语句
        String sql = "update t_bank set money = money - ? where account = ? ;";
        // 创建statement
        PreparedStatement ps = connection.prepareStatement(sql);
        // 占位符赋值
        ps.setObject(1, money);
        ps.setObject(2, account);
        // 执行sql语句
        ps.executeUpdate();
        // 关闭链接
        ps.close();

        System.out.println("减钱成功!");
    }
}

银行卡业务方法,调用dao方法以及测试

public class BankService {

    @Test
    public void test() throws Exception {
        transfer("zhangsan", "lisi", 500);
    }

    /**
     * TODO:
     *  事务添加是在业务方法中
     *  利用try catch 代码块开启和提交事务、事务回滚
     *  将connection传入到dao,dao只负责使用,对象在finally中进行资源释放
     *
     * @param addAccount
     * @param subAccount
     * @param money
     * @throws Exception
     */
    public void transfer(String addAccount, String subAccount, int money) throws Exception {

        BankDao bankDao = new BankDao();
        // 一个事务的最基本要求是同一个链接对象 connection
        // 注册驱动
        Connection connection = JDBCUtilsV2.getConnection();

        // 开启事务
        try {
            // 关闭事务自动提交
            connection.setAutoCommit(false);

            // 执行数据库操作
            bankDao.add(addAccount, money);
            System.out.println("==========");
            bankDao.sub(subAccount, money);

            // 事务提交
            connection.commit();
        } catch (Exception e) {
            // 事务回滚
            connection.rollback();
            // 抛出异常
            throw e;
        } finally {
            JDBCUtilsV2.freeConnection();
        }

    }
}

四. BaseDao封装

JDBC连接和操作数据库,按照连接几个基本步骤操作就可以了,但是做数据CRUD的时候除了sql语句不同之外,其他代码都是一样的。可以封装一个BaseDao方法,把数据库操作的几个基本过程封装为几个独立的方法,把所有数据库操作类的共有属性抽象出来,然后数据库操作类都继承这个BaseDao类即可。

话不多说,直接上代码:

/**
 * @Description 抽取公共代码, 封装一个BaseDao工具类
 *
 * TODO:封装两个方法,一个简化非DQL,一个简化DQL
 */
public abstract class BaseDao {

    /**
     * 封装简化非DQL语句
     *
     * @param sql    带占位符的SQL语句
     * @param params 占位符的值,传入占位符的值必须等于sql参数中占位符的位置
     * @return 执行影响的行数
     */
    public int executeUpdate(String sql, Object... params) throws SQLException {
        // 获取链接
        Connection connection = JDBCUtilsV2.getConnection();
		
        // 创建 preparedStatement 对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
		
        // 占位符赋值,此处的可变参数可以当作数组使用
        for (int i = 1; i <= params.length; i++) {
            preparedStatement.setObject(i, params[i - 1]);
        }

        // 发送sql语句
        int rows = preparedStatement.executeUpdate();
        preparedStatement.close();

        // 是否回收连接,需要考虑是不是事务
        if (connection.getAutoCommit()) {
            // 没有开启事务,正常回收连接
            JDBCUtilsV2.freeConnection();
        }
        // 如果开启了事务,不要管连接即可,提交给业务层处理
        //connection.setAutoCommit(false);
        return rows;
    }

    /**
     * 将查询的结果封装到一个实体类集合
     * 非DQL语句封装方法 -> 返回值 固定为int
     * DQL语句封装方法 -> 返回值 ?
     *
     * @param clazz  要接值的实体类集合的模板对象
     * @param sql    查询语句,要求列名或者别名等于实体类的属性名
     * @param params 占位符的值,和?位置对应传递
     * @param <T>    声明的结果的泛型
     * @return 查询的实体类集合
     */
    public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws Exception {
        // 获取连接
        Connection connection = JDBCUtilsV2.getConnection();
		
        // 创建preparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
		
        // 占位符赋值
        if (params == null && params.length != 0) {
            for (int i = 1; i < params.length; i++) {
                preparedStatement.setObject(i, params[i - 1]);
            }
        }

        // 发送SQL语句
        ResultSet resultSet = preparedStatement.executeQuery();
		
        List<T> list = new ArrayList<>();

        // 获取列的信息对象
        // TODO: metData 装的当前结果集列的信息对象 (可以获取列的名称根据下角标,可以获取列的数量)
        ResultSetMetaData metaData = resultSet.getMetaData();

        // 遍历水平列
        int columnCount = metaData.getColumnCount();

        while (resultSet.next()) {
            // 调用类的无参构造函数实例化对象
            T t = clazz.newInstance();
			
            // 自动遍历列
            for (int i = 1; i <= columnCount; i++) {
                Object value = resultSet.getObject(i);
                // 获取指定列下角标的列的名称! ResultSetMetaData
                // getColumnLabel: 会获取别名,如果没有写别名才是列的名称
                // getColumnName : 只会获取列的名称
                String properyName = metaData.getColumnLabel(i);

                // 反射给对象的属性值赋值
                Field field = clazz.getDeclaredField(properyName);
                // 属性可以设置,打破private的修饰限制
                field.setAccessible(true);
                // 参数1:要赋值的对象   参数2:具体的属性值
                field.set(t, value);
            }
			
            list.add(t);
        }

        // 关闭资源
        resultSet.close();
        preparedStatement.close();
        // 没有事务可以关闭
        if (connection.getAutoCommit()) {
            JDBCUtilsV2.freeConnection();
        }
		
        return list;
    }
}

测试代码:

public class PSCRUDDemo extends BaseDao {

    @Test
    public void testInsert() throws SQLException {
        String sql = "insert into t_user(account, password, nickname) values(?, ?, ?);";
        int rows = executeUpdate(sql, "test", "12345678", "test");
        System.out.println("插入" + rows + "行数据成功");
    }

    @Test
    public void testDelete() throws Exception {

        String sql = "delete from t_user where id = ?";
        int rows = executeUpdate(sql, 116644);
        System.out.println("删除" + rows + "行数据成功");
    }

    @Test
    public void testUpdate() throws Exception {
        String sql = "update t_user set nickname =? where id =?";
        int rows = executeUpdate(sql, "孙猴子", 1);
        System.out.println("修改" + rows + "行数据完成");
    }

    /**
     * 查询所有数据,结果封装到list<Map>集合中
     */
    @Test
    public void testSelect() throws Exception {
        String sql = "select * from t_user";
        List<User> users = executeQuery(User.class, sql);
        System.out.println(users);

    }

}

温故而知新。

JDBC相关的内容告一段落,欢迎大家指正,互相学习。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 《Java核心技术》第10版是一本经典的Java编程指南,由Cay S. Horstmann和Gary Cornell共同撰写。该书旨在帮助初学者和有经验的开发人员提高他们的Java编程技能。 这本书首先介绍了Java语言的基本概念,包括变量、数据类型、运算符、控制流程等。然后,它深入探讨了面向对象编程的概念,如类、对象、继承、多态等。此外,该书还涵盖了异常处理、输入输出、字符串处理、集合框架等Java核心库的使用。 《Java核心技术》第10版特别强调了Java 8中引入的新特性,如Lambda表达式、函数式接口、流API等。这些新功能使得Java编程更加简洁和高效。 此外,该书还涵盖了Java的网络编程、多线程、GUI编程以及数据库访问等高级主题。它提供了大量的示例代码和练习题,帮助读者巩固所学知识。 总的来说,《Java核心技术》第10版是一本全面而深入的Java编程指南。无论是初学者还是有经验的开发人员,都可以从中学到很多有关Java编程的知识和技巧。这本书的内容丰富,易于理解,是学习Java的一本不可或缺的参考书籍。 ### 回答2: 《Java核心技术》第10版是由Horstmann和Cornell编写的经典Java编程指南。这本书全面而深入地介绍了Java的核心概念和关键技术,一直以来都是Java开发者的首选教材。 本书分为两卷,第一卷主要讲解Java的基础知识,包括Java语言基础、面向对象编程、集合框架、Java I/O、并发编程等内容。作者通过易懂的示例和练习题,帮助读者理解和掌握Java的基本语法和常用类库,培养良好的编码习惯和思维方式。 第二卷则深入探讨了Java的高级主题,如网络编程、数据库访问、图形用户界面和Web开发等。作者详细介绍了Java的各种高级技术和工具,如多线程、网络编程、JDBC、Swing、JavaFX和Servlet等,帮助读者进一步提升Java应用程序的能力和质量。 这本书的特点是内容全面、深入浅出,适合初学者和有一定Java基础的开发者阅读。每个章节都有丰富的实例和练习,读者可以通过动手实践来加深对Java知识的理解和掌握。此外,书中还介绍了一些最佳实践和常见问题的解决方法,帮助读者在应用开发中避免一些常见的陷阱和问题。 总之,《Java核心技术》第10版是一本权威、全面而深入的Java编程指南,无论是初学者还是有经验的开发者,都可以从中获益良多。它不仅帮助读者掌握Java的基础知识和核心技术,还培养了良好的编程思想和实践能力。无论是用于学习还是作为参考资料,这本书都是不可或缺的。 ### 回答3: 《Java核心技术》是一本经典的Java编程技术书籍,第10版是该书的最新版本。该书由Cay S. Horstmann和Gary Cornell合著,致力于帮助读者全面理解和掌握Java语言的基础知识和高级概念。 第10版的《Java核心技术》共分为两个卷,总共包含16章。第一卷主要介绍Java的基础知识,包括基础语法、控制流程、数组、继承、多态、接口和内部类等。此外,还深入讲解了异常处理、泛型、集合框架、并发编程等重要概念和技术。这些内容为Java初学者提供了一个坚实的基础,并帮助他们编写简单的Java应用程序。 第二卷则更加深入地讨论了Java的高级特性和技术。其中,介绍了图形化用户界面(GUI)编程、事件处理、Swing组件、文件I/O、网络编程和数据库连接等。此外,还涉及了Java的XML和Web服务、高级数据库访问、安全性和国际化等主题。 无论是初学者还是有经验的开发者,都可以从《Java核心技术》中获得收益。该书以清晰简洁的语言和丰富的示例代码来解释概念,并提供了大量的实战经验和技巧。每一章都包含练习题和思考题,帮助读者巩固所学知识,并拓展思考能力。此外,该书还提供了丰富的在线资源和补充材料,如示例代码、练习题答案和附加阅读。 总的来说,《Java核心技术》第10版是一本全面而权威的Java编程指南。通过阅读和学习该书,读者可以建立起坚实的Java基础,并掌握高级的编程概念和技术,从而能够编写出高质量的Java应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码云说

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

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

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

打赏作者

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

抵扣说明:

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

余额充值