【MyBatis】| 在WEB中应⽤MyBatis(使⽤MVC架构模式)

目录

一:在WEB中应⽤MyBatis(使⽤MVC架构模式)

1. 前期准备

2. 核心代码实现

3. 事务控制

4. 三大对象的作用域


一:在WEB中应⽤MyBatis(使⽤MVC架构模式)

目标:

①掌握mybatis在web应⽤中怎么⽤

②mybatis三⼤对象的作⽤域和⽣命周期

③ThreadLocal原理及使⽤

④巩固MVC架构模式

⑤为学习MyBatis的接⼝代理机制做准备

实现功能:银⾏账户转账

使⽤技术: HTML + Servlet + MyBatis

WEB应⽤的名称: bank

1. 前期准备

(1)数据库表准备

设计表结构

 插入数据

 (2)在Web应用中使用Mybatis搭建环境,创建一个Web应用,并添加本地的Tomcat服务器,然后在进行资源的配置

①在pom.xml文件中引入依赖





  4.0.0
  
  mybatis-004-web
  com.bjpowernode
  mybatis-004-web
  1.0-SNAPSHOT
  war

  
    UTF-8
    1.8
    1.8
  

  
    
    
      org.mybatis
      mybatis
      3.5.10
    
    
    
      mysql
      mysql-connector-java
      5.1.23
    
    
    
      ch.qos.logback
      logback-classic
      1.2.11
    
    
    
      javax.servlet
      javax.servlet-api
      3.1.0
    
  


②一些配置文件的引入

【MyBatis】| 在WEB中应⽤MyBatis(使⽤MVC架构模式)_第1张图片

③编写前端转账页面index.html




    
    银行账户转账


    
转出账号:
转入账号:
转账余额:

④使用MVC架构模式创建包

解释:dao是连接数据库、pojo是普通的java类、service是编写业务、utils是工具类、web是servlet

(1)按照三层架构分析:web是表示层、service是业务层、dao是持久化层。
(2)按照MVC架构模式分析:pojo、dao、service属于M、所有的.jsp属于V、web属于C。(3)MVC架构模式和三层的关系:M是包括业务层和持久层,V和C属于表示层。

pojo类,封装账户信息

package com.bjpowernode.bank.pojo;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.pojo
 * @Project:mybatis
 * @name:Account
 * @Date:2023/1/1 17:53
 */
public class Account {
    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;
    }
    // setter and getter
    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;
    }
    // 重写toString
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
}

 工具类,连接数据库的类

package com.bjpowernode.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 SqlSessionUtils {
    public SqlSessionUtils() {
    }
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

}

2. 核心代码实现

细节:在web.xml文件中有一个配置,metadata-complete="true">表示只支持配置文件的形式,不支持注解的形式开发;等于false表示两个都支持!

主要框架形式和所需要的展示页面

①bao包:连接数据库的操作,面向接口编程,接口和对应的额实现类

②exception包:抛出异常,一个是余额不足抛出的异常,一个是转账过程出现的异常

③pojo包:封装数据的,普通的java类

④service包:编写业务逻辑代码的,面向接口编程,接口和对应的额实现类

⑤utils包:连接数据库的代码封装

⑥web包:编写servlet,是司令官,进行调度的

【MyBatis】| 在WEB中应⽤MyBatis(使⽤MVC架构模式)_第2张图片

①index.html是前端提交数据的页面,输入转账信息的

②money-not-enough.html是转出余额不足,所展示结果的页面

③success.html是转账成功的页面

④transfer-error.html是转账出现问题的页面

【MyBatis】| 在WEB中应⽤MyBatis(使⽤MVC架构模式)_第3张图片

①AccountMapper.xml:专门编写sql语句的

②jdbc.properties:连接数据库所需要的配置信息

③logbaxk.xml:日志配置信息

④mybatis-config.xml:引入日志、读取连接数据库配置文件、连接数据库、关联AccountMapper.xml配置文件等信息的核心配置文件

【MyBatis】| 在WEB中应⽤MyBatis(使⽤MVC架构模式)_第4张图片

(1)编写servlet,根据前端发送的请求,编写AccountServlet,这个Servlet相当于司令官C,调度两个秘书M和V

package com.bjpowernode.bank.web;

import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.service.AccountService;
import com.bjpowernode.bank.service.impl.AccountServiceImpl;

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;
import java.io.PrintWriter;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.web
 * @Project:mybatis
 * @name:AccountServlet
 * @Date:2023/1/1 19:23
 */
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    // 编写业务逻辑的类,声明为成员变量,其它方法也能调用
    private AccountService accountService = new AccountServiceImpl();

    @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"));
        // AccountService accountService = new AccountServiceImpl(); // 面向接口编程
        try {
            // 调用service的转账方法完成转账(调度业务层-M)
            accountService.transfer(fromActno,toActno,money);
            // 调用View完成展示结果
            // 转账成功,跳转页面
            response.sendRedirect(request.getContextPath()+"/success.html");
        } catch (MoenyNotEnoughException e) {
            // 钱不够,跳转页面
            response.sendRedirect(request.getContextPath()+"/money-not-enough.html");
        } catch (TransferException e) {
            // 转账失败,跳转页面
            response.sendRedirect(request.getContextPath()+"/transfer-error.html");
        }

    }
}

 余额不足money-not-enough.html页面




    
    余额不足


    

余额不足

 转账成功success.html页面




    
    转账成功


    

转账成功

转账失败transfer-error.html页面




    
    转账失败


    

转账失败

(2)上面逻辑发现是司令官Servlet调用业务逻辑代码(M)完成转账操作,所以需要编写AccountService接口和AccountServiceImpl实现类

AccountService接口

package com.bjpowernode.bank.service;

import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;

public interface AccountService {
    /**
     * 账户转账
     * @param fromActno 转账账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    void transfer(String fromActno,String toActno,double money) throws MoenyNotEnoughException, TransferException;
}

AccountServiceImpl实现类

package com.bjpowernode.bank.service.impl;

import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.bao.impl.AccountDaoImpl;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.service.AccountService;

/** 编写业务逻辑代码
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.service.impl
 * @Project:mybatis
 * @name:AccountServiceImpl
 * @Date:2023/1/1 19:40
 */
public class AccountServiceImpl implements AccountService {---
   // 修改数据的类
   private AccountDao accountDao = new AccountDaoImpl();
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoenyNotEnoughException, TransferException {
        // 判断转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            // 如果转出账户余额不足,抛出异常提示用户
            throw new MoenyNotEnoughException("对不起,余额不足");
        }
        // 如果余额充足,更新转出和转入账户余额
        Account toAct = accountDao.selectByActno(toActno);
        // 修改内存中的余额
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance(toAct.getBalance()+money);
        // 修改数据库当中余额
        int count = accountDao.updateByActno(fromAct);
        count += accountDao.updateByActno(toAct);
        if (count != 2) {
            // 转账异常
            throw new TransferException("转账异常");
        }
    }
}

 余额不足抛出异常MoenyNotEnoughException

package com.bjpowernode.bank.exception;

public class MoenyNotEnoughException extends Exception{
    // 两个构造方法
    public MoenyNotEnoughException() {
    }
    public MoenyNotEnoughException(String message) {
        super(message);
    }
}

 转账失败抛出异常TransferException

package com.bjpowernode.bank.exception;

public class TransferException extends Exception{
    public TransferException() {
    }
    public TransferException(String message) {
        super(message);
    }
}

(3)在编写业务逻辑发现又需要数据库的操作,这就需要一个类专门连接数据库,专门处理数据的操作;面向接口编程,编写AccountDao接口和AccountDaoImpl类

AccountDao接口

package com.bjpowernode.bank.bao;

import com.bjpowernode.bank.pojo.Account;

/**
 * 负责对表中数据进行CRUD
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.bao
 * @Project:mybatis
 * @name:AccountDao
 * @Date:2023/1/1 19:48
 */
public interface AccountDao {
    /**
     * 根据账号查询账户信息
     * @param actno
     * @return 返回的是账户信息
     */
   Account selectByActno(String actno);

    /**
     * 更新账户信息
     * @param act
     * @return 1表示更新成功
     */
   int updateByActno(Account act);
}

AccountDaoImpl类

package com.bjpowernode.bank.bao.impl;

import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.bao.impl
 * @Project:mybatis
 * @name:AccountDaoImpl
 * @Date:2023/1/1 19:57
 */
public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        sqlSession.close();
        return account;
    }

    @Override
    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
        int count = sqlSession.update("account.updateByActno", act);
        sqlSession.commit();
        sqlSession.close();
        return count;
    }
}

编写sql语句的AccountMapper.xml






    

    
        update t_act set balance = #{balance} where actno = #{actno};
    

连接数据库的配置文件jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123

核心配置文件mybatis-config.xml




    
    
    
    
        
    
    
        
        
            
            
                
                
                
                
            
        
    
    
        
        
    

3. 事务控制

注:当前的代码没有事务控制,如果在第一条更新语句之后,第二条更新语句之前增加一个空指针异常,那么就会出现钱丢失的情况,这就需要事务的控制!

(1)实际上事务的控制不应该放在Dao层,不能更新一条就提交一次;应该发到Service层,所有逻辑走完了,在提交。

(2)我们在Service层直接增加控制事务的代码,也是不行的;因为在这里获得的sqlSession对象和执行update语句获得的sqlSession对象不是同一个sqlSession对象。

package com.bjpowernode.bank.service.impl;

import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.bao.impl.AccountDaoImpl;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.service.AccountService;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.service.impl
 * @Project:mybatis
 * @name:AccountServiceImpl
 * @Date:2023/1/1 19:40
 */
public class AccountServiceImpl implements AccountService {
   private AccountDao accountDao = new AccountDaoImpl();
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoenyNotEnoughException, TransferException {

        // 添加事务控制代码
        SqlSession sqlSession = SqlSessionUtils.openSession();

        // 判断转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            // 如果转出账户余额不足,抛出异常提示用户
            throw new MoenyNotEnoughException("对不起,余额不足");
        }
        // 如果余额充足,更新转出和转入账户余额
        Account toAct = accountDao.selectByActno(toActno);
        // 修改内存中的余额
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance(toAct.getBalance()+money);
        // 修改数据库当中余额
        int count = accountDao.updateByActno(fromAct);
        count += accountDao.updateByActno(toAct);
        if (count != 2) {
            // 转账异常
            throw new TransferException("转账异常");
        }

        // 提交事务
        sqlSession.commit();
        sqlSession.close();
    }
}

(3)使用ThreadLocal控制,本质上是一个Map集合,它的key存储的是线程对象,value存储的是sqlSession对象;我们把sqlSession对象存到这个Map集合里面,对于同一个线程,以后取出来的都是同一个sqlSession对象。

在工具类SqlSessionUtils中引入ThreadLocal

package com.bjpowernode.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;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.mybatis.utils
 * @Project:mybatis
 * @name:SqlSessionUtils
 * @Date:2022/12/29 14:11
 */

public class SqlSessionUtils {
    public SqlSessionUtils() {
    }
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 全局的,服务器级别的,一个服务器当中定义一个
    private static ThreadLocal local = new ThreadLocal<>();

    // 获取会话对象
    public static SqlSession openSession(){
        // 先从ThreadLocal当中获取
        SqlSession sqlSession = local.get();
        // 第一次获取肯定是空的
        if (sqlSession == null) {
            // 是空的,在去工厂中获取
            sqlSession = sqlSessionFactory.openSession();
            // 将sqlSession对象绑定到当前线程上
            local.set(sqlSession);
        }
        return sqlSession;
    }

    // 关闭sqlSession对象
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
            // 移除sqlSession对象与当前线程的绑定关系
            // 因为Tomcat服务器是支持多线程的,被当前用户用到的t1线程,有可能继续被其他用户使用
            local.remove();
        }
    }
}

修改数据库AccountDaoImpl类,不应该在这里面commit和close

package com.bjpowernode.bank.bao.impl;

import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.bao.impl
 * @Project:mybatis
 * @name:AccountDaoImpl
 * @Date:2023/1/1 19:57
 */
public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        return account;
    }

    @Override
    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
        int count = sqlSession.update("account.updateByActno", act);
        return count;
    }
}

在Service层AccountServiceImpl类中增加事务的控制

package com.bjpowernode.bank.service.impl;

import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.bao.impl.AccountDaoImpl;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.service.AccountService;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.service.impl
 * @Project:mybatis
 * @name:AccountServiceImpl
 * @Date:2023/1/1 19:40
 */
public class AccountServiceImpl implements AccountService {
   private AccountDao accountDao = new AccountDaoImpl();
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoenyNotEnoughException, TransferException {

        // 添加事务控制代码
        SqlSession sqlSession = SqlSessionUtils.openSession();

        // 判断转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            // 如果转出账户余额不足,抛出异常提示用户
            throw new MoenyNotEnoughException("对不起,余额不足");
        }
        // 如果余额充足,更新转出和转入账户余额
        Account toAct = accountDao.selectByActno(toActno);
        // 修改内存中的余额
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance(toAct.getBalance()+money);
        // 修改数据库当中余额
        int count = accountDao.updateByActno(fromAct);
        count += accountDao.updateByActno(toAct);
        if (count != 2) {
            // 转账异常
            throw new TransferException("转账异常");
        }

        // 提交事务
        sqlSession.commit();
        // 调用方法关闭
        SqlSessionUtils.close(sqlSession);
    }
}

4. 三大对象的作用域

(1)SqlSessionFactoryBuilder:这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

(2)SqlSessionFactory:SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,除非连接另一个数据库,一个数据库就对应一个SqlSessionFactory对象。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,所以封装的时候是放到静态代码块里。因此 SqlSessionFactory 的最佳作用域是应用作用域。 

(3)SqlSession:每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域(一次请求)一个线程对应一个SqlSession对象

补充:我们发现Dao实现类AccountDaoImp的每个方法代码就两行,而且还有一行是重复的;那么这个类能不能不写了,在内存中生成这个接口的实现类?使用javassist技术,可以只给接口,根据这个接口在内存中自动生成这个实现类,这就是接下来要学的技术!

package com.bjpowernode.bank.bao.impl;

import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.bao.impl
 * @Project:mybatis
 * @name:AccountDaoImpl
 * @Date:2023/1/1 19:57
 */
public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        return account;
    }

    @Override
    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
        int count = sqlSession.update("account.updateByActno", act);
        return count;
    }
}

你可能感兴趣的:(第四步:SSM框架,mybatis,mvc,架构)