动态创建代理对象的工具类
package com.wsd.util;import org.apache.ibatis.javassist.ClassPool; import org.apache.ibatis.javassist.CtClass; import org.apache.ibatis.javassist.CtMethod; import org.apache.ibatis.session.SqlSession;import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays;/*** 使用javassist库动态生成dao接口的实现类* @author: Mr.Wang* @create: 2023-07-08 18:44**/ public class ProxyUtil {public static Object getMyMapper(SqlSession sqlSession, Class daoInterface){//mybatis 依赖中内置了 javassist,不需要再引入javassist dependency//获取一个默认的 ClassPool 实例//Javassist 是一个 Java 字节码编辑库,它可以在运行时修改已加载的类或者生成新的类。// ClassPool 是 Javassist 的核心组件,它是一个类容器,负责存储和管理字节码(.class 文件)ClassPool pool = ClassPool.getDefault();// 生成代理类CtClass ctClass = pool.makeClass(daoInterface.getPackage().getName() + ".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;// 获取SqlCommondType 获取配置文件中该sql tag 的类型String sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();if ("SELECT".equals(sqlCommondTypeName)) {methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.wsd.util.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.wsd.util.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);//添加访问修饰符publicctMethod.setModifiers(Modifier.PUBLIC);// 将方法添加到类ctClass.addMethod(ctMethod);} catch (Exception 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);}} }
pom.xml
<?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/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.wsd</groupId><artifactId>web-mybatis01</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>web-mybatis01 Maven Webapp</name><!-- FIXME change it to the project's website --><url>http://www.example.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.7</maven.compiler.source><maven.compiler.target>1.7</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>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>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency></dependencies><build><finalName>web-mybatis01</finalName><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --><plugins><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-war-plugin</artifactId><version>3.2.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin></plugins></pluginManagement></build> </project>
dao interface
package com.wsd.dao;import com.wsd.pojo.Account;/*** @author: Mr.Wang* @create: 2023-07-02 01:30**/ public interface AccountDao {/*** 根据账号获取账户信息* @param actno 账号* @return 账户信息*/Account selectByActno(String actno);/*** 更新账户信息* @param act 账户信息* @return 1表示更新成功,其他值表示失败*/int update(Account act); }
mapper.xml
<?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="com.wsd.dao.AccountDao"><select id="selectByActno" resultType="com.wsd.pojo.Account">select * from t_act where actno = #{actno}</select><update id="update">update t_act set balance = #{balance} where actno = #{actno}</update> </mapper>
mybatis 配置文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""https://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>
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password=root
获取sqlsession的工具类
package com.wsd.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;/*** @description: Utility class for mybatis* @author: Mr.Wang* @create: 2023-06-17 17:38**/public class SqlSessionUtil {private SqlSessionUtil(){}private static SqlSessionFactory sqlSessionFactory;//保存sqlSession , 一个线程一个private static ThreadLocal<SqlSession> sqlSessionThreadLocal = new ThreadLocal<>();/*** 类加载时初始化sqlSessionFactory对象*/static {try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (Exception e) {e.printStackTrace();}}/*** 每调用一次openSession()可获取一个新的会话。** @return 新的会话对象*/public static SqlSession openSession() {// Get a sqlSession instance from sqlSessionThreadLocalSqlSession sqlSession = sqlSessionThreadLocal.get();//如果 sqlSessionThreadLocal 未获取到 sqlSession,让工厂生产一个新的sqlSessionif(sqlSession == null){sqlSession = sqlSessionFactory.openSession();sqlSessionThreadLocal.set(sqlSession);}return sqlSession;}/*** @description close sqlSession and remove it from sqlSessionThreadLocal* @param sqlSession* @return*/public static void close(SqlSession sqlSession) {if(sqlSession != null){sqlSession.close();//remove from sqlSessionThreadLocalsqlSessionThreadLocal.remove();}} }
封装数据的 pojo class
package com.wsd.pojo;/*** @description: pojo for Account* @author: Mr.Wang* @create: 2023-07-01 23:41**/ public class Account {private Long id;private String actno;private Double balance;@Overridepublic String toString() {return "Account{" +"id=" + id +", actno='" + actno + '\'' +", balance=" + balance +'}';}public Account() {}public Account(Long id, String actno, Double balance) {this.id = id;this.actno = actno;this.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;} }
service interface
package com.wsd.service;import com.wsd.exception.AppException; import com.wsd.exception.MoneyNotEnoughException;public interface AccountService {void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException; }
实现类
package com.wsd.service.impl;import com.wsd.dao.AccountDao; import com.wsd.exception.AppException; import com.wsd.exception.MoneyNotEnoughException; import com.wsd.pojo.Account; import com.wsd.service.AccountService; import com.wsd.util.ProxyUtil; import com.wsd.util.SqlSessionUtil; import org.apache.ibatis.session.SqlSession;/*** @author: Mr.Wang* @create: 2023-07-02 01:19**/ public class AccountServiceImpl implements AccountService {//使用工具类生成代理对象private AccountDao accountDao = (AccountDao) ProxyUtil.getMyMapper(SqlSessionUtil.openSession(), AccountDao.class);@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException,AppException{//创建sql sessionSqlSession sqlSession = SqlSessionUtil.openSession();// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);//余额不足,抛出异常if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库int count = accountDao.update(fromAct);/*//模拟异常String s = null;s.toString();*/count += accountDao.update(toAct);if (count != 2) {throw new AppException("转账失败,未知原因!");}sqlSession.commit();SqlSessionUtil.close(sqlSession);} }
Exception
package com.wsd.exception;/*** @author: Mr.Wang* @create: 2023-07-02 11:57**/ public class AppException extends Exception {public AppException() {}public AppException(String message) {super(message);} }
package com.wsd.exception;/*** @author: Mr.Wang* @create: 2023-07-02 09:06**/ public class MoneyNotEnoughException extends Exception{public MoneyNotEnoughException() {}public MoneyNotEnoughException(String message) {super(message);} }
servlet
package com.wsd.web;import com.wsd.exception.AppException; import com.wsd.exception.MoneyNotEnoughException; import com.wsd.service.AccountService; import com.wsd.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;/*** @description:* @author: Mr.Wang* @create: 2023-07-02 00:52**/ @WebServlet("/transfer") public class AccountServlet extends HttpServlet {private AccountService accountService = new AccountServiceImpl();@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//获取页面表单数据String fromActno = req.getParameter("fromActno");String toActno = req.getParameter("toActno");Double money = Double.parseDouble( req.getParameter("money") ) ;//调用servicetry {accountService.transfer(fromActno,toActno,money);//Show the result after transfer successresp.sendRedirect(req.getContextPath() + "/success.html");} catch (MoneyNotEnoughException e) {resp.sendRedirect(req.getContextPath() + "/error1.html");} catch (Exception e) {resp.sendRedirect(req.getContextPath() + "/error2.html");}} }
页面
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>银行账户转账</title> </head> <body> <!--/mybatis是应用的根,部署web应用到tomcat的时候一定要注意这个名字--> <form action="/mybatis/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>
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>transfer result</title> </head> <body> <h1>The transfer failed because of insufficient balance</h1> </body> </html>
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>transfer result</title> </head> <body> <h1>The transfer failed, error occurred</h1> </body> </html>
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>transfer result</title> </head> <body> <h1>The transfer success</h1> </body> </html>
test:
table before test:
mybatis 框架中的sqlSession.getMapper(接口类) 可以生成代理对象
//sqlsession.getMapper生成代理对象 private AccountDao accountDao = (AccountDao) SqlSessionUtil.openSession().getMapper(AccountDao.class);package com.wsd.service.impl;import com.wsd.dao.AccountDao; import com.wsd.exception.AppException; import com.wsd.exception.MoneyNotEnoughException; import com.wsd.pojo.Account; import com.wsd.service.AccountService; import com.wsd.util.ProxyUtil; import com.wsd.util.SqlSessionUtil; import org.apache.ibatis.session.SqlSession;/*** @author: Mr.Wang* @create: 2023-07-02 01:19**/ public class AccountServiceImpl implements AccountService {//sqlsession.getMapper生成代理对象private AccountDao accountDao = (AccountDao) SqlSessionUtil.openSession().getMapper(AccountDao.class);@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException,AppException{//创建sql sessionSqlSession sqlSession = SqlSessionUtil.openSession();// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);//余额不足,抛出异常if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库int count = accountDao.update(fromAct);/*//模拟异常String s = null;s.toString();*/count += accountDao.update(toAct);if (count != 2) {throw new AppException("转账失败,未知原因!");}sqlSession.commit();SqlSessionUtil.close(sqlSession);} }
test: