事物:
事物指的是逻辑上的一组操作,组成这组操作的各个单元要么全部成功,要么全部失败
简而言之,一组操作要么都成功,要么都失败; 一件事情有n个组成单元 要不这n个组成单元同时成功 要不n个单元就同时失败。
mysql的事务
1)默认的事务:
一条sql语句就是一个事务,默认就开启事务并提交事务
MySql默认自动提交,即执行一条sql语句就提交一次事务;
2)手动事务:
1)显示的开启一个事务:start transaction;
2)事务提交:commit代表从开启事务到事务提交 中间的所有的sql都认为有效 真正的更新数据库
3)事务的回滚:rollback 代表事务的回滚 从开启事务到事务回滚 中间的所有的 sql操作都认为无效数据库没有被更新
MySql中可以有两种方式对事务进行管理:
方式一:关闭mysql的自动事务
set autocommit = 0;
注意:这个设置只对当前的连接有效,新开的窗口,依然是自动事务
方式二:手动开启一个事务:
start transaction; -- 手动开启一个事务
-- 假装做了一系列的sql操作
commit; -- 提交事务 : 相当于把数据持久保存到数据据库中
rollback; -- 回滚事务 : 相当于撤销刚才所有的操作
注意:
1)事务开启之后,最终必须提交或者回滚
2)oracle中的默认是手动事务,mysql默认是自动事务
3)控制事务的connnection必须是同一个
执行sql的connection与开启事务的connnection必须是同一个才能对事务进行控制
jdbc中的事务操作:
Connection接口的API(jdk api中搜connection )
- setAutoCommit(false) : 设置默认不自动提交事务
- commit(); 提交事务。将数据持久保存在数据库中
- rollback(); 事务的回滚。撤销事物中的操作
- rollback(savepoint sp); 事务的回滚至保存点
转账案例:
事务的控制需要在同一个connection之中,所以下述案例中统一传了一个对象connection,保证是在同一个连接之中。
package com.itheima.test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import com.itheima.domain.Transfer;
public class Test01 {
@Test
public void test01() throws Exception{
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//连接数据库
String url = "jdbc:mysql://localhost:3306/day21";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);
//关闭自动事务
connection.setAutoCommit(false);
//封装一个javaBean
Transfer transfer = new Transfer();
transfer.setFromUser("jack");
transfer.setToUser("rose");
transfer.setMoney(500);
try {
//转出钱
transferOut(connection, transfer);
//如果发生意外
//int i = 5/0;
//转入钱
transferIn(connection, transfer);
//提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
//事务回滚
connection.rollback();
System.out.println("转账失败");
}finally{
//关闭资源
connection.close();
}
}
/**
* @Title: Test01.java
* @Description: TODO(资金从转账人账户转出)
* @param connection
* @author jjizh
* @date 2017年6月30日 下午8:33:00
* @version V1.0
* @throws SQLException
*/
public void transferOut(Connection connection,Transfer transfer) throws SQLException{
//获得转出金额和从谁转出
double money = transfer.getMoney();
String name = transfer.getFromUser();
//创建执行sql语句的对象
String sql = "update user set money = money - ? where name = ? ";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
prepareStatement.setDouble(1, money);
prepareStatement.setString(2, name);
//执行sql语句
prepareStatement.executeUpdate();
//关闭资源
prepareStatement.close();
}
/**
* @Title: Test01.java
* @Description: TODO(资金转入给收款人)
* @param connection
* @param transfer
* @throws SQLException
* @author jjizh
* @date 2017年6月30日 下午8:48:22
* @version V1.0
*/
public void transferIn(Connection connection,Transfer transfer) throws SQLException{
//获得转入金额和转入给谁
double money = transfer.getMoney();
String name = transfer.getToUser();
//创建执行sql语句的对象
String sql = "update user set money = money + ? where name = ? ";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
prepareStatement.setDouble(1, money);
prepareStatement.setString(2, name);
//执行sql语句
prepareStatement.executeUpdate();
//关闭资源
prepareStatement.close();
}
}
DBUtils事务操作
DBUtils中的api
* commitAndClose(Connection conn);
* commitAndCloseQuietly(Connection conn); 不抛异常
* rollbackAndClose(Connction conn);
* rollbackAndCloseQuietly(Connection conn);不抛异常
queryRunner操作sql:
1)new QueryRunner(DataSource ds): 默认就是自动事务(这是我们之前一直用的方法)
query(String sql,ResultSetHandler rsh,Object…params);
update(String sql,Object…params);
使用上述方法的时候,我们没有调用过setAutoCommit(false),最后也没有提交过事务,也没有关闭资源
2)new QueryRunner() : 手动事务
query(Connection conn,String sql,ResultSetHandler rsh,Object…params);
update(Connection conn,String sql,Object…params);
使用query和update方法的时候,我们需要传入一个connection对象,最后需要手动提交或者回滚事务,还需要手动释放conn;
案例补充知识点:
1)主动抛异常
throw new Exception("")
if(!flag2){
throw new RuntimeException("对不起,转入失败");
}
2)获取异常中的抛出信息
Exception.getMessage();
catch (Exception e) {
e.printStackTrace();
request.setAttribute("msg", e.getMessage());
}
代码:
Transferservlet:
public class TransferServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
try {
//创建转账对象
Transfer t = new Transfer();
//接收参数
Map<String, String[]> map = request.getParameterMap();
//将参数传入到对象中去
BeanUtils.populate(t, map);
//调用service完成转账
TransferService ts = new TransferService();
ts.transfer(t);
request.setAttribute("msg", "转账成功");
request.setAttribute("transfer", t);
request.getRequestDispatcher("/index.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("msg", e.getMessage());
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
TransferService:
public class TransferService {
public void transfer(Transfer t) throws Exception {
Connection connection = null;
try {
//获得连接
connection = JDBCUtils.getConnection();
//开启手动事务
connection.setAutoCommit(false);
TransferDao td = new TransferDao();
//转出
boolean flag1 = td.moneyOut(connection,t);
if(!flag1){
throw new RuntimeException("对不起,转出失败");
}
//转入
boolean flag2 = td.moneyIn(connection,t);
if(!flag2){
throw new RuntimeException("对不起,转入失败");
}
//提交commit
connection.commit();
} catch (Exception e) {
JDBCUtils.rollbackQietly(connection);
throw e;
} finally{
//释放资源
JDBCUtils.closeQuietly(connection);
}
}
}
转账案例终极版:
ThreadLocal
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
例如:
private static ThreadLocal tl = new ThreadLocal<T>();
private static ThreadLocal tl = new ThreadLocal<Connection>();
threadLocal相当于一个map,以当前线程的id作为一个key,对应存入一个value
api中:
set(T value)直接传入一个value,设置一个值
get():不需要传入任何参数,key就是当前线程
remove():不需要传入任何一个参数, key就是当前线程
所以转账的优化流程可以优化成这个样子:
所以:
service层的代码优化成为:
TransferService_thrl:
public class TransferService_thrl {
public void transfer(Transfer t) throws Exception {
try {
//开启手动事物
JDBCUtils.startTransaction();
TransferDao_thrl td = new TransferDao_thrl();
//转出
boolean flag1 = td.moneyOut(t);
if(!flag1){
throw new RuntimeException("转出失败!!!!");
}
//转入
boolean flag2 = td.moneyIn(t);
if(!flag2){
throw new RuntimeException("转入失败!!!!");
}
//提交事务
JDBCUtils.commitAndClose();
} catch (Exception e) {
JDBCUtils.rollbackAndClose();
e.printStackTrace();
}
}
}
JDBCUtils中的函数优化为:
public class JDBCUtils {
private static DataSource ds = new ComboPooledDataSource();
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
public static DataSource getDataSource(){
return ds;
}
public static Connection getConnection() throws SQLException{
//从当前线程中去获取connection;
Connection connection = tl.get();
if(connection == null){
connection = ds.getConnection();
tl.set(connection);
}
return connection;
}
//开启一个事务
public static void startTransaction() throws SQLException {
Connection connection = getConnection();
connection.setAutoCommit(false);
}
//提交事务
public static void commitAndClose() {
try {
Connection connection = getConnection();
connection.commit();
connection.close();
} catch (SQLException e) {
// quiet
// e.printStackTrace();
}
}
public static void rollbackAndClose() {
try {
Connection connection = getConnection();
connection.rollback();
connection.close();
} catch (SQLException e) {
// quiet
// e.printStackTrace();
}
}
}
备注:servlet中的service每次是在一个新的线程中去执行的
事务特性ACID
1)原子性:Atomicity
- 事务不可切分,事务中可以包含多个操作,这些操作要么都成功,要么都失败
2)一致性:Consistency
- 事务执行前后的业务状态要和其它业务状态保持一致
3)隔离性:isolation
- 一个事务的执行,不受其它事务的影响
4)持久性:Durability
- 事务一旦提交,数据就持久保存到数据库中
并发访问问题:
如果不考虑隔离性,事务存在3种并发访问问题:
- 脏读: 一个事务读取到了另一个事务没有提交的数据
- 不可重复读:在一个事务中,两次查询的结果不一致(针对update)
- 虚读(幻读):在一个事务中,两次查询的结果不一致(针对insert)
设置隔离级别,避免问题产生
- read uncommitted 读未提交
- 上面所有的问题全都不可避免
- read committed 读取已提交
- 可以避免脏读的发生,不可重复读和幻读会发生
- repeatable read 可重复读
- 可以避免脏读和不可重复读的发生,不能避免幻读的发生
- serializable 串行化
- 可以避免所有的问题,一个事务在执行,另外一个事务等待
- 安全性: serializable > repeatable read > read committed > read uncommitted
效率刚好和上面的相反
mysql的默认隔离级别: repeatable read
oracle的默认隔离级别: read commttied – read only – serializable
java代码修改数据库的隔离级别(了解):
- conn.setTransactionIsolation(int level)
MySql中:
1)将数据库的隔离级别设为read uncommitted
set session transaction isolation level read uncommitted;
2)查看隔离级别
select @@tx_isolation;
3)将数据库的隔离级别设为read committed
set session transaction isolation level read committed;
不能避免不可重复读和幻读的发生
4)将数据库的隔离级别设为repeatable read
set session transaction isolation level repeatable read;
5)将数据库的隔离级别设为serializable
set session transaction isolation level serializable;
jsp中输出菱形
在控制台中输出:
@Test
public void test01(){
int size = 10;
for(int x=-size;x<=size;x++){
for(int y=-size;y<=size;y++){
if(y+x<=size && y+x>=-size && y-x<=size && y-x>=-size ){
System.out.print("*");
}else{
System.out.print(" ");
}
}
System.out.println();
}
}
}
在jsp页面输出
因为jsp页面中使用jstl,循环是只能从0开始,不能够从负数开始的,所以需要手工调整坐标系位置
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>输出菱形</title>
</head>
<body>
<c:forEach var="x" begin="0" end="5" step="1" varStatus="xStatus">
<c:forEach var="y" begin="0" end="5" step="1" varStatus="yStatus">
<c:choose>
<c:when test="${x+y<=6 && x+y>=2 && x-y<=2 && x-y>=-2}">
*
</c:when>
<c:otherwise>
</c:otherwise>
</c:choose>
</c:forEach>
<br/>
</c:forEach>
</body>
</html>
JSP三层架构
包名: