mybatisPlus大大的提高了我们开发的速度。因为不需要关心sql。建立好的对象通过mybatisPlus语法糖来拼接sql。但是坏处是语法糖不好统一维护。到处都是语法糖。
所以我们规定建立一层Dao,dao层负责统一管理sql。因为要去除xml里面的sql。写sql容易出问题(字符串容易写错,不同数据库还需要关心不同的特性)
建议Dao统一继承此BaseDao
一个表对应一个实体、一个mapper,一个Dao
Dao继承BaseDao,需要实体继承BaseDomain,mapper继承CustomBaseMapper
如果实体不继承baseDomain,mapper不继承CustomBaseMapper,则dao也无法继承BaseDao
如何有效(强制)的避免以下相同拼接的sql出现在多处?
例如以下的sql拼接语法糖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public DemoService { @Autowired DemoDao demoDao; public void doSomeA() { ... List<Object> list = this.demoDao.lambdaQuery() .eq("Demo.field1", 1) .eq("Demo.field2", 3) .like("Demo.field3", "like") .list(); ... } public void doSomeB() { ... List<Object> list = this.demoDao.lambdaQuery() .eq("Demo.field1", 1) .eq("Demo.field2", 3) .like("Demo.field3", "like") .list(); ... } }
|
虽然我们可以定制规范来约束大家把.点.点.的sql拼接的语法封装成一个方法。但是在人员越来越多的情况下,没有强制的规范约束会变得越来越乱。
如何强制约束
- 通过编写maven的编译插件,检测代码是否有冗余的,或者sql拼接出现在非Dao层中直接编译不通过,予以警告。但是由于学习成本比较大,编译平台过多,就放弃了(有心者可以按照此方法来实现,欢迎交流探讨)。
- 通过Java的调用栈来判断是谁在调用(本文的实现)
原理
通过看源码,语法糖拼接sql之前必定会new com.baomidou.mybatisplus.core.conditions.Wrapper
该类是语法糖对象顶层的超类(abstract class)。
通过切入wrapper的构造方法,判断是谁new的,如果调用者是别的模块,比如在service层中new的Wrapper,则直接报异常。如果是在dao层new的,则允许通过。
我们用javassist工具来切入com.baomidou.mybatisplus.core.conditions.Wrapper 的构造方法。
上代码
spring factories 配置容器启动的监听类
1
| org.springframework.context.ApplicationListener=com.DaoVerify
|
factories可参考springBoot容器启动流程
ENV可参考 获取spring启动环境的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| @Slf4j public static class DaoVerify implements ApplicationListener<ApplicationPreparedEvent> {
@SneakyThrows @Override public void onApplicationEvent(ApplicationPreparedEvent event) {
log.info("begin Weaving for runtime check"); if (ENV.isProd()) { log.info("Non-dev does not check"); return; }
ClassLoader loader = ISqlSegment.class.getClassLoader(); log.info("begin Weaving into Dao for verify caller ,verify package name :dao ,classLoader:{}", loader); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new LoaderClassPath(loader)); CtClass wrapper = pool.get("com.baomidou.mybatisplus.core.conditions.Wrapper"); if (wrapper.isFrozen()) { log.info("isFrozen"); return; }
{ StringBuilder sb = new StringBuilder(); StackTraceElement[] ste = (new Throwable()).getStackTrace(); int daoCount = 0; for (int i = 0; i < ste.length; ++i) { StackTraceElement s = ste[i]; if (s.getClassName().contains("com.baomidou.mybatisplus.core.toolkit.Wrappers$EmptyWrapper")){ return; } String className = s.getClassName(); sb.append(className).append(":").append(s.getMethodName()).append(":").append(s.getLineNumber()).append("\\\\n"); if (className.startsWith("你的项目的Dao层的包名") && !className.contains("BaseDao") && s.getLineNumber() > 0){ daoCount++; } } if (daoCount == 0) { throw new RuntimeException("请把sql拼接的条件写在Dao中\n" + sb); } } wrapper.getConstructors()[0].insertBeforeBody("需要把上面括号中的代码粘贴到此处,为了读者阅读方便就给提到了上面。"); wrapper.toClass(loader, null); log.info("weaving succeed"); } }
|
总结
配置好容器启动监听之后,赶在语法糖超类加载之前,利用javassist修改字节码工具。修改语法糖超类的构造方法。
构造方法执行的时候我们通过(new Throwable)查看java的stackTrace调用栈,如果没有我们的Dao层调用的话,直接抛出异常,这样就起到了强制校验的功能了。