1. 普通的解决事务问题方法 71
直接把connection当作参数传进去
代码在com.powernode.bank.mvc
管理调度AccountServlet
package com.powernode.bank.mvc;
import com.powernode.bank.exceptions.AppException;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
//账户servlet小程序 67和70
//AccountServlet是一个司令官。他负责调度其他组件来完成任务。
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {//把AccountServlet当作一个Controller
//调用业务方法处理业务(调度Model处理业务) 70
private AccountService accountService = new AccountService();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//接收数据 70
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
double money = Double.parseDouble(request.getParameter("money"));
//调用业务方法处理业务(调度Model处理业务)
try {
accountService.transfer(fromActno,toActno,money);
//程序执行到这里说明成功
//展示数据(调度view左业务展示)
//重定向
response.sendRedirect(request.getContextPath()+"/success.jsp");
} catch (MoneyNotEnoughException e){
//执行到这里转账失败,余额不足
//展示数据(调度view左业务展示)
//重定向
response.sendRedirect(request.getContextPath()+"/moneynotenough.jsp");
}catch (Exception e) {
//执行到这里转账失败
//展示数据(调度view左业务展示)
//重定向
response.sendRedirect(request.getContextPath()+"/error.jsp");
}
}
}
业务处理AccountService
package com.powernode.bank.mvc;
//这个类是负责处理account业务的 69
import com.powernode.bank.exceptions.AppException;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.utils.DBUtil;
import java.sql.Connection;
import java.sql.SQLException;
/**
* service翻译为:业务。
* AccountService:专门处理Account业务的一个类。
* 在该类中应该编写纯业务代码。(只专注业务。不写别的。不和其他代码混合在一块。)
* 只希望专注业务,能够将业务完美实现,少量bug。
*
* 业务类一般起名:XxxService、XxxBiz.....
*/
public class AccountService {
// 为什么定义到这里?因为在每一个业务方法中都可以需要连接数据库。
private AccountDao accountDao = new AccountDao();
// 这里的方法起名,一定要体现出,你要处理的是什么业务。
// 我们要提供一个能够实现转账的业务方法(一个业务对应一个方法。)
/**
* 完成转账的业务逻辑
* @param fromActno 转出账号
* @param toActno 转入账号
* @param money 转账金额
*/
public void transfer(String fromActno,String toActno,double money)
throws MoneyNotEnoughException, AppException {
//service层控制事务 71
try (Connection connection = DBUtil.getConnection();){
//开启事务(需要使用Connection对象)71
connection.setAutoCommit(false);
//查询余额是否充足(根据账号将查询到的对象封装成一个account账户返回)
//fromAct是一个account账户对象
Account fromAct = accountDao.selectByActno(fromActno,connection);
//余额不足
if(fromAct.getBalance()<money){
throw new MoneyNotEnoughException("对不起,余额不足");
}
//到这里说余额充足
//我们继续查另一个账户余额
//toAct是一个account账户对象
Account toAct = accountDao.selectByActno(toActno,connection);
//修改余额(只是修改了内存中java对象的余额)
fromAct.setBalance(fromAct.getBalance()-money);
toAct.setBalance(toAct.getBalance()+money);
//更新数据库中的余额
int count = accountDao.update(fromAct,connection);
//模拟异常
// String s = null;
// s.toString();
count += accountDao.update(toAct,connection);
if(count != 2){
throw new AppException("账户转账异常!!");
}
//提交事务
connection.commit();
} catch (SQLException e) {
throw new AppException("账户转账异常!!!");
//回滚事务(不屑也没关系)
}
}
}
负责CRUD AccountDao
package com.powernode.bank.mvc;
//这个类负责Account的数据操作(增删改查) 67
import com.powernode.bank.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* AccountDao是负责Account数据的增删改查的。
* 1. 什么是DAO?
* Data Access Object(数据访问对象)
* 2. DAO实际上是一种设计模式,属于JavaEE的设计模式之一。(不是23种设计模式。)
* 3. DAO只负责数据库表的CRUD,没有任何业务逻辑在里面。
* 4. 没有任何业务逻辑,只负责表中数据增删改查的对象,有一个特殊的称谓:DAO对象。
* 5. 为什么叫做AccountDao呢?
* 这是因为这个DAO是专门处理t_act这张表的。
* 如果处理t_user表的话,可以叫做:UserDao
* 如果处理t_student表的话,可以叫做:StudentDao
* 6. 一般情况下:一张表会对应一个DAO对象。
* 7. DAO中的方法名很固定了,一般都是:
* insert
* deleteByXxx
* update
* selectByXxx
* selectAll
*/
public class AccountDao{
//负责往数据库中插入账户信息 67
//之所以是int类是因为,插入操作返回的是受影响的行数
public int insert(Account act,Connection conn){
PreparedStatement ps = null;
int count = 0;
try {
String sql = "insert into t_act(actno,balance) values(?,?)";
//获取预编译对象
ps = conn.prepareStatement(sql);
ps.setString(1,act.getActno());
ps.setDouble(2,act.getBalance());
//执行
count = ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException();
}finally {
//关闭资源
DBUtil.close(null,ps,null);
}
return count;
}
//删除数据(根据id删除) 67
public int deleteById(Long id,Connection conn){
PreparedStatement ps = null;
int count = 0;
try {
String sql = "delete from t_act where id = ?";
ps = conn.prepareStatement(sql);
ps.setLong(1,id);
count = ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,ps,null);
}
return 0;
}
//更新数据 67
public int update(Account act,Connection conn){
PreparedStatement ps = null;
int count = 0;
try {
String sql = "update t_act set balance = ? , actno = ? where id = ?";
ps = conn.prepareStatement(sql);
ps.setDouble(1, act.getBalance());
ps.setString(2, act.getActno());
ps.setLong(3, act.getId());
count = ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DBUtil.close(null, ps, null);
}
return count;
}
//根据账号查找数据 67
public Account selectByActno(String actno,Connection conn){
PreparedStatement ps = null;
ResultSet rs = null;
Account act = null;
try {
String sql = "select id,balance from t_act where actno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,actno);
rs = ps.executeQuery();
if(rs.next()){
Long id = rs.getLong("id");
Double balance = rs.getDouble("balance");
//将结果集封装成一个java对象
act = new Account();
act.setId(id);
act.setActno(actno);
act.setBalance(balance);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DBUtil.close(null, ps, rs);
}
return act;
}
//获取所有的账户 67
public List selectAll(Connection conn){
PreparedStatement ps = null;
ResultSet rs = null;
List list = new ArrayList<>();
try {
String sql = "select id,actno,balance from t_act";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()){
//取出数据
Long id = rs.getLong("id");
String actno = rs.getString("actno");
Double balance = rs.getDouble("balance");
//封装对象
//这是一个一个的封装
/* Account account = new Account();
account.setId(id);
account.setActno(actno);
account.setBalance(balance);*/
//使用构造方法封装
Account account = new Account(id, actno, balance);
//加到list集合
list.add(account);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DBUtil.close(null, ps, rs);
}
return list;
}
}
javabean Account
package com.powernode.bank.mvc;
//这是一个Account的javabean负责封装数据库里的对象 67
/**
* 账户实体类:封装账户信息的。
* 一般是一张表一个。
* pojo对象。
* 有的人也会把这种专门封装数据的对象,称为bean对象。(javabean:咖啡豆)
* 有的人也会把这种专门封装数据的对象,称为领域模型对象。domain对象。
* 不同的程序员有不同的习惯。
*
* pojo、bean、domain.....
*/
public class Account { // 这种普通简单的对象被成为pojo对象。
// 一般这种属性不建议设计为基本数据类型,建议使用包装类。防止null带来的问题。
//private long id;
private Long id; //主键
private String actno;//账号
//private double balance;
private Double balance;//余额
public Account() {
}
public Account(Long id, String actno, Double balance) {
this.id = id;
this.actno = actno;
this.balance = balance;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
}
工具类com.powernode.bank.utils
DBUtil
package com.powernode.bank.utils;
import java.sql.*;
import java.util.ResourceBundle;
//工具类 66
public class DBUtil {
//得到配置文件的属性
private static ResourceBundle bundle = ResourceBundle.getBundle("resources/jdbc");
private static String driver = bundle.getString("driver");
private static String url = bundle.getString("url");
private static String user = bundle.getString("user");
private static String password = bundle.getString("password");
//之所以设置为私有的private 是因为不让创建对象,因为工具类中的方法都是静态的,不需要创建对象
private DBUtil(){};
//DBUtil类加载时注册驱动
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接对象
//这里没有使用数据库连接池,直接创建连接对象。
//返回连接对象
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
//关闭资源
/**
* 关闭资源
* @param conn 连接对象
* @param stmt 数据库操作对象
* @param rs 结果集对象
*/
public static void close(Connection conn, Statement stmt, ResultSet rs){
if(rs !=null){
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(stmt !=null){
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(conn !=null){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
异常com.powernode.bank.exceptions
AppException
package com.powernode.bank.exceptions;
//app异常类 69
public class AppException extends Exception{
public AppException() {
}
public AppException(String msg) {
super(msg);
}
}
MoneyNotEnoughException
package com.powernode.bank.exceptions;
//钱不足异常类 69
public class MoneyNotEnoughException extends Exception{
//以下是两个构造器
//无参构造器
public MoneyNotEnoughException() {
}
//有参构造器
public MoneyNotEnoughException(String msg) {
super(msg);
}
}
配置文件resources
jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mvc
user=root
password=lzl
index.jsp
<%--银行转账页面 63--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>转账页面</title>
<base href="https://www.ctyun.cn/portal/link.html?target=%24%7BpageContext.request.scheme%7D%3A%2F%2F%24%7BpageContext.request.serverName%7D%0A%3A%24%7BpageContext.request.serverPort%7D%24%7BpageContext.request.contextPath%7D%2F">
</head>
<body>
<form action="transfer" method="post">
转出账户:<input type="text" name="fromActno"><br>
转入账户:<input type="text" name="toActno"><br>
转账金额:<input type="text" name="money"><br>
<input type="submit" value="转账"/>
</form>
</body>
</html>
error.jsp
<%--转账失败页面 70--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>转账失败</title>
</head>
<body>
<h1>转账失败</h1>
</body>
</html>
moneynotenough.jsp
<%--余额不足页面 70--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>转账失败</title>
</head>
<body>
<h1>余额不足</h1>
</body>
</html>
success.jsp
<%--转账成功页面 70--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>转账成功</title>
</head>
<body>
<h1>转账成功</h1>
</body>
</html>