设计模式与MyBatis框架:SqlSessionFactory中的工厂、MapperProxy中的代理、缓存中的装饰器

说实话,很多Java开发者天天用MyBatis,但未必注意到它骨子里流淌着设计模式的血液。我当年第一次看MyBatis源码时,有种「原来设计模式可以这么用」的顿悟感。今天咱们就挑三个最典型的模式来聊聊——工厂、代理、装饰器,看看它们是怎么在MyBatis里各司其职的。

一、SqlSessionFactory中的工厂模式

工厂模式,说白了就是帮你把「创建对象」这件事封装起来。你想想看,如果每次用MyBatis都要手动new一个SqlSession,那代码得多难看?

核心思想:把对象的创建逻辑集中管理,客户端只需要告诉工厂「我要什么」,不用关心「怎么造」。

MyBatis里的SqlSessionFactory就是典型的工厂。它负责创建SqlSession实例。我习惯把SqlSessionFactory理解成一个「会话工厂」——你给它一个配置,它就能生产出数据库会话。

// 传统方式:手动创建,耦合度高
// SqlSession session = new SqlSessionImpl(...);

// 工厂方式:解耦,统一管理
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
SqlSession session = factory.openSession();

这里有个细节值得注意:SqlSessionFactory本身也是通过建造者模式创建的。SqlSessionFactoryBuilder负责解析配置文件,然后构建出工厂实例。这种组合使用设计模式的方式,在成熟框架里非常常见。

我的经验:我在项目中遇到过有人把SqlSessionFactory写成单例,但忘了它是线程安全的。其实MyBatis官方文档明确说了,SqlSessionFactory应该是单例的,一个数据库对应一个实例。别重复创建,浪费资源。

二、MapperProxy中的代理模式

用过MyBatis的都知道,我们只需要写一个接口,不用写实现类,就能直接调用数据库操作。这是怎么做到的?答案就是动态代理。

MapperProxy实现了InvocationHandler接口,它拦截所有对Mapper接口的方法调用。每次调用时,它都会做三件事:

  1. 解析方法上的注解或XML映射,获取SQL语句
  2. 通过SqlSession执行SQL
  3. 返回结果
// 你写的接口
public interface UserMapper {
    User selectById(int id);
}

// MyBatis内部帮你生成的代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1);  // 实际调用的是MapperProxy的invoke方法

嗯,这里要注意:代理模式的核心价值在于「控制访问」。MyBatis用代理模式,把SQL执行、参数绑定、结果映射这些复杂逻辑全部隐藏起来了。你看到的只是一个干净的接口调用。

避坑指南:我曾经遇到过一个性能问题——在循环里反复调用session.getMapper()。每次获取Mapper都会创建新的代理对象,虽然开销不大,但循环上千次就明显慢了。正确的做法是:在方法开始处获取一次Mapper,复用即可。

三、缓存中的装饰器模式

装饰器模式,我理解就是「给对象穿衣服」——一层层地往上加功能,而不改变原有对象的结构。MyBatis的缓存设计就是教科书级的装饰器应用。

MyBatis有一级缓存和二级缓存。一级缓存是SqlSession级别的,默认开启。二级缓存是namespace级别的,需要手动开启。但你可能不知道,这些缓存是通过装饰器层层包装实现的。

// 核心接口
public interface Cache {
    void putObject(Object key, Object value);
    Object getObject(Object key);
}

// 基础实现:PerpetualCache
public class PerpetualCache implements Cache {
    private Map<Object, Object> cache = new HashMap<>();
    // 基本put/get操作
}

// 装饰器:给缓存加过期时间
public class ScheduledCache implements Cache {
    private Cache delegate;
    private long flushInterval;
    // 在get时检查是否过期
}

// 装饰器:给缓存加同步锁
public class SynchronizedCache implements Cache {
    private Cache delegate;
    // 所有方法加synchronized
}

你看,PerpetualCache是基础实现,然后ScheduledCache、SynchronizedCache、LoggingCache等装饰器一层层包上去。每个装饰器只负责一件事,组合起来就形成了功能完整的缓存系统。

为什么用装饰器而不是继承?因为缓存的功能组合是动态的。你可以在配置里决定用哪些装饰器,继承做不到这种灵活性。装饰器模式让你像搭积木一样组合功能。

我个人觉得,MyBatis的缓存装饰器设计是整框架里最优雅的部分之一。它完美体现了「开闭原则」——对扩展开放,对修改关闭。想加新功能?写个新装饰器就行,不用动原有代码。

四、三者如何协作

这三个模式在MyBatis里不是孤立的,它们协同工作:

  • 工厂模式负责创建SqlSession,这是入口
  • 代理模式负责拦截Mapper调用,这是桥梁
  • 装饰器模式负责增强缓存功能,这是优化

你想想看,一次完整的查询流程是这样的:

  1. 通过SqlSessionFactory工厂拿到SqlSession
  2. 通过SqlSession获取Mapper代理对象
  3. 调用代理对象的方法,触发缓存装饰器链
  4. 缓存没命中,才真正执行SQL

这个流程里,每个模式各司其职,互不干扰。这就是设计模式的魅力——它们不是炫技,而是为了解决实际问题而生的。

我的建议:如果你在写自己的框架或工具库,不妨参考MyBatis这种「模式组合」的思路。别想着用一个模式解决所有问题,而是让不同模式各管一摊,组合起来形成完整方案。

五、知识体系图

下面这张图展示了本章三个设计模式在MyBatis中的位置和关系:

MyBatis中的设计模式协作图 工厂模式 SqlSessionFactory 创建SqlSession实例 封装配置解析逻辑 代理模式 MapperProxy 拦截接口方法调用 动态生成实现类 装饰器模式 缓存装饰器链 PerpetualCache +ScheduledCache等 一次查询的完整流程 1. SqlSessionFactory.openSession() → 创建SqlSession 2. SqlSession.getMapper() → 返回MapperProxy代理对象 3. 调用代理方法 → 触发缓存装饰器链检查 4. 缓存未命中 → 执行SQL → 结果存入缓存 三个模式各司其职,组合成完整的ORM框架

从这张图可以清楚看到:工厂模式在最前面负责创建,代理模式在中间负责桥接,装饰器模式在底层负责优化。它们各自解决不同层面的问题,组合起来才构成了MyBatis强大的ORM能力。

总结一下:设计模式不是纸上谈兵的理论,而是解决实际问题的利器。MyBatis用工厂模式管理对象创建,用代理模式实现接口调用,用装饰器模式增强缓存功能。下次你写代码时,不妨想想:我能不能也用这些模式来解耦、扩展、优化?

公众号:蓝海资料掘金营,微信deep3321