1.数据库表的设计和准备数据
2. 实现步骤
2.1环境搭建
1. idea中新建Maven WEB应用(mybatis-004-web),Create from archetype勾选上,选择如图所示webapp
2.配置依赖
在pom.xml文件中添加依赖
<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>
<!--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>
3.idea配置Tomcat,并部署应用到tomcat
4. 修改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="false">
</web-app>
5.在resources目录下新建、编辑jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=root
6.在resources目录下新建、编辑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="powernodeDB">
<environment id="powernodeDB">
<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>
7.在resources目录下新建、编写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">
</mapper>
8.在resources目录下新建、编写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>[%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>
2.2 前端页面
在webapp中新建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>
在webapp中新建success.html(同理创建error1.html和error2.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账成功</title>
</head>
<body>
<h1>转账成功</h1>
</body>
</html>
2.3创建包
- 普通的java类
- com.powernode.bank.pojo
- 工具包
- com.powernode.bank.utils
- 表示层
- com.powernode.bank.web
- 业务逻辑层
- com.powernode.bank.service
- com.powernode.bank.service.impl
- 持久化层
- com.powernode.bank.dao
- com.powernode.bank.dao.impl
- 异常包
- com.powernode.bank.exception
1.新建pojo类,放到com.powernode.bank.pojo包中
package com.powernode.pojo;
/**
* 账户类,封装账户数据
*/
public class Account {
private Long id;
private String actno;
private Double balance;
......
//此处省略构造方法、getting setting toString方法
2.新建SqlSessionUtil工具类,放到com.powernode.bank.utils包中
package com.powernode.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;
/**
* MyBatis工具类
*/
public class SqlSessionUtil {
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
// ThreadLocal对象,一个服务器定义一个即可
// 把SqlSession对象放到ThreadLocal当中,为了保证一个线程对应一个SqlSession
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
/**
* 获取SqlSession会话对象
* @return 会话对象
*/
public static SqlSession openSession(){
SqlSession sqlSession = local.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();
// 移除sqlSession对象和当前线程的绑定关系
local.remove();
}
}
}
2.4 后端代码实现
1. 编写控制器AccountServlet
package com.powernode.web;
import com.powernode.exceptions.MoneyNotEnoughException;
import com.powernode.exceptions.TransferException;
import com.powernode.service.AccountService;
import com.powernode.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;
@WebServlet("/transfer")
public class AccountServlet 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"));
// 调用service的转账方法完成转账(调业务层)
try {
accountService.transfer(fromActno, toActno, money);
// 走到这,转账成功
// 调用View完成展示结果
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");
}
}
}
2. 编写业务层AccountService接口以及AccountServiceImpl
package com.powernode.service;
import com.powernode.exceptions.MoneyNotEnoughException;
import com.powernode.exceptions.TransferException;
/**
* 账户业务类
*/
public interface AccountService {
/**
* 账户转账业务
* @param fromActno 转出账号
* @param toActno 转入账号
* @param money 转账金额
*/
void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
}
package com.powernode.service.impl;
import com.powernode.dao.AccountDao;
import com.powernode.dao.impl.AccountDaoImpl;
import com.powernode.exceptions.MoneyNotEnoughException;
import com.powernode.exceptions.TransferException;
import com.powernode.pojo.Account;
import com.powernode.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
// 1.判断转出账户余额是否充足(select)
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
// 2.余额不足,提示用户
throw new MoneyNotEnoughException("余额不足");
}
// 3. 余额充足,更新转出账户余额(update)
// 先更新内存中java对象account余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 添加事务控制代码
SqlSession sqlSession = SqlSessionUtil.openSession();
// 4.更新转入账户余额(update)
int count = accountDao.updateByActno(fromAct);
/*// 模拟异常
String s = null;
s.toString();*/
count += accountDao.updateByActno(toAct);
if (count != 2) {
throw new TransferException("转账异常");
}
// 提交事务
sqlSession.commit();
// 关闭事务
SqlSessionUtil.close(sqlSession);
}
}
3. 编写AccountMapper.xml中SQL语句
<select id="selectByActno" resultType="com.powernode.pojo.Account">
select * from t_act where actno = #{actno}
</select>
<update id="updateByActno">
update t_act set balance = #{balance} where actno = #{actno}
</update>
4. 编写DAO层AccountDao接口,以及AccountDaoImpl实现类
package com.powernode.dao;
import com.powernode.pojo.Account;
/**
* 账户的DAO对象,负责t_act表中数据据的CRUD
*/
public interface AccountDao {
/**
* 根据账号查询账户信息
* @param actno 账号
* @return 账户信息
*/
Account selectByActno(String actno);
/**
* 更新账户信息
* @param act 被更新的账户信息
* @return 1表示成功
*/
int updateByActno(Account act);
}
package com.powernode.dao.impl;
import com.powernode.dao.AccountDao;
import com.powernode.pojo.Account;
import com.powernode.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
return account;
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.updateByActno", act);
return count;
}
}
5. 编写两个异常类
package com.powernode.exceptions;
/**
* 余额不足异常
*/
public class MoneyNotEnoughException extends Exception{
public MoneyNotEnoughException() {
}
public MoneyNotEnoughException(String message) {
super(message);
}
}
package com.powernode.exceptions;
/**
* 转账异常
*/
public class TransferException extends Exception{
public TransferException() {
}
public TransferException(String message) {
super(message);
}
}
6.启动服务器,打开浏览器,输入地址:http://localhost:8080/bank,
测试:
3.MyBatis核心对象的作用域
SqlSessionFactoryBuilder 只是用来buildFactory的,利用完即可丢弃
SqlSessionFactory 一个数据库对应一个Factory,最好不要丢弃
SqlSession 一个线程对应一个SqlSession
4. 分析当前程序存在的问题
dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类能不能动态的生成,以后可以不写这个类吗?
使用javassist动态生成类