在WEB项目中使用MyBatis(以银行转账业务为例)

  • 使用maven新建项目,在pom文件中将打包方式改为war,并重载(reload)项目。在pom文件中引入相关依赖

  • 编写mybatis配置文件及logback配置文件:mybatis-config.xml,XxxMapper.xml,logback.xml

  • 右键项目,找到openMouduleSettings。双击红色路径,这一步是在项目中引入web框架

  • 在webapp目录下新建一个html文件(当作欢迎页使用),编写前端代码。项目逻辑:从前往后

  • 编写后端代码

  • 采用三层架构

  • 建包:web(表现层)、service(业务层)、dao(可持久层)、util(工具包)、exceptions(异常包)、pojo(javaBean)

  • 在web包下创建Servlet类,接收前端用户提交的数据,并调用相关业务方法。于此同时,编写pojo类

    • package com.deng.bank.web;
      
      import com.deng.bank.exceptions.moneyNotEnoughException;
      import com.deng.bank.exceptions.transferException;
      import com.deng.bank.service.AccountService;
      import com.deng.bank.service.impl.AccountServiceIm;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      @WebServlet("/transfer")
      public class AccountServlet extends HttpServlet {
          public void doPost(HttpServletRequest request, HttpServletResponse response)
              throws ServletException, IOException {
              //get data from page
              String fromActno = request.getParameter("fromActno");
              String toActno = request.getParameter("toActno");
              Double money = Double.parseDouble(request.getParameter("money"));
      
              AccountService accountService = new AccountServiceIm();
              try {
                  accountService.transfer(fromActno,toActno,money);
                  response.sendRedirect(request.getContextPath() + "/Success.html");
              } catch (moneyNotEnoughException e) {
                  response.sendRedirect(request.getContextPath() + "/Error1.html");
              } catch (transferException e) {
                  response.sendRedirect(request.getContextPath() + "/Error2.html");
              }catch(Exception e){
                  response.sendRedirect(request.getContextPath() + "/Error2.html");
              }
          }
      }
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45

      * 层与层之间通过接口交互。在service包和dao包下建立接口,并创建相应的子类实现该接口

      * ![](../../../笔记/MyBatis/9.在WEB中应用MyBatis.assets/image-20231109104834245.png)

      * 业务层调用dao层接口获取数据库连接,进行相应业务逻辑代码的编写

      * ```java
      package com.deng.bank.service.impl;

      import com.deng.bank.dao.AccountDao;
      import com.deng.bank.dao.impl.AccountDaoIm;
      import com.deng.bank.exceptions.moneyNotEnoughException;
      import com.deng.bank.exceptions.transferException;
      import com.deng.bank.pojo.Account;
      import com.deng.bank.service.AccountService;
      import com.deng.bank.utils.SqlSessionUtil;
      import org.apache.ibatis.session.SqlSession;

      public class AccountServiceIm implements AccountService {
      private AccountDao accountDao = new AccountDaoIm();

      public void transfer(String fromActno,String toActno,Double money) throws moneyNotEnoughException, transferException {
      SqlSession sqlSession = SqlSessionUtil.getSqlSession();
      Account fromAct = accountDao.select(fromActno);
      if(fromAct.getBalance() < money){
      throw new moneyNotEnoughException("余额不足");
      }else{
      Account toAct = (Account)accountDao.select(toActno);
      fromAct.setMoney(fromAct.getBalance() - money);
      toAct.setMoney(toAct.getBalance() + money);
      int count = accountDao.update(fromAct);
      String str = null;
      str.toString();
      count += accountDao.update(toAct);
      if(count != 2){
      throw new transferException("转账失败,未知错误");
      }

      sqlSession.commit();
      }
      SqlSessionUtil.close();
      }
      }

  • 如果我们在dao层进行事务管理,那么当业务层25-26行出现类似情况的异常时,本来应该终止转账,但由于我们将事务设置在dao层,那么fromAct账户的金额会发生改变,而toAct账户的金额却不会改变,相当于有一笔钱不翼而飞,这对银行来说是致命的错误。因此我们需要将事务管理设置在业务层。我们之前的做法是:每执行一次数据库CRUD操作就需要创建一个SqlSession对象,这显然无法做到原子性。

  • 因此我们引入ThreadLocal对象,将SqlSession对象和当前线程绑定,从而实现事务的原子性

    • package com.deng.bank.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 SqlSessionUtil {
          private static SqlSessionFactory sqlSessionFactory;
      
          static{
              try {
                  sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
      
          public static SqlSession getSqlSession(){
              SqlSession sqlSession = local.get();
              if(sqlSession == null){
                  sqlSessionFactory.openSession();
              }
              return sqlSession;
          }
      
          public static void close(){
              SqlSession sqlSession = local.get();
              if (sqlSession!=null) {
                  sqlSession.close();
                  local.remove();
              }
          }
      }