当前位置: 首页 > news >正文

MyBatis03-一个完整的bank transfer MVC框架的代码

一、代码示例

1-1、前端代码

index.html页面(转账页面)

<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>

一个success.html和三个error.html

<body> <h1>转账成功!!!!</h1> </body>
<body> <h1>未知异常</h1> </body>
<body> <h1>转账失败!!!!</h1> </body>
<body> <h1>余额不足!!!!</h1> </body>

1-2、后台代码

0、工具类

/** * mybatis工具类 */ public class SqlSessionUtils { private static SqlSessionFactory sqlSessionFactory; // 工具类中的构造方法都是private,目的:防止创造对象! private SqlSessionUtils(){ } // 类加载时执行 // SqlSessionUtils工具类在进行第一次加载的时候,解析mybatis-config.xml文件,创建sqlSessionFactory对象 static { try { sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { System.err.println("读取 mybatis-config.xml 出错了!!"); e.printStackTrace(); } } // 全局的,服务器级别的,一个服务器当中定义一个即可 private static ThreadLocal<SqlSession> local = new ThreadLocal<>(); // 工具类中所有的方法都是静态的,目的:直接采用类名即可调用,不用new public static SqlSession openSession(){ SqlSession sqlSession = local.get(); if (sqlSession == null){ sqlSession = sqlSessionFactory.openSession(); // 将sqlsession对象绑定到当前线程上 local.set(sqlSession); } return sqlSession; } /** * 关闭SqlSession对象(从当前线程中移除SqlSession对象) */ public static void close(SqlSession sqlSession){ if (sqlSession != null){ sqlSession.close(); // 注意,移除SqlSession对象和当前线程的绑定关系 // 因为Tomcat服务器支持线程池,也就是说,用过的线程对象t1,可能下一次还会使用这个t1线程 local.remove(); } } }

1、从mybatis-config.xml配置文件中获取sqlSessionFactory

2、再从sqlSessionFactory中获取 sqlSession:打开一个新的会话(可以理解为开启一次数据库访问)

类本身:单例工具类
  • 为什么是单例

    • 构造器是private SqlSessionUtils(),外部无法new

    • 内部sqlSessionFactorystatic,整个 JVM 只有一份

    • 方法都是static,不需要实例化,直接SqlSessionUtils.openSession()调用。

  • 意味着
    这个类在 JVM 里就只有一份,不管有多少线程,它们调用的都是同一个SqlSessionUtils


内部的 ThreadLocal:让每个线程有独立 SqlSession
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
  • ThreadLocal是静态的,也只有一份,但每个线程在这个 ThreadLocal 上存的值互相独立

    • 线程 A 调用openSession()时,先get()null→ 创建一个SqlSession→ 存到local

    • 线程 B 调用时,也会得到自己的SqlSession

    • 线程 A 和线程 B 的SqlSession不会互相干扰。

  • 这就是常见的「单例 + ThreadLocal」组合

    类本身是单例(全局工具类),内部用 ThreadLocal 让每个线程隔离各自的状态。


为什么这样设计(Web/服务器场景)
  • 在 Tomcat 这类容器中,请求会被分配到线程池里的线程处理;

  • 每个线程处理自己的数据库会话(SqlSession);

  • 如果直接用一个全局唯一的SqlSession,多线程访问会产生线程安全问题;

  • 所以用 ThreadLocal 保证同一个请求/线程共享同一个SqlSession,线程之间互不干扰

  • 同时提供close()方法做local.remove(),防止线程池复用时数据泄漏。


【回顾】:SqlSession类

SqlSessionFactory:是一个线程安全的单例对象用来创建 SqlSession 的工厂,就像数据库的连接池工厂。

SqlSession 是什么?

是 MyBatis 操作数据库的核心接口,相当于你和数据库沟通的“会话对象”。

你可以用sqlSession做这些事:

操作方法
执行查询selectOne(),selectList()
执行插入insert()
执行更新update()
执行删除delete()
获取 Mapper 接口代理对象sqlSession.getMapper(YourMapper.class)
控制事务(如果不是自动提交)commit(),rollback()

【注意点】:

  • 如果你用的是手动事务(默认是),要记得手动commit()rollback()

  • 最后一定要sqlSession.close(),否则会占用数据库连接资源。


1、controller处理类

@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")); // 调用service方法,完成转账 try { accountService.transfer(fromActno, toActno, money); // 程序走到这里,说明转账成功 // 调用view,完成展示结果 response.sendRedirect(request.getContextPath() + "/success.html"); } catch (MoneyNotEnoughException e) { e.printStackTrace(); response.sendRedirect(request.getContextPath() + "/error2.html"); } catch (TransferException e) { e.printStackTrace(); response.sendRedirect(request.getContextPath() + "/error1.html"); }catch (Exception e){ response.sendRedirect(request.getContextPath() + "/error.html"); } }

【注意点】:

request.getContextPath()讲解

这是 Servlet/JSP 里一个很常见的用法。

  • 返回当前 Web 应用的上下文路径(Context Path)

  • 通俗理解:就是你部署这个 Web 应用时的「根路径」

访问地址getContextPath()返回
http://localhost:8080/myapp/index.jsp/myapp
http://localhost:8080/(应用部署在根目录)""(空字符串)
http://localhost:8080/shop/cart/shop

response.sendRedirect(...)
  • 告诉浏览器:请重新去请求一个新的 URL(返回 302重定向响应)。

  • 浏览器接到后会自动跳转到指定的地址。


2、业务层service类

public class AccountServiceImpl implements AccountService { private AccountDao accountDao = new AccountDaoImpl(); @Override public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException { // 添加事务控制代码 // 确保不同的dao拿到的是同一个sqlSession SqlSession sqlSession = SqlSessionUtils.openSession(); // 1、判断装出账户的余额是否充足 Account fromAct = accountDao.selectByActno(fromActno); Double balance = fromAct.getBalance(); if(money > balance){ // 2、若是余额不足,提示用户 throw new MoneyNotEnoughException("对不起,余额不足"); } // 3、若是转出账户余额充足,更新转出账户余额 Account toAct = accountDao.selectByActno(toActno); fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); int count = accountDao.updateByActno(fromAct); // 模拟异常,判断事务 String s = null; s.toString(); // 4、更新转入账户的余额 count += accountDao.updateByActno(toAct); if (count != 2) { // 转账失败,抛出异常 throw new TransferException("转账失败"); } // 提交事务 sqlSession.commit(); // 提交和关闭事务 SqlSessionUtils.close(sqlSession); } }

【注意点1】:

【注意点2】:在service层实现事务的控制!

因为dao层只是单纯的数据库操作,代码尽可能的少,一个dao层的方法对应一个sql操作。


3、dao层,数据库处理

public class AccountDaoImpl implements AccountDao { @Override public Account selectByActno(String actno) { SqlSession sqlSession = SqlSessionUtils.openSession(); Account selectByActno = (Account) sqlSession.selectOne("selectByActno", actno); return selectByActno; } @Override public int updateByActno(Account act) { SqlSession sqlSession = SqlSessionUtils.openSession(); int count = sqlSession.update("updateByActno", act); return count; } }

【注意】:

一般用mybatis的话,包名不叫dao,而是mapper;

在 MyBatis 的语境下,DAO 层和 Mapper 层是同一个东西,只是叫法不同

dao层的接口也叫:xxxMapper!


二、注意点说明

1、controller层,service层和dao层之间对象的注入,使用接口传递!

2、要确保同一个事物之间,多个不同的dao使用的是一个SqlSession,这样才能回滚成功!使用ThreadLocal实现!

3、事务的控制要在service层!


三、ThreadLocal讲解

ThreadLocal是 Java 中的一个类,主要作用是为每个线程提供独立的变量副本

也就是说,多个线程访问同一个ThreadLocal对象时,彼此之间是隔离的,互不干扰。

ThreadLocal就像是一个线程的私有小抽屉,每个线程只能访问自己抽屉里的数据。

一句话记忆:

ThreadLocal= 线程级别的全局变量,同一线程内处处可访问,线程之间互不干扰。

用完后一定要remove(),否则线程池环境下会内存泄漏


3-1、使用场景举例:

1. 每个线程都要保存自己的用户信息

public class UserContext { private static ThreadLocal<String> currentUser = new ThreadLocal<>(); public static void setUser(String user) { currentUser.set(user); } public static String getUser() { return currentUser.get(); } }

如果两个线程分别执行:

UserContext.setUser("Alice"); UserContext.setUser("Bob");

各自线程中调用UserContext.getUser()返回的将是各自的"Alice""Bob",不会互相影响。

2.多线程中的数据库连接(每个线程一份 Connection)

private static final ThreadLocal<Connection> connHolder = new ThreadLocal<>(); public Connection getConnection() { Connection conn = connHolder.get(); if (conn == null) { conn = DriverManager.getConnection(url, user, pwd); connHolder.set(conn); } return conn; // 保证同一线程内获取的是同一个连接 }

3. SimpleDateFormat 线程安全

// SimpleDateFormat 不是线程安全的,但用 ThreadLocal 可以安全复用 private static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public String format(Date date) { return dateFormat.get().format(date); // 每个线程有自己的实例 }

核心点SimpleDateFormat不是"只读地查一下时间",它在 format/parse 的过程中会反复修改自己内部的 Calendar 对象。这个修改过程不是瞬间完成的,中间会被其他线程插入。

现代 Java 更推荐的做法

如果你用的是Java 8 或更高版本,现在更推荐用DateTimeFormatter,它是线程安全的,不需要 ThreadLocal:

// Java 8+ 推荐写法 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public String format(LocalDate date) { return date.format(FORMATTER); // 直接共享,完全线程安全 }

但如果是维护老项目,或者必须用SimpleDateFormat,你图中的ThreadLocal写法就是标准做法。


3-2、注意事项:

  1. ThreadLocal 使用完要 remove()
    否则会发生内存泄漏,尤其是在使用线程池的场景下。

    try { threadLocal.set("some value"); // 使用 threadLocal 的值 } finally { threadLocal.remove(); // 清理 }
  2. ThreadLocal 是基于当前线程Thread对象内部的一个ThreadLocalMap存储的。


3-3、总结:

要点内容
类名java.lang.ThreadLocal<T>
作用为每个线程提供独立变量副本
常用方法.set(T value).get().remove()
常见用途登录用户上下文、SimpleDateFormat、数据库连接等线程隔离变量
必须清除?是,线程池中尤其要注意调用remove()

3-4、与new对象的核心区别:

问题:我每次new一个对象不也能实现隔离吗?那为什么还要用ThreadLocal呢?

方式变量作用域线程隔离性使用便捷性生命周期绑定
new普通对象每次创建新对象,变量归属对象是(对象不共享)需要自己传来传去和对象绑定
ThreadLocal同一个对象内部,按线程维度自动隔离变量副本是(每个线程自动独立)全局共享对象,线程自动隔离和线程绑定

打个比方来解释:

你可以想象你在写一个网站,用户访问一个页面时,会在后台起一个线程来处理。

你用:

private static ThreadLocal<String> currentUser = new ThreadLocal<>();

这个currentUser是一个全局的工具变量但它背后是为每个线程“自动”分配了一个独立的副本你不需要手动传来传去

而如果你:

new User("Alice")

你确实可以做到隔离,但你得自己把这个User对象一层层传入你程序的每个方法,比如Controller → Service → DAO,很麻烦。

ThreadLocal可以让你任何地方静态方法就获取到当前线程自己的“用户”,就像你随时从自己口袋掏出东西一样,其他人拿不到。


3-5、对比代码来看效果

❌ 普通对象传递(你得传来传去):

public void controller(User user) { service(user); } public void service(User user) { dao(user); } public void dao(User user) { System.out.println(user.getName()); }

✅ ThreadLocal(不需要一层层传)

public class UserContext { private static ThreadLocal<String> currentUser = new ThreadLocal<>(); public static void set(String user) { currentUser.set(user); } public static String get() { return currentUser.get(); } public static void clear() { currentUser.remove(); } }

使用:

UserContext.set("Alice"); // 后续在任何方法里都能获取当前线程自己的用户 String user = UserContext.get();

3-6、ThreadLocal 的最大价值:

  • 你不需要手动传值(框架/工具类能全局访问)

  • 不会线程共享(线程自动隔离

  • 适合做“与当前线程相关的上下文”


3-7、经典应用场景:

  • 当前登录用户信息(Spring Security 就是这么做的)

  • 每线程一个SimpleDateFormat

  • 每线程一个数据库连接(在某些 ORM 框架中)


3-8、底层实现原理

Java 的每个Thread对象内部都有一个ThreadLocalMap

// Thread 类中的源码(简化版) class Thread { ThreadLocal.ThreadLocalMap threadLocals = null; }

当你调用threadLocal.set(value)时,实际上是以ThreadLocal对象key,把值存进了当前线程自己的ThreadLocalMap中。

线程 A 的内存: threadLocals = { ThreadLocal@1a2b3c → "用户A的会话", ThreadLocal@4d5e6f → "数据库连接A" } 线程 B 的内存: threadLocals = { ThreadLocal@1a2b3c → "用户B的会话", // 同一个 ThreadLocal 对象! ThreadLocal@4d5e6f → "数据库连接B" }

关键点同一个ThreadLocal实例,在不同线程的 map 中对应不同的 value

【注意】:这里的 key 不是线程的 key,而是ThreadLocalMap里的 key。

1、拆解结构

// 假设有两个 ThreadLocal 静态变量 // 对象 @1a2b3c private static final ThreadLocal<User> USER_CONTEXT = new ThreadLocal<>(); // 对象 @4d5e6f private static final ThreadLocal<Connection> CONN_HOLDER = new ThreadLocal<>();

线程 A 的内存:

Thread-A { threadLocals: ThreadLocalMap { 条目1: { key: ThreadLocal@1a2b3c(弱引用), value: User对象("用户A") }, 条目2: { key: ThreadLocal@4d5e6f(弱引用), value: Connection对象("连接A") } } }

线程 B 的内存:

Thread-B { threadLocals: ThreadLocalMap { 条目1: { key: ThreadLocal@1a2b3c(弱引用), value: User对象("用户B") }, 条目2: { key: ThreadLocal@4d5e6f(弱引用), value: Connection对象("连接B") } } }

2、关键理解

层级说明
ThreadLocal@1a2b3c全局唯一的静态对象,存在堆中,所有线程共享这个引用
Thread-A.threadLocals线程 A私有的 Map
Thread-B.threadLocals线程 B私有的 Map

同一个ThreadLocal对象,作为 key 出现在不同线程的私有 Map 中,对应不同的 value。


3-9、内存泄漏问题

这是ThreadLocal最容易踩的坑:

1、为什么会泄漏?

ThreadLocalMap的 key 是弱引用(WeakReference)的ThreadLocal对象

// ThreadLocalMap 中的 Entry 结构 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; // 强引用! Entry(ThreadLocal<?> k, Object v) { super(k); // key 是弱引用 value = v; // value 是强引用 } }

问题场景

  1. 线程 A 执行完任务,但线程池复用了该线程(线程不会销毁)

  2. ThreadLocal变量在外部不再被强引用

  3. GC 回收了弱引用的 key(ThreadLocal对象)

  4. ThreadLocalMap中的 Entry 还在,value 是强引用,无法回收

  5. 结果:value 永远滞留在内存中,造成泄漏

2、解决方案:用完必须 remove()

try { threadLocal.set(value); // 业务逻辑... } finally { threadLocal.remove(); // 必须清理! }

3、现代替代方案:InheritableThreadLocal & TransmittableThreadLocal

(1)InheritableThreadLocal— 子线程继承父线程的值
// 子线程可以获取父线程设置的值 InheritableThreadLocal<String> inheritable = new InheritableThreadLocal<>(); inheritable.set("父线程的值"); new Thread(() -> { System.out.println(inheritable.get()); // 输出:"父线程的值" }).start();

局限只适用于直接创建new Thread(),对线程池无效(线程池的线程不是新建的)。

(2)TransmittableThreadLocal(阿里开源)

线程池场景下的完美解决方案,需要引入transmittable-thread-local依赖:

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>(); // 包装线程池 ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(4)); context.set("主线程的值"); ttlExecutor.submit(() -> { System.out.println(context.get()); // 能正确获取:"主线程的值" });
总结对比
特性ThreadLocalInheritableThreadLocalTransmittableThreadLocal
线程隔离
子线程继承✅(仅new Thread)✅(支持线程池)
线程池支持
需手动remove

3-10、ThreadLocal的标准定义方式

private static finalThreadLocal的标准定义方式。

标准写法
public class UserContext { private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>(); }
为什么要static

核心原因:确保所有线程共享同一个ThreadLocal实例

ThreadLocal的隔离机制是以实例为维度的。如果不用static,每个对象都有自己的ThreadLocal实例,就会破坏设计意图:

// ❌ 错误:非 static public class UserContext { private final ThreadLocal<User> currentUser = new ThreadLocal<>(); // 每次 new UserContext() 都会创建新的 ThreadLocal 实例 // 线程 A 在实例1里 set,在实例2里 get → 拿到 null }
// ✅ 正确:static public class UserContext { private static final ThreadLocal<User> currentUser = new ThreadLocal<>(); // 全局唯一实例,所有线程都用它作为 key // 线程 A 的 currentUser.set() 和 currentUser.get() 操作的是同一个 key }

原理回顾ThreadLocalMapThreadLocal对象本身作为 key 的。如果 key 不唯一,就找不到对应的 value。

为什么要private

防止外部直接操作,通过封装的方法控制访问:

public class UserContext { private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>(); public static void set(User user) { CURRENT_USER.set(user); } public static User get() { return CURRENT_USER.get(); } public static void remove() { CURRENT_USER.remove(); } }

这样外部只能UserContext.get(),不能直接UserContext.CURRENT_USER.set(...),避免误用。

为什么要final
  • 防止引用被重新赋值(CURRENT_USER = new ThreadLocal<>()

  • 保证 key 的稳定性,一旦赋值不可更改

  • 语义明确:这是一个常量引用

特殊情况:不用 static 的场景

只有一种情况不需要static:当ThreadLocal本身就是实例的一部分,且每个实例需要独立的线程隔离空间。

// 例如:每个数据库连接池实例有自己的线程隔离 public class ConnectionPool { // 每个 ConnectionPool 实例有独立的线程隔离 private final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>(); public Connection getConnection() { Connection conn = connectionHolder.get(); if (conn == null) { conn = createConnection(); connectionHolder.set(conn); } return conn; } }

但这种情况很少见,绝大多数场景(用户上下文、事务管理、日志追踪等)都是全局唯一ThreadLocal

总结
修饰符作用是否必须
private封装,防止外部直接操作强烈建议
static确保全局唯一 key,线程隔离正确工作必须
final防止引用被篡改,语义明确强烈建议

所以标准就是:

private static final ThreadLocal<T> NAME = new ThreadLocal<>();

四、作用域(Scope)和生命周期

4-1、SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了

因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

4-2、SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

即:一个数据库一个SqlSessionFactory

使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。

因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

4-3、SqlSession

每个线程都应该有它自己的SqlSession 实例

SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域

绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。

如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它

这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

将SqlSession与ThreadLocal绑定,确保一个线程,一个SqlSession实例。


五、使用javassist生成类(※)

观察DAO的实现类:

发现,没有业务逻辑,代码比较少,而且比较固定,就是简单的增删改查,所以,可以使用 javassist动态生成类,不用自己手动写!

Javassist是 Java 中一个非常强大的字节码操作库,可以用来在运行时,动态生成类、修改已有类的方法、字段等。名字是 Java + Assist(辅助)的合成词。


5-1、Javassist 能做什么?

  • 动态生成一个类(Class)

  • 给类添加字段、方法、构造器

  • 修改已有类的方法体(比如用于 AOP)

  • 生成字节码文件(.class)

  • mybatis底层也是用的Javassist!


5-2、基本使用步骤(以动态创建类为例):

假设我们要生成一个名为com.example.Person的类,包含一个字段name和一个sayHello()方法。

1、Step 1:引入依赖(以 Maven 为例)

<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.29.2-GA</version> </dependency>

Step 2:示例代码 — 动态生成类

import javassist.*; public class JavassistDemo { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); // 1. 获取类池 CtClass ctClass = pool.makeClass("com.example.Person"); // 2. 创建一个类名 // 3. 添加字段 private String name; CtField nameField = new CtField(pool.get("java.lang.String"), "name", ctClass); nameField.setModifiers(Modifier.PRIVATE); ctClass.addField(nameField); // 4. 添加 getName 和 setName 方法 ctClass.addMethod(CtNewMethod.getter("getName", nameField)); ctClass.addMethod(CtNewMethod.setter("setName", nameField)); // 5. 添加一个 sayHello 方法 CtMethod sayHello = CtNewMethod.make( "public void sayHello() { System.out.println(\"Hello, my name is \" + this.name); }", ctClass ); ctClass.addMethod(sayHello); // 6. 写出class文件(可选) ctClass.writeFile("output"); // 输出目录 // 7. 加载到当前 JVM 并使用 Class<?> clazz = ctClass.toClass(); Object obj = clazz.newInstance(); // 使用反射调用方法 clazz.getMethod("setName", String.class).invoke(obj, "Alice"); clazz.getMethod("sayHello").invoke(obj); // 输出:Hello, my name is Alice } }

执行后你会得到一个类Person

public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void sayHello() { System.out.println("Hello, my name is " + this.name); } }

代码说明:

步骤操作说明
ClassPool.getDefault()获取类池管理要操作/创建的类
makeClass(String name)创建类动态定义类名
CtField创建字段包含类型、字段名、所属类等信息
CtMethod创建方法可以自己写字符串或用辅助方法
toClass()加载类返回 Java 的Class<?>对象
writeFile()写文件可将生成的.class文件保存到硬盘

5-3、注意事项:

  • Javassist 操作的是字节码层面运行时效率比反射高,但调试较困难。

  • 不支持 lambda、泛型等高级语法(可以手动拼接源码字符串来实现)。

虽然可以用 Javassist 自动生成简单的 CRUD 类,但写起来比直接敲几行 Java 代码还麻烦,因此一般不推荐自己去造轮子。

Javassist = 适合框架开发者做动态增强

业务层 CRUD 直接写类或用现成代码生成器更简单。


5-4、mybatis动态生成dao接口的实现类

MyBatis 的一个核心功能就是可以自动、动态地为你生成DAO 接口的实现类(代理类:dao接口的代理),原理就是Javassist

使用mybatis代理机制的前提:

1、XXXMapper.xml文件中的namespace必须是dao接口的全限定名称;

2、id必须是dao接口的方法名。

使用方法:

你只需要写Mapper 接口(DAO 接口)和对应的XML或注解,MyBatis 会在运行时自动帮你生成接口的实现类

本质就是:

sqlSession.getMapper(Dao接口.class);

它就动态生成了Dao接口的一个代理对象(实现类)

http://www.jsqmd.com/news/1202047/

相关文章:

  • Hermes在Windows上的正确部署:WSL2配置与跨系统协同指南
  • 宝玑中国官方售后服务中心|官方地址及联系电话权威信息通告(2026年7月最新) - 亨得利钟表维修中心
  • yuzu模拟器中文支持完全指南:从方块文字到完美显示的蜕变之旅
  • 2026年北京刑事辩护律师避坑指南:5位办案经验丰富推荐 - 本地品牌推荐
  • ImNodes与Dear ImGui集成指南:打造专业级节点图形界面
  • 宝珀中国官方售后服务中心|官方热线及全部网点地址权威信息公示(2026年7月更新) - 宝珀官方售后服务中心
  • 为什么顶尖咨询公司开始禁用传统PPT?Canva AI自动化工作流正在重构演示经济学
  • AI智能体开发终极指南:从零基础到实战部署的完整教程
  • 为什么选择WeChat Assistant?10大实用功能让你的微信管理效率提升300%
  • 毕设项目分享 深度学习车型检测算法(源码分享)
  • !toy.struct
  • ImNodes完全指南:如何基于Dear ImGui构建高效节点图系统
  • 黄冈人卖金避坑指南:同城老店街坊亲测无套路 - 人间烟火小记
  • 宁波奢二网收帝舵王子,浪琴康卡斯,现金结算即时到位 - 大牌深度测评
  • SpotifyLyrics社区贡献指南:如何报告问题和提交改进
  • 原神抽卡记录永久保存方案:免费开源工具解决三大玩家痛点
  • 如何使用ChatGPT写推荐信?
  • 三步解锁123云盘VIP特权:免费突破下载限制,享受会员级体验
  • 拆解Linux内核网络数据搬运工:sk_buff从生到死的完整生命周期与零拷贝优化的底层密码
  • 2026长沙望城区高端首饰出手干货,梵克雅宝四叶草实时金价参考 - 逸程奢侈品回收中心
  • [Bug已解决] nn.Sequential 类型注解对嵌套列表过于宽泛导致静态检查误报解决方案
  • MyBatis02-mybatis-config.xml配置文件讲解
  • Palantir Java Format:现代Java代码格式化工具的终极指南
  • OptiScaler技术问题排查实战指南:从诊断到修复的完整方案
  • 宁波收江诗丹顿传承,本地多年经营稳定 - 大牌深度测评
  • 2026南京黄金首饰回收测评攻略|五大正规机构对比,高价变现避坑指南 - 每日生活报
  • GPT-5.6 与 Claude 4.8 协作写文章教程:框架生成、内容润色与流程总结
  • 昇腾CANN/asc-devkit: L1与BiasTable Buffer数据搬运
  • 2026南京黄金首饰回收测评攻略|五大正规机构横向对比,高端珠宝变现避坑指南 - 每日生活报
  • 5分钟掌握Scrapling:Python爬虫从零到精通实战指南