• 【零基础入门MyBatis系列】第六篇——在Web中应用MyBatis


    一、概述与准备工作

    • 我们通过一个银行转账的案例来实现在Web应用中应用mybatis
    • 了解 mybatis 中三大对象的作用域
    • 采用 MVC 的架构模式,应用了 ThreadLocal 的线程绑定机制
    • 准备工作如下:

    🌔 1、我们需要提前设计一个简单的表 t_act,包含三个属性

    在这里插入图片描述
    直接在 Navicat 中手动添加两条记录
    在这里插入图片描述
    🌔 2、在我们的项目中添加一个新的模块 mybatis-004-web

    (1)与之前有所不同,此次选择 Maven Archetype,然后在Archetype处指定webapp【快速生成web项目】

    (2)手动升级 web.xml 版本,我直接找到我 tomcat 安装文件夹中的 web.xml 复制上面的内容:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                          https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
             version="5.0"
             metadata-complete="true">
    </web-app>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 此处有一个需要注释的地方:
      • 如果 metadata-complete="true",那么仅支持在web.xml文件中去配置
      • 如果metadata-complete="false",那么也可以通过注解去配置【面向注解开发】

    (3)配置 pom.xml 文件,确定打包方式和配置依赖

    • 因为是web项目,所以打包方式为 war 包,
    • 采用MVC的架构模式,所以我们需要 mybatis、mysql、junit、logback、servlet 的依赖
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.powernode</groupId>
      <artifactId>mybatis-004-web</artifactId>
      <packaging>war</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>mybatis-004-web Maven Webapp</name>
      <url>http://localhost:8080/bank</url>
      <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.30</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>
        </dependency>
        <!--servlet依赖-->
        <dependency>
          <groupId>jakarta.servlet</groupId>
          <artifactId>jakarta.servlet-api</artifactId>
          <version>5.0.0</version>
          <scope>provided</scope>
        </dependency>
    
      </dependencies>
      <build>
        <finalName>mybatis-004-web</finalName>
      </build>
    </project>
    
    • 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
    • 46
    • 47
    • 48
    • 这里面也有个需要我们注意的地方:【版本为】
      • 如果我们 tomcat 的版本是10或以上版本,就不能使用下面的依赖导入 servlet
      <dependency>
      	<groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
      </dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 需要替换为以下的依赖:
      <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>5.0.0</version>
      <scope>provided</scope>
      </dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    (4)配置 tomcat 服务器

    • 第一步如图所示找到 tomcat 服务器
      在这里插入图片描述
    • 去配置我们的 tomcat
      在这里插入图片描述

    (5)引入我们的 xml 文件

    • mybatis 的核心配置文件 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>
        <settings>
            <setting name="logImpl" value="SLF4J"/>
        </settings>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                    <property name="username" value="root"/>
                    <property name="password" value="111111"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="AccountMapper.xml"/>
        </mappers>
    </configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 数据库操作的映射文件 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">
    <mapper namespace="account">
        <select id="selectByActno" resultType="com.powernode.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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 日志的核心文件 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>
        <!-- 按照每天生成日志文件 -->
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
                <!--日志文件保留天数-->
                <MaxHistory>30</MaxHistory>
            </rollingPolicy>
            <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>
            <!--日志文件最大的大小-->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <MaxFileSize>100MB</MaxFileSize>
            </triggeringPolicy>
        </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>
    
    • 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

    二、构建我们的Bank项目

    • 对于web的页面没有使用 jsp,全部使用的都是html类型的文件

    🌔 1、编写我们的web页面

    (1)index.html 前端表单页面【数据收集】

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>银行账户转账</title>
    </head>
    <body>
    <!--form表单,用于接收信息-->
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (2)success.html 转账成功页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>转账报告</title>
    </head>
    <body>
    <h1>转账成功!</h1>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (3)error1.html 余额不足异常

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>转账报告</title>
    </head>
    <body>
    <h1>余额不足!!!</h1>>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (4)error.html 其他异常

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>转账报告</title>
    </head>
    <body>
    <h1>转账失败,未知原因!!!</h1>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    🌔 2、创建各种包和文件

    • com.powernode.bank.pojo >> 普通Java类,用于封装数据库表中的字段
    • com.powernode.bank.service >> 业务处理层
    • com.powernode.bank.dao >> 数据库的增删改查
    • com.powernode.bank.web >> 表示层
    • com.powernode.bank.exception >> 异常层
    • com.powernode.bank.utils >> 工具类

    (1)在 pojo 包下我们创建一个 Account 账户类,包含id、账户号和余额

    • 普通Java类的含义大抵是:私有属性、构造方法、getset方法、toString方法【私有属性对应表中的字段】
    • 代码如下:
    package com.powernode.bank.pojo;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class Account {
        private Long id;
        private String actno;
        private Double balance;
    
        public Account(Long id, String actno, Double balance) {
            this.id = id;
            this.actno = actno;
            this.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;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", actno='" + actno + '\'' +
                    ", balance=" + balance +
                    '}';
        }
    }
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50

    (2)在 dao 包下,我们创建对数据库操作的接口AccountDao,并提供一个实现类AccountDaoImpl

    • 数据库操作的接口:
    package com.powernode.bank.dao;
    
    import com.powernode.bank.pojo.Account;
    
    /**
     * 账户的Dao对象,负责对 t_act 表进行增删改查
     * Dao 中的方法与业务逻辑不存在联系
     * @author Bonbons
     * @version 1.0
     */
    public interface AccountDao {
        /**
         * 根据账号查询账户信息
         * @param actno 账户id
         * @return 返回账户的信息
         */
        Account selectByActno(String actno);
    
        /**
         * 更新账户的信息
         * @param act 被更新的账户
         * @return 返回更新结果(成功/失败)
         */
        int updateActno(Account act);
    }
    
    • 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
    • 我们在dao包下创建一个impl包,在这个包下写接口的实现类 AccountDaoImpl
    package com.powernode.bank.dao.impl;
    
    import com.powernode.bank.dao.AccountDao;
    import com.powernode.bank.pojo.Account;
    import com.powernode.bank.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class AccountDaoImpl implements AccountDao {
        @Override
        public Account selectByActno(String actno) {
            //开启会话,根据actno查询账户
            SqlSession sqlSession = SqlSessionUtil.openSession();
            Account account = (Account) sqlSession.selectOne("account.selectByActno",actno);
    //        关闭会话,返回账户信息
    //        sqlSession.close();
            return account;
        }
    
        @Override
        public int updateActno(Account act) {
            //开启会话
            SqlSession sqlSession = SqlSessionUtil.openSession();
            //修改余额
            int count = sqlSession.update("account.updateByActno", act);
    //        提交事务,关闭会话
    //        sqlSession.commit();
    //        sqlSession.close();
            //返回影响数据库表中记录的条数
            return count;
        }
    }
    
    • 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
    • 此处可以发现,我将事务提交、连接关闭的代码块注释掉了,为什么?

    如果是查询账户还好说,没啥太大的影响,但是如果是完成转账操作,我们调用方法之后就直接提交事务是不严谨的,如果在此期间出现了异常,那么转账操作也会成功执行。

    所以我们把事务提交、连接关闭放到了具体事务处理的部分。【通过ThreadLocal将线程与连接对象绑定起来】

    • 还有一个注意的点,此处调用的方法要与我们的 AccountMapepr.xml 映射文件对应上

    (3)在service包下,我们来写具体的业务,同样也需要写一个接口和对应的实现类【转账业务】

    • AccountService接口:【就是对业务的约束】
    package com.powernode.bank.service;
    
    import com.powernode.bank.exception.MoneyNotEnoughException;
    import com.powernode.bank.exception.TransferException;
    
    /**
     * 负责处理账户相关的业务
     * 在不同层之间需要提供接口
     * @author Bonbons
     * @version 1.0
     */
    public interface AccountService {
        /**
         * 账户转账业务[见名知意]
         * @param fromActno 转出账户
         * @param toActno 转入账户
         * @param money 转账金额
         */
        void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 同样创建一个 impl 子包,下面创建一个业务的实现类 AccountServiceImpl
    package com.powernode.bank.service.impl;
    
    import com.powernode.bank.dao.AccountDao;
    import com.powernode.bank.dao.impl.AccountDaoImpl;
    import com.powernode.bank.exception.MoneyNotEnoughException;
    import com.powernode.bank.exception.TransferException;
    import com.powernode.bank.pojo.Account;
    import com.powernode.bank.service.AccountService;
    import com.powernode.bank.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class AccountServiceImpl implements AccountService{
        //数据库操作的对象
        private AccountDao accountDao= new AccountDaoImpl();
        @Override
        public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
            //创建会话对象
            SqlSession sqlSession = SqlSessionUtil.openSession();
            //获取转出账户
            Account fromAct = accountDao.selectByActno(fromActno);
    
            //判断转出账户的余额是否充足
            if(fromAct.getBalance() < money){
                //余额不足给出提示,要对自己抛出的自定义异常处理,直接抛到调用它的方法
                throw new MoneyNotEnoughException("对不起,余额不足");
            }
    
            //余额充足,进行转账
            //查询转入账户的信息
            Account toAct = accountDao.selectByActno(toActno);
            //更新内存中转出、转入账户的余额
            fromAct.setBalance(fromAct.getBalance() - money);
            toAct.setBalance(toAct.getBalance() + money);
            //更新数据库中的余额,我们可以通过count查看是否更新成功
            int count = 0;
            count += accountDao.updateActno(fromAct);
            count += accountDao.updateActno(toAct);
            //转账失败
            if(count != 2){
                throw new TransferException("转账异常,未知原因");
            }
            //能执行到这里说明没问题,我们提交事务关闭会话
            sqlSession.commit();
            //特殊的关闭
            SqlSessionUtil.close(sqlSession);
        }
    }
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 几个注意的点:
      • 尽管此处给出的是抛出异常的方式,但是我们在表示层是通过 html 页面来显示异常信息的
      • 也可看到并不是直接调用 sqlSession.close();关闭连接的,而是调用了我们工具类中内置的关闭方法【会在工具类的位置说明】
      • 我们获取银行账户的对象,对内存中对象的信息进行修改,最后再通过 update 方法写回数据库表中

    (4)在我们的 exception 包下写我们的自定义异常类,此处只包含两个 余额不足和其他转账异常

    • 其实内部方法十分简单,就是调用构造方法传递我们想要输出的异常信息
    • 余额不足异常类 MoneyNotEnoughException
    package com.powernode.bank.exception;
    
    /**
     * 余额不足异常
     * 继承了运行时异常
     * @author Bonbons
     * @version 1.0
     */
    public class MoneyNotEnoughException extends Exception{
        //提供无参和有参的异常构造方法
        public MoneyNotEnoughException(){}
        public MoneyNotEnoughException(String msg){
            super(msg);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 其他转账异常类 TransferException
    package com.powernode.bank.exception;
    
    /**
     * 转账异常
     * @author Bonbons
     * @version 1.0
     */
    public class TransferException extends Exception{
        public TransferException(){}
        public TransferException(String msg){
            super(msg);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (5)在 utils 包下,是我们的工具类 SqlSessionUtil 【创建会话和关闭会话】

    package com.powernode.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 Bonbons
     * @version 1.0
     */
    public class SqlSessionUtil {
        private SqlSessionUtil(){}
    
        //定义一个SqlSession
        private static final SqlSessionFactory sqlSessionFactory;
        //在类加载的时候初始化SqlSessionFactory
        static {
            try {
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        //定义一个全局的ThreadLocal,可以保证一个SqlSession对应一个线程
        private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
    
        //通过一个公有的方法为外部提供会话的对象 >> 确保同一个线程操作的是同一个连接对象
        public static SqlSession openSession(){
            //我们用local去获取会话
            SqlSession sqlSession = local.get();
            //如果当前没有开启的会话就去创建一个,如果get到了就用这个[确保我们操作的是同一个连接对象]
            if(sqlSession == null){
                sqlSession = sqlSessionFactory.openSession();
                //将SqlSession对象绑定到当前线程上
                local.set(sqlSession);
            }
            return sqlSession;
        }
    
        /**
         * 关闭SqlSession对象并从当前线程中解绑
         * @param sqlSession 会话对象
         */
        public static void close(SqlSession sqlSession){
            if(sqlSession != null){
                sqlSession.close();
                local.remove();
            }
        }
    }
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 对于SqlSessionFactoryBuilder我们仅仅使用它的build方法,所以创建完SqlSessionFactory对象后就没用了
    • 对于SqlSessionFactory对应一个数据库,所以在类初始化的时候创建一个就够了,我们采用静态代码块的方式完成
    • 对于SqlSession我们希望一个线程对应一个,也可以理解为一个线程对应一个业务操作
    • 为了避免调用openSqlSession()就创建一个连接对象,我们引入 ThreadLocal 作为全局变量
      • 在调用openSession的时候,如果已经存在连接对象了,我们就将这个连接对象返回
      • 如果此时没有会话,我们就利用会话工厂去创建一个会话,并将这个连接对象绑定到当前的线程上
      • 这也就是为什么在关闭会话的时候,需要设置一个单独的方法,关闭会话后立即解除绑定

    (6)在 web 包下,我们创建一个 AccountServlet 类作为表示层

    package com.powernode.bank.web; /**
     * @author Bonbons
     * @version 1.0
     */
    
    import com.powernode.bank.exception.MoneyNotEnoughException;
    import com.powernode.bank.exception.TransferException;
    import com.powernode.bank.service.AccountService;
    import com.powernode.bank.service.impl.AccountServiceImpl;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    
    //@WebServlet("/transfer") 未找到资源,可能是注解没有生效,我们去web.xml中配置一下
    //在web.xml中将 metadata-complete="false" 代表支持web.xml配置也支持注解配置
    
    //@WebServlet("/transfer")
    
    public class AccountServlet extends HttpServlet {
        //因为其他方法也可能用到业务类的对象,所以设置为全局变量,父类引用执行子类对象[多态]
        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类型
            double money = Double.parseDouble(request.getParameter("money"));
    
            //并不处理业务,调用service的转账方法完成转账,在这块属于一个控制器
            try {
                //转账操作可能出现异常,所以需要我们对异常进行捕获
                accountService.transfer(fromActno, toActno, money);
                //调用视图层(View)查看结果
                response.sendRedirect(request.getContextPath() + "/success.html");
            } catch (MoneyNotEnoughException e) {
                //利用页面来实现异常 >> 牛
                response.sendRedirect(request.getContextPath() + "/error1.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
    • 46
    • 47
    • 48
    • 49

    三、测试我们的项目

    🌔 1、运行tomcat,在网页填写表单

    在这里插入图片描述
    🌔 2、查看数据库表在执行操作前后的变换

    (1)执行操作前
    在这里插入图片描述

    (2)执行操作后
    在这里插入图片描述
    在这里插入图片描述


    四、MyBatis三大对象的作用域

    • 这个与Web项目没啥关系,不过理解这部分也很重要,其实我在前面的SqlSessionUtil中就有提及
    • 引用 MyBatis 的中文官方文档,对此展开详细的论述

    1、SqlSessionFactoryBuilder

    • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。【以保证所有的 XML 解析资源】
    • 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

    2、SqlSessionFactory

    • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。【不要创建多次】
    • 因此 SqlSessionFactory最佳作用域是应用作用域。【最简单的就是使用单例模式或者静态单例模式】

    3、SqlSession

    • 每个线程都应该有它自己的 SqlSession 实例。
    • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 相关阅读:
    DevTools 热加载为什么快?
    【Maven】maven安装、IDEA创建maven的web项目、添加依赖、集成Tomcat
    Unreal 各类指针是怎么回事
    spring中的messageSource(国际化)
    ARM pwn 入门 (3)
    大龄转行当程序员:只能选择小众技术,避免与年轻人竞争?
    TypeScript接口
    对原数组有影响的几个方法
    一个Behance被封,还有无数国内网站给我灵感
    Linux远程控制
  • 原文地址:https://blog.csdn.net/qq_61323055/article/details/127670727