mybatisplus 如何动态创建 mapper 接口。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lawler
V2EX    Java

mybatisplus 如何动态创建 mapper 接口。

  •  
  •   lawler 2022-01-23 22:54:46 +08:00 4786 次点击
    这是一个创建于 1367 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在写一个涉及单表操作较多的系统。

    框架组合,spring boot + mybatis plus 。 代码结构,entity -> mapper -> service -> controller

    但是要创建非常多的空白 mapper/service/controller 的 java 文件。

    maper 是个纯接口的空文件,不想写这么多空文件在代码里,如何做到动态注入“接口”文件?

    @Mapper public interface EntityMapper extends BaseMapper {

    }

    甚至,service ,和 Iservice ,也希望通过实体动态生成,直接映射出来接口。

    或者有其他方案?可以尽可能的专注业务功能。

    32 条回复    2022-01-28 13:57:47 +08:00
    geligaoli
        1
    geligaoli  
       2022-01-23 23:32:01 +08:00
    那你得按照 mybatis 的方式,建立 Mapper 的代理对象.实际反而更麻烦. mybatisplus 有很好的代码生成器干嘛不用.
    alva0
        2
    alva0  
       2022-01-23 23:52:33 +08:00 via Android
    Graphql 可能满足你的要求
    jptx
        3
    jptx  
       2022-01-24 00:02:16 +08:00
    service 之类的可以省掉的,直接用 ActiveRecord 模式即可,也可以顺着往下看 SimpleQuery 工具类,但是这两个都没法省下 mapper 。想省 mapper 的话还得另外想办法。
    https://baomidou.com/pages/49cc81/#activerecord-%E6%A8%A1%E5%BC%8F
    lawler
        4
    lawler  
    OP
       2022-01-24 00:42:40 +08:00
    @geligaoli #1 一千多张表,生成一堆空文件在代码里,很糟心的。
    EscYezi
        5
    EscYezi  
       2022-01-24 03:32:46 +08:00 via iPhone   1
    @lawler 统一放一个包里(比如 generated ),只通过插件生成,不去碰那个包。眼不见心不烦
    sagaxu
        6
    sagaxu  
       2022-01-24 06:25:12 +08:00 via Android
    ClassLoader 八股文背的那么溜,总算有机会用了
    xuanbg
        7
    xuanbg  
       2022-01-24 08:03:53 +08:00   1
    我都是只用 mybatis ,从来不用 plus 。个人认为楼主也可以抛弃 mybatis plus ,手写 sql 它不香吗?
    VeryZero
        8
    VeryZero  
       2022-01-24 08:55:23 +08:00
    @xuanbg 先审题,一千多张表手写 sql ,咋想的。。
    zliea
        9
    zliea  
       2022-01-24 08:57:38 +08:00
    Graphql+1
    BaseMapper ???
    thetbw
        10
    thetbw  
       2022-01-24 09:04:14 +08:00
    直接前端传 sql 执行吧,我就见过这种
    ic2y
        11
    ic2y  
       2022-01-24 09:20:06 +08:00
    封装一个通用级的动态 mapper , 支持动态传递表名、字段、where order 等。
    Suaxi
        12
    Suaxi  
       2022-01-24 09:34:54 +08:00
    写个通用 BaseMapper ,枚举里加一个[表名].class 字段,用的时候传具体的表名,反射出对应表的 IService 就可以了
    lawler
        13
    lawler  
    OP
       2022-01-24 09:41:34 +08:00
    @alva0 #2 @ztechstack #9 @thetbw #10
    一个单体应用,并不是一个接口程序,虽然说都是 from 操作,但更多的还是数据的读取展示。

    @jptx #3 有考虑过,但依然还是要生成很多空包。
    @EscYezi #5 会增加包体积,增量发布时更是一言难尽,因为要人工 review 后留档发布代码的 class 图片快照。

    @
    xuanbg #7 懒
    lawler
        14
    lawler  
    OP
       2022-01-24 09:46:39 +08:00
    @ic2y #11 @Suaxi #12
    其实我尝试过,因为 spring 在加载 mybatisplus 时,会初始化 basemaper 的实现,所以通用 mapper 必须做手动实现,需要写大量代码,是非常复杂的一种方式。还有一种是加载前旁注,但会有环境上下文问题。还有一种是加载后修改,但会绑定实体失败。

    如果有现成参考的例子的话,麻烦贴个地址,或者仓库名称,我去学习一下。
    makinomura
        15
    makinomura  
       2022-01-24 10:22:50 +08:00   1
    1. 自定义注解处理器编译时自动生成接口文件
    2. asm 运行时动态生成 class
    lawler
        16
    lawler  
    OP
       2022-01-24 10:42:36 +08:00
    @makinomura #15
    都做过了。看我 14 楼的恢复,注入 bean 时机翻了很多资料,没找到的。。

    1 ,2 步之后得到 cls(接口类),然后注册 bean 。
    BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) SpringUtil.getBeanFactory();
    RootBeanDefinition bean = new RootBeanDefinition(cls);
    beanFactory.registerBeanDefinition(className, bean);

    问题是,在注册依赖 mapper 时报错。注册依赖改为 lazy ,可以不报错,但是 bean 是接口类,不能实例化为 bean 。lazy 首次加载时就报错了。

    接下来,考虑通过 mybatisplus 的类,自动实现生成 mapper 接口的实现类,于是有了下边的代码。
    MybatisConfiguration mmr=new MybatisConfiguration();
    mmr.addMapper(cls); // 这个方法是通过接口类,实现实现类动态生成并加载的。经测试无效。

    所以,思路应该没错,或许是时机或者方法没找对。
    wolfie
        17
    wolfie  
       2022-01-24 10:53:29 +08:00   1
    1. 定义一个通用的 BaseService 、BaseMapper 。
    2. 根据 DO 动态创建一堆 BaseService
    3. 重写 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#currentModelClass
    4. 通用 BaseService 方法内自定义环绕,搭配动态表名 https://baomidou.com/pages/2a45ff/#dynamictablenameinnerinterceptor
    lawler
        18
    lawler  
    OP
       2022-01-24 11:10:09 +08:00
    @wolfie #17 这个文档我看过,是一种思路,但是没有尝试,一来要做大量的改造工作,二来,看到参数是 map 不利于维护。而且跟我实际想要实现的效果不太一样。

    本意是,spring 容器可以通过类型推导加载 bean 。如通过 @Autowired 注入 List<User>、List<Account>..
    List 和 BaseMapper 是一样的接口类。

    我只需要,
    @Autowired BaseMaper<User>、BaseMaper<Account>就可以拿到对应动态生成的实现类。


    换句话说,我理想中的效果是 BaseMaper<T>/Service<T>/Controller <T>,T 是任意表对象,就可以实现,一套 MVC 控制模板。
    micean
        19
    micean  
       2022-01-24 11:16:58 +08:00
    用 jdbctemplate
    自己实现 resultset 的 handler 就可以了
    Suaxi
        20
    Suaxi  
       2022-01-24 11:51:17 +08:00
    @lawler 直接 BaseMaper<T>/Service<T>/Controller <T>好像不行,项目组里目前用的是这种实现方式
    ![]( https://s6.jpg.cm/2022/01/24/LpUXnT.png)

    ![]( https://s6.jpg.cm/2022/01/24/LpUctE.png)

    ![]( https://s6.jpg.cm/2022/01/24/LpU8j6.png)

    新增表的时候手动填一个 xxxDao ,枚举里再加上对应新增的表
    monkeyWie
        21
    monkeyWie  
       2022-01-24 12:02:38 +08:00
    @lawler #18 应该可以实现,但是不能按 type 注入,然后你得找到 mybatis-plus mapper 的实现,批量注册 bean 就行了
    makinomura
        22
    makinomura  
       2022-01-24 12:03:51 +08:00
    @lawler BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 这个里面注册 beanDefinition 即可,注意要使用 MapperFactoryBean
    aguesuka
        23
    aguesuka  
       2022-01-24 17:09:22 +08:00
    你是不是想只要有实体类 Entity, 就可以 @Autowired 注入 BaseMapper<Entity> 然后统一使用 LambdaQueryWrapper?
    这样的话有两个步骤, 根据 Entity.class 生成 EntityWapper implements BaseMapper<Entity>, 将 EntityWapper 注入到 Spring. 现在的进展如何了?
    lawler
        24
    lawler  
    OP
       2022-01-24 21:48:28 +08:00
    @makinomura #22 尝试很久,搞不会了,XY 问题太多。麻烦大佬再指点一下。

    1 ,BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
    - 注册过程中,我需要写大量逻辑判断,来确认哪些是数据库实体。
    - 或者,我需要一个自定义注解来过滤上个问题?

    2 ,MapperFactoryBean 该如何使用?
    - new 一个不可能,因为有上下文环境。
    - 不 new 的时候,他还在第 1 步中,需要我写逻辑判断处理摘出来。再使用他?
    - 鉴于以上两个问题,我不知道怎么使用他。
    wolfie
        25
    wolfie  
       2022-01-25 14:34:18 +08:00   1
    https://github.com/wolfiesonfire/dynamic-mapper

    使用 byte buddy 运行时创建 mapper + service 。
    但是 ServiceImpl 的 baseMapper, Autowired 有点问题。
    lawler
        26
    lawler  
    OP
       2022-01-25 15:25:43 +08:00
    @wolfie #25 十分感谢,service 的 mapper 注入我倒是有办法,我今天有点忙,明天我验证一下,没有问题的话把后续完善一下提 pr 给你看看。

    另,小年快乐。
    keshawnvan
        27
    keshawnvan  
       2022-01-25 16:49:01 +08:00
    看下 tkMybatis 提供的 ActiveRecord 模式
    makinomura
        28
    makinomura  
       2022-01-26 10:37:31 +08:00
    @lawler #24 我简单写了个 demo
    makinomura
        29
    makinomura  
       2022-01-26 10:38:16 +08:00
    public class DomainScanner extends ClassPathBeanDefinitionScanner {
    public DomainScanner(BeanDefinitionRegistry registry) {
    super(registry, false);
    }

    @Override
    protected boolean isCandidateComponent(MetadataReader metadataReader) {
    return true;
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitiOnHolders= super.doScan(
    basePackages);
    for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
    ScannedGenericBeanDefinition beanDefinition = (ScannedGenericBeanDefinition) beanDefinitionHolder
    .getBeanDefinition();
    String beanClassName = beanDefinition.getBeanClassName();
    Class<?> entityClazz = null;
    try {
    entityClazz = Class.forName(beanClassName);
    } catch (ClassNotFoundException e) {
    throw new IllegalArgumentException(beanClassName);
    }
    DynamicMapperCreator dynamicMapperCreator = new DynamicMapperCreator();
    Class<?> mapperClazz = dynamicMapperCreator
    .getOrCreateMapperClazz(entityClazz);
    beanDefinition.setBeanClass(MapperFactoryBean.class);
    ConstructorArgumentValues cOnstructorArgumentValues= new ConstructorArgumentValues();
    constructorArgumentValues.addIndexedArgumentValue(0, mapperClazz);
    beanDefinition
    .setConstructorArgumentValues(constructorArgumentValues);
    beanDefinition.getPropertyValues().add("sqlSessionFactory",
    new RuntimeBeanReference("sqlSessionFactory"));
    }

    return beanDefinitionHolders;
    }
    }

    public class MapperRegister implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(
    BeanDefinitionRegistry registry) throws BeansException {
    new DomainScanner(registry).scan("umoo.wang.domain");
    }

    @Override
    public void postProcessBeanFactory(
    ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
    }
    Chinsung
        30
    Chinsung  
       2022-01-26 11:28:02 +08:00
    可以用 java agent 的方式,去扫描你自定义某个包下的所有 do 类,然后通过字节码框架,比如 bytebuddy 这种,生成所有的 mapper 类
    Chinsung
        31
    Chinsung  
       2022-01-26 11:30:10 +08:00
    @Chinsung #30 不过这样会在编译期有问题
    lawler
        32
    lawler  
    OP
       2022-01-28 13:57:47 +08:00
    回复一下说下现状。
    #25 #29 方案都试过,可以是可以。但还是存在我强调的加载时机问题。

    主要问题是,当代码中存在,@PostConstruct 时,因未被编译并注册,所以找不到依赖。

    目前已经放弃了这个想法。但计划空闲的时候,参考 lombok 的方式,注解生成。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2973 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 36ms UTC 13:07 PVG 21:07 LAX 06:07 JFK 09:07
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86