一个简单的转账Servlet Demo
使用MVC三层架构实现
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#mainApp{
font-size: 20px;
font-family: "Microsoft YaHei UI",serif;
text-align: center;
margin-top: 200px;
font-weight: bold;
}
</style>
</head>
<body>
<div id="mainApp">
<form action="servlet/transfer" method="post">
<p><label for="transOut">请填写转出用户名:</label><input type="text" id="transOut" name="transOut" required /></p>
<p><label for="transIn">请填写转入用户名:</label><input type="text" id="transIn" name="transIn" required /></p>
<p><label for="money">请填写转账数目:</label><input type="text" id="money" name="money" required /></p>
<p><input type="submit"><input type="reset"></p>
</form>
</div>
</body>
</html>
后端
数据库工具类保证数据库调用的统一
public class C3P0Utils {
/**
* 获取连接池
* @return 返回c3p0默认连接池
*/
public static DataSource getDataSource(){
return new ComboPooledDataSource();
}
/**
* 获取连接
* @return 返回一个基于c3p0连接池的连接
*/
public static Connection getConnection(){
try {
return getDataSource().getConnection();
} catch (SQLException e){
throw new RuntimeException("无法获取连接,请检查数据库配置文件");
}
}
/**
* 实现资源的释放
* 细节在于首先是对于顺序的先开后关
* 对于每个对象都要有try...catch保证哪怕报错了其他的对象也可以关闭
* @param connection 数据库连接
* @param ps 预编译sql对象
* @param resultSet 数据库结果集
*/
public static void release(Connection connection, PreparedStatement ps, ResultSet resultSet){
try {
if (resultSet != null){
resultSet.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (ps != null){
ps.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (connection != null){
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
model用于封装数据库对象
public class User {
private String name;
private BigDecimal money;
public User() {
}
public User(String name, BigDecimal money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getMoney() {
return money;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
}
Dao层用于操作数据库
/**
* 此接口规定对User数据库的操作
* @author Rainful
* @create 2021/07/29
*/
public interface UserDao {
/**
* 通过name查询用户
* @param connection 数据库连接
* @param ps 预编译sql对象
* @param sql sql语句
* @param name 用户名
* @param money 钱数
* @throws SQLException 抛出一个查询错误让业务代码回滚
* @return 返回一个可以查找到的用户
*/
int moneyTransfer(Connection connection, PreparedStatement ps,
String sql, String name, double money) throws SQLException;
}
public class UserDaoImpl implements UserDao {
@Override
public int moneyTransfer(Connection connection, PreparedStatement ps,
String sql, String name, double money) throws SQLException {
ps = connection.prepareStatement(sql);
ps.setDouble(1, money);
ps.setString(2, name);
return ps.executeUpdate();
}
}
业务层用于调用Dao层验证从控制层传来的参数等
/**
* 此接口用于规范数据库查询
* @author Rainful
* @create 2021/07/29
*/
public interface UserServlet {
/**
* 业务层调用dao层完成数据库更新及控制事务
* @param name1 转出账户用户名
* @param name2 转入账户用户名
* @param money 修改
* @return 修改结果
*/
boolean moneyTransfer(String name1, String name2, double money);
}
public class UserServletImpl implements UserServlet {
private final UserDao userDao;
public UserServletImpl() {
this.userDao = new UserDaoImpl();
}
@Override
public boolean moneyTransfer(String name1, String name2, double money) {
Connection connection = null;
PreparedStatement ps = null;
try {
connection = C3P0Utils.getConnection();
// 开启事务
connection.setAutoCommit(false);
// 转出账户
int transOutRow = transfer(connection, ps, name1, money, -1);
// 转入账户
int transInRow = transfer(connection, ps, name2, money, 1);
// 提交事务
connection.commit();
return transOutRow > 0 && transInRow > 0;
} catch (Exception e) {
// 发生异常进行回滚处理
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
return false;
} finally {
// 关闭数据库连接
try {
connection.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
C3P0Utils.release(connection, ps, null);
}
}
/**
* 返回转账sql的影响行数
*
* @param connection 数据库连接
* @param ps 预编译sql对象
* @param name 账户更改姓名
* @param money 更改的钱数
* @param value 为了保证方法共用性而设置的修改参数
* 转入为 1, 转出为 -1
* @return 返回影响的行数
* @throws SQLException 抛出sql异常回滚
*/
private int transfer(Connection connection, PreparedStatement ps, String name, double money, int value) throws SQLException {
// 转出账户的话因为钱是减少的
String sql = "update account set money = money + ? where name = ?";
return userDao.moneyTransfer(connection, ps, sql, name, money * value);
}
}
控制层用于接收前端数据传输给业务层做逻辑判断
@WebServlet("/servlet/transfer")
public class TransferMoney extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String transOut = req.getParameter("transOut");
String transIn = req.getParameter("transIn");
String money = req.getParameter("money");
// 三者都在前端进行了非空判断
// 后续如果再加上判断就好了,因为要防止前端被人恶意修改传输数据过来
long moneyNum;
System.out.println(money);
//System.out.println(Long.parseLong(money));
try {
moneyNum = Long.parseLong(money);
} catch (Exception e){
resp.getWriter().print("金额不符合规范");
return;
}
// 调用业务层进行处理
UserServlet userServlet = new UserServletImpl();
boolean flag = userServlet.moneyTransfer(transOut, transIn, moneyNum);
if (flag){
resp.getWriter().print("转账成功");
} else {
resp.getWriter().print("转账失败");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
其他
-
编码转换过滤
@WebFilter("/servlet/*") public class CodeChange implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); chain.doFilter(request, response); } }
-
权限管理过滤防止恶意直接访问servlet
@WebFilter("/servlet/*") public class FilterServlet implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String money = request.getParameter("money"); if (money == null){ ((HttpServletResponse)response).sendRedirect("../index.html"); } chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } }
总结
-
bug反思
- 整体来说一次性写完,但是在数据库调用的时候发现数据库没有修改,经过单元测试排除方法最后发现调用sql的时候参数传递问题
-
整个servlet优点
- 完成了全部功能,实现了sql调用的时候connection复用,对一次业务进行connection的统一关闭
- sql调用的时候进行参数传递到dao层可以一个方法完成增加和减少
- 实现了MVC三层架构,并且使用接口实现多态,并且规范了实现类的行为
- 实现了编码转换及权限过滤
-
优化方向
- 后续增加业务的时候,可以抽取sql代码完成sqlUtils类的封装规范Dao层的sql调用
- 增加常用变量的时候可以进行一个静态变量工具类的封装等