开发web前的准备
创建账户表t_act并设置字段及其数据类型
向账户表中插入数据两条数据
准备index.html页面实现转账的交互操作
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⽂件)
第二步: 配置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对象是同⼀个
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();
}
}
}
业务规范
不同功能的类放在不同的包
层与层使用接口进行衔接, 让层与层之间的耦合度降低
java.lang.ThreadLocal类中里面有个Map集合(key是当前线程 , value是我们想要存储的对象)
Service层开启事务的sqlSession对象需要和Dao层执行SQL语句的sqlSession对象是同一个才能真正控制事务
编写核心代码并控制事务
第一步: 在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
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接口的实现类
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);
}
}