一、账户转账小项目
1.项目结构:
![](https://i-blog.csdnimg.cn/blog_migrate/e5193a2ac79cbf5c4772ad1c0be488a0.png)
2.简单介绍一下包:
- dao 持久化层(专门负责数据表的CRUD,没有任何业务上的操作)
- impl 接口实现
- exception 异常类
- pojo 对象
- service 业务逻辑层
- util 工具
- web 表示层(页面)
3.介绍相关类(从前端往后端走)
1.transfer.html 转账界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账界面</title>
</head>
<body>
<form action="/mybatis_project_war_exploded/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>
2.web.xml 进行界面跳转文件配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app metadata-complete="true"
version="4.0"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee">
<welcome-file-list>
<welcome-file>transfer.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>transfer</servlet-name>
<servlet-class>com.jiang.web.AccountServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>transfer</servlet-name>
<url-pattern>/transfer</url-pattern>
</servlet-mapping>
</web-a
3.AccountServlet类 接收数据并进行处理 (调用service层)
- servlet不处理业务,只调用相关层
- 层与层之间要用接口去连接---面向接口编程
package com.jiang.web;
import com.jiang.exceptions.MoneyNotEnoughException;
import com.jiang.exceptions.TransferException;
import com.jiang.service.AccountService;
import com.jiang.service.impl.AccountServiceImpl;
import org.apache.ibatis.javassist.CannotCompileException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AccountServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String fromActno = req.getParameter("fromActno");
String toActno = req.getParameter("toActno");
String m = req.getParameter("money");
double money = Double.parseDouble(m);
//1.servlet不处理业务的,
// 2.此处调用service的转账方法进行转账
//3.表示层调业务逻辑层
//4.层与层之间要用接口去连接(面向接口编程)
AccountService accountService=new AccountServiceImpl();
try {
accountService.transfer(fromActno,toActno,money);
//5.调用视图层,成功就跳转成功界面
resp.sendRedirect(req.getContextPath()+"/success.html");
} catch (MoneyNotEnoughException e) {
resp.sendRedirect(req.getContextPath()+"/moneynotenough.html");
} catch (TransferException e) {
resp.sendRedirect(req.getContextPath()+"/transfererror.html");
} catch (NullPointerException e) {
resp.sendRedirect(req.getContextPath() + "/transfererror.html");
} catch (CannotCompileException e) {
e.printStackTrace();
}
}
}
4. AccountService接口(实现层与层连接)
- 接口方法不需要public修饰(多余的)
package com.jiang.service;
import com.jiang.exceptions.MoneyNotEnoughException;
import com.jiang.exceptions.TransferException;
import org.apache.ibatis.javassist.CannotCompileException;
//账户业务类
public interface AccountService {
//1.修饰符“public”对于接口方法是多余的
public void transfer(String fromActno,String toActno,double money) throws MoneyNotEnoughException, TransferException, CannotCompileException;
}
5.AccountServiceImpl 实现类(存在部分后面学习的内容先不讲---即mybatis自动生成dao的方法)
package com.jiang.service.impl;
import com.jiang.dao.AccountDao;
import com.jiang.dao.impl.AccountDaoImpl;
import com.jiang.exceptions.MoneyNotEnoughException;
import com.jiang.exceptions.TransferException;
import com.jiang.pojo.Account;
import com.jiang.service.AccountService;
import com.jiang.utils.GenerateDaoProxy;
import com.jiang.utils.MybatisTool;
import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
@Override
//这部分是前端接受的数据(表示层传来的数据给业务逻辑层)
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException, CannotCompileException {
//我们自己的
//AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(MybatisTool.getsqlsession(), AccountDao.class);
//mybatis实现的代理机制
//AccountDao accountDao = MybatisTool.getsqlsession().getMapper(AccountDao.class);
//事务控制部分:
SqlSession sqlSession=MybatisTool.getsqlsession();
//1.判断转出账户余额是否充足(select)
AccountDaoImpl accountDao = new AccountDaoImpl();
//测试GenerateDaoProxy
//Object o = GenerateDaoProxy.generate(sql, AccountDao.class);
Account fromaccount = accountDao.selectByActno(fromActno);
Account toaccount = accountDao.selectByActno(toActno);
Double fromActnoMoney = fromaccount.getbalance();
Double toaccountMoney = toaccount.getbalance();
//2.如果充足,则转账成功,
//3.不充足,则转账失败
if (fromActnoMoney<money) {
//不充足
//子类不能抛比父类更宽泛的异常,
//这里会自动给父接口抛异常
//不能try,catch 自己创造异常自己抓?
throw new MoneyNotEnoughException("钱不够!");
}else {
//充足
fromaccount.setbalance(fromActnoMoney-money);
toaccount.setbalance(toaccountMoney+money);
int count = accountDao.updateActno(fromaccount);
//模拟中间异常,查看是否会少钱
//String string=null;
//string.toString();
//结果:少钱 --- 事务控制没有控制好。
count += accountDao.updateActno(toaccount);
if (count!=2) {
throw new TransferException("转账失败");
}
//事务的提交与关闭 首先是不好使得,因为事务不一致
//解决方法: ThreadLocal绑定一个sqlsession
sqlSession.commit();
//sql.close(); 此处就调用工具类的关闭
MybatisTool.close(sqlSession);
}
}
}
其中调用了工具类、持久层(数据库操作)、相关异常类,下面来介绍一下
6.mybatis 工具类(获取sqlsession,后期会有优化涉及到事务管理)
package Tool;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
public class MybatisTool {
private MybatisTool() {
}
public static SqlSessionFactory sqlSessionFactory;
static {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
try {
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatisconfig.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getsqlsession() {
return sqlSessionFactory.openSession();
}
}
7.dao层(CRUD)
package com.jiang.dao;
import com.jiang.pojo.Account;
public interface AccountDao {
Account selectByActno(String actno);
int updateActno(Account account);
}
其实现类:
package com.jiang.dao.impl;
import com.jiang.dao.AccountDao;
import com.jiang.pojo.Account;
import com.jiang.utils.MybatisTool;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = MybatisTool.getsqlsession();
Object o = sqlSession.selectOne("account.selectone", actno);
Account account=(Account) o;
return account;
}
@Override
public int updateActno(Account account) {
SqlSession sqlSession = MybatisTool.getsqlsession();
int count = sqlSession.update("account.updateone", account);
return count;
}
}
8. MoneyNotEnoughException和 TransferException 异常类
package com.jiang.exceptions;
public class MoneyNotEnoughException extends Exception{
public MoneyNotEnoughException(){}
public MoneyNotEnoughException(String msg){
super(msg);
}
}
package com.jiang.exceptions;
public class TransferException extends Exception{
public TransferException(){}
public TransferException(String msg){super(msg);}
}
自己不清楚的知识点:
- double money = Double.parseDouble(m); 转化为double类型
但是以上会出现一个问题:
(AccountServiceImpl)当两者进行更新时,中间出现异常的话,会少钱,者就涉及到了一个事务控制管理
int count = accountDao.updateActno(fromaccount);
//模拟中间异常,查看是否会少钱
String string=null;
string.toString();
//结果:少钱 --- 事务控制没有控制好。
count += accountDao.updateActno(toaccount);
两者的事务不一致,所以会出现问题:
怎样解决??? --- ThreadLocal
9.MybatisTool更新
将一个sqlsession存入ThreaLocal中,从中取。
package com.jiang.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
public class MybatisTool {
private MybatisTool() {
}
public static SqlSessionFactory sqlSessionFactory;
static {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
try {
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("Config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
//全局的服务器级别的 一个服务器当中只能定义一个
//第二点:只要调用了openSqlSession方法就会创建一个新的sqlsession对象 不安全
//怎样安全,就是放在local里面,每个地方都可以用
//生命周期:
//sqlsessionbuilder 为sqlsession而生 创完就无
//sqlsessionfactory 一直运行 不用重复创建多次
//sqlsession 一个线程一个sqlsession
private static ThreadLocal<SqlSession> local=new ThreadLocal<>();
public static SqlSession getsqlsession() {
SqlSession sqlSession = local.get();
if (sqlSession==null) {
sqlSession = sqlSessionFactory.openSession();
//第一次,得将其绑定到该线程上
local.set(sqlSession);
}
return sqlSession;
}
//关闭sqlsession对象,并从当前线程移除sqlsession
public static void close(SqlSession sqlSession){
if (sqlSession!=null) {
sqlSession.close();
local.remove();
//因为tomcat是支持线程池的,如果当先t1线程没有移除的话,下次还会使用该线程
}
}
}