基于MyBatis的银行转账系统开发实战:从环境搭建到动态代理实现
目标:
-
掌握mybatis在web应用中怎么用
-
mybatis三大对象的作用域和生命周期
-
ThreadLocal原理及使用
-
巩固MVC架构模式
-
为学习MyBatis的接口代理机制做准备
实现功能:
-
银行账户转账
使用技术:
-
HTML + Servlet + MyBatis
WEB应用的名称:
-
bank
一、需求描述
二、数据库表的设计和准备数据
create table t_act
(id bigint not null,actno varchar(255) null,balance decimal(15,2) null,constraint t_act_pkprimary key (id)
);
三、实现步骤
第一步:环境搭建
-
IDEA中创建Maven WEB应用(mybatis-004-web)
-
IDEA配置Tomcat,这里Tomcat使用10+版本。并部署应用到tomcat。
这2个有什么区别?
- 文件结构:
war exploded
是展开式的 WAR 包,它以目录结构形式存在,其中的文件(如.class 文件、资源文件等)是直接可见、可编辑的;而war
是压缩打包后的文件,所有内容被压缩在一个文件里。 - 部署和更新:
war exploded
在开发阶段便于快速部署和热更新,修改文件后无需重新打包即可生效;war
包部署时,每次修改内容后通常需重新打包再部署到服务器 。 - 应用场景:
war exploded
更适合开发调试阶段,能提高开发效率;war
包常用于生产环境部署,便于传输和管理,且占用空间相对较小。
-
默认创建的maven web应用没有java和resources目录,包括两种解决方案
-
第一种:自己手动加上。
-
-
第二种:修改maven-archetype-webapp-1.4.jar中的配置文件
-
web.xml文件的版本较低,可以从tomcat10的样例文件中复制,然后修改
改为false是支持配置文件,同时支持注解
<?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/jakartaeehttps://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version="5.0"metadata-complete="true"></web-app>
-
删除index.jsp文件,因为我们这个项目不使用JSP。只使用html。
-
确定pom.xml文件中的打包方式是war包。
-
引入相关依赖
-
编译器版本修改为17
-
引入的依赖包括:mybatis,mysql驱动,junit,logback,servlet。
-
<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>org.example1</groupId><artifactId>mybatis-004-web</artifactId><packaging>war</packaging><version>1.0-SNAPSHOT</version><name>mybatis-004-web Maven Webapp</name><url>http://maven.apache.org</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>
这些是干什么用的
这段代码是 Maven 项目配置文件(pom.xml
)中的 <build>
部分,主要用于定义项目构建过程的配置和插件管理。以下是各部分的作用:
1. <finalName>
xml
<finalName>mybatis-004-web</finalName>
- 作用:指定项目打包后的文件名(不带扩展名)。
- 示例:若打包为 WAR 文件,最终会生成
mybatis-004-web.war
。 - 默认行为:若不配置,Maven 会使用
artifactId-version
作为文件名(如mybatis-004-web-1.0-SNAPSHOT.war
)。
2. <pluginManagement>
xml
<pluginManagement><plugins><!-- 各种插件配置 --></plugins>
</pluginManagement>
- 作用:统一管理插件的版本和配置,但不会直接执行,需要在子模块中显式引用才会生效。
- 目的:避免多个子模块中重复配置相同插件,确保版本一致性。
3. 各插件的作用
(1)maven-clean-plugin
xml
<plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version>
</plugin>
- 作用:清理项目生成的临时文件(如
target
目录)。 - 常用命令:
mvn clean
。
(2)maven-resources-plugin
xml
<plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version>
</plugin>
- 作用:复制项目资源文件(如
src/main/resources
下的配置文件)到输出目录。
(3)maven-compiler-plugin
xml
<plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version>
</plugin>
- 作用:编译 Java 源代码(
src/main/java
)为字节码(.class
文件)。 - 常见配置:可指定 Java 版本(如
<source>11</source>
和<target>11</target>
)。
(4)maven-surefire-plugin
xml
<plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version>
</plugin>
- 作用:运行单元测试(
src/test/java
下的测试类)。 - 常用命令:
mvn test
。
(5)maven-war-plugin
xml
<plugin><artifactId>maven-war-plugin</artifactId><version>3.2.2</version>
</plugin>
- 作用:将 Web 项目打包为 WAR 文件,用于部署到 Tomcat 等容器。
- 默认配置:会自动包含
src/main/webapp
下的 Web 资源。
(6)maven-install-plugin
xml
<plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version>
</plugin>
- 作用:将项目打包后的文件安装到本地 Maven 仓库(
~/.m2/repository
),供其他本地项目依赖。 - 常用命令:
mvn install
。
(7)maven-deploy-plugin
xml
<plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version>
</plugin>
- 作用:将项目部署到远程 Maven 仓库(如 Nexus),供团队共享。
- 常用命令:
mvn deploy
(需配置仓库地址和权限)。
-
引入相关配置文件,放到resources目录下(全部放到类的根路径下)
-
mybatis-config.xml
-
AccountMapper.xml
-
logback.xml
-
jdbc.properties
-
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbc
jdbc.username=root
jdbc.password=abc123
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//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>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="account"></mapper>
第二步:前端页面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>
第三步:创建pojo包、service包、dao包、web包、utils包
第四步:定义pojo类:Account
package bank.pojo;/*** 账户类,封装账户数据。*/
public class Account {private Long id;private String actno;private Double balance;@Overridepublic String toString() {return "Account{" +"id=" + id +", actno='" + actno + '\'' +", 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;}public Account(Long id, String actno, Double balance) {this.id = id;this.actno = actno;this.balance = balance;}public Account() {}
}
第五步:编写AccountDao接口,以及AccountDaoImpl实现类
分析dao中至少要提供几个方法,才能完成转账:
-
转账前需要查询余额是否充足:selectByActno
-
转账时要更新账户:update
package bank.dao;import bank.pojo.Account;/*** 账户的DAO对象。负责t_act表中数据的CRUD.* 强调一下:DAO对象中的任何一个方法和业务不挂钩。没有任何业务逻辑在里面。* DAO中的方法就是做CRUD的。所以方法名大部分是:insertXXX deleteXXX updateXXX selectXXX*/
public interface AccountDao {/*** 根据账号查询账户信息。* @param actno 账号* @return 账户信息*/Account selectByActno(String actno);/*** 更新账户信息* @param act 被更新的账户对象* @return 1表示更新成功,其他值表示失败。*/int updateByActno(Account act);}
package bank.dao.impl;import bank.dao.AccountDao;
import bank.pojo.Account;
import bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountDaoImpl implements AccountDao {@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account act = (Account)sqlSession.selectOne("selectByActno", actno);sqlSession.close();return act;}@Overridepublic int update(Account act) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("update", act);sqlSession.commit();sqlSession.close();return count;}
}
第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射文件了
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--sqlMapper.xml文件的编写者,提供者是谁?使用你mybatis框架的java程序员负责提供的。-->
<!--要想使用这种机制:namespace必须是dao接口的全限定名称。-->
<mapper namespace="bank.dao.AccountDao"><!--要想使用这种机制:id必须是dao接口的方法名。--><select id="selectByActno" resultType="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>
第七步:编写AccountService接口以及AccountServiceImpl
package bank.exceptions;/*** 余额不足异常。*/
public class MoneyNotEnoughException extends Exception {public MoneyNotEnoughException(){}public MoneyNotEnoughException(String msg){super(msg);}
}
package bank.exceptions;/*** 转账异常*/
public class TransferException extends Exception{public TransferException(){}public TransferException(String msg){}
}
import bank.exceptions.MoneyNotEnoughException;
import bank.exceptions.TransferException;/*** 注意:业务类当中的业务方法的名字在起名的时候,最好见名知意,能够体现出具体的业务是做什么的。* 账户业务类*/
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 {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}try {// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库accountDao.update(fromAct);accountDao.update(toAct);} catch (Exception e) {throw new AppException("转账失败,未知原因!");}}
}
SQLSessionUtil类
import java.io.IOException;/*** MyBatis工具类*/
public class SqlSessionUtil {// 工具类的构造方法一般都是私有化的。// 工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。// 为了防止new对象,构造方法私有化。private SqlSessionUtil(){}private static SqlSessionFactory sqlSessionFactory;// 类加载时执行// SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象。static {try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}/*** 获取会话对象。* @return 会话对象*/public static SqlSession openSession(){return sqlSessionFactory.openSession();}}
第八步:编写AccountController(Web)
package bank.web;import bank.exceptions.MoneyNotEnoughException;
import bank.exceptions.TransferException;
import bank.service.AccountService;
import bank.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();@Overrideprotected 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");} catch (Exception e){response.sendRedirect(request.getContextPath() + "/error2.html");}}}
4.MyBatis对象作用域以及事务问题
⑴MyBatis核心对象的作用域
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {// 你的应用逻辑代码
}
⑵.事务问题
在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使用事务机制,在transfer方法开始执行时开启事务,直到两个更新都成功之后,再提交事务,我们尝试将transfer方法进行如下修改:
public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}try {// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库(添加事务)SqlSession sqlSession = SqlSessionUtil.openSession();accountDao.update(fromAct);// 模拟异常String s = null;s.toString();accountDao.update(toAct);sqlSession.commit();sqlSession.close();} catch (Exception e) {throw new AppException("转账失败,未知原因!");}}
}
运行前注意看数据库表中当前的数据:
执行程序:
再次查看数据库表中的数据:
傻眼了吧!!!事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service和dao中使用的SqlSession对象不是同一个。 怎么办?为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到ThreadLocal当中。修改SqlSessionUtil工具类:
public class SqlSessionUtil {private static SqlSessionFactory sqlSessionFactory;/*** 类加载时初始化sqlSessionFactory对象*/static {try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (Exception e) {e.printStackTrace();}}private static ThreadLocal<SqlSession> local = new ThreadLocal<>();/*** 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。** @return 新的会话对象*/public static SqlSession openSession() {SqlSession sqlSession = local.get();if (sqlSession == null) {sqlSession = sqlSessionFactory.openSession();local.set(sqlSession);}return sqlSession;}/*** 关闭SqlSession对象* @param sqlSession*/public static void close(SqlSession sqlSession){if (sqlSession != null) {sqlSession.close();}local.remove();}
}
修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除。
public class AccountDaoImpl implements AccountDao {@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);return act;}@Overridepublic int update(Account act) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.update", act);return count;}
}
修改service中的方法:
public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}try {// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库(添加事务)SqlSession sqlSession = SqlSessionUtil.openSession();accountDao.update(fromAct);// 模拟异常String s = null;s.toString();accountDao.update(toAct);sqlSession.commit();SqlSessionUtil.close(sqlSession); // 只修改了这一行代码。} catch (Exception e) {throw new AppException("转账失败,未知原因!");}}
}
当前数据库表中的数据:
再次运行程序:
查看数据库表:没有问题。
再测试转账成功:
如果余额不足呢:
账户的余额依然正常:
5.分析当前程序存在的问题
我们来看一下DaoImpl的代码
public class AccountDaoImpl implements AccountDao {@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);return act;}@Overridepublic int update(Account act) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.update", act);return count;}
}
我们不难发现,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类我们能不能动态的生成,以后可以不写这个类吗?答案:可以。
二、使用javassist生成类
来自百度百科:
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
1.Javassist的使用
我们要使用javassist,首先要引入它的依赖
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.29.1-GA</version>
</dependency>
样例代码:
public class JavassistTest {public static void main(String[] args) throws Exception {// 获取类池ClassPool pool = ClassPool.getDefault();// 创建类CtClass ctClass = pool.makeClass("com.powernode.javassist.Test");// 创建方法// 1.返回值类型 2.方法名 3.形式参数列表 4.所属类CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);// 设置方法的修饰符列表ctMethod.setModifiers(Modifier.PUBLIC);// 设置方法体ctMethod.setBody("{System.out.println(\"hello world\");}");// 给类添加方法ctClass.addMethod(ctMethod);// 调用方法Class<?> aClass = ctClass.toClass();Object o = aClass.newInstance();Method method = aClass.getDeclaredMethod("execute");method.invoke(o);}
}
运行要注意:加两个参数,要不然会有异常。
-
--add-opens java.base/java.lang=ALL-UNNAMED
-
--add-opens java.base/sun.net.util=ALL-UNNAMED
运行结果:
2.使用Javassist生成DaoImpl类
使用Javassist动态生成DaoImpl类
/*** 使用javassist库动态生成dao接口的实现类**/
public class GenerateDaoByJavassist {/*** 根据dao接口生成dao接口的代理对象** @param sqlSession sql会话* @param daoInterface dao接口* @return dao接口代理对象*/public static Object getMapper(SqlSession sqlSession, Class daoInterface) {ClassPool pool = ClassPool.getDefault();// 生成代理类CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");// 接口CtClass ctInterface = pool.makeClass(daoInterface.getName());// 代理类实现接口ctClass.addInterface(ctInterface);// 获取所有的方法Method[] methods = daoInterface.getDeclaredMethods();Arrays.stream(methods).forEach(method -> {// 拼接方法的签名StringBuilder methodStr = new StringBuilder();String returnTypeName = method.getReturnType().getName();methodStr.append(returnTypeName);methodStr.append(" ");String methodName = method.getName();methodStr.append(methodName);methodStr.append("(");Class<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {methodStr.append(parameterTypes[i].getName());methodStr.append(" arg");methodStr.append(i);if (i != parameterTypes.length - 1) {methodStr.append(",");}}methodStr.append("){");// 方法体当中的代码怎么写?// 获取sqlId(这里非常重要:因为这行代码导致以后namespace必须是接口的全限定接口名,sqlId必须是接口中方法的方法名。)String sqlId = daoInterface.getName() + "." + methodName;// 获取SqlCommondTypeString sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();if ("SELECT".equals(sqlCommondTypeName)) {methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");methodStr.append("return (" + returnTypeName + ")obj;");} else if ("UPDATE".equals(sqlCommondTypeName)) {methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");methodStr.append("return count;");}methodStr.append("}");System.out.println(methodStr);try {// 创建CtMethod对象CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);ctMethod.setModifiers(Modifier.PUBLIC);// 将方法添加到类ctClass.addMethod(ctMethod);} catch (CannotCompileException e) {throw new RuntimeException(e);}});try {// 创建代理对象Class<?> aClass = ctClass.toClass();Constructor<?> defaultCon = aClass.getDeclaredConstructor();Object o = defaultCon.newInstance();return o;} catch (Exception e) {throw new RuntimeException(e);}}
}
相关文章:
基于MyBatis的银行转账系统开发实战:从环境搭建到动态代理实现
目标: 掌握mybatis在web应用中怎么用 mybatis三大对象的作用域和生命周期 ThreadLocal原理及使用 巩固MVC架构模式 为学习MyBatis的接口代理机制做准备 实现功能: 银行账户转账 使用技术: HTML Servlet MyBatis WEB应用的名称&am…...
纹理采样+光照纹理采样
普通纹理显示 导入纹理 1.将纹理拷贝到项目中 2.配置纹理 纹理显示原理 原始纹理(边长是),如果原始图的边长不是,游戏引擎在运行时,会自动将 纹理的边长补偿为,所以补偿是有损耗的(纹理不一定是…...
408真题笔记
2024 年全国硕士研究生招生考试 计算机科学与技术学科联考 计算机学科专业基础综合 (科目代码:408) 一、单项选择题 第 01~40 小题,每小题 2 分,共 80 分。下列每小题给出的四个选项中,只有一个…...
【Shell 脚本编程】详细指南:第一章 - 基础入门与最佳实践
Shell 脚本编程完全指南:第一章 - 基础入门与最佳实践 引言:Shell 脚本在现代开发中的重要性 Shell 脚本作为 Linux/Unix 系统的核心自动化工具,在 DevOps、系统管理、数据处理等领域扮演着关键角色。本章将系统性地介绍 Shell 脚本的基础知…...
PostgreSQL数据库操作SQL
数据库操作SQL 创建 创建数据库 create database db_test;创建并指定相关参数 with owner : 所有者encoding : 编码connection limit :连接限制 create database db_test1 with owner postgresencoding utf-8connection limit 100;修改 修改数据库名称 renam…...
RAG工程-基于LangChain 实现 Advanced RAG(预检索-查询优化)(下)
Multi-Query 多路召回 多路召回流程图 多路召回策略利用大语言模型(LLM)对原始查询进行拓展,生成多个与原始查询相关的问题,再将原始查询和生成的所有相关问题一同发送给检索系统进行检索。它适用于用户查询比较宽泛、模糊或者需要…...
VBA数据库解决方案第二十讲:Select From Where条件表达式
《VBA数据库解决方案》教程(版权10090845)是我推出的第二套教程,目前已经是第二版修订了。这套教程定位于中级,是学完字典后的另一个专题讲解。数据库是数据处理的利器,教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…...
Linux架构篇、第1章_02源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62
Linux_基础篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62 版本号: 1.0,0 作者: 老王要学习 日期: 2025.05.01 适用环境: Centos7 文档说明 本文…...
【Machine Learning Q and AI 读书笔记】- 03 小样本学习
Machine Learning Q and AI 中文译名 大模型技术30讲,主要总结了大模型相关的技术要点,结合学术和工程化,对LLM从业者来说,是一份非常好的学习实践技术地图. 本文是Machine Learning Q and AI 读书笔记的第3篇,对应原…...
Webug4.0靶场通关笔记08- 第11关万能密码登录(SQL注入漏洞)
目录 第13关 万能密码登录 1.打开靶场 2.源码分析 3.渗透方法1 4.渗透方法2 第13关 万能密码登录 本文通过《webug靶场第13关 万能密码登录》来进行渗透实战。 万能密码是利用 SQL 注入漏洞,构造出能够绕过登录验证的特殊密码字符串。通常,登录验…...
terraform中statefile文件的实现原理及作用
Terraform 的 State 文件(terraform.tfstate)是其基础设施即代码(IaC)机制的核心组件,用于记录和管理云资源的实际状态。以下是其实现原理及核心作用的详细分析: 一、State 文件的实现原理 1. 数据结构与…...
7.0/Q1,GBD数据库最新文章解读
文章题目:Cardiovascular disease s mortality in Brazilian municipalities: estimates from the Global Burden of Disease study, 2000-2018 DOI:10.1016/j.lana.2025.101106 中文标题:巴西城市的心血管疾病死亡率:来自2000-20…...
linux 使用nginx部署next.js项目,并使用pm2守护进程
前言 本文基于:操作系统 CentOS Stream 8 使用工具:Xshell8、Xftp8 服务器基础环境: node - 请查看 linux安装node并全局可用pm2 - 请查看 linux安装pm2并全局可用nginx - 请查看 linux 使用nginx部署vue、react项目 所需服务器基础环境&…...
0基础 | Proteus电路仿真 | 电机使用
目录 电机类型 51单片机对直流电机的控制 基于89C51主控的直流电机控制电路仿真 代码《基于Keil C51》 51单片机对步进电机的控制 控制代码《基于Keil C51》 基于89C51主控的步进电机控制电路仿真 电机类型 直流电机 步进电机 51单片机对直流电机的控制 直流电机&#…...
人工智能100问☞第14问:人工智能的三大流派(符号主义、联结主义、行为主义)有何区别?
目录 一、通俗解释 二、专业解析 三、权威参考 一、通俗解释 人工智能的三大流派,就像三位不同性格的工程师用各自的方法造机器人: 1、符号主义(逻辑派) 核心:用“教科书式规则”教机器思考。比如教计算机下棋,先写一本《国际象棋必胜法则》,机器…...
油气人工地震资料信号处理中,机器学习和AI应用
在油气人工地震资料信号处理中,机器学习和AI可以应用于多个环节,显著提升数据质量、解释效率和勘探准确性。以下是主要应用场景及对应的开源工具推荐: 1. 数据预处理 应用场景: 噪声压制(如随机噪声、多次波、面波&am…...
Python数据分析课程实验-1
1.1数据分析简介 当今世界对信息技术的依赖程度日渐加深,每天都会产生和存储海量的数据。数据的来源多种多样一自 动检测系统、传感器和科学仪器等。不知你有没有意识到,你每次从银行取钱、买东西、写博客、发微博也会产生新的数据。 什么是数据呢?数据实际上不同于…...
算法--模拟题目
算法–模拟问题 1576. 替换所有的问号 思路:遍历字符串,找到?, 然后遍历字符a 到 z 找到不等于前后字符,替换即可 class Solution { public:string modifyString(string s) {for(int i 0; i < s.size(); i){if(s[i] ?){//替换for(char a a; a < z; a){//当字符不等…...
PDF24 Tools:涵盖20+种PDF工具,简单高效PDF工具箱,支持一键编辑/转换/合并
一、软件介绍 PDF 24 Tools是一款由德国公司开发的PDF编辑工具,拥有18年的历史,并且一直免费使用,这在同类软件中非常难得。 早在许久之前,我就推荐过这款工具的免费网页版,但由于网页使用起来可能不太方便且速度较慢…...
12.多边形的三角剖分 (Triangulation) : Fisk‘s proof
目录 1.Fisks proof Trangulation Coloring Domination Pigeon-Hold Principle Generation 2.Orthogonal Polygons (正交多边形) Necessity of floor(n4) Sufficiency by convex Quadrilateralization Generalization 1.Fisks proof Trangulation 引入内对角线&…...
数据库基本概念:数据库的定义、特点、分类、组成、作用
一:数据库相关概念 1.1 定义 (1)数据库:存储数据的仓库 (2)数据库管理系统:模拟和管理数据库的大型软件 (3)SQL:操作关系型数据库的编程语言,定义…...
PostgreSQL 数据库下载和安装
官网: PostgreSQL: Downloads 推荐下载网站:EDB downloads postgresql 我选了 postgresql-15.12-1-windows-x64.exe 鼠标双击,开始安装: 安装路径: Installation Directory: D:\Program Files\PostgreSQL\15 Serv…...
【c++】【STL】queue详解
目录 queue的作用什么是容器适配器queue的接口构造函数emptysizefrontback queue类的实现 queue的作用 queue是stl库提供的一种容器适配器,也就是我们数据结构中学到的队列,是非常常用的数据结构,特点是遵循LILO(last in last ou…...
循环插入数据库行
文章目录 循环插入数据库行 循环插入数据库行 -- 声明变量 DECLARE i INT 201;-- 开始循环 WHILE i < 200 BEGIN-- 插入数据INSERT INTO T_AGVPOS (POS) VALUES (i);SET i i 1; END;...
QMK机械键盘固件开发指南:从源码到实践
QMK机械键盘固件开发指南:从源码到实践 前言 QMK(Quantum Mechanical Keyboard)是一款开源的键盘固件,支持众多自定义键盘的功能配置。通过QMK,您可以完全掌控键盘的每一个按键,实现复杂的宏指令、多层按…...
Unity SpriteMask(精灵遮罩)
🏆 个人愚见,没事写写笔记 🏆《博客内容》:Unity3D开发内容 🏆🎉欢迎 👍点赞✍评论⭐收藏 🔎SpriteMask:精灵遮罩 💡作用就是对精灵图片产生遮罩,…...
AdaBoost算法详解:原理、实现与应用指南
AdaBoost算法详解:原理、实现与应用指南 1. 引言 在机器学习领域,AdaBoost(Adaptive Boosting) 是最早提出的集成学习(Ensemble Learning)**算法之一,由Yoav Freund和Robert Schapire于1995年…...
Flink流式计算核心:DataStream API与时间语义深度解析
本文将围绕Flink最核心的DataStream API展开,结合其独特的时间语义体系,深入解析Flink如何实现对无界流数据的精准控制,并通过真实业务场景案例演示其工程实践方法。 一、DataStream API:Flink处理无界流的“中枢神经” Flink的A…...
C# 方法的结构与执行详解
在编程世界里,方法是一块具有名称的代码,它就像是一个功能盒子,我们可以使用方法的名称从别的地方执行其中的代码,还能把数据传入方法并接收数据输出。方法是类的函数成员,主要由方法头和方法体两个部分构成。 方法头…...
《AI大模型应知应会100篇》第41篇:多轮对话设计:构建高效的交互式应用
第41篇:多轮对话设计:构建高效的交互式应用 摘要 在银行客服机器人突然准确回答出用户第7次追问的信用卡额度规则时,在医疗问诊系统记住患者既往病史的瞬间,多轮对话技术正在创造令人惊叹的交互体验。本文将以工业级案例为经&am…...
【Day 14】HarmonyOS分布式数据库实战
一、分布式数据库基础 1. 核心概念速记表 术语解释示例场景分布式数据库数据自动同步到同账号设备手机添加商品→平板立即显示KV数据模型键值对存储(类似JSON){"cart_item1": {"name":"牛奶","price":10}}数据…...
terraform 删除资源前先校验资源是否存在关联资源
Terraform 删除资源前校验关联资源的解决方案 在使用 Terraform 进行资源删除操作时,确实存在直接删除可能影响关联资源的风险。以下是几种在删除前校验关联资源的方法: 1. 使用 Terraform Data Sources 进行预检查 在删除主资源前,可以通…...
如何免费使用 DeepSeek-Prover-V2?
近日,Deepseek 发布了一个新模型,这是一个在数学推理方面表现卓越的模型,即 DeepSeek Prover V2。 DeepSeek-Prover-V2 是一个专门使用 Lean 4 证明助手进行形式化定理证明的高级语言模型。 简单来说, DeepSeek-Prover-V2 旨在支持数学家和计算机科学家创建和验证形式化证…...
dify+ollama+知识库 部署
这篇文章的前提是已经部署了deepseek和ollama deepseek和ollama安装 代码、配置 本地电脑如果是Windows的话,需要安装Git # 拉取Dify代码 git clone https://github.com/langgenius/dify.git复制配置 进入dify\docker目录 复制.env.example到.env 复制.middlewa…...
补题:K - Magic Tree (Gym - 105231K)
来源:问题 - K - Codeforceshttps://codeforces.com/gym/105231/problem/K 题目描述: 一、题目分析 本题给定一个2行m列的网格,从(1, 1)格子开始进行深度优先搜索,每个格子可到达至少一个边相邻的格子且不重复访问,…...
文章记单词 | 第58篇(六级)
一,单词释义 naive:英 [naɪˈiːv , nɑːˈiːv] 美 [naɪˈiːv , nɑːˈiːv],形容词,意为 “天真的;幼稚的;轻信的;易受骗的;无经验的;率真的;质朴的”…...
红利底波是什么意思?
红利低波是一种结合了红利策略和低波策略的投资策略,主要选取股息率高且波动率低的股票进行投资,具有 “高收益低风险” 的特点,适合大多数投资者的权益资产配置。以下是具体介绍: 策略构成要素 红利策略 :关注股息率…...
缓存:缓解读库压力的高效方案与应用实践
在软件开发和系统设计中,使用缓存来缓解读库压力是一种常见且有效的优化策略,以下是具体的介绍: 一、缓存的基本概念 缓存是一种临时数据存储区域,它存储了经常访问的数据副本。当应用程序需要访问数据时,首先会检查…...
17. LangChain流式响应与实时交互:打造“类ChatGPT“体验
引言:从"等待加载"到"即时对话"的革命 2025年某在线教育平台的AI助教引入流式交互后,学生平均对话轮次提升3.2倍,完课率提高47%。本文将基于LangChain的异步流式架构,揭秘如何实现毫秒级响应的自然对话体验。…...
仿腾讯会议——服务器结构讲解
总功能 1、数据库类 1、进入mysql 2、查看当前数据库 2、线程池 3、网络类 阻塞和非阻塞是通过套接字来实现的,所以不能发送和接收的阻塞状态不同...
【笔记】深度学习模型训练的 GPU 内存优化之旅③:内存交换篇
开设此专题,目的一是梳理文献,目的二是分享知识。因为笔者读研期间的研究方向是单卡上的显存优化,所以最初思考的专题名称是“显存突围:深度学习模型训练的 GPU 内存优化之旅”,英文缩写是 “MLSys_GPU_Memory_Opt”。…...
(B题|矿山数据处理问题)2025年第二十二届五一数学建模竞赛(五一杯/五一赛)解题思路|完整代码论文集合
我是Tina表姐,毕业于中国人民大学,对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在,我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…...
【2025最新】为什么用ElasticSearch?和传统数据库MySQL与什么区别?
Elasticsearch 深度解析:从原理到实践 一、为什么选择 Elasticsearch? 数据模型 Elasticsearch 是基于文档的搜索引擎,它使用 JSON 文档来存储数据。在 Elasticsearch 中,相关的数据通常存储在同一个文档中,而不是分散…...
华为云Astro大屏连接器创建操作实例:抽取物联网iotda影子设备数据的连接器创建
目录 样图(API连接器创建成功) 说明 操作场景(以Astro大屏抽取iotda影子参数为例) 实际操作步骤 新建连接器 设置基本信息。 接口鉴权方式,支持API鉴权、AK/SK、API Key和无身份验证 无身份验证 AK/SK认证(目前暂不能用) API Key认证(第三方使用) API鉴权认…...
C#泛型集合深度解析(九):掌握System.Collections.Generic的核心精髓
一、泛型集合革命:告别装箱拆箱的性能噩梦 1.1 泛型与非泛型集合性能对比 // 非泛型集合(ArrayList) ArrayList arrayList = new ArrayList(); arrayList.Add(100); // 装箱发生 int value = (int)arrayList[0]; // 拆箱发生// 泛型集合(List<T>) List<…...
人工智能-深度学习之卷积神经网络
深度学习 mlp弊端卷积神经网络图像卷积运算卷积神经网络的核心池化层实现维度缩减卷积神经网络卷积神经网络两大特点卷积运算导致的两个问题:图像填充(padding)结构组合问题经典CNN模型LeNet-5模型AlexNet模型VGG-16模型 经典的CNN模型用于新…...
《软件设计师》复习笔记(11.1)——生命周期、CMM、开发模型
目录 一、信息系统生命周期 系统规划阶段 系统分析阶段(逻辑设计) 系统设计阶段(物理设计) 系统实施阶段 系统运行与维护阶段 二、能力成熟度模型(CMM/CMMI) CMM 五级模型 CMMI 两种表示方法 真题…...
AI大模型基础设施:主流的几款开源AI大语言模型的本地部署成本
以下是对目前主流开源AI大语言模型(如DeepSeek R1、LLaMA系列、Qwen等)本地部署成本的详细分析,涵盖计算机硬件、显卡等成本,价格以美元计算。成本估算基于模型参数规模、硬件需求(GPU、CPU、RAM、存储等)以…...
Narendra自适应控制器设计
上一篇介绍了在系统结构中引入前馈和反馈的结构,然后利用李雅普诺夫稳定性理论设计MRACS,在基于输入输出形式中,利用李雅普诺夫稳定性设计的自适应率中包含了误差的导数,这降低了系统的抗干扰性,为了避免这一缺点&…...
为什么大模型偏爱Markdown
Markdown 的简洁之美 我们常见的文档格式,比如HTML、JSON、XML或者Markdown,Markdown是最简洁的。 比如要展示一行标题,相比复杂的HTML标签,使用Markdown我们只需要在文本前加个井号: <heading level“1”>这是…...