一、事务处理
事务(Transaction,简写为tx):在数据库中,事务是指一组逻辑操作,不论成功与失败都作为一个整体进行工作,要么全部执行成功,要么全部不执行(执行失败)。
1、处理事务的两个动作
1)提交(commit):在整个事务中,所有的逻辑操作都正常执行成功,则提交事务。
2)回滚(rollback):在整个事务中,有一个或多个逻辑操作执行失败,则回滚事务,撤销该事务中的所有操作,恢复到最初的状态。
2、事务的ACID属性
1)原子性(Aromicity):事务是应用中不可再分的最小逻辑执行单位,要么都执行成功,要么都不执行。
2)一致性(Consistency):事务结束后,数据库中的数据是合法正确的(数据不被破坏)。
3)隔离性(Isolation):并发执行的事务之间彼此相互独立、互不干扰。
4)持久性(Durability):事务提交后,数据时永久性对的、不可回滚。
3、在代码中处理事务
1)在JDBC中缺省情况下,事务是自动提交的,控制事务必须先设置事务为手动提交
2)手动提交事务
3)若出现异常必须回滚事务
Connection接口方法:
-
-
void
setAutoCommit(boolean autoCommit)
将此连接的自动提交模式设置为给定状态。
void
commit()
使自上次提交/回滚以来所做的所有更改都将永久性,并释放此
Connection
对象当前持有的任何数据库锁。void
rollback()
撤消在当前事务中所做的所有更改,并释放此
Connection
对象当前持有的任何数据库锁。
-
/**
* 赵云向赵子龙转账1000
*/
@Test
public void testTx() {
Connection conn = JdbcUtil.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1、设置事务手动提交
conn.setAutoCommit(false);
//查询赵云账号余额
String sql = "SELECT * FROM t_user WHERE username=? and account >=?";
ps = conn.prepareStatement(sql);
ps.setString(1,"赵云");
ps.setLong(2,1000L);
rs = ps.executeQuery();
if (!rs.next()){
throw new RuntimeException("余额不足!");
}
//赵云减1000
sql = "UPDATE t_user set account=account-? WHERE username=?";
ps = conn.prepareStatement(sql);
ps.setLong(1,1000L);
ps.setString(2,"赵云");
ps.executeUpdate();
//模拟异常
int i = 1/0;
//赵子龙加1000
sql = "UPDATE t_user set account=IFNULL(account,0) +? WHERE username=?";
ps = conn.prepareStatement(sql);
ps.setLong(1,1000L);
ps.setString(2,"赵子龙");
ps.executeUpdate();
//2、手动提交事务
conn.commit();
}catch (Exception e){
e.printStackTrace();
try {
//3、回滚事务
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
JdbcUtil.close(conn, ps, rs);
}
}
二、批处理操作
批量操作(batch):当需要成批插入或者更新记录时,可以采用Java的批量更新机制,它允许多条语句一次性提交为数据库批量处理。通常情况下比单独提交处理更高效。
JDBC的批量处理语句的方法:
1、Statement接口批处理
一次性可以执行多条SQL语句,需要编译多次。
-
-
void
addBatch(String sql)
将给定的SQL命令添加到此
Statement
对象的当前命令列表中。int[]
executeBatch()
将一批命令提交到数据库以执行,并且所有命令都执行成功,返回一个更新计数的数组。
void
clearBatch()
清空此
Statement
对象的当前SQL命令列表。
-
/**
* Statement 未使用批量处理
* InnodDB耗时:6111ms
* 添加rewriteBatchedStatements=true后
* InnodDB耗时:6042ms
* @throws Exception
*/
@Test
public void testSaveByStatement() throws Exception {
long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.getConnection();
Statement st = null;
for (int i = 0; i <= 3000; i++) {
String sql = "INSERT INTO t_user(username,age) VALUES('aa',"+i+")";
st = conn.createStatement();
st.executeUpdate(sql);
}
System.out.println(System.currentTimeMillis() - begin);
JdbcUtil.close(conn,st,null);
}
/**
* Statement 使用批量处理
* InnodDB耗时:5974ms
* 添加rewriteBatchedStatements=true后
* InnodDB耗时:5288ms
* @throws Exception
*/
@Test
public void testSaveByStatementBatch() throws Exception {
long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.getConnection();
Statement st = conn.createStatement();
for (int i = 0; i <= 3000; i++) {
String sql = "INSERT INTO t_user(username,age) VALUES('aa',"+i+")";
st.addBatch(sql); //将SQL语句添加到批处理
if(i % 300 == 0){
st.executeBatch();//执行操作
st.clearBatch();//清除操作
}
}
System.out.println(System.currentTimeMillis() - begin);
JdbcUtil.close(conn,st,null);
}
2、PreparedStatement接口批处理
执行一条SQL语句,编译一次,执行SQL语句的参数不同
-
-
void
addBatch()
向这个
PreparedStatement
对象的一批命令添加一组参数。void
clearParameters()
立即清除当前参数值
-
/**
* PreparedStatement 未使用批量处理
* InnodDB耗时:6040ms
* 添加rewriteBatchedStatements=true后
* InnodDB耗时:6118ms
* @throws Exception
*/
@Test
public void testSaveByPreparedStatement() throws Exception {
long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.getConnection();
PreparedStatement ps = null;
for (int i = 0; i <= 3000; i++) {
String sql = "INSERT INTO t_user(username,age) VALUES(?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,"aa");
ps.setInt(2,i);
ps.executeUpdate();
}
System.out.println(System.currentTimeMillis() - begin);
JdbcUtil.close(conn,ps,null);
}
/**
* PreparedStatement 使用批量处理
* InnodDB耗时:6194ms
* 添加rewriteBatchedStatements=true后
* InnodDB耗时:438ms
* @throws Exception
*/
@Test
public void testSaveByPreparedStatementBatch() throws Exception {
long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.getConnection();
String sql = "INSERT INTO t_user(username,age) VALUES(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i <= 3000; i++) {
ps.setString(1,"aa");
ps.setInt(2,i);
ps.addBatch(); //将SQL语句添加到批处理
if(i % 300 == 0){
ps.executeBatch();//执行操作
ps.clearBatch();//清除操作
ps.clearParameters();//清除参数
}
}
System.out.println(System.currentTimeMillis() - begin);
JdbcUtil.close(conn,ps,null);
}
结论:
MySQ数据库的模板引擎对批处理操作也有影响,参考文章:MySQL的各种引擎归纳总结
MySQL数据库既不支持PreparedStatement的性能优化,也不支持JDBC的批量操作,但是MySQL在新的JDBC驱动中,通过设置参数支持批处理操作,所以,不管是单语句还是批处理操作,推荐使用PreparedStatement。
url=jdbc:mysql://localhost:3306/jdbcdemo?rewriteBatchedStatements=true
三、对 BLOB类型操作
BLOB是可变的数据类型,一般在开发中,不会把二进制的文件(比如图片,视频和音频等)存放在数据库中。而是把文件的存储路径保存到数据库中。
java.nio.file.Files类:
-
-
static long
copy(InputStream in, Path target, CopyOption... options)
将输入流中的所有字节复制到文件。
static long
copy(Path source, OutputStream out)
将文件中的所有字节复制到输出流。
static Path
copy(Path source, Path target, CopyOption... options)
将文件复制到目标文件。
-
PreparedStatement接口:
-
-
void
setBlob(int parameterIndex, Blob x)
将指定的参数设置为给定的
java.sql.Blob
对象。void
setBlob(int parameterIndex, InputStream inputStream)
将指定的参数设置为
InputStream
对象。void
setBlob(int parameterIndex, InputStream inputStream, long length)
将指定的参数设置为
InputStream
对象。
-
ResultSet接口:
Blob接口:
-
-
InputStream
getBinaryStream()
将此
Blob
实例指定的BLOB
值作为流Blob
。InputStream
getBinaryStream(long pos, long length)
返回包含部分
Blob
值的InputStream
对象,从pos指定的字节开始,长度为长度字节。
-
模拟BLOB类型存放一张图片
/**
* 将磁盘文件保存到数据库
* @throws Exception
*/
@Test
public void saveBLOB() throws Exception {
String sql = "INSERT INTO t_user(username,age,image) VALUES(?,?,?)";
Connection conn = JdbcUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"赵云2");
ps.setInt(2,18);
FileInputStream inputStream = new FileInputStream(new File("E:/java/赵云.jpg"));
ps.setBlob(3,inputStream);
ps.executeUpdate();
JdbcUtil.close(conn, ps, null);
}
/**
* 将数据库文件保存到磁盘
* @throws Exception
*/
@Test
public void getBLOB() throws Exception {
String sql = "select id,image from t_user where username = ?";
Connection conn = JdbcUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"赵云2");
ResultSet rs = ps.executeQuery();
if(rs.next()){
Blob image = rs.getBlob("image");
//文件copy
Files.copy(image.getBinaryStream(),Paths.get("E:/java/赵云image.jpg"));
}
JdbcUtil.close(conn, ps, rs);
}
四、获取自动生成的主键
需求:新增一条记录,返回主键id
1、Statement接口
-
-
static int
NO_GENERATED_KEYS
常数表示生成的密钥不应该可用于检索。
static int
RETURN_GENERATED_KEYS
常数表示生成的密钥应该可用于检索。
-
-
-
int
executeUpdate(String sql, int autoGeneratedKeys)
执行给定的SQL语句并用给定的标志来向驱动程序发出信号,以了解该
Statement
对象产生的自动生成的密钥是否应该可用于检索。ResultSet
getGeneratedKeys()
检索由执行此
Statement
对象而创建的任何自动生成的密钥。
-
@Test
public void testSaveByStatement() throws Exception {
String sql = "INSERT INTO t_user(username,age) VALUES('aa',17)";
Connection conn = JdbcUtil.getConnection();
Statement st = conn.createStatement();
//设置可以获取自动生成的主键
st.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS);
ResultSet rs = st.getGeneratedKeys();
if(rs.next()){
Long id = rs.getLong(1);//获取第一列 主键
System.out.println(id); //2
}
JdbcUtil.close(conn, st, null);
}
2、PreparedStatement接口
-
-
PreparedStatement
prepareStatement(String sql, int autoGeneratedKeys)
创建一个默认的
PreparedStatement
对象,该对象具有检索自动生成的密钥的能力。
-
@Test
public void testSaveByPreparedStatement() throws Exception {
String sql = "INSERT INTO t_user(username,age) VALUES(?,?)";
Connection conn = JdbcUtil.getConnection();
//设置可以获取自动生成的主键
PreparedStatement ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
ps.setString(1, "ss");
ps.setInt(2, 18);
ps.executeUpdate();
//获取自动生成的主键
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
Long id = rs.getLong(1);//获取第一列 主键
System.out.println(id); //3
}
JdbcUtil.close(conn, ps, null);
}
站在前辈的肩膀上,每天进步一点点
ends~