JDBC - (03)JDBC的拓展


image-20230206074352715

JDBC -(03)JDBC的拓展

1. 主键回显和主键值获取

1.1 使用场景

什么情况下需要用主键回显呢?

举个例子,用户购买商品的时候,订单支付成功,此时在数据库会生成一个订单信息,订单有订单编号,这个订单编号返回给客户,那么用户就可以根据这个订单编号查询订单信息。

1.2 使用方法

1.创建prepareStatement的时候,告知携带回数据库自增长的主键,

        PreparedStatement preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);

2.获取封装着主键值得结果集对象,一行一列,获取对应的数据

ResultSet resultSet = preparedStatement.getGeneratedKeys(); //获取结果集对象
resultSet.next();//光标下移一行
int id = resultSet.getInt(1); //获取下标为1的值

1.3 应用举例

向t_user表插入一条数据 并获取回显的主键

package com.julissa.api.preparestatement;

import org.junit.jupiter.api.Test;

import java.sql.*;

public class KeyReturn {
    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {
        /**
         * 向t_user表插入一条数据 并获取回显的主键
         * account tom
         * password tom123
         * nickname 汤姆
         */
        String account = "tom";
        String password = "tom123";
        String nickname = "汤姆";

        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取数据库连接
        Connection connection = DriverManager.getConnection
            ("jdbc:mysql://localhost:3306/julissa","root","123456");
        //3.编写sql
        String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
        //4.创建预编译statement,并传入sql语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
        //5.给动态值设置值
        preparedStatement.setString(1,account);
        preparedStatement.setString(2,password);
        preparedStatement.setString(3,nickname);
        //6.执行sql语句,并获取返回结果
        int i = preparedStatement.executeUpdate();
        //7.判读是否插入成功 1成功
        if(i > 0) {
            System.out.println("插入数据成功");
            //获取回显的主键
            //返回的结果集对象 一行一列 id=值
            ResultSet resultSet = preparedStatement.getGeneratedKeys();
            resultSet.next();//光标下移一行
            int id = resultSet.getInt(1);
            System.out.println("id = " + id);

        }else {
            System.out.println("插入数据失败");

        }
        //8.关闭资源
        preparedStatement.close();
        connection.close();
    }
}

执行结果如下:
image-20230315103611544

2. 批量数据插入

2.1 应用场景

什么情况下需要用批量插入呢?

当我们向数据库中某一张表插入数据时,一条sql语句就向数据库提交一次数据,但有些时候,插入的数据是几千条,几万条甚至更多,这样不仅占用服务资源而且效率也会非常的慢,所以用使用一个容器把预处理后的SQL语句放在容器中,再一同提交至SQL服务器,减少了提交次数,降低了服务资源的占用。使用这个思想,JDBC提供了批处理语句。

没有批量数据插入之前:

@Test
public void testInsert3() throws ClassNotFoundException, SQLException {
    /**
         * 向t_user表插入10000条数据,并返回总共执行的时间
         *
         */
    String account = "rose";
    String password = "rose";
    String nickname = "罗斯";

    //1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.获取数据库连接
    Connection connection = DriverManager.getConnection
        ("jdbc:mysql://localhost:3306/julissa?rewriteBatchedStatements=true","root","123456");
    //3.编写sql
    String sql = "insert into person(account,password,nickname) value(?,?,?)";
    //4.创建预编译statement,并传入sql语句
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        preparedStatement.setString(1,account+i);
        preparedStatement.setString(2,password+i);
        preparedStatement.setString(3,nickname+i);
        preparedStatement.executeUpdate();
        System.out.println(i + "插入成功");
    }
    long endTime = System.currentTimeMillis();
    System.out.println("插入10000条数据总共耗时:" + (endTime - startTime) + "毫秒");
    //8.关闭资源
    preparedStatement.close();
    connection.close();
}

插入10000条数据耗时:

image-20230315122909574

2.2 使用方法

1.在数据库连接路径后面添加 ?rewriteBatchedStatements=true 允许批量插入

2.insert into values【必须是values】语句不能添加;结束

3.不是执行每天语句,是批量添加addBatch()

4.遍历添加完毕后,统一批量执行executeBatch():

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

  • addBatch(String):添加需要批处理的SQL语句或参数;
  • executeBatch():执行批处理语句;
  • clearBatch():清空缓存的数据

2.4 应用举例

批量向person表插入10000条数据

@Test
public void testInsert3() throws ClassNotFoundException, SQLException {
    /**
         * 向t_user表插入10000条数据,并返回总共执行的时间
         *
         */
    String account = "marry";
    String password = "marry123";
    String nickname = "玛丽";

    //1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.获取数据库连接
    Connection connection = DriverManager.getConnection
        ("jdbc:mysql://localhost:3306/julissa?rewriteBatchedStatements=true","root","123456");
    //3.编写sql
    String sql = "insert into person(account,password,nickname) values(?,?,?)";
    //4.创建预编译statement,并传入sql语句
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        preparedStatement.setString(1,account+i);
        preparedStatement.setString(2,password+i);
        preparedStatement.setString(3,nickname+i);
        preparedStatement.addBatch();//追加到values后面
    }
    preparedStatement.executeBatch();//执行批量操作
    long endTime = System.currentTimeMillis();
    System.out.println("插入10000条数据总共耗时:" + (endTime - startTime) + "毫秒");
    //8.关闭资源
    preparedStatement.close();
    connection.close();
}

执行结果如下:

image-20230315123531010

可以看出,批量操作大大提高了数据插入的效率

3. 数据库事务实现

3.1 事务的概念

数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓存欸的多条语句执行结果统一判定.

一个事务内所有语句都成功即事务成功,我们可以触发commit提交事务来结束事务,更新数据.

一个事务内任意一条语句失效,即事务失败,我们可以触发rollback回滚结束事务.

事务的优势:

允许我们在失败的情况下,数据回归到业务之前的状态

事务的特性:

  • 原子性:原子性指的是事务是一个不可分割的单位,事务中的操作要么都发生,要么都不发生。
  • 一致性:事务必须使数据库从一个一致性状态变换到另一个一致性状态。
  • 隔离性:事务的隔离型是指一个事务的执行不能被其他事务干扰。
  • 持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的。

事务的类型:

  • 自动提交:每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚。(MySQL)
  • 手动提交:手动开启事务,添加语句,手动提交或手动回滚即可。

sql开启事务方式:

  • 针对自动提交,关闭自动提交即可,每条语句添加以后,最终手动提交或者回滚(推荐)
SET autocommit = off //关闭当前连接自动事务提交方式,只有当前连接有效
# 编写sql语句     
SQL
SQL
SQL
# 手动提交或者回滚【结束当前的事务】
COMMIT / ROLLBACK;
  • 手动开启事务,开启事务代码,添加SQL语句,事务提交或者事务回滚(不推荐)

3.2 应用场景

一个事务涉及多条修改数据库语句

例如:经典的转账案例,转账业务(加钱和减钱)

​ 批量删除(涉及多个删除)

​ 批量添加(涉及多个插入)

3.3 使用方法

JDBC中事务的使用方法:

try{
    connection.setAutoCommit(false);//关闭自动提交
    //当前连接的数据库操作,都不会自动提交
    //数据库动作
    connection.commit();//事务提交
}catch(Exception e) {
    connection.rollback();//事务回滚
}

3.4 应用举例

案例:银行转账业务

3.4.1 数据库数据准备

创建t_bank表:money设置不能为负数

image-20230315132841842

向表中添加数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gY7wtcpw-1678879639159)(null)]

3.4.2 程序结构

image-20230315133506031

3.5.3 没有进行数据库事务管理的情况

持久层BankDao:

package com.julissa.api.transaction;

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

/**
 * @author Julissa
 * Description 操作数据表t_bank的类
 */
public class BankDao {

    /**
     * 银行转账加钱的方法
     * @param account 加钱的账号
     * @param money    加钱的金额
     */
    public void add(String account, int money) throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.连接数据库
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/julissa", "root", "123456");
        //3.编写sql语句
        String sql = "update t_bank set money = money + ? where account = ?;";
        //4.创建prepareStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.占位符赋值
        preparedStatement.setInt(1, money);
        preparedStatement.setString(2, account);
        //6.执行sql语句
        preparedStatement.executeUpdate();
        //7.释放资源
        preparedStatement.addBatch();
        connection.close();
        System.out.println("---加钱成功---");
    }
    /**
     * 银行转账减钱的方法
     * @param account  减钱的账号
     * @param money    减钱的金额
     */
    public void sub(String account, int money) throws Exception{
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.连接数据库
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/julissa", "root", "123456");
        //3.编写sql语句
        String sql = "update t_bank set money = money - ? where account = ?;";
        //4.创建prepareStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.占位符赋值
        preparedStatement.setInt(1, money);
        preparedStatement.setString(2, account);
        //6.执行sql语句
        preparedStatement.executeUpdate();
        //7.释放资源
        preparedStatement.addBatch();
        connection.close();
        System.out.println("---减钱成功---");
    }
}

业务层BankService:

package com.julissa.api.transaction;

/**
 * @author Julissa
 * Description 转账业务的实现
 */
public class BankService {

    /**
     * @param addAccount 转账加钱的账号
     * @param subAccount 转账减钱的账号
     * @param money 转账的金额
     */

    public void transfer(String addAccount,String subAccount,int money) throws Exception {

        BankDao bankDao = new BankDao();
        bankDao.add(addAccount,money);
        bankDao.sub(subAccount,money);
    }
}

测试类:

package com.julissa.api.transaction;

import org.junit.jupiter.api.Test;

/**
 * 转账业务的测试类
 */
public class TestTransfer {
    @Test
    public void testTransfer() throws Exception {
        BankService bankService = new BankService();
        bankService.transfer("julissa", "tom", 500);
    }
}

执行结果:

执行第一次转账业务:

image-20230315151242438

image-20230315151301447

执行第二次转账业务:

image-20230315151402582

image-20230315151416673

执行第三次转账业务:

由于tom账号余额为0,转账失败

image-20230315151448833

查看数据库

image-20230315151557478

julissa账号上的金额却增加成功,这在实际上是不被允许的,要求要么加钱和减钱同时成功,要么同时失败。

3.5.4 在进行数据库事务管理的情况下

持久层BankDao:

package com.julissa.api.transaction;

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

/**
 * @author Julissa
 * Description 操作数据表t_bank的类
 */
public class BankDao {

    /**
     * 银行转账加钱的方法
     * @param account 加钱的账号
     * @param money    加钱的金额
     */
    public void add(String account, int money,Connection connection) throws Exception {

        //3.编写sql语句
        String sql = "update t_bank set money = money + ? where account = ?;";
        //4.创建prepareStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.占位符赋值
        preparedStatement.setInt(1, money);
        preparedStatement.setString(2, account);
        //6.执行sql语句
        preparedStatement.executeUpdate();
        preparedStatement.close();
        System.out.println("---加钱成功---");
    }
    /**
     * 银行转账减钱的方法
     * @param account  减钱的账号
     * @param money    减钱的金额
     */
    public void sub(String account, int money,Connection connection) throws Exception{

        //3.编写sql语句
        String sql = "update t_bank set money = money - ? where account = ?;";
        //4.创建prepareStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.占位符赋值
        preparedStatement.setInt(1, money);
        preparedStatement.setString(2, account);
        //6.执行sql语句
        preparedStatement.executeUpdate();
        preparedStatement.close();
        System.out.println("---减钱成功---");
    }
}

业务层BankService:

package com.julissa.api.transaction;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * @author Julissa
 * Description 转账业务的实现
 */
public class BankService {
    /**
     *  TODO:
     *      1.事务添加是在业务方法中
     *      2.利用try catch代码块开启事务和提交事务,和事务回滚
     *      3.将connection传入Dao层,dao层只使用conncetion,不关闭
     *
     */
    /**
     * @param addAccount 转账加钱的账号
     * @param subAccount 转账减钱的账号
     * @param money 转账的金额
     */

    public void transfer(String addAccount,String subAccount,int money) throws Exception {

        BankDao bankDao = new BankDao();
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.连接数据库
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/julissa", "root", "123456");

        try{
            connection.setAutoCommit(false);//关闭自动提交
            //一个事务最基本的要求,就是同一个连接对象
            bankDao.add(addAccount,money,connection);
            bankDao.sub(subAccount,money,connection);
            connection.commit();//事务提交
        }catch (Exception e){
            connection.rollback();//事务回滚
            throw e;
        }
        //释放资源
        connection.close();
    }
}

测试类:

package com.julissa.api.transaction;

import org.junit.jupiter.api.Test;

/**
 * 转账业务的测试类
 */
public class TestTransfer {
    @Test
    public void testTransfer() throws Exception {
        BankService bankService = new BankService();
        bankService.transfer("julissa", "tom", 500);
    }
}

执行结果:

初始数据库信息:

image-20230315153157185

执行第一次转账业务:

image-20230315151242438

image-20230315151301447

执行第二次转账业务:

image-20230315151402582

image-20230315151416673

执行第三次转账业务:

由于tom账号余额为0,转账失败

image-20230315153238014

查看数据库

image-20230315153249694

证明转账失败时加钱和减钱同时失败。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值