V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lawler
V2EX  ›  Java

mybatisplus 如何动态创建 mapper 接口。

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

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

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

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

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

    @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   ·   实用小工具   ·   2962 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 13:27 · PVG 21:27 · LAX 05:27 · JFK 08:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.