🌔 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>
metadata-complete="true",那么仅支持在web.xml文件中去配置metadata-complete="false",那么也可以通过注解去配置【面向注解开发】(3)配置 pom.xml 文件,确定打包方式和配置依赖
<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>
<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 服务器


(5)引入我们的 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>
<?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>
<?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、编写我们的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>
(2)success.html 转账成功页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账报告</title>
</head>
<body>
<h1>转账成功!</h1>
</body>
</html>
(3)error1.html 余额不足异常
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账报告</title>
</head>
<body>
<h1>余额不足!!!</h1>>
</body>
</html>
(4)error.html 其他异常
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账报告</title>
</head>
<body>
<h1>转账失败,未知原因!!!</h1>
</body>
</html>
🌔 2、创建各种包和文件
(1)在 pojo 包下我们创建一个 Account 账户类,包含id、账户号和余额
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 +
'}';
}
}
(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);
}
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;
}
}
如果是查询账户还好说,没啥太大的影响,但是如果是完成转账操作,我们调用方法之后就直接提交事务是不严谨的,如果在此期间出现了异常,那么转账操作也会成功执行。
所以我们把事务提交、连接关闭放到了具体事务处理的部分。【通过ThreadLocal将线程与连接对象绑定起来】
(3)在service包下,我们来写具体的业务,同样也需要写一个接口和对应的实现类【转账业务】
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;
}
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);
}
}
sqlSession.close();关闭连接的,而是调用了我们工具类中内置的关闭方法【会在工具类的位置说明】(4)在我们的 exception 包下写我们的自定义异常类,此处只包含两个 余额不足和其他转账异常
package com.powernode.bank.exception;
/**
* 余额不足异常
* 继承了运行时异常
* @author Bonbons
* @version 1.0
*/
public class MoneyNotEnoughException extends Exception{
//提供无参和有参的异常构造方法
public MoneyNotEnoughException(){}
public MoneyNotEnoughException(String msg){
super(msg);
}
}
package com.powernode.bank.exception;
/**
* 转账异常
* @author Bonbons
* @version 1.0
*/
public class TransferException extends Exception{
public TransferException(){}
public TransferException(String msg){
super(msg);
}
}
(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();
}
}
}
(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、运行tomcat,在网页填写表单

🌔 2、查看数据库表在执行操作前后的变换
(1)执行操作前

(2)执行操作后


1、SqlSessionFactoryBuilder
2、SqlSessionFactory
3、SqlSession