设计模式与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接口的方法调用。每次调用时,它都会做三件事:
- 解析方法上的注解或XML映射,获取SQL语句
- 通过SqlSession执行SQL
- 返回结果
// 你写的接口
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调用,这是桥梁
- 装饰器模式负责增强缓存功能,这是优化
你想想看,一次完整的查询流程是这样的:
- 通过SqlSessionFactory工厂拿到SqlSession
- 通过SqlSession获取Mapper代理对象
- 调用代理对象的方法,触发缓存装饰器链
- 缓存没命中,才真正执行SQL
这个流程里,每个模式各司其职,互不干扰。这就是设计模式的魅力——它们不是炫技,而是为了解决实际问题而生的。
我的建议:如果你在写自己的框架或工具库,不妨参考MyBatis这种「模式组合」的思路。别想着用一个模式解决所有问题,而是让不同模式各管一摊,组合起来形成完整方案。
五、知识体系图
下面这张图展示了本章三个设计模式在MyBatis中的位置和关系:
从这张图可以清楚看到:工厂模式在最前面负责创建,代理模式在中间负责桥接,装饰器模式在底层负责优化。它们各自解决不同层面的问题,组合起来才构成了MyBatis强大的ORM能力。
总结一下:设计模式不是纸上谈兵的理论,而是解决实际问题的利器。MyBatis用工厂模式管理对象创建,用代理模式实现接口调用,用装饰器模式增强缓存功能。下次你写代码时,不妨想想:我能不能也用这些模式来解耦、扩展、优化?
公众号:蓝海资料掘金营,微信deep3321