Добавьте крылья в Mybatis-Plus для поддержки многотабличных запросов

Java
Добавьте крылья в Mybatis-Plus для поддержки многотабличных запросов

использовал раньшеMybatis-Plus, если честно, я лично предпочитаюMybatis-Plus.

ORMИспользуются еще два фреймворка.JPAа такжеMybatis. Говорят, что домашнее использованиеMybatisБолее того, для иностранного использованияJPAболее.

а такжеMybatis-PlusвMybatisНа основе , было добавлено много новых функций.

Давайте придерживаться функций, представленных на официальном сайте, и это многословно:

  1. Неинвазивность: делайте только улучшения без изменений, их внедрение не повлияет на существующие проекты, гладко как шелк.
  2. Низкие потери: базовый CURD автоматически вводится при запуске, производительность практически без потерь и прямая объектно-ориентированная операция.
  3. Мощные операции CRUD: встроенный общий преобразователь и общая служба, большинство операций CRUD для одной таблицы можно реализовать с небольшой настройкой и более мощными условными конструкторами для удовлетворения различных потребностей использования.
  4. Поддержка вызова лямбда-форм: с помощью лямбда-выражений вы можете легко написать различные условия запроса, и вам не нужно беспокоиться о написании неправильных полей.
  5. Поддерживает автоматическую генерацию первичного ключа: поддерживает до 4 стратегий первичного ключа (включая распределенный генератор уникальных идентификаторов — Sequence), которые можно свободно настроить для идеального решения проблемы первичного ключа.
  6. Поддержка режима ActiveRecord: поддержка вызова формы ActiveRecord, класс сущностей может выполнять мощные операции CRUD только путем наследования класса Model.
  7. Поддержка настраиваемых глобальных общих операций: поддержка внедрения глобального общего метода (запись один раз, использование в любом месте)
  8. Встроенный генератор кода: используйте код или подключаемый модуль Maven для быстрого создания кода уровня Mapper, Model, Service, Controller, механизма поддержки шаблонов и других настраиваемых конфигураций, ожидающих вашего использования.
  9. Встроенный плагин пейджинга: на основе физического пейджинга MyBatis разработчикам не нужно заботиться о конкретных операциях.После настройки плагина запись пейджинга эквивалентна обычному запросу списка.
  10. Плагин подкачки поддерживает несколько баз данных: MySQL, MariaDB, Oracle, DB2, H2, HSQL, SQLite, Postgre, SQLServer и т. д.
  11. Встроенный плагин анализа производительности: может выводить Sql-операторы и время их выполнения.Рекомендуется включать эту функцию во время разработки и тестирования, что позволяет быстро выявлять медленные запросы
  12. Встроенный подключаемый модуль глобального перехвата: обеспечивает интеллектуальный анализ и блокировку операций удаления и обновления для всей таблицы, а также может настраивать правила перехвата для предотвращения ошибочных операций.

Подробности смотрите на официальном сайте:mybatis.plus/Новое доменное имя официального сайта тоже корова🍺. В любом случае, все сказано и сделано.

Что касаетсяJPA, хотя я лично думаю, что это слишком жестко, но есть и места, которые стоит изучить.

давно, сMybatis-PlusЕсть хлопотная проблема, то есть если набор данных существует в нескольких таблицах, эти таблицы могут быть один-к-одному, один-ко-многим или многие-к-одному, то мне нужно настроить несколько, если я хочу найти их всех.Mapperметод запроса. Количество строк кода сильно увеличивается.

видел это раньшеMybatis-PlusИсходный код , я думал как сделатьMybatis-PlusПоддержка запросов на объединение нескольких таблиц. Но найти было трудно. потому чтоMybatis-PlusНижний слой поддерживает только одну таблицу.

видел недавноJPAиз@OneToOne,@OneToMany,@ManyToManyЭти аннотации, вдруг у меня в голове мелькнула мысль, будто что-то вродеJPAНасколько проще использовать аннотации таким образом?

Заранее заявляю, это все мои собственные мысли, я не виделJPAисходный код, поэтому реализация может быть такой же, какJPAРазные.

просто сделай это

  1. добавить аннотацию
  2. Обработка аннотаций
  3. Релиз пакета

Некоторые люди могут не знать, на самом делеMybatisОн также поддерживает перехватчики, в этом случае вы можете использовать перехватчики для обработки аннотаций.

Аннотация One2One

@Inherited
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface One2One {

    /**
     * 本类主键列名
     */
    String self() default "id";

    /**
     * 本类主键在关联表中的列名
     */
    String as();

    /**
     * 关联的 mapper
     */
    Class<? extends BaseMapper> mapper();

}

Скажем, если есть две таблицы,Aа такжеBэто отношение один к одному,AповерхностьidсуществуетBстолa_id, связанные таким образом. существуетAиспользуйте эту аннотацию в классе сущностей,selfто естьid,а такжеasто естьa_id,это значитAизidв видеa_idпоинтересоваться, иmapperто естьBизMapper, ниже приведен примерAто естьUserAccount,Bто естьUserAddress.

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "UserAccount对象", description = "用户相关")
public class UserAccount extends Model<UserAccount> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty(value = "昵称")
    private String nickName;

    @TableField(exist = false)
    //把id的值 作为userId  在 UserAddressMapper中 查询
    @One2One(self = "id", as = "user_id", mapper = UserAddressMapper.class)
    private UserAddress address;

    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

Перехватчик Mybatis One2OneInterceptor

Я не буду подробно представлять здесь перехватчик, я уже писал несколько статей о перехватчике Mybatis, если вам интересно, вы можете пойти и посмотреть его.

Перехватчик Mybatis реализует хранение и запрос данных типа Geometry.

Перехватчик Mybatis печатает полный SQL

@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Slf4j
public class One2OneInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if (result == null) {
            return null;
        }
        if (result instanceof ArrayList) {
            ArrayList list = (ArrayList) result;
            for (Object o : list) {
                handleOne2OneAnnotation(o);
            }
        } else {
            handleOne2OneAnnotation(result);
        }
        return result;
    }

    @SneakyThrows
    private void handleOne2OneAnnotation(Object o) {
        Class<?> aClass = o.getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            One2One one2One = field.getAnnotation(One2One.class);
            if (one2One != null) {
                String self = one2One.self();
                Object value = MpExtraUtil.getValue(o, self);
                String as = one2One.as();
                Class<? extends BaseMapper> mapper = one2One.mapper();
                BaseMapper baseMapper = SpringBeanFactoryUtils.getApplicationContext().getBean(mapper);
                QueryWrapper<Object> eq = Condition.create().eq(as, value);
                Object one = baseMapper.selectOne(eq);
                field.set(o, one);
            }
        }
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

MybatisПерехватчики могут перехватывать для различных сценариев, таких как:

  1. Executor: метод перехвата исполнителя.
  2. ParameterHandler: Перехватить обработку параметров.
  3. ResultHandler: Прервать обработку результирующего набора.
  4. StatementHandler: перехватывает обработку сборок синтаксиса Sql.

Здесь нужно найти аннотацию возвращаемого объекта путем перехвата набора результатов.После нахождения аннотации он автоматически запросит базу данных в соответствии с конфигурацией аннотации.После нахождения результата данные будут инкапсулированы в возвращаемый набор результатов. . Это избавит вас от необходимости настраивать его несколько раз.Mapperметод запроса.

Сложность: Хотя в аннотации указано, чтоMapper, а то что получается в перехватчике это ещеBaseMapper, при использованииBaseMapperЭто действительно сложно сделать запрос, я перепробовал много методов, но все в порядке.Mybatis-Plusподдерживать использованиеCondition.create().eq(as, value);Условия сваркиSQL, то вы можете использоватьbaseMapper.selectOne(eq);чтобы узнать.

public class MpExtraUtil {

    @SneakyThrows
    public static Object getValue(Object o, String name) {
        Class<?> aClass = o.getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getName().equals(name)) {
                return field.get(o);
            }
        }
        throw new IllegalArgumentException("未查询到名称为:" + name + " 的字段");
    }
}

MpExtraUtilиспользовать отражение, чтобы получитьidзначение .

Поговорим об аннотации «многие ко многим».

@Inherited
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Many2Many {

    /**
     * 本类主键列名
     */
    String self() default "id";

    /**
     * 本类主键在中间表的列名
     */
    String leftMid();

    /**
     * 另一个多方在中间表中的列名
     */
    String rightMid();

    /**
     * 另一个多方在本表中的列名
     */
    String origin();

    /**
     * 关联的 mapper
     */
    Class<? extends BaseMapper> midMapper();

    /**
     * 关联的 mapper
     */
    Class<? extends BaseMapper> mapper();

}

Предположим, естьA,A_B,Bтри стола, вAИспользуйте эту аннотацию в классе сущностейselfто естьAпервичный ключ таблицыid,leftMidто естьAТаблицаidимя в промежуточной таблице, т.е.a_id,а такжеrightMidдаBИмя первичного ключа таблицы в промежуточной таблице:b_id, originто естьBИсходное имя собственного первичного ключа таблицы, то естьid,midMapperэто промежуточная таблицаMapper, это,A_BсоответствующийMapper,mapperдаBТаблицаMapper.

Это действительно немного извилисто.

есть еще один@One2Manyне сказать и@One2Oneто же, что и дляMany2One, с другой точки зрения, это@One2One.

инструкции

Сначала создается таблица, а затем генератор кода генерирует код одним щелчком мыши.

CREATE TABLE `user_account` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `nick_name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '昵称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户相关';

CREATE TABLE `user_address` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '地址id',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `address` varchar(200) DEFAULT NULL COMMENT '详细地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


CREATE TABLE `user_class` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '课程id',
  `class_name` varchar(20) DEFAULT NULL COMMENT '课程名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


CREATE TABLE `user_hobby` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '爱好id',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `hobby` varchar(40) DEFAULT NULL COMMENT '爱好名字',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


CREATE TABLE `user_mid_class` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '中间表id',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `class_id` bigint(20) DEFAULT NULL COMMENT '课程id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Добавьте зависимости, которые были опубликованы в центральном репозитории и могут использоваться напрямую:

<dependency>
    <groupId>top.lww0511</groupId>
    <artifactId>mp-extra</artifactId>
    <version>1.0.1</version>
</dependency>

Добавить аннотацию к классу запуска

@EnableMpExtra

Настроить перехватчик

Потому что общий проект будет настраивать свои собственныеMybatisConfiguration, после того, как я настрою его здесь, упакую, а затем импортирую, он не вступит в силу.

Поэтому я подумал о компромиссе.

доMybatisConfigurationчерезnewиз, теперь черезMybatisExtraConfig.getMPConfig();получить, таким образом, полученныйMybatisConfigurationПерехватчик уже добавлен.

всеMybatis-PlusПример класса конфигурации, обратите внимание на строку 43:

@Slf4j
@Configuration
@MapperScan(basePackages = "com.ler.demo.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfig {

    private static final String BASE_PACKAGE = "com.ler.demo.";

    @Bean("dataSource")
    public DataSource dataSource() {
        try {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost/mp-extra?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
            dataSource.setUsername("root");
            dataSource.setPassword("adminadmin");

            dataSource.setInitialSize(1);
            dataSource.setMaxActive(20);
            dataSource.setMinIdle(1);
            dataSource.setMaxWait(60_000);
            dataSource.setPoolPreparedStatements(true);
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
            dataSource.setTimeBetweenEvictionRunsMillis(60_000);
            dataSource.setMinEvictableIdleTimeMillis(300_000);
            dataSource.setValidationQuery("SELECT 1");
            return dataSource;
        } catch (Throwable throwable) {
            log.error("ex caught", throwable);
            throw new RuntimeException();
        }
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setVfs(SpringBootVFS.class);
        factoryBean.setTypeAliasesPackage(BASE_PACKAGE + "entity");

        Resource[] mapperResources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml");
        factoryBean.setMapperLocations(mapperResources);
        // 43行 获取配置
        MybatisConfiguration configuration = MybatisExtraConfig.getMPConfig();
        configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.addInterceptor(new SqlExplainInterceptor());
        configuration.setUseGeneratedKeys(true);
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }

    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "transactionManager")
    public PlatformTransactionManager platformTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "transactionTemplate")
    public TransactionTemplate transactionTemplate(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }

}

Установление отношений в классах сущностей

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "UserAccount对象", description = "用户相关")
public class UserAccount extends Model<UserAccount> {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty(value = "昵称")
    private String nickName;

    @TableField(exist = false)
    //把id的值 作为userId  在 UserAddressMapper中 查询
    @One2One(self = "id", as = "user_id", mapper = UserAddressMapper.class)
    private UserAddress address;

    @TableField(exist = false)
    @One2Many(self = "id", as = "user_id", mapper = UserHobbyMapper.class)
    private List<UserHobby> hobbies;

    @TableField(exist = false)
    @Many2Many(self = "id", leftMid = "user_id", rightMid = "class_id", origin = "id"
            , midMapper = UserMidClassMapper.class, mapper = UserClassMapper.class)
    private List<UserClass> classes;

    @Override
    protected Serializable pkVal() {
        return this.id;
    }

}

В основном такие комментарии. да, добавь@TableField(exist = false), иначе будет сообщено об ошибке.

тестовый интерфейс

@Slf4j
@RestController
@RequestMapping("/user")
@Api(value = "/user", description = "用户")
public class UserAccountController {

    @Resource
    private UserAccountService userAccountService;

    @Resource
    private UserAccountMapper userAccountMapper;

    @ApiOperation("查询一个")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "", value = "", required = true),
    })
    @GetMapping(value = "/one", name = "查询一个")
    public HttpResult one() {
        //service
        UserAccount account = userAccountService.getById(1L);
        //mapper
        // UserAccount account = userAccountMapper.selectById(1L);
        //AR模式
        // UserAccount account = new UserAccount();
        // account.setId(1L);
        // account = account.selectById();
        return HttpResult.success(account);
    }
}

Интерфейс очень простой, вызов встроенногоgetById, но нашел все соответствующие данные, все из-за настроенных аннотаций.

Видно, что на самом деле отправлено несколькоSQL. ПервыйuserAccountService.getById(1L), последние отправляются автоматически.

исходный кодgit ee.com/GitHub-2635…

GitHub: GitHub.com/l2d2/tickets - о...

Примерgit ee.com/GitHub-2635…

Суммировать

Я действительно не хочу публиковать слишком много кода, но на самом деле это довольно просто. Адрес исходного кода и адрес примера все опубликованы. Если вам интересно, вы можете взглянуть. Если вы считаете, что это полезно, вы можете заказать егоStar. Каждый может внести свой вклад.

Наконец, приглашаю всех обратить внимание на мой официальный аккаунт, вместе учиться и вместе добиваться успехов. давай 🤣

поискБлог Наньчжаообрати внимание на