动态创建代理对象的工具类
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); //添加访问修饰符public ctMethod.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
4.0.0 com.wsd web-mybatis01 1.0-SNAPSHOT war web-mybatis01 Maven Webapp http://www.example.com UTF-8 1.7 1.7 org.mybatis mybatis 3.5.10 mysql mysql-connector-java 8.0.30 ch.qos.logback logback-classic 1.2.11 javax.servlet javax.servlet-api 4.0.1 provided web-mybatis01 maven-clean-plugin 3.1.0 maven-resources-plugin 3.0.2 maven-compiler-plugin 3.8.0 maven-surefire-plugin 2.22.1 maven-war-plugin 3.2.2 maven-install-plugin 2.5.2 maven-deploy-plugin 2.8.2 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
update t_act set balance = #{balance} where actno = #{actno} mybatis 配置文件
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
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 sqlSessionThreadLocal SqlSession sqlSession = sqlSessionThreadLocal.get(); //如果 sqlSessionThreadLocal 未获取到 sqlSession,让工厂生产一个新的sqlSession if(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 sqlSessionThreadLocal sqlSessionThreadLocal.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; @Override public 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); @Override public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException,AppException{ //创建sql session SqlSession 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(); @Override protected 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") ) ; //调用service try { accountService.transfer(fromActno,toActno,money); //Show the result after transfer success resp.sendRedirect(req.getContextPath() + "/success.html"); } catch (MoneyNotEnoughException e) { resp.sendRedirect(req.getContextPath() + "/error1.html"); } catch (Exception e) { resp.sendRedirect(req.getContextPath() + "/error2.html"); } } }
页面
银行账户转账
transfer result The transfer failed because of insufficient balance
transfer result The transfer failed, error occurred
transfer result The transfer success
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); @Override public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException,AppException{ //创建sql session SqlSession 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: