动态创建代理对象的工具类
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
<?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 mapper PUBLIC "-//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 configuration PUBLIC "-//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 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"); } } }
页面
<!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); @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: