文章目录
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();
}
}
执行结果如下:
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条数据耗时:
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();
}
执行结果如下:
可以看出,批量操作大大提高了数据插入的效率
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设置不能为负数
向表中添加数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gY7wtcpw-1678879639159)(null)]
3.4.2 程序结构
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);
}
}
执行结果:
执行第一次转账业务:
执行第二次转账业务:
执行第三次转账业务:
由于tom账号余额为0,转账失败
查看数据库
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);
}
}
执行结果:
初始数据库信息:
执行第一次转账业务:
执行第二次转账业务:
执行第三次转账业务:
由于tom账号余额为0,转账失败
查看数据库
证明转账失败时加钱和减钱同时失败。