一、实现功能
通过模拟 银行转账业务,来熟悉对mybatis的使用
二、环境搭建
1、引入依赖
<dependencies> <!--mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <!--引入logback日志框架依赖,这个日志框架实现了slf4j规范--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> <!--servlet依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> </dependencies>
2、添加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="development"> <environment id="development"> <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>
3、添加jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/powernode?useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=root
4、添加日志文件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"> <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n </pattern> </encoder> </appender> <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"/> <root level="DEBUG"> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> </root> </configuration>
5、添加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>
6、添加简单页面文件 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="转账"><br> </form> </body> </html
7、增加工具类SqlSessionUtil
package com.powernode.bank.util; 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; public class SqlSessionUtil { private SqlSessionUtil(){}; private static SqlSessionFactory sqlSessionFactory; static { try { sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } /** * 获取会话对象, * @return */ public static SqlSession openSqlSession(){ return sqlSessionFactory.openSession(); } }
8、增加实体类 Account
package com.powernode.bank.pojo; public class Account { private Long id ; private String actno; private Double 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; } }
9、mysql建表 t_act
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_act -- ---------------------------- DROP TABLE IF EXISTS `t_act`; CREATE TABLE `t_act` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `actno` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '账号', `balance` decimal(15, 2) NULL DEFAULT NULL COMMENT '余额', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of t_act -- ---------------------------- INSERT INTO `t_act` VALUES (1, 'act001', 30002.00); INSERT INTO `t_act` VALUES (2, 'act002', 19998.00); SET FOREIGN_KEY_CHECKS = 1;
10、展示项目目录结构
三、后台代码实现
1、创建AccountServlet
package com.powernode.bank.web; import com.powernode.bank.exceptions.MoneyNotEnoughException; import com.powernode.bank.exceptions.TransferException; import com.powernode.bank.service.AccountServiceImpl; import com.powernode.bank.service.IAccount; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/transfer") public class AccountServlet extends HttpServlet { private IAccount accountService = new AccountServiceImpl(); 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); //调用视图层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"); } } }
2、创建业务接口IAccount
package com.powernode.bank.service; import com.powernode.bank.exceptions.MoneyNotEnoughException; import com.powernode.bank.exceptions.TransferException; /** * 账户业务类 */ public interface IAccount { //转账 void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException; }
3、创建业务接口实现类AccountServiceImpl
package com.powernode.bank.service; import com.powernode.bank.dao.AccountDao; import com.powernode.bank.dao.impl.AccountDaoImpl; import com.powernode.bank.exceptions.MoneyNotEnoughException; import com.powernode.bank.exceptions.TransferException; import com.powernode.bank.pojo.Account; public class AccountServiceImpl implements IAccount { private AccountDao accountDao = new AccountDaoImpl(); @Override public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException { Account fromAct = accountDao.selectByActno(fromActno); if(fromAct.getBalance() < money){ throw new MoneyNotEnoughException("对不起,余额不足"); } //先更新内存中java对象的余额 Account toAct = accountDao.selectByActno(toActno); fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); int count = accountDao.updateByActno(fromAct); count += accountDao.updateByActno(toAct); if(count != 2){ throw new TransferException("对不起,转账异常"); } } }
4、创建持久层接口AccountDao,以及实现类AccountDaoImpl
package com.powernode.bank.dao; import com.powernode.bank.pojo.Account; public interface AccountDao { Account selectByActno(String actno); int updateByActno(Account act); }
package com.powernode.bank.dao.impl; import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import com.powernode.bank.util.SqlSessionUtil; import org.apache.ibatis.session.SqlSession; public class AccountDaoImpl implements AccountDao { @Override public Account selectByActno(String actno) { SqlSession sqlSession = SqlSessionUtil.openSqlSession(); Account account = sqlSession.selectOne("account.selectByActno", actno); sqlSession.commit(); sqlSession.close(); return account; } @Override public int updateByActno(Account act) { SqlSession sqlSession = SqlSessionUtil.openSqlSession(); int count = sqlSession.update("account.updateByActno", act); sqlSession.commit(); sqlSession.close(); return count; } }
5、增加两个异常类
MoneyNotEnoughException
package com.powernode.bank.exceptions; /** * 余额不足异常 */ public class MoneyNotEnoughException extends Exception { public MoneyNotEnoughException() { } public MoneyNotEnoughException(String message) { super(message); }
TransferException
package com.powernode.bank.exceptions; /** * 转账异常 */ public class TransferException extends Exception { public TransferException() { } public TransferException(String message) { super(message); } }
6、运行测试页面
转账前
转账后
四、加入事务控制
如果转账过程中,出现了异常,此时代码是有问题的,我们对原有代码进行改造
SqlSessionUtil 改造
1、将SqlSession放到ThreadLocal中,确保同一个线程当中,获取到的SqlSession对象是同一个。 2、关闭连接的时候,记得从ThreadLocal中移除当前线程中的SqlSession对象,因为tomcat服务器是支持多线程的。
package com.powernode.bank.util; 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; public class SqlSessionUtil { private SqlSessionUtil(){}; private static SqlSessionFactory sqlSessionFactory; static { try { sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } //全局的,服务器级别的,一个服务器当中定义一个即可 private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>(); /** * 获取会话对象, * @return */ public static SqlSession openSqlSession(){ SqlSession sqlSession = threadLocal.get(); if(sqlSession == null){ sqlSession = sqlSessionFactory.openSession(); threadLocal.set(sqlSession); } return sqlSession; } public static void close(SqlSession sqlSession){ if(sqlSession != null){ sqlSession.close(); threadLocal.remove(); } } }
AccountServiceImpl
package com.powernode.bank.service; import com.powernode.bank.dao.AccountDao; import com.powernode.bank.dao.impl.AccountDaoImpl; import com.powernode.bank.exceptions.MoneyNotEnoughException; import com.powernode.bank.exceptions.TransferException; import com.powernode.bank.pojo.Account; import com.powernode.bank.util.SqlSessionUtil; import org.apache.ibatis.session.SqlSession; public class AccountServiceImpl implements IAccount { private AccountDao accountDao = new AccountDaoImpl(); @Override public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException { SqlSession sqlSession = SqlSessionUtil.openSqlSession(); Account fromAct = accountDao.selectByActno(fromActno); if(fromAct.getBalance() < money){ throw new MoneyNotEnoughException("对不起,余额不足"); } //先更新内存中java对象的余额 Account toAct = accountDao.selectByActno(toActno); fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); int count = accountDao.updateByActno(fromAct); count += accountDao.updateByActno(toAct); if(count != 2){ throw new TransferException("对不起,转账异常"); } sqlSession.commit(); SqlSessionUtil.close(sqlSession); } }
AccountDaoImpl 改造
package com.powernode.bank.dao.impl; import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import com.powernode.bank.util.SqlSessionUtil; import org.apache.ibatis.session.SqlSession; public class AccountDaoImpl implements AccountDao { @Override public Account selectByActno(String actno) { SqlSession sqlSession = SqlSessionUtil.openSqlSession(); Account account = sqlSession.selectOne("account.selectByActno", actno); return account; } @Override public int updateByActno(Account act) { SqlSession sqlSession = SqlSessionUtil.openSqlSession(); int count = sqlSession.update("account.updateByActno", act); return count; } }




