老杜 JavaWeb 讲解(二十一)——通过银行账户转账业务讲解MVC架构
老杜-通过银行账户转账业务讲解MVC架构
老杜-银行账户转账(mvc001)
这个项目将层层迭代,最终成为MVC架构的项目。
老杜第一次写代码并没有使用JDBC的封装类,但大差不差,这里即使用了之前的DBUtil.java,代码依然很杂乱。
建立数据库
数据库名:mvc
字符集:utf8mb4
排序规则:utf8mb4_unicode_ci
建表:t_act
项目目录
具体代码
AppException.java
package com.zwm.bank.exceptions;
public class AppException extends Exception {
public AppException() {
super();
}
public AppException(String message) {
super(message);
}
}
MonerNotEnoughException.java
package com.zwm.bank.exceptions;
public class MonerNotEnoughException extends Exception {
public MonerNotEnoughException() {
}
public MonerNotEnoughException(String message) {
super(message);
}
}
AccountTransferServlet.java
package com.zwm.bank.web.servlet;
import com.zwm.bank.exceptions.AppException;
import com.zwm.bank.exceptions.MonerNotEnoughException;
import com.zwm.bank.web.utils.DBUtil;
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;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@WebServlet("/transfer")
public class AccountTransferServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取响应流对象
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//获取转账相关的信息
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
double money = Double.parseDouble(request.getParameter("money"));
Connection conn = null;
PreparedStatement ps = null;
PreparedStatement ps2 = null;
PreparedStatement ps3 = null;
ResultSet rs = null;
try {
// 转账之前判断账户余额是否充足
//获取连接
conn = DBUtil.getConnection();
//获取预编译的数据库对象
String sql = "select balance from t_act where actno=?";
ps = conn.prepareStatement(sql);
ps.setString(1, fromActno);
//执行SQL语句,返回结果集
rs = ps.executeQuery();
if (rs.next()) {
double balance = rs.getDouble("balance");
//余额不足的情况下(使用异常机制)
if (balance < money) {
throw new MonerNotEnoughException("抱歉,余额不足");
}
//当运行到这里说明,余额一定充足
//开始转账,fromActno账户上减去money ,toActno账户上加上money
//开启事务,取消自动提交,改为手动提交,业务完成之后再提交。
conn.setAutoCommit(false);
//1.先更新from账户的余额
String sql2 = "update t_act set balance=balance- ? where actno= ?";
ps2 = conn.prepareStatement(sql2);
ps2.setDouble(1, money);
ps2.setString(2, fromActno);
int count = ps2.executeUpdate();
//2.再更新to账户的余额
String sql3 = "update t_act set balance=balance+ ? where actno= ?";
ps3 = conn.prepareStatement(sql3);
ps3.setDouble(1, money);
ps3.setString(2, toActno);
//两次执行结果累计
count += ps3.executeUpdate();
if(count != 2){
throw new AppException("App出错了,请联系管理员。");
}
//手动提交
conn.commit();
//项目能运行到这里,代表转账成功。
out.print("转账成功");
}
} catch (Exception e) {
//保险起见,回滚事务。
try {
if(conn != null){
conn.rollback();
}
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
out.print(e.getMessage());
}finally {
DBUtil.closeForResultSet(rs);
DBUtil.closeForStatement(ps);
DBUtil.closeForStatement(ps2);
DBUtil.closeForStatement(ps3);
DBUtil.closeForConnection(conn);
}
}
}
DBUtil.java
package com.zwm.bank.web.utils;
import java.sql.*;
import java.util.ResourceBundle;
public class DBUtil {
//静态变量:在类加载时,执行
//顺序:自上而下
//属性资源文件绑定,根据属性配置文件的key获取value。
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");
static {
//注册驱动只需要注册一次,放在静态代码块中.DBUtil类加载的时候执行。
try {
//com.mysql.jdbc.Driver是连接数据库的驱动,不能写死,因为以后可能要连接Oracle数据库。
//如果直接写死,后续更改可能要修改java代码,显然违背OCP原则(对扩展开放,对修改关闭)。
//Class.forName("com.mysql.jdbc.Driver");
Class.forName(driver);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 获取数据库连接对象
* @return connection连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
//注册驱动只需要注册一次, 因此放在静态代码块中。
//获取连接
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
*释放资源
* @param connection 连接对象
* @param statement 数据库操作对象
* @param resultSet 结果集对象
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet){
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static void closeForStatement(Statement statement){
if (statement != null) {
try {
statement.close();
System.out.println("statement closed");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static void closeForConnection(Connection connection){
if (connection != null) {
try {
connection.close();
System.out.println("Connection closed");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static void closeForResultSet( ResultSet resultSet){
if (resultSet != null) {
try {
resultSet.close();
System.out.println("resultSet closed");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名称
user=用户名
password=密码
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
<title>银行账户转账</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
.container {
width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.form-group input[type="text"] {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.form-submit {
text-align: center;
}
.form-submit input[type="submit"] {
padding: 10px 20px;
font-size: 16px;
background-color: #4CAF50;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>银行账户转账</h1>
<form action="transfer" method="post">
<div class="form-group">
<label>转出账户:</label>
<input type="text" name="fromActno">
</div>
<div class="form-group">
<label>转入账户:</label>
<input type="text" name="toActno">
</div>
<div class="form-group">
<label>转账金额:</label>
<input type="text" name="money" pattern="[0-9]+" title="请输入数字">
</div>
<div class="form-submit">
<input type="submit" value="转账">
</div>
</form>
</div>
</body>
</html>
项目存在问题
分析代码: 1.负责数据的接收。 2.负责了核心业务的处理。 3.负责数据库表中数据的CRUD操作。 4.负责页面的展示。 5.负责异常处理。 缺点: 1.代码的复用性太差。 原因:没有进行组件分工,没有独立组件的概念。 2.耦合度太高,代码很难扩展。代码耦合度太高,无法实现复用。扩展力太差。 3.操作数据库的代码和业务逻辑代码混合在一起,编写业务代码的时候很容易出错。 4.代码还要负责页面的数据展示。 如何解决上述问题:使用MVC架构进行分层。 对应现实: 公司里面一般都有很多员工。每个员工各司其职,为什么? 进行明确的职能分工,就算某个人出现问题,也不会对公司造成重大影响。
MVC介绍
MVC(Model-View-Controller)是一种常用的软件架构模式,用于将应用程序的逻辑、数据和用户界面分离开来,以提高代码的可维护性和可扩展性。它将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。
- 模型(Model):模型负责管理应用程序的数据和业务逻辑。它包括数据结构、数据库操作、文件读写等与数据相关的功能。模型不关心数据是如何显示或如何被用户操作的,只负责对数据进行处理和管理。
- 视图(View):视图是应用程序的用户界面,负责数据的显示和呈现给用户。它可以是一个网页、一个窗口、一个控件等用户可以直接看到和操作的界面元素。视图从模型中获取数据,并将其呈现给用户。视图不包含任何业务逻辑,只负责数据的展示。
- 控制器(Controller):控制器是模型和视图之间的桥梁,负责接收用户的输入、处理用户请求,并更新模型和视图。它根据用户的操作调用相应的模型方法,获取数据并更新模型,然后通知视图进行更新,以反映最新的数据状态。控制器还可以处理用户的输入验证、权限控制等与应用程序逻辑相关的功能。
MVC架构模式的优点包括:
- 分离关注点:将数据、界面和逻辑分离,使得各个组件的职责清晰,易于开发和维护。
- 提高代码复用性:模型和视图可以独立于彼此存在,可以更轻松地进行修改、替换和重用。
- 支持并行开发:不同的开发人员可以同时工作在模型、视图和控制器上,加快开发效率。
- 支持多种界面表现形式:由于视图与模型解耦,可以方便地为应用程序添加多个不同的用户界面。
老杜-银行账户转账(mvc002)
项目目录:
具体代码:
AppException.java
package com.zwm.bank.exceptions;
public class AppException extends Exception {
public AppException() {
super();
}
public AppException(String message) {
super(message);
}
}
MonerNotEnoughException.java
package com.zwm.bank.exceptions;
public class MonerNotEnoughException extends Exception {
public MonerNotEnoughException() {
}
public MonerNotEnoughException(String message) {
super(message);
}
}
Account.java
package com.zwm.bank.mvc;
/**
* @author 猪无名
* @date 2023/8/14 14 26
* @version 1.0
* @since 1.0
* discription:账户实体类 ,封装账户信息。一般一张表对应一个实体类。
*/
public class Account {
//封装类里面的属性一般不使用基本数据类型,建议使用包装类,因为传过来的数值可能为null。
private Long id;//主键
private String actno;//账户
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 +
'}';
}
}
AccountDao.java
package com.zwm.bank.mvc;
import com.zwm.bank.utils.DBUtil;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class AccountDao {
/**
* 插入账户信息
* @param act
* @return 1表示插入成功
*/
public int insert(Account act){
Connection connection = null;
PreparedStatement preparedStatement = null;
int count = 0;
try {
connection = DBUtil.getConnection();
String sql = "insert into t_act(actno,balance) values(?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,preparedStatement,null);
}
return count;
}
/**
* 根据id(主键)删除账户信息
* @param id
* @return
*/
public int deleteById(Long id){
Connection connection = null;
PreparedStatement preparedStatement = null;
int count = 0;
try {
connection = DBUtil.getConnection();
String sql = "delete from t_act where id = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setLong(1,id);
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,preparedStatement,null);
}
return count;
}
/**
* 更新账户信息
* @param act
* @return
*/
public int update(Account act){//负责更新
Connection connection = null;
PreparedStatement preparedStatement = null;
int count = 0;
try {
connection = DBUtil.getConnection();
String sql = "update t_act set actno = ? ,balance = ? where id = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
preparedStatement.setLong(3,act.getId());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,preparedStatement,null);
}
return count;
}
/**
* 根据actno(账号)查询账户信息
* @param actno
* @return Account
*/
public Account selectByActno(String actno){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
Account act = null;
try {
connection = DBUtil.getConnection();
String sql = "select id,balance from t_act where actno = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,actno);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
long id = resultSet.getLong("id");
Double balance = resultSet.getDouble("balance");
//将结果集封装为一个对象。
act = new Account();
act.setId(id);
act.setActno(actno);
act.setBalance(balance);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,preparedStatement,resultSet);
}
return act;
}
/**
* 查询所有账户信息
* @return List
*/
public List<Account> selectAll(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Account> list = new ArrayList<>();
try {
connection = DBUtil.getConnection();
String sql = "select id,actno,balance from t_act ";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
//取出数据
long id = resultSet.getLong("id");
String actno = resultSet.getString("actno");
Double balance = resultSet.getDouble("balance");
//封装对象
Account act = new Account(id,actno,balance);
//加到List集合
list.add(act);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,preparedStatement,resultSet);
}
return list;
}
}
AccountService.java
package com.zwm.bank.mvc;
import com.zwm.bank.exceptions.AppException;
import com.zwm.bank.exceptions.MonerNotEnoughException;
/**
* @version 1.0
* @since 1.0
* discription:专门处理account业务的一个类。在这个类里面只专注业务,不写别的。命名为***Service,***Biz... ...
*/
public class AccountService {
private AccountDao accountDao = new AccountDao();
//这里的方法起名要体现业务。事务也应该在这进行控制,一般一个业务对应一个事务。
public void transfer(String fromActno, String toActno, double money) throws MonerNotEnoughException, AppException {
//1.根据账号,查询余额是否充足。
Account fromAct = accountDao.selectByActno(fromActno);
if(fromAct.getBalance()<money){
throw new MonerNotEnoughException("抱歉,余额不足。");
}
//2.程序到这一步,说明余额充足。
Account toAct = accountDao.selectByActno(toActno);
//3.修改余额(只修改内存中的java对象的余额)
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//4.更新数据库中的数据
int count = accountDao.update(fromAct);
count += accountDao.update(toAct);
if(count!=2){
throw new AppException("账户转账异常!!!");
}
}
}
AccountServlet.java
package com.zwm.bank.mvc;
import com.zwm.bank.exceptions.AppException;
import com.zwm.bank.exceptions.MonerNotEnoughException;
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;
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
AccountService accountService = new AccountService();
//之前在这个方法内做了很多的事情,现在思考简化一下。
//让它充当一个司令官(Controller),他负责调度其他组件(Model,View)来完成任务。
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//接收数据
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
Double money = Double.parseDouble(request.getParameter("money"));
//调用业务方法处理业务
try {
accountService.transfer(fromActno,toActno,money);
//程序运行到这里,代表转账成功。
response.sendRedirect(request.getContextPath()+"/success.jsp");
}catch (MonerNotEnoughException e){
//执行到这里失败,代表余额不足。
response.sendRedirect(request.getContextPath()+"/moneyNotEnough.jsp");
}catch (AppException e) {
//程序运行到这里,代表转账失败。
response.sendRedirect(request.getContextPath()+"/fail.jsp");
}
//展示处理结果
}
}
DBUtil.java
package com.zwm.bank.utils;
import java.sql.*;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;
/**
* discription:JDBC工具类
* @version 1.0
* @since 1.0
*/
public class DBUtil {
private static final 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 DBUtil(){}
//类加载时,注册驱动。
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**这里没有使用数据库连接池,直接创建连接对象。
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
* 关闭资源
* @param connection 连接对象
* @param statement 执行对象
* @param resultSet 结果集
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mvc
user=root
password=root
index.jsp
<%--
Created by IntelliJ IDEA.
User: 11933
Date: 2023/8/10
Time: 12:07
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
<title>银行账户转账</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
.container {
width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.form-group input[type="text"] {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.form-submit {
text-align: center;
}
.form-submit input[type="submit"] {
padding: 10px 20px;
font-size: 16px;
background-color: #4CAF50;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>银行账户转账</h1>
<form action="transfer" method="post">
<div class="form-group">
<label>转出账户:</label>
<input type="text" name="fromActno">
</div>
<div class="form-group">
<label>转入账户:</label>
<input type="text" name="toActno">
</div>
<div class="form-group">
<label>转账金额:</label>
<input type="text" name="money" pattern="[0-9]+" title="请输入数字">
</div>
<div class="form-submit">
<input type="submit" value="转账">
</div>
</form>
</div>
</body>
</html>
moneyNotEnough.jsp
<%--
Created by IntelliJ IDEA.
User: 11933
Date: 2023/8/14
Time: 17:32
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>转账失败</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
text-align: center;
padding-top: 50px;
}
.container {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
max-width: 400px;
margin: 0 auto;
padding: 30px;
}
h1 {
font-size: 24px;
color: #333;
}
p {
font-size: 18px;
color: #666;
margin-bottom: 20px;
}
.error-icon {
width: 80px;
height: 80px;
background-image: url('<%= request.getContextPath() %>/images/error-icon.png');
background-size: cover;
margin: 0 auto 20px;
}
.btn {
display: inline-block;
background-color: #F44336;
color: #fff;
padding: 10px 20px;
border-radius: 4px;
text-decoration: none;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #d32f2f;
}
</style>
</head>
<body>
<div class="container">
<div class="error-icon"></div>
<h1>转账失败</h1>
<p>余额不足。</p>
<a class="btn" href="index.jsp">返回</a>
</div>
</body>
</html>
success.jsp
<%--
Created by IntelliJ IDEA.
User: 11933
Date: 2023/8/14
Time: 17:16
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>转账成功</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
text-align: center;
padding-top: 50px;
}
.container {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
max-width: 400px;
margin: 0 auto;
padding: 30px;
}
h1 {
font-size: 24px;
color: #333;
}
p {
font-size: 18px;
color: #666;
margin-bottom: 20px;
}
.success-icon {
width: 80px;
height: 80px;
background-image: url('<%= request.getContextPath() %>/images/success-icon.png');
background-size: cover;
margin: 0 auto 20px;
}
.btn {
display: inline-block;
background-color: #4CAF50;
color: #fff;
padding: 10px 20px;
border-radius: 4px;
text-decoration: none;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<div class="success-icon"></div>
<h1>转账成功</h1>
<p>您的转账已成功完成。</p>
</div>
</body>
</html>
fail.jsp
<%--
Created by IntelliJ IDEA.
User: 11933
Date: 2023/8/14
Time: 17:32
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>转账失败</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
text-align: center;
padding-top: 50px;
}
.container {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
max-width: 400px;
margin: 0 auto;
padding: 30px;
}
h1 {
font-size: 24px;
color: #333;
}
p {
font-size: 18px;
color: #666;
margin-bottom: 20px;
}
.error-icon {
width: 80px;
height: 80px;
background-image: url('<%= request.getContextPath() %>/images/error-icon.png');
background-size: cover;
margin: 0 auto 20px;
}
.btn {
display: inline-block;
background-color: #F44336;
color: #fff;
padding: 10px 20px;
border-radius: 4px;
text-decoration: none;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #d32f2f;
}
</style>
</head>
<body>
<div class="container">
<div class="error-icon"></div>
<h1>转账失败</h1>
<p>很抱歉,您的转账未能成功。</p>
<a class="btn" href="index.jsp">返回</a>
</div>
</body>
</html>
MVC代码逻辑:
对应三层架构:
老杜-银行账户转账(mvc003)
解决mvc002事务不同步的问题。
修改代码:
AccountDao.java
package bank.mvc;
import bank.utils.DBUtil;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class AccountDao {
/**
* 插入账户信息
* @param act
* @return 1表示插入成功
*/
public int insert(Account act,Connection connection){
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "insert into t_act(actno,balance) values(?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 根据id(主键)删除账户信息
* @param id
* @return
*/
public int deleteById(Long id,Connection connection){
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "delete from t_act where id = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setLong(1,id);
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 更新账户信息
* @param act
* @return
*/
public int update(Account act,Connection connection){//负责更新
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "update t_act set actno = ? ,balance = ? where id = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
preparedStatement.setLong(3,act.getId());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 根据actno(账号)查询账户信息
* @param actno
* @return Account
*/
public Account selectByActno(String actno,Connection connection){
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
Account act = null;
try {
String sql = "select id,balance from t_act where actno = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,actno);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
long id = resultSet.getLong("id");
Double balance = resultSet.getDouble("balance");
//将结果集封装为一个对象。
act = new Account();
act.setId(id);
act.setActno(actno);
act.setBalance(balance);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,resultSet);
}
return act;
}
/**
* 查询所有账户信息
* @return List
*/
public List<Account> selectAll(Connection connection){
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Account> list = new ArrayList<>();
try {
String sql = "select id,actno,balance from t_act ";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
//取出数据
long id = resultSet.getLong("id");
String actno = resultSet.getString("actno");
Double balance = resultSet.getDouble("balance");
//封装对象
Account act = new Account(id,actno,balance);
//加到List集合
list.add(act);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,resultSet);
}
return list;
}
}
AccountService.java
package bank.mvc;
import bank.exceptions.AppException;
import bank.exceptions.MonerNotEnoughException;
import bank.utils.DBUtil;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @version 1.0
* @since 1.0
* discription:专门处理account业务的一个类。在这个类里面只专注业务,不写别的。命名为***Service,***Biz... ...
*/
public class AccountService {
private AccountDao accountDao = new AccountDao();
//这里的方法起名要体现业务。事务也应该在这进行控制,一般一个业务对应一个事务。
public void transfer(String fromActno, String toActno, double money)
throws MonerNotEnoughException, AppException {
//开启事务(需要获取Connection对象,这个操作很不正常,因为需要在业务层写JDBC的代码。但先这样写。)
try ( Connection connection = DBUtil.getConnection()){
//禁止自动提交
connection.setAutoCommit(false);
//1.根据账号,查询余额是否充足。
Account fromAct = accountDao.selectByActno(fromActno,connection);
if(fromAct.getBalance()<money){
throw new MonerNotEnoughException("抱歉,余额不足。");
}
//2.程序到这一步,说明余额充足。
Account toAct = accountDao.selectByActno(toActno,connection);
//3.修改余额(只修改内存中的java对象的余额)
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//4.更新数据库中的数据
int count = accountDao.update(fromAct,connection);
count += accountDao.update(toAct,connection);
if(count!=2){
throw new AppException("账户转账异常!!!");
}
//提交事务
connection.commit();
} catch (SQLException e) {
throw new AppException("账户转账异常!!!");
}
}
}
遗留问题:
通过传参可以实现Connection对象是同一个,但代码很丑陋,需要进一步修改。
老杜-银行账户转账(mvc004-ThreadLocal)
在一个类的方法里面调用另外一个类的方法,这种操作属于同一线程中的操作。
将Connection对象和线程对象绑定,放在一个Map集合里面,这样就可以实现Connection对象是同一个。
代码简单模拟ThreadLocal
package com.zwm.ThreadLocal;
public class Connection {
}
package com.zwm.ThreadLocal;
public class DBUtil {
//静态变量,类加载的时候执行,只执行一次。
private static MyThreadLocal<Connection> local = new MyThreadLocal<>(); //全局Map集合
public static Connection getConnection() {
//第一次调用get方法,一定是空的。所以在第一次的时候要进行绑定。
Connection connection = local.get();
//当第一次调用为空时,进行Connection对象的绑定。
if (connection == null) {
connection = new Connection();
local.set(connection);
}
return connection;
}
}
package com.zwm.ThreadLocal;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal<T> {
/**
* 所有需要和当前线程绑定的数据要放到这个容器当中。
*/
private Map<Thread,T> map = new HashMap<>();
/**
* 向ThreadLocal中绑定
*/
public void set(T obj){
map.put(Thread.currentThread(),obj);
}
/**
* 从ThreadLocal中获取
*/
public T get(){
return map.get(Thread.currentThread());
}
/**
* 删除ThreadLocal中的数据
*/
public void remove(){
map.remove(Thread.currentThread());
}
}
package com.zwm.ThreadLocal;
public class Test {
public static void main(String[] args) {
UserService userService = new UserService();
Thread thread = Thread.currentThread();
System.out.println(thread);
userService.save();
}
}
package com.zwm.ThreadLocal;
public class UserDao {
public void insert(){
Thread thread = Thread.currentThread();
System.out.println(thread);
Connection connection = DBUtil.getConnection();
System.out.println(connection);
System.out.println("User DAO insert");
}
}
package com.zwm.ThreadLocal;
public class UserService {
private UserDao userDao = new UserDao();
public void save(){
Thread thread = Thread.currentThread();
System.out.println(thread);
Connection connection = DBUtil.getConnection();
System.out.println(connection);
userDao.insert();
}
}
java.lang包下的ThreadLocal
ThreadLocal是Java中的一个线程局部变量,它为每个线程提供了一个独立的变量副本。在多线程环境中,每个线程可以独立地操作自己的ThreadLocal变量,而不会对其他线程产生影响。
ThreadLocal通常用于解决多线程并发访问共享变量时的线程安全问题。通过将变量声明为ThreadLocal类型,可以确保每个线程都拥有自己的变量副本,避免了线程间相互干扰的问题。
使用ThreadLocal时,每个线程通过调用ThreadLocal的get()方法获取自己的变量副本,并通过set()方法设置自己的变量值。这样,即使多个线程同时访问同一个ThreadLocal变量,它们之间的数据也是独立的,彼此不会相互干扰。
值得注意的是,当线程结束后,ThreadLocal变量的副本会被自动清除,从而避免了内存泄漏的问题。
ThreadLocal提供了一种简单而有效的方式来实现线程间的数据隔离,使得每个线程都可以独立地操作自己的变量,从而提高了多线程程序的性能和可靠性。
去除线程安全这些问题,可以将它看作一个Map集合,里面存储的key为当前线程,value为保存的对象。
具体修改部分代码:
DBUtil.java
package bank.utils;
import java.sql.*;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class DBUtil {
private static final 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 DBUtil(){}
//类加载时,注册驱动。
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
/**这里没有使用数据库连接池,而是直接创建连接对象。
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
Connection connection = threadLocal.get();
if(connection == null){
connection = DriverManager.getConnection(url, user, password);
threadLocal.set(connection);
}
return connection;
}
/**
* 关闭资源
* @param connection 连接对象
* @param statement 执行对象
* @param resultSet 结果集
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
//Tomcat服务器是支持线程池的,线程池的线程是复用的,所以需要把连接对象从线程池中移除。
threadLocal.remove();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
AccountService.java
package bank.mvc;
import bank.exceptions.AppException;
import bank.exceptions.MonerNotEnoughException;
import bank.utils.DBUtil;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @version 1.0
* @since 1.0
* discription:专门处理account业务的一个类。在这个类里面只专注业务,不写别的。命名为***Service,***Biz... ...
*/
public class AccountService {
private AccountDao accountDao = new AccountDao();
//这里的方法起名要体现业务。事务也应该在这进行控制,一般一个业务对应一个事务。
public void transfer(String fromActno, String toActno, double money)
throws MonerNotEnoughException, AppException {
//开启事务(需要获取Connection对象,这个操作很不正常,因为需要在业务层写JDBC的代码。但先这样写。)
try (Connection connection = DBUtil.getConnection()){
//禁止自动提交
connection.setAutoCommit(false);
//1.根据账号,查询余额是否充足。
Account fromAct = accountDao.selectByActno(fromActno);
if(fromAct.getBalance()<money){
throw new MonerNotEnoughException("抱歉,余额不足。");
}
//2.程序到这一步,说明余额充足。
Account toAct = accountDao.selectByActno(toActno);
//3.修改余额(只修改内存中的java对象的余额)
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//4.更新数据库中的数据
int count = accountDao.update(fromAct);
//模拟异常
//String s = null;
//s.toString();
count += accountDao.update(toAct);
if(count!=2){
throw new AppException("账户转账异常!!!");
}
//提交事务
connection.commit();
} catch (SQLException e) {
throw new AppException("账户转账异常!!!");
}
}
}
AccountDao.java
package bank.mvc;
import bank.utils.DBUtil;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @version 1.0
* @since 1.0
* discription:负责Account数据的增删改查
*/
public class AccountDao {
/**
* 插入账户信息
* @param act
* @return 1表示插入成功
*/
public int insert(Account act){
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "insert into t_act(actno,balance) values(?,?)";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 根据id(主键)删除账户信息
* @param id
* @return
*/
public int deleteById(Long id){
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "delete from t_act where id = ?";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setLong(1,id);
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 更新账户信息
* @param act
* @return
*/
public int update(Account act){//负责更新
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "update t_act set actno = ? ,balance = ? where id = ?";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
preparedStatement.setLong(3,act.getId());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 根据actno(账号)查询账户信息
* @param actno
* @return Account
*/
public Account selectByActno(String actno){
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
Account act = null;
try {
String sql = "select id,balance from t_act where actno = ?";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setString(1,actno);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
long id = resultSet.getLong("id");
Double balance = resultSet.getDouble("balance");
//将结果集封装为一个对象。
act = new Account();
act.setId(id);
act.setActno(actno);
act.setBalance(balance);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,resultSet);
}
return act;
}
/**
* 查询所有账户信息
* @return List
*/
public List<Account> selectAll(){
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Account> list = new ArrayList<>();
try {
String sql = "select id,actno,balance from t_act ";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
//取出数据
long id = resultSet.getLong("id");
String actno = resultSet.getString("actno");
Double balance = resultSet.getDouble("balance");
//封装对象
Account act = new Account(id,actno,balance);
//加到List集合
list.add(act);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,resultSet);
}
return list;
}
}
老杜-银行账户转账(mvc005+last-包的结构+接口)
在实际开发中,合理的包结构可以提高代码的可维护性和可读性,并且便于团队协作开发。
三层架构层与层之间交互是通过接口的。降低耦合度,提高扩展力。
项目结构:
具体代码:
AccountDao.java
package bank.dao;
import bank.pojo.Account;
import java.util.List;
public interface AccountDao {
int insert(Account act);
int deleteById(Long id);
int update(Account act);
Account selectByActno(String actno);
List<Account> selectAll();
}
AccountDaoImpl.java
package bank.dao.impl;
import bank.dao.AccountDao;
import bank.pojo.Account;
import bank.utils.DBUtil;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class AccountDaoImpl implements AccountDao {
/**
* 插入账户信息
* @param act
* @return 1表示插入成功
*/
public int insert(Account act){
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "insert into t_act(actno,balance) values(?,?)";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 根据id(主键)删除账户信息
* @param id
* @return
*/
public int deleteById(Long id){
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "delete from t_act where id = ?";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setLong(1,id);
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 更新账户信息
* @param act
* @return
*/
public int update(Account act){//负责更新
PreparedStatement preparedStatement = null;
int count = 0;
try {
String sql = "update t_act set actno = ? ,balance = ? where id = ?";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setString(1,act.getActno());
preparedStatement.setDouble(2,act.getBalance());
preparedStatement.setLong(3,act.getId());
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,null);
}
return count;
}
/**
* 根据actno(账号)查询账户信息
* @param actno
* @return Account
*/
public Account selectByActno(String actno){
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
Account act = null;
try {
String sql = "select id,balance from t_act where actno = ?";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
preparedStatement.setString(1,actno);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
long id = resultSet.getLong("id");
Double balance = resultSet.getDouble("balance");
//将结果集封装为一个对象。
act = new Account();
act.setId(id);
act.setActno(actno);
act.setBalance(balance);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,resultSet);
}
return act;
}
/**
* 查询所有账户信息
* @return List
*/
public List<Account> selectAll(){
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Account> list = new ArrayList<>();
try {
String sql = "select id,actno,balance from t_act ";
preparedStatement = DBUtil.getConnection().prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
//取出数据
long id = resultSet.getLong("id");
String actno = resultSet.getString("actno");
Double balance = resultSet.getDouble("balance");
//封装对象
Account act = new Account(id,actno,balance);
//加到List集合
list.add(act);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(null,preparedStatement,resultSet);
}
return list;
}
}
AccountService.java
package bank.service;
import bank.exceptions.AppException;
import bank.exceptions.MonerNotEnoughException;
public interface AccountService {
void transfer(String fromActno, String toActno, double money)
throws MonerNotEnoughException, AppException;
}
AccountServiceImpl.java
package bank.service.impl;
import bank.dao.AccountDao;
import bank.dao.impl.AccountDaoImpl;
import bank.exceptions.AppException;
import bank.exceptions.MonerNotEnoughException;
import bank.pojo.Account;
import bank.service.AccountService;
import bank.utils.DBUtil;
import java.sql.Connection;
import java.sql.SQLException;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();//多态
//这里的方法起名要体现业务。事务也应该在这进行控制,一般一个业务对应一个事务。
public void transfer(String fromActno, String toActno, double money)
throws MonerNotEnoughException, AppException {
//开启事务(需要获取Connection对象,这个操作很不正常,因为需要在业务层写JDBC的代码。但先这样写。)
try (Connection connection = DBUtil.getConnection()){
//禁止自动提交
connection.setAutoCommit(false);
//1.根据账号,查询余额是否充足。
Account fromAct = accountDao.selectByActno(fromActno);
if(fromAct.getBalance()<money){
throw new MonerNotEnoughException("抱歉,余额不足。");
}
//2.程序到这一步,说明余额充足。
Account toAct = accountDao.selectByActno(toActno);
//3.修改余额(只修改内存中的java对象的余额)
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//4.更新数据库中的数据
int count = accountDao.update(fromAct);
//模拟异常
//String s = null;
//s.toString();
count += accountDao.update(toAct);
if(count!=2){
throw new AppException("账户转账异常!!!");
}
//提交事务
connection.commit();
} catch (SQLException e) {
throw new AppException("账户转账异常!!!");
}
}
}
AccountServlet.java
package bank.web;
import bank.exceptions.AppException;
import bank.exceptions.MonerNotEnoughException;
import bank.service.AccountService;
import bank.service.impl.AccountServiceImpl;
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;
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
AccountService accountService = new AccountServiceImpl();
//之前在这个方法内做了很多的事情,现在思考简化一下。
//让它充当一个司令官(Controller),他负责调度其他组件(Model,View)来完成任务。
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//接收数据
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
Double money = Double.parseDouble(request.getParameter("money"));
//调用业务方法处理业务
try {
accountService.transfer(fromActno,toActno,money);
//程序运行到这里,代表转账成功。
response.sendRedirect(request.getContextPath()+"/success.jsp");
}catch (MonerNotEnoughException e){
//执行到这里失败,代表余额不足。
response.sendRedirect(request.getContextPath()+"/moneyNotEnough.jsp");
}catch (AppException e) {
//程序运行到这里,代表转账失败。
response.sendRedirect(request.getContextPath()+"/fail.jsp");
}
//展示处理结果
}
}
其余代码没有变化。
目前项目依然存在缺陷:
①:在service方法里面控制事务。(可以使用动态代理机制解决这个问题)
②:AccountService类中
private AccountDao accountDao = new AccountDaoImpl();
虽然我们使用了接口来衔接三层架构,但是在AccountService类中,依然使用了AccountDaoImpl这个类名。耦合度依然很高。
(Spring的Ioc容器解决这个问题)
知识点补充:
①:Double.parseDouble()
Double.parseDouble()
方法是Java中的一个静态方法,用于将字符串转换为double类型的数值。它接受一个表示数值的字符串作为参数,并返回对应的double值。
使用Double.parseDouble()
方法的基本语法如下:
double result = Double.parseDouble(str);
其中,str
是要转换的字符串,result
是转换后的double数值。
使用该方法时需要注意以下几点:
- 字符串必须表示合法的数字,否则将抛出
NumberFormatException
异常。 - 允许字符串中包含前导空格和尾随空格。
- 允许字符串中使用正负号('+'或'-')表示正负数。
- 允许字符串中使用科学计数法表示数值,例如"1.23e+10"。
- 如果字符串为空或为null,将抛出
NullPointerException
异常。
示例:
String str = "3.14";
double result = Double.parseDouble(str);
System.out.println(result); // 输出: 3.14
如果要将其他类型的字符串转换为double类型,可以先将其转换为合法的数值格式,然后再使用Double.parseDouble()
进行转换。
②:pattern
要给<input type="text" name="money">
文本框添加限制条件,使其只能输入数字,可以使用HTML5中的pattern
属性结合正则表达式来实现。以下是修改后的代码:
<div class="form-group">
<label>转账金额:</label>
<input type="text" name="money" pattern="[0-9]+" title="请输入数字">
</div>
在上述代码中,我添加了pattern="[0-9]+"
属性到<input>
标签中,表示只允许输入数字。[0-9]+
是一个正则表达式,表示1个或多个数字。
另外,我还添加了title="请输入数字"
属性,用于显示在鼠标悬停时的提示信息,提醒用户只能输入数字。
通过以上修改,当用户在转账金额文本框中输入非数字字符时,将无法提交表单,并且会显示提示信息。
请注意,虽然在前端通过正则表达式限制用户输入的内容,但为了确保数据的安全性和正确性,后端服务器也应该对接收到的数据进行验证和处理。
③:DAO
DAO(Data Access Object)是一种设计模式(JavaEE设计模式),用于在应用程序和数据库之间提供数据访问的接口。在Java Web开发中,DAO层通常用于处理与数据库交互的操作,没有任何的业务逻辑。
一般一张表对应一个dao。
④:POJO/JavaBean/domain/领域模型对象(叫法不同,代表一个东西)
POJO(Plain Old Java Object)指的是一个普通的Java对象,它不依赖于任何特定的框架或库,并且遵循JavaBean的标准。POJO对象通常用于封装数据,无业务逻辑,并且易于编写和理解。
一个典型的POJO对象包含以下特征:
- 私有字段:使用private关键字声明的成员变量。
- 公共访问方法:每个字段都有对应的getter和setter方法。
- 无参构造方法:用于创建对象的默认构造方法。
- 有参构造方法:用于初始化对象并设置字段值的构造方法。
- 可选的重写方法:例如toString、equals和hashCode等。
下面是一个示例POJO对象的代码:
public class Person {
private String name;
private int age;
public Person() {
// 无参构造方法
}
public Person(String name, int age) {
// 有参构造方法
this.name = name;
this.age = age;
}
// getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 可选重写方法
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
上述代码定义了一个简单的Person类作为POJO对象,包含了name和age两个私有字段,以及对应的getter和setter方法。这个类没有任何与框架或库相关的依赖,可以直接在Java应用程序中使用。
⑤:SSM(下一步要学习的)
SSM 是指 Spring + Spring MVC + MyBatis,是一种常用的 Java Web 应用开发框架。
Spring 是一个轻量级的 Java 开发框架,提供了依赖注入(DI)和面向切面编程(AOP)等功能,简化了企业级应用的开发。
Spring MVC 是 Spring 框架的一部分,是一个基于 MVC(Model-View-Controller)设计模式的 Web 开发框架,用于构建灵活、高性能的 Web 应用程序。
MyBatis 是一个优秀的持久层框架,它通过 XML 或注解的方式将数据库操作和 Java 对象映射起来,方便快捷地实现数据持久化操作。
在 SSM 中,Spring 负责整个应用的框架搭建和管理,提供了一系列的容器和扩展点;Spring MVC 主要负责处理客户端请求和返回响应,完成 Web 层面的处理;MyBatis 则负责与数据库的交互,执行 SQL 语句,将查询结果映射为 Java 对象。
SSM 框架结合了 Spring 的依赖注入和面向切面编程的特性,使得应用开发更加简化和灵活;同时,采用了 MVC 设计模式,提供了良好的分层架构,促进了项目的结构清晰和代码的可维护性;MyBatis 则通过简单易用的方式,将数据库操作与 Java 对象的映射完成,提高了开发效率。