Table of contents
One: Apply MyBatis in the WEB (using the MVC architecture pattern)
4. The scope of the three major objects
One: Apply MyBatis in the WEB (using the MVC architecture pattern)
Target:
① Master how to use mybatis in web applications
②The scope and life cycle of the three mybatis objects
③ThreadLocal principle and use
④Consolidate the MVC architecture model
⑤ Prepare for learning the interface proxy mechanism of MyBatis
Realized function: bank account transfer
Technology used: HTML + Servlet + MyBatis
The name of the WEB application: bank
1. Preliminary preparation
(1) Database table preparation
design table structure
insert data
(2) Use Mybatis to build the environment in the web application, create a web application, add a local Tomcat server, and then configure resources
①Introduce dependencies in the pom.xml file
<?xml version="1.0" encoding="UTF-8"?>
<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>
<name>mybatis-004-web</name>
<groupId>com.bjpowernode</groupId>
<artifactId>mybatis-004-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<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>5.1.23</version>
</dependency>
<!--logback依赖-->
<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>3.1.0</version>
</dependency>
</dependencies>
</project>
②Introduction of some configuration files
③ Write the front-end transfer page 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>
④ Use the MVC architecture pattern to create a package
Explanation: dao is to connect to the database, pojo is an ordinary java class, service is to write business, utils is a tool class, web is a servlet
(1) According to the analysis of the three-tier architecture: web is the presentation layer, service is the business layer, and dao is the persistence layer.
(2) Analysis according to the MVC architecture pattern: pojo, dao, and service belong to M, all .jsp belongs to V, and web belongs to C. (3) The relationship between the MVC architecture pattern and the three layers: M includes the business layer and the persistence layer, and V and C belong to the presentation layer.
pojo class, encapsulating account information
package com.bjpowernode.bank.pojo;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.pojo
* @Project:mybatis
* @name:Account
* @Date:2023/1/1 17:53
*/
public class Account {
private Long id;
private String actno;
private Double balance;
// 构造方法
public Account() {
}
public Account(Long id, String actno, Double balance) {
this.id = id;
this.actno = actno;
this.balance = balance;
}
// setter and getter
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;
}
// 重写toString
@Override
public String toString() {
return "Account{" +
"id=" + id +
", actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
}
Tool class, the class that connects to the database
package com.bjpowernode.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;
public class SqlSessionUtils {
public SqlSessionUtils() {
}
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
}
2. Core code implementation
Details: There is a configuration in the web.xml file, metadata-complete="true"> indicates that only the form of configuration file is supported, and development in the form of annotation is not supported; equal to false indicates that both are supported!
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="false">
</web-app>
The main frame form and the required display pages
① Bao package: the operation of connecting to the database, interface-oriented programming, interface and corresponding implementation class
②exception package: Throwing exceptions, one is the exception thrown by insufficient balance, and the other is the exception that occurs during the transfer process
③pojo package: encapsulates data, ordinary java class
④service package: write business logic code, interface-oriented programming, interface and corresponding implementation class
⑤utils package: code package to connect to the database
⑥ web package: write servlet, it is the commander, for scheduling
①index.html is the page for submitting data at the front end, where you enter the transfer information
②money-not-enough.html is the page where the transfer balance is insufficient and the results are displayed
③success.html is the page where the transfer is successful
④transfer-error.html is the page where there is a problem with the transfer
①AccountMapper.xml: specially written sql statement
②jdbc.properties: configuration information required to connect to the database
③logback.xml: log configuration information
④mybatis-config.xml: core configuration file for importing logs, reading connection database configuration files, connecting databases, and associating AccountMapper.xml configuration files and other information
(1) Write a servlet, write AccountServlet according to the request sent by the front end, this Servlet is equivalent to Commander C, and dispatches two secretaries M and V
package com.bjpowernode.bank.web;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.service.AccountService;
import com.bjpowernode.bank.service.impl.AccountServiceImpl;
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;
import java.io.PrintWriter;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.web
* @Project:mybatis
* @name:AccountServlet
* @Date:2023/1/1 19:23
*/
@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"));
// AccountService accountService = new AccountServiceImpl(); // 面向接口编程
try {
// 调用service的转账方法完成转账(调度业务层-M)
accountService.transfer(fromActno,toActno,money);
// 调用View完成展示结果
// 转账成功,跳转页面
response.sendRedirect(request.getContextPath()+"/success.html");
} catch (MoenyNotEnoughException e) {
// 钱不够,跳转页面
response.sendRedirect(request.getContextPath()+"/money-not-enough.html");
} catch (TransferException e) {
// 转账失败,跳转页面
response.sendRedirect(request.getContextPath()+"/transfer-error.html");
}
}
}
Insufficient balance money-not-enough.html page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>余额不足</title>
</head>
<body>
<h1>余额不足</h1>
</body>
</html>
Transfer successful success.html page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账成功</title>
</head>
<body>
<h1>转账成功</h1>
</body>
</html>
Transfer failed transfer-error.html page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账失败</title>
</head>
<body>
<h1>转账失败</h1>
</body>
</html>
(2) The above logic found that Commander Servlet calls the business logic code (M) to complete the transfer operation, so it is necessary to write the AccountService interface and the AccountServiceImpl implementation class
AccountService interface
package com.bjpowernode.bank.service;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
public interface AccountService {
/**
* 账户转账
* @param fromActno 转账账号
* @param toActno 转入账号
* @param money 转账金额
*/
void transfer(String fromActno,String toActno,double money) throws MoenyNotEnoughException, TransferException;
}
AccountServiceImpl implementation class
package com.bjpowernode.bank.service.impl;
import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.bao.impl.AccountDaoImpl;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.service.AccountService;
/** 编写业务逻辑代码
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.service.impl
* @Project:mybatis
* @name:AccountServiceImpl
* @Date:2023/1/1 19:40
*/
public class AccountServiceImpl implements AccountService {---
// 修改数据的类
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoenyNotEnoughException, TransferException {
// 判断转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
// 如果转出账户余额不足,抛出异常提示用户
throw new MoenyNotEnoughException("对不起,余额不足");
}
// 如果余额充足,更新转出和转入账户余额
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("转账异常");
}
}
}
Insufficient balance throws an exception MoenyNotEnoughException
package com.bjpowernode.bank.exception;
public class MoenyNotEnoughException extends Exception{
// 两个构造方法
public MoenyNotEnoughException() {
}
public MoenyNotEnoughException(String message) {
super(message);
}
}
An exception TransferException is thrown when the transfer fails
package com.bjpowernode.bank.exception;
public class TransferException extends Exception{
public TransferException() {
}
public TransferException(String message) {
super(message);
}
}
(3) When writing business logic, it is found that database operations are required, which requires a class to connect to the database and process data operations; interface-oriented programming, write AccountDao interface and AccountDaoImpl class
AccountDao interface
package com.bjpowernode.bank.bao;
import com.bjpowernode.bank.pojo.Account;
/**
* 负责对表中数据进行CRUD
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.bao
* @Project:mybatis
* @name:AccountDao
* @Date:2023/1/1 19:48
*/
public interface AccountDao {
/**
* 根据账号查询账户信息
* @param actno
* @return 返回的是账户信息
*/
Account selectByActno(String actno);
/**
* 更新账户信息
* @param act
* @return 1表示更新成功
*/
int updateByActno(Account act);
}
AccountDaoImpl class
package com.bjpowernode.bank.bao.impl;
import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.bao.impl
* @Project:mybatis
* @name:AccountDaoImpl
* @Date:2023/1/1 19:57
*/
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtils.openSession();
Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
sqlSession.close();
return account;
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtils.openSession();
int count = sqlSession.update("account.updateByActno", act);
sqlSession.commit();
sqlSession.close();
return count;
}
}
AccountMapper.xml for writing sql statements
<?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.bjpowernode.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>
The configuration file jdbc.properties for connecting to the database
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123
Core configuration file 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" />
<!--引入日志-->
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
<environments default="mybatisDB">
<!--连接的是mybatis数据库-->
<environment id="mybatisDB">
<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>
<!--关联AccountMapper.xml配置文件-->
<mapper resource="AccountMapper.xml"/>
</mappers>
</configuration>
3. Transaction Control
Note: The current code has no transaction control. If a null pointer exception is added after the first update statement and before the second update statement, money will be lost, which requires transaction control!
(1) In fact, the control of the transaction should not be placed on the Dao layer, and it should be submitted once if one cannot be updated; it should be sent to the Service layer, and all logic is completed before submitting.
(2) It is not acceptable for us to directly add codes for controlling transactions at the Service layer; because the sqlSession object obtained here and the sqlSession object obtained by executing the update statement are not the same sqlSession object.
package com.bjpowernode.bank.service.impl;
import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.bao.impl.AccountDaoImpl;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.service.AccountService;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.service.impl
* @Project:mybatis
* @name:AccountServiceImpl
* @Date:2023/1/1 19:40
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoenyNotEnoughException, TransferException {
// 添加事务控制代码
SqlSession sqlSession = SqlSessionUtils.openSession();
// 判断转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
// 如果转出账户余额不足,抛出异常提示用户
throw new MoenyNotEnoughException("对不起,余额不足");
}
// 如果余额充足,更新转出和转入账户余额
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();
sqlSession.close();
}
}
(3) Use ThreadLocal control, which is essentially a Map collection. Its key stores the thread object, and its value stores the sqlSession object; we store the sqlSession object in this Map collection, and for the same thread, it will be taken out later All are the same sqlSession object.
Introduce ThreadLocal in the tool class SqlSessionUtils
package com.bjpowernode.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:朗朗乾坤
* @Package:com.bjpowernode.mybatis.utils
* @Project:mybatis
* @name:SqlSessionUtils
* @Date:2022/12/29 14:11
*/
public class SqlSessionUtils {
public SqlSessionUtils() {
}
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
// 全局的,服务器级别的,一个服务器当中定义一个
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
// 获取会话对象
public static SqlSession openSession(){
// 先从ThreadLocal当中获取
SqlSession sqlSession = local.get();
// 第一次获取肯定是空的
if (sqlSession == null) {
// 是空的,在去工厂中获取
sqlSession = sqlSessionFactory.openSession();
// 将sqlSession对象绑定到当前线程上
local.set(sqlSession);
}
return sqlSession;
}
// 关闭sqlSession对象
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
// 移除sqlSession对象与当前线程的绑定关系
// 因为Tomcat服务器是支持多线程的,被当前用户用到的t1线程,有可能继续被其他用户使用
local.remove();
}
}
}
Modify the database AccountDaoImpl class, you should not commit and close in it
package com.bjpowernode.bank.bao.impl;
import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.bao.impl
* @Project:mybatis
* @name:AccountDaoImpl
* @Date:2023/1/1 19:57
*/
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtils.openSession();
Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
return account;
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtils.openSession();
int count = sqlSession.update("account.updateByActno", act);
return count;
}
}
Add transaction control in the Service layer AccountServiceImpl class
package com.bjpowernode.bank.service.impl;
import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.bao.impl.AccountDaoImpl;
import com.bjpowernode.bank.exception.MoenyNotEnoughException;
import com.bjpowernode.bank.exception.TransferException;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.service.AccountService;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.service.impl
* @Project:mybatis
* @name:AccountServiceImpl
* @Date:2023/1/1 19:40
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoenyNotEnoughException, TransferException {
// 添加事务控制代码
SqlSession sqlSession = SqlSessionUtils.openSession();
// 判断转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
// 如果转出账户余额不足,抛出异常提示用户
throw new MoenyNotEnoughException("对不起,余额不足");
}
// 如果余额充足,更新转出和转入账户余额
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();
// 调用方法关闭
SqlSessionUtils.close(sqlSession);
}
}
4. The scope of the three major objects
(1) SqlSessionFactoryBuilder: This class can be instantiated, used and discarded, once the SqlSessionFactory is created, it is no longer needed . So the best scope for an instance of SqlSessionFactoryBuilder is method scope (that is, local method variables).
(2) SqlSessionFactory: Once SqlSessionFactory is created, it should always exist during the running of the application . There is no reason to discard it or recreate another instance, unless another database is connected, and a database corresponds to a SqlSessionFactory object . The best practice of using SqlSessionFactory is not to create multiple times during the running of the application, so it is placed in a static code block when encapsulating. So the best scope for SqlSessionFactory is application scope .
(3 ) SqlSession: Each thread should have its own instance of SqlSession. Instances of SqlSession are not thread-safe and therefore cannot be shared, so its best scope is request or method scope (one request) . A thread corresponds to a SqlSession object .
Supplement: We found that each method code of the Dao implementation class AccountDaoImp has only two lines, and one line is repeated; so can this class not be written, and the implementation class of this interface can be generated in memory? Using javassist technology, you can only give the interface, and automatically generate the implementation class in memory according to this interface. This is the technology to learn next!
package com.bjpowernode.bank.bao.impl;
import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.bao.impl
* @Project:mybatis
* @name:AccountDaoImpl
* @Date:2023/1/1 19:57
*/
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtils.openSession();
Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
return account;
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtils.openSession();
int count = sqlSession.update("account.updateByActno", act);
return count;
}
}