JSP模式&JDBC高级
今日任务
Ø 使用MVC设计模式开发一个转账案例
教学导航
教学目标 | 掌握JSP的设计模式 了解MYSQL的事务管理 掌握JDBC的事务管理 会使用DBUtils完成事务的管理 |
教学方法 | 案例驱动法 |
1.1 案例一:使用MVC设计模式完成转账的案例:
1.1.1 需求:
在页面中输入几个信息:收款人,付款人,转账金额.点击提交按钮可以完成转账的功能.在转账的过程中进行事务的管理.
1.1.2 分析:
1.1.2.1 技术分析:
【JSP的模式】
Model1:JSP+JavaBean 比较适合开发一些小应用.
Model2:JSP+Servlet+JavaBean 可以开发任何一个应用. MVC设计模式:
【MVC设计模式】
MVC是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
【EE开发中的三层结构】
JavaEE开发中通常将一个应用分成:WEB层,业务层,持久层
【JavaBean】
JavaBean:JavaBean就是一个满足的特定格式要求的Java类.
* 有一个无参数的构造:
* 属性私有
* 私有属性提供public的get或set方法.
属于Model1年代的标签.数据提交到JSP中.
<jsp:useBean>
<jsp:setProperty>
<jsp:getProperty>
<jsp:useBean id="u" class="com.itheima.domain.User" scope="page"></jsp:useBean>
<jsp:setProperty property="*" name="u"/>
<jsp:getProperty property="username" name="u"/>
<jsp:getProperty property="password" name="u"/>
【事务管理-JDBC】
Ø 事务的概述:
什么是事务:事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
事务作用:保证在一个事务中多次操作要么全都成功,要么全都失败.
Ø MYSQL中的事务管理:
创建一个表:账户表.
create database web13;
use web13;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values (null,'守义',10000);
insert into account values (null,'凤儿',10000);
insert into account values (null,'如花',10000);
进行事务的管理:
MYSQL中可以有两种方式进行事务的管理:MYSQL数据库默认事务是自动提交的.写一条SQL语句,事务就已经提交了.Oracle数据库事务不自动提交.手动执行commit;
一种:手动开启事务的方式:
* start transaction;
* update account set money=money-1000 where name='守义';
* update account set money=money+1000 where name='凤儿';
* commit;或者rollback;
二种:设置MYSQL中的自动提交的参数:
show variables like '%commit%';
* 设置自动提交的参数为OFF:
set autocommit = 0; -- 0:OFF 1:ON
【JDBC进行事务管理】
Connection:
* 事务管理的API:
1.1.2.2 步骤分析:
Ø 步骤一:设计页面三个文本框.
Ø 步骤二:提交到Servlet
n 接收参数:
n 封装参数:
n 调用业务层处理数据
n 页面跳转
1.1.3 代码实现:
设计转账的页面:
<h1>转账的页面</h1>
<form action="" method="post">
<table border="1" width="400">
<tr>
<td>付款人:</td>
<td><input type="text" name="from"/></td>
</tr>
<tr>
<td>收款人:</td>
<td><input type="text" name="to"/></td>
</tr>
<tr>
<td>转账金额:</td>
<td><input type="text" name="money"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="转账"/></td>
</tr>
</table>
</form>
Servlet:
/**
* 转账的Servlet
*/
public class AccountServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 1.接收数据:
* 2.封装数据:
* 3.调用业务层:
* 4.页面跳转作出响应:
*/
request.setCharacterEncoding("UTF-8");
// 接收数据:
String from = request.getParameter("from");
String to = request.getParameter("to");
double money = Double.parseDouble(request.getParameter("money"));
// 调用业务层:
AccountService accountService = new AccountService();
accountService.transfer(from,to,money);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
Service:
public class AccountService {
/**
* 业务层转账的方法:
* @param from:付款人
* @param to:收款人
* @param money:转账金额
*/
public void transfer(String from, String to, double money) {
// 调用DAO:
AccountDao accountDao = new AccountDao();
try {
accountDao.outMoney(from, money);
// int d = 1/0;
accountDao.inMoney(to, money);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
DAO:
public class AccountDao {
/**
* 付款的方法
* @param name
* @param money
* @throws SQLException
*/
public void outMoney(String name,double money) throws SQLException{
Connection conn = null;
PreparedStatement pstmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写一个SQL:
String sql = "update account set money = money-? where name=?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setDouble(1, money);
pstmt.setString(2, name);
// 执行SQL:
pstmt.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
pstmt.close();
conn.close();
}
}
/**
* 收款的方法
* @param name
* @param money
* @throws SQLException
*/
public void inMoney(String name,double money) throws SQLException{
Connection conn = null;
PreparedStatement pstmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写一个SQL:
String sql = "update account set money = money+? where name=?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setDouble(1, money);
pstmt.setString(2, name);
// 执行SQL:
pstmt.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
pstmt.close();
conn.close();
}
}
}
通过两种Service编写完成事务的管理:
* 一种向下传递Connection:
* 二种将Connection绑定到当前的线程中:
1.1.4 总结:
1.1.4.1 事务的特性:
事务有四大特性:
* 原子性:强调事务的不可分割.
* 一致性:事务的执行的前后,数据的完整性保持一致.
* 隔离性:一个事务在执行的过程中,不应该受到其他事务的干扰.
* 持久性:事务一旦结束,数据就持久到数据库中.
1.1.4.2 如果不考虑事务的隔离性,引发一些安全性问题:
读问题:三类
* 脏读:一个事务读到了另一个事务未提交的数据.
* 不可重复读:一个事务读到了另一个事务已经提交(update)的数据.引发一个事务中的多次查询结果不一致.
* 虚读/幻读:一个事务读到了另一个事务已经提交的(insert)数据.导致多次查询的结果不一致
1.1.4.3 解决读问题:
设置事务的隔离级别:
* read uncommitted:脏读,不可重复读,虚读都可能发生.
* read committed:避免脏读,但是不可重复读和虚读有可能发生.
* repeatable read:避免脏读和不可重复读,但是虚读有可能发生的.
* serializable:避免脏读,不可重复读和虚读.(串行化的-不可能出现事务并发访问)
安全性:serializable > repeatable read > read committed > read uncommitted
效率 :serializable< repeatable read < read committed < read uncommitted
MYSQL:repeatable read
Oracle:read committed
1.1.4.4 演示脏读的发生
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
* select @@tx_isolation
步骤三:设置A窗口的隔离级别为read uncommitted
* set session transaction isolation level read uncommitted;
步骤四:在两个窗口中分别开启事务:
* start transaction;
步骤五:在B窗口中转账操作:
* update account set money = money - 1000 where name='守义';
* update account set money = money + 1000 where name='凤儿';
**** 在B窗口中没有提交事务的!!!
步骤六:在A窗口中进行查询:
* 已经转账成功!!!(脏读:一个事务中读到了另一个事务未提交的数据)
1.1.4.5 演示避免脏读,不可重复读发生
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
* select @@tx_isolation
步骤三:设置A窗口的隔离级别为read committed;
* set session transaction isolation level read committed;
步骤四:在两个窗口中分别开启事务:
* start transaction;
步骤五:在B窗口中完成转账的操作:
* update account set money = money - 1000 where name='守义';
* update account set money = money + 1000 where name='凤儿';
**** 在B窗口中没有提交事务!!!
步骤六:在A窗口中进行查询:
* 没有转账的结果!!!(已经避免了脏读)
步骤七:在B窗口中提交事务!!!
* commit;
步骤八:在A窗口中进行查询:
* 转账成功!!!(不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致一次事务中多次查询结果不一致.)
1.1.4.6 演示避免不可重复读:
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
* select @@tx_isolation
步骤三:设置A窗口的隔离级别为repeatable read;
* set session transaction isolation level repeatable read;
步骤四:在两个窗口中分别开始事务:
* start transaction;
步骤五:在B窗口中完成转账的操作:
* update account set money = money - 1000 where name='守义';
* update account set money = money + 1000 where name='凤儿';
***** 在B窗口没有提交事务!!!
步骤六:在A窗口中进行查询:
* 没有转账成功!!!(已经避免脏读)
步骤七:在B窗口提交事务!!!
* commit;
步骤八:在A窗口进行查询:
* 转账没有成功!!(避免不可重复读)
步骤九:在A窗口中结束事务,再重新查询.
1.1.4.7 演示隔离级别为serializable
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
* select @@tx_isolation
步骤三:设置A窗口的隔离级别为serializable;
* set session transaction isolation level serializable;
步骤四:在两个窗口中分别开启事务:
* start transaction;
步骤五:在B窗口中完成一个insert操作:
* insert into account values (null,'芙蓉',10000);
***** 事务没有提交!!
步骤六:在A窗口中进行查找:
* 没有任何结果!!!(不可以事务并发执行的)
步骤七:在B窗口中结束事务!
1.1.4.8 JDBC的隔离级别的设置:(了解)
Connection中的方法:
Connection中提供了隔离级别的常量:
1.1.4.9 使用DBUtils的进行事务的管理:
QueryRunner:
* 构造:
QueryRunner();
QueryRunner(DataSource ds);
* 方法:
T query(String sql,ResultSetHanlder<T> rsh,Object... params);
T query(Connection conn,String sql,ResultSetHanlder<T> rsh,Object... params);
int update(String sql,Object... params);
int update(Connection conn,String sql,Object... params);
方法分类:
* 没有事务:
QueryRunner(DataSource ds);
T query(String sql,ResultSetHanlder<T> rsh,Object... params);
int update(String sql,Object... params);
* 有事务:
QueryRunner();
T query(Connection conn,String sql,ResultSetHanlder<T> rsh,Object... params);
int update(Connection conn,String sql,Object... params);