(7)在WEB应用中使用MyBatis完成业务转账功能

在WEB应用中使用MyBatis

开发web前的准备

创建账户表t_act并设置字段及其数据类型
(7)在WEB应用中使用MyBatis完成业务转账功能_第1张图片
向账户表中插入数据两条数据
(7)在WEB应用中使用MyBatis完成业务转账功能_第2张图片
准备index.html页面实现转账的交互操作
(7)在WEB应用中使用MyBatis完成业务转账功能_第3张图片

DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>银⾏账户转账title>
    head>
    <body>
        
        <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>

环境搭建

第一步: 创建Module , 使用Maven Archetype原型创建WEB应用mybatis-crud(如果项⽬不使⽤JSP可以删除自动生成的index.jsp⽂件)
(7)在WEB应用中使用MyBatis完成业务转账功能_第4张图片

第二步: 配置pom.xml文件: 指定打包⽅式为war包并导入相关依赖mybatis依赖 , mysql驱动依赖 , logback依赖 ,servlet依赖

第三步: 配置Tomcat服务器的相关参数并部署web应⽤到Tomcat


<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <groupId>com.powernodegroupId>
    <artifactId>mybatis-004-webartifactId>
    <version>1.0-SNAPSHOTversion>
    <packaging>warpackaging>
    
    <name>mybatis-004-web Maven Webappname>
    <url>http://localhost:8080/bankurl>
    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>17maven.compiler.source>
        <maven.compiler.target>17maven.compiler.target>
    properties>
    
    <dependencies>
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.10version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.30version>
        dependency>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.13.2version>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.11version>
        dependency>
        
        <dependency>
            <groupId>jakarta.servletgroupId>
            <artifactId>jakarta.servlet-apiartifactId>
            <version>5.0.0version>
            <scope>providedscope>
        dependency>
    dependencies>
    <build>
        <finalName>mybatis-004-webfinalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-clean-pluginartifactId>
                    <version>3.1.0version>
                plugin>
                <plugin>
                    <artifactId>maven-resources-pluginartifactId>
                    <version>3.0.2version>
                plugin>
                <plugin>
                    <artifactId>maven-compiler-pluginartifactId>
                    <version>3.8.0version>
                plugin>
                <plugin>
                    <artifactId>maven-surefire-pluginartifactId>
                    <version>2.22.1version>
                plugin>
                <plugin>
                    <artifactId>maven-war-pluginartifactId>
                    <version>3.2.2version>
                plugin>
                <plugin>
                    <artifactId>maven-install-pluginartifactId>
                    <version>2.5.2version>
                plugin>
                <plugin>
                    <artifactId>maven-deploy-pluginartifactId>
                    <version>2.8.2version>
                plugin>
            plugins>
        pluginManagement>
    build>
project>

编写配置/映射文件

在类的根路径下(resources⽬录)创建配置文件: jdbc.properties, mybatis-config.xml,AccountMapper.xml , logback.xml

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

MyBtis的核心配置文件mybatis-config.xml


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="dev">
        <environment id="dev">
            <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>

SQL语句的映射文件AccountMapper.xml


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>

编写SqlSessionUtil工具类,使用ThreadLocal存储当前线程对应的sqlSession对象,保证service和dao中使⽤的SqlSession对象是同⼀个

  • 因为Tomcat服务器支持线程池 , 所以关闭sqlSession对象后要从大Map集合中移除否则如果别人下次使用到这个线程就会拿到你已经关闭的连接对象
public class SqlSessionUtil {
    private SqlSessionUtil(){}
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

    // 获取会话对象
    public static SqlSession openSession(){
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            // 将sqlSession对象绑定到当前线程上
            local.set(sqlSession);
        }
        return sqlSession;
    }

    // 关闭SqlSession对象(从当前线程中移除SqlSession对象)
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
            // 移除SqlSession对象和当前线程的绑定关系
            local.remove();
        }
    }

}

业务规范

不同功能的类放在不同的包

  • 创建pojo包放实体类、service放业务类的接口、dao包放dao类的接口、web/controller包放控制类、utils包放工具类、exceptions包放异常类

层与层使用接口进行衔接, 让层与层之间的耦合度降低

  • 在dao包和service包的子包impl包中提供service和dao接口的实现类 , 在控制层调用业务层,业务层调用持久层之间都是面向接口编程

java.lang.ThreadLocal类中里面有个Map集合(key是当前线程 , value是我们想要存储的对象)

  • 第一次获取元素的时候ThreadLocal中的Map集合是空的所以需要我们手动创建元素并添加到Map集合中

Service层开启事务的sqlSession对象需要和Dao层执行SQL语句的sqlSession对象是同一个才能真正控制事务

  • sqlSession对象的关闭也应该交给业务逻辑层 , 不能在DAO中的方法关闭

编写核心代码并控制事务

第一步: 在web包下定义AccountServlet类作为Controller接收用户的请求,然后调度其他组件来完成任务

@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    // 将AccountService声明为实例变量保证在其他方法中也可以用
    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 {
            // 调用service的转账方法完成转账(调业务层)
            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");
        }
    }
}

第二步: 在service包下编写AccountService接口提供处理业务的方法,在service的子包impl下编写它的实现类AccountServiceImpl

  • 事务一定是在业务逻辑层进行控制 , 一般一个业务方法对应一个完整的事务(但也有一个业务方法调用其他业务方法的情况)
  • 在业务层开启事务的sqlSession对象一定要和Dao层执行SQL语句的sqlSession对象是同一个才能控制事务
public interface AccountService {
    /**
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    // 子类不能比父类抛出更宽泛的异常,子类抛父类必须也要抛
    void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
}

public class AccountServiceImpl implements AccountService {
    // 将AccountDao声明为实例变量保证在其他业务方法中也可以用
    private AccountDao accountDao = new AccountDaoImpl();

    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
        // 添加事务控制代码
        SqlSession sqlSession = SqlSessionUtil.openSession();

        // 1. 执行查询语句判断转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            // 2. 如果转出账户余额不足,提示用户
            throw new MoneyNotEnoughException("对不起,余额不足!");
        }
        // 3. 如果转出账户余额充足,先更新内存中转出账户和转入账户对应的java对象Balance
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
         // 4. 更新数据库中转出账户的余额(update)
        int count = accountDao.updateByActno(fromAct);

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

        // 5. 更新数据库中转入账户余额(update)
        count += accountDao.updateByActno(toAct);
        if (count != 2) {
            throw new TransferException("转账异常,未知原因");
        }
        // 提交事务
        sqlSession.commit();
        // 关闭事务
        SqlSessionUtil.close(sqlSession);
    }
}

第三步: 在pojo包下定义Account实体类封装账户数据

public class Account {
    private Long id;
    private String actno;
    private Double balance;
    // set和get以及toString方法
    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }
    public Account() {
    }
}

第四步: 在dao包下编写AccountDao接口,dao的子包impl下编写它的实现类AccountDaoImpl(利用MyBatis执行SQL映射文件中的SQL语句)

public interface AccountDao {
    // 根据账号查询账户信息
    Account selectByActno(String actno);
    // 更新账户信息.1表示更新成功,其他值表示失败
    int updateByActno(Account act);
    //其他增删的方法...
}

public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String arg0) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", arg0);
        return account;
    }

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

第五步: 将自定义异常放在exception包下

//余额不足异常
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException(){}
    public MoneyNotEnoughException(String msg){
        super(msg);
    }
}

//App异常
public class AppException extends Exception{
    public AppException(){}
    public AppException(String msg){
        super(msg);
    }
}

动态生成dao实现类

dao实现类中的⽅法一般都是通过SqlSession对象调⽤增删改查等⽅法,这个类中的⽅法没有任何业务逻辑,我们可以动态的⽣成这个类

只要按照MaBatis的规定创建规范的XxxMapper.xml文件, MaBatis解析完xml文件就会为你动态生成dao接口的实现类

  • namespace必须是dao接口的全限定名称, Id必须是接口中的方法名

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.bank.dao.AccountDao">
    <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>

public class AccountServiceImpl implements AccountService {
    // 手动编写AccountDao的实现类并创建对象返回
    // private AccountDao accountDao = new AccountDaoImpl();

    // 使用MyBatis的代理机制,在内存中动态生成AccountDao的实现类,然后创建代理类的实例
    private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
        // 添加事务控制代码
        SqlSession sqlSession = SqlSessionUtil.openSession();

        // 1. 执行查询语句判断转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            // 2. 如果转出账户余额不足,提示用户
            throw new MoneyNotEnoughException("对不起,余额不足!");
        }
        // 3. 如果转出账户余额充足,先更新内存中转出账户和转入账户对应的java对象Balance
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
        // 4. 更新数据库中转出账户的余额(update)
        int count = accountDao.updateByActno(fromAct);

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

        // 5. 更新数据库中转入账户余额(update)
        count += accountDao.updateByActno(toAct);
        if (count != 2) {
            throw new TransferException("转账异常,未知原因");
        }
        // 提交事务
        sqlSession.commit();
        // 关闭事务
        SqlSessionUtil.close(sqlSession);
    }

}

你可能感兴趣的:(MyBatis,前端,mybatis)