首页 > 其他分享 >6. 在WEB中应用MyBatis(使用MVC架构模式)

6. 在WEB中应用MyBatis(使用MVC架构模式)

时间:2024-06-22 09:25:49浏览次数:23  
标签:WEB north sqlSession MVC MyBatis import com public bank

学习目标:

  • 掌握mybatis在web应用中怎么用
  • mybatis三大对象的作用域和生命周期
  • ThreadLocal原理及使用
  • 巩固MVC架构模式
  • 为学习MyBatis的接口代理机制做准备

实现功能:银行账户转账

使用技术:HTML + Servlet + Mybatis

1. 需求描述

image

2. 数据库表的设计和准备数据

创建数据库表 :t_act

image

填充数据

image

3. 实现步骤

第一步:环境搭建

  1. 使用Maven创建web项目
  2. 在pom.xml文件中添加依赖:mybatis,mysql驱动,junit,logback,servlet 依赖 ,其中servlet依赖的引入和Tomcat 的版本号有关
  3. 在资源目录下引入相关配置文件 mybatis-config.xml , AccountMapper.xml , logback.xml , jdbc.properties
  4. 修改web.xml的版本
  5. 使用MVC进行演示 ,需要创建相关的包

image

相关依赖的引入:

<dependencies>
    <!--mybatis核心依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.10</version>
    </dependency>
    <!--mysql驱动依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.26</version>
    </dependency>
    <!--Junit依赖-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
    <!--引入logback依赖-->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.11</version>
      <!--<scope>test</scope>-->
    </dependency>
    <!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
    <!--servlet 依赖-->
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>6.0.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

jdbc.properties 文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/north_mybatis
jdbc.username=root
jdbc.password=root

mybatis-config.xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="jdbc.properties"/>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--一定要注意这里的路径哦!!!-->
        <mapper resource="AccountMapper.xml"/>
    </mappers>
</configuration>

AccountMapper.xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace先随意写一个-->
<mapper namespace="com.north.bank.dao.AccountDao">
    <!--要想使用这种机制:id必须是dao接口的方法名。-->
    <select id="selectByActno" resultType="com.north.bank.pojo.Account">
        select * from t_act where actno = #{actno}
    </select>

    <update id="updateByActno">
        update t_act set balance = #{balance} where actno = #{actno}
    </update>
</mapper>

logback.xml 日志文件

<?xml version="1.0" encoding="UTF-8"?>

<configuration debug="false">
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!--mybatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>

</configuration>

第二步:前端页面的搭建

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>银行账户转账</title>
</head>
<body>
<!--/bank是应用的根,部署web应用到tomcat的时候一定要注意这个名字-->
<form action="/bank/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>

success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账成功</title>
</head>
<body>
  <h1>转账成功</h1>
</body>
</html>

error1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账失败</title>
</head>
<body>
    <h1>对不起 ,余额不足</h1>
</body>
</html>

error2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账异常</title>
</head>
<body>
    <h1>转账异常 , 未知原因</h1>
</body>
</html>

第三步:创建pojo包、service包、dao包、web包、utils包

image

SqlSessionUtils 工具类

package com.north.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 North
 * @Date 2024/4/4
 * 学习目标:工具类的实现
 */
public class SqlSessionUtil {
    private SqlSessionUtil() {}
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

    /**
     * 开启事务
     * @return
     */
    public static SqlSession openSession() {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            // 将sqlSession 对象绑定到当前线程上
            local.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭SqlSession对象(从当前线程中移除SqlSession对象)
     * @param sqlSession
     */
    public static void close(SqlSession sqlSession) {
        if (sqlSession != null) {
            sqlSession.close();
            // 注意移除SqlSession对象和当前线程的绑定关系
            // 因为Tomcat 服务器支持线程池 ,也就是说 ,用过的线程t1 , 可能下一次还会使用这个t1线程
            local.remove();
        }
    }
}

第四步:定义pojo类:Account

package com.north.bank.pojo;

/**
 * @Author North
 * @Date 2024/4/6
 */
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;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", 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;
    }
}

第五步:编写AccountDao接口,以及AccountDaoImpl实现类

AccountDao 接口

package com.north.bank.dao;

import com.north.bank.pojo.Account;

/**
 * 账户数据访问对象
 * @Author North
 * @Date 2024/4/6
 */
public interface AccountDao {
    /**
     * 根据账号获取账户信息
     * @param actno 账号
     * @return 账户信息
     */
    Account selectByActon(String actno);

    /**
     * 更新账户信息
     * @param account 账户信息
     * @return 1 表示成功 ,其他表示失败
     */
    int updateByActno(Account account);
}

AccountDaoImpl 实现类

package com.north.bank.dao.impl;

import com.north.bank.dao.AccountDao;
import com.north.bank.pojo.Account;
import com.north.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author North
 * @Date 2024/4/6
 */
public class AccountDaoImpl implements AccountDao {
    /**
     * 查询账户信息
     * @param actno 账号
     * @return
     */
    @Override
    public Account selectByActon(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("selectByActno", actno);
        // System.out.println(account);
        // sqlSession.close();
        return account;
    }

    /**
     * 更新账户信息
     * @param account 账户信息
     * @return
     */
    @Override
    public int updateByActno(Account account) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int rows = sqlSession.update("updateByActno", account);
        // sqlSession.commit();
        // sqlSession.close();
        return rows;
    }
}

第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射文件了

AccountMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace先随意写一个-->
<mapper namespace="com.north.bank.dao.AccountDao">
    <!--要想使用这种机制:id必须是dao接口的方法名。-->
    <select id="selectByActno" resultType="com.north.bank.pojo.Account">
        select * from t_act where actno = #{actno}
    </select>

    <update id="updateByActno">
        update t_act set balance = #{balance} where actno = #{actno}
    </update>
</mapper>

第七步:编写AccountService接口以及AccountServiceImpl

AccountService 接口

package com.north.bank.service;

import com.north.bank.expections.MoneyNotException;
import com.north.bank.expections.TransferException;

/**
 * @author  North
 * @date 2024/4/6
 * 账户业务类
 * @version  1.0
 * @since  1.0
 */
public interface AccountService {
    /**
     * 银行账户转账
     * @param fromActno 转出账户
     * @param toActno 转入账户
     * @param money 转入金额
     */
    void transter(String fromActno , String toActno , double money) throws MoneyNotException, TransferException;
}

AccountServiceImpl 实现类

package com.north.bank.service.impl;

import com.north.bank.dao.AccountDao;
import com.north.bank.dao.impl.AccountDaoImpl;
import com.north.bank.expections.MoneyNotException;
import com.north.bank.expections.TransferException;
import com.north.bank.pojo.Account;
import com.north.bank.service.AccountService;
import com.north.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author North
 * @Date 2024/4/6
 */
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao = new AccountDaoImpl();

    /**
     * @param fromActno 转出账户
     * @param toActno 转入账户
     * @param money 转入金额
     */
    @Override
    public void transter(String fromActno, String toActno, double money) throws MoneyNotException, TransferException {
        // 添加事务代码
        SqlSession sqlSession = SqlSessionUtil.openSession();

        // 1. 判断转出账户的余额是否充足
        Account fromAct = accountDao.selectByActon(fromActno);
        // 2. 如果转出账户余额不足 ,提示用户
        if (fromAct.getBalance() < money) {
            throw new MoneyNotException("对不起 ,余额不足");
        }
        // 3. 如果转出账户余额充足 ,更新转出账户余额
        Account acton = accountDao.selectByActon(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        acton.setBalance(acton.getBalance() + money);
        // 4. 更新钻入账户余额
        int count = accountDao.updateByActno(fromAct);

        // 模拟异常
        String s = null;
        s.toString();

        count += accountDao.updateByActno(acton);
        if (count != 2) {
            throw new TransferException("转账异常 , 未知原因");
        }
        sqlSession.commit();
        SqlSessionUtil.close(sqlSession);
    }
}

第八步:自定义异常

MoneyNotException​类

package com.north.bank.expections;

/**
 * @Author North
 * @Date 2024/4/6
 */
public class MoneyNotException extends Exception{
    public MoneyNotException() {}
    public MoneyNotException(String msg) {
        super(msg);
    }
}

TransferException​类

package com.north.bank.expections;

/**
 * @Author North
 * @Date 2024/4/6
 * 转账异常
 */
public class TransferException extends Exception{
    public TransferException() {}
    public TransferException(String msg) {
        super(msg);
    }
}

第九步:编写AccountController

package com.north.bank.web;

import com.north.bank.expections.MoneyNotException;
import com.north.bank.expections.TransferException;
import com.north.bank.service.AccountService;
import com.north.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;

/**
 * @Author North
 * @Date 2024/4/6
 */
@WebServlet("/transfer")
public class AccountController 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"));
        try {
            // 调用业务方法完成转账
            accountService.transter(fromActno, toActno, money);
            // 程序能够走到这里 ,说明转账成功了
            // 调用View 完成展示结果
            response.sendRedirect(request.getContextPath() + "/success.html");
        } catch (MoneyNotException 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");
        }
    }
}

4. MyBatis对象作用域以及事务问题

MyBatis核心对象的作用域

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

5. 分析当前程序存在的问题

package com.north.bank.dao.impl;

import com.north.bank.dao.AccountDao;
import com.north.bank.pojo.Account;
import com.north.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

/**
 * @Author North
 * @Date 2024/4/6
 */
public class AccountDaoImpl implements AccountDao {
    /**
     * 查询账户信息
     * @param actno 账号
     * @return
     */
    @Override
    public Account selectByActon(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("selectByActno", actno);
        // System.out.println(account);
        // sqlSession.close();
        return account;
    }

    /**
     * 更新账户信息
     * @param account 账户信息
     * @return
     */
    @Override
    public int updateByActno(Account account) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int rows = sqlSession.update("updateByActno", account);
        // sqlSession.commit();
        // sqlSession.close();
        return rows;
    }
}

我们不难发现,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类我们能不能动态的生成,以后可以不写这个类吗?答案:可以。

标签:WEB,north,sqlSession,MVC,MyBatis,import,com,public,bank
From: https://www.cnblogs.com/NorthPoet/p/18261833/6-apply-mybatis-in-the-web-using-the-mvc-arch

相关文章

  • 6. 在WEB中应用MyBatis(使用MVC架构模式)
    学习目标:掌握mybatis在web应用中怎么用mybatis三大对象的作用域和生命周期ThreadLocal原理及使用巩固MVC架构模式为学习MyBatis的接口代理机制做准备实现功能:银行账户转账使用技术:HTML+Servlet+Mybatis1.需求描述​​2.数据库表的设计和准备数据创建数据库表......
  • 6. 在WEB中应用MyBatis(使用MVC架构模式)
    学习目标:掌握mybatis在web应用中怎么用mybatis三大对象的作用域和生命周期ThreadLocal原理及使用巩固MVC架构模式为学习MyBatis的接口代理机制做准备实现功能:银行账户转账使用技术:HTML+Servlet+Mybatis1.需求描述​​2.数据库表的设计和准备数据创建数据库表......
  • ServBay 下一代Web开发环境
    ServBay是一个集成式、图形化的本地化Web开发环境。开发者通过ServBay几分钟就能部署一个本地化的开发环境。解决了Web开发者(比如PHP、Nodejs)、测试工程师、小型团队安装和维护开发测试环境的问题,同时可以快速的进行环境的升级以及维护。ServBay还将Web服务器,数据库,邮件服务器......
  • python web框架哪家强?Flask、Django、FastAPI对比
    前言当你掌握了python的基础知识,并且会用和HTML和CSS编写简单的静态网页。现在你只需再掌握一个pythonweb框架的知识,就可以开始编写一个动态的网站了。目前市面比较流程的pythonweb框架有三个flask、Django、FastAPI。接下来我们对比一下。他们三个各自有什么特点。Flas......
  • webshell总结
    一、webshell概况1.webshell概念:经常有客户的网站碰到被上传小马和大马,这里的“马”是木马的意思,可不是真实的马。通常,攻击者利用文件上传漏洞,上传一个可执行并且能被解析的脚本文件,通过这个脚本来获得服务器端执行命令的能力,也就是我们经常听到的WebShell,而这个脚本文件就......
  • mybatis控制台打印Sql的两种方式 获取昨天时间 00:00:00
    logging:level:com.atguigu:debug123第一种方式1.maven工程下pom.xml添加log4j日志依赖<!--日志--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</vers......
  • 带你学习Mybatis之拦截器
    mybatis拦截器mybatis拦截器也叫做插件,mybatis允许开发者自定义拦截器对SQL语句执行过程中的某一个点进行拦截。默认mybatis允许拦截Mybatis中的四大核心对象:Executor中的方法、ParameterHandler的方法、ResultSetHandler的方法以及StatementHandler中的方法Executor......
  • springMvc 接收文件 MultipartFile
    使用SpringMVC 的controller中接收文件,如果只是接收一个文件,声明MultipartFile或者指定@RequestParam注解,参数(strReqBody和 images)与前端参数一致即可例:第一种方式publicvoiduploadImg(MultipartFileimg){}第二种public void uploadImg(SringstrReqBody,@Req......
  • Web服务请求的几种异步处理方式
    我们先通过下面两张图来看下网络Web请求的异步处理和同步请求处理的区别:在上面两个流程图中有三个角色:客户端、Web容器和业务后端服务。两个流程中客户端对Web容器的请求,都是同步的。因为它们在请求客户端时都处于阻塞等待状态(涉及到用户态和内核态的切换),并没有进行异步处......
  • 【JavaWeb】Servlet快速入门
    具体的实现步骤如下:创建Web项目web-demo,导入Servlet依赖坐标<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><!--此处为什么需要添加该标签?provid......