После использования перехватчика MyBatis рыбачить пришлось долго. 🐟

Java задняя часть
После использования перехватчика MyBatis рыбачить пришлось долго. 🐟

Это седьмой день моего участия в первом испытании обновлений 2022. Подробную информацию о мероприятии см.:Вызов первого обновления 2022 г.

Сцены

При разработке серверных сервисов популярной комбинацией фреймворков является SSM (SpringBoot + Spring + MyBatis).Когда мы разрабатываем некоторые бизнес-системы, будет много таблиц бизнес-данных, и информация в таблицах будет начинаться с новой вставки, В целом Многие операции могут быть выполнены в течение жизненного цикла.

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

Предположим, наша таблица заказовt_orderРезультат выглядит следующим образом:

Когда заказ создан, его необходимо установитьinsert_by,insert_time,update_by,update_timeзначение;

При обновлении статуса заказа вам нужно только обновитьupdate_by,update_timeзначение .

Как с этим обращаться?

Маггловская практика

Самый простой способ, и самый простой способ думать об этом, — это обрабатывать соответствующие поля в коде каждой бизнес-обработки.

Например, в методе создания заказа обработка выглядит следующим образом:

public void create(Order order){
    // ...其他代码
    // 设置审计字段
    Date now = new Date();
    order.setInsertBy(appContext.getUser());
    order.setUpdateBy(appContext.getUser());
    order.setInsertTime(now);
    order.setUpdateTime(now);
    orderDao.insert(order);
}

Способ обновления заказа установлен толькоupdateByиupdateTime:

public void update(Order order){
    // ...其他代码

    // 设置审计字段
    Date now = new Date();
    order.setUpdateBy(appContext.getUser());
    order.setUpdateTime(now);
    orderDao.insert(order);
}

Хотя этот метод может завершить функцию, есть некоторые проблемы:

  • Необходимо решить, какие поля устанавливать в каждом методе в соответствии с разной бизнес-логикой;
  • При наличии большего количества бизнес-моделей в бизнес-методе каждой модели необходимо задавать настройки, а повторяющихся кодов слишком много.

Затем, когда мы узнаем, что с этим методом есть проблема, мы должны найти хороший метод, не так ли?

элегантный подход

Поскольку наша структура уровня сохраняемости больше использует MyBatis, мы используем перехватчики MyBatis для выполнения наших функций.

Прежде всего, давайте разберемся, что такое перехватчик?

Что такое перехватчик?

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

Перехватчик MyBatis может перехватывать интерфейсы Executor, StatementHandler, PameterHandler и ResultSetHandler, то есть он будет проксировать эти четыре типа объектов.

Первоначальная цель дизайна перехватчика - позволить пользователям не изменять исходный код MyBatis в процессе обработки MyBatis и может быть интегрирована во весь процесс выполнения в виде подключаемого модуля.

Например в MyBatisExecutorимеютBatchExecutor,ReuseExecutor,SimpleExecutorиCachingExecutor, если эти несколько реализацийqueryНи один из методов не может удовлетворить ваши потребности Мы можем перехватить исходный код MyBatis без прямой модификации исходного кода путем установки перехватчика.Executorинтерфейсqueryметод после перехвата реализует собственную логику метода запроса.

Перехватчики в MyBatis представлены интерфейсом Interceptor, имеющим три метода.

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

Метод плагина используется перехватчиком для инкапсуляции целевого объекта.С помощью этого метода мы можем вернуть сам целевой объект или вернуть для него прокси.

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

Метод setProperties используется для указания некоторых свойств в файле конфигурации Mybatis.

Обновите поля аудита с помощью перехватчиков

Так как же нам реализовать функцию присвоения значений полям аудита через перехватчики?

Когда мы заказываем создание и модификацию, по сути, это реализация вставки MyBatis, оператор обновления MyBatis обрабатывается Исполнителем.

Мы, исполнитель через перехватчик, можем перехватить, затем установить значение атрибута insert_by, insert_time, update_by, update_time, подобное может быть перехватчиком, который будет вставлен в объект данных в соответствии с выполнением оператора.

пользовательский перехватчик

настроитьInterceptorСамое главное добитьсяpluginМетоды иinterceptметод.

существуетpluginВ методе мы можем решить, следует ли перехватывать, а затем решить, какой целевой объект вернуть.

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

заpluginЧто касается метода, Mybatis уже предоставил нам реализацию. Есть Мибатис по имениPluginкласс со статическим методом в немwrap(Object target,Interceptor interceptor), с помощью которого вы можете решить, является ли возвращаемый объект целевым объектом или соответствующим прокси.

Но тут еще есть проблема, то есть как мы узнаем в перехватчике, что вставляемая таблица имеет поля аудита, которые нужно обработать?

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

то естьНам нужно уметь различать, какие объекты нуждаются в обновлении поля аудита..

Здесь мы можем определить интерфейс, чтобы все модели, которым необходимо обновить поле аудита, реализовывали этот интерфейс единообразно.Этот интерфейс действует как маркер.

public interface BaseDO {
}

public class Order implements BaseDO{

    private Long orderId;

    private String orderNo;

    private Integer orderStatus;

    private String insertBy;

    private String updateBy;

    private Date insertTime;

    private Date updateTime;
    //... getter ,setter
}

Далее мы можем реализовать наш собственный перехватчик.

@Component("ibatisAuditDataInterceptor")
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class})})
public class IbatisAuditDataInterceptor implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(IbatisAuditDataInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 从上下文中获取用户名
        String userName = AppContext.getUser();
        
        Object[] args = invocation.getArgs();
        SqlCommandType sqlCommandType = null;
        
        for (Object object : args) {
            // 从MappedStatement参数中获取到操作类型
            if (object instanceof MappedStatement) {
                MappedStatement ms = (MappedStatement) object;
                sqlCommandType = ms.getSqlCommandType();
                logger.debug("操作类型: {}", sqlCommandType);
                continue;
            }
            // 判断参数是否是BaseDO类型
            // 一个参数
            if (object instanceof BaseDO) {
                if (SqlCommandType.INSERT == sqlCommandType) {
                    Date insertTime = new Date();
                    BeanUtils.setProperty(object, "insertedBy", userName);
                    BeanUtils.setProperty(object, "insertTimestamp", insertTime);
                    BeanUtils.setProperty(object, "updatedBy", userName);
                    BeanUtils.setProperty(object, "updateTimestamp", insertTime);
                    continue;
                }
                if (SqlCommandType.UPDATE == sqlCommandType) {
                    Date updateTime = new Date();
                    BeanUtils.setProperty(object, "updatedBy", userName);
                    BeanUtils.setProperty(object, "updateTimestamp", updateTime);
                    continue;
                }
            }
            // 兼容MyBatis的updateByExampleSelective(record, example);
            if (object instanceof ParamMap) {
                logger.debug("mybatis arg: {}", object);
                @SuppressWarnings("unchecked")
                ParamMap<Object> parasMap = (ParamMap<Object>) object;
                String key = "record";
                if (!parasMap.containsKey(key)) {
                    continue;
                }
                Object paraObject = parasMap.get(key);
                if (paraObject instanceof BaseDO) {
                    if (SqlCommandType.UPDATE == sqlCommandType) {
                        Date updateTime = new Date();
                        BeanUtils.setProperty(paraObject, "updatedBy", userName);
                        BeanUtils.setProperty(paraObject, "updateTimestamp", updateTime);
                        continue;
                    }
                }
            }
            // 兼容批量插入
            if (object instanceof DefaultSqlSession.StrictMap) {
                logger.debug("mybatis arg: {}", object);
                @SuppressWarnings("unchecked")
                DefaultSqlSession.StrictMap<ArrayList<Object>> map = (DefaultSqlSession.StrictMap<ArrayList<Object>>) object;
                String key = "collection";
                if (!map.containsKey(key)) {
                    continue;
                }
                ArrayList<Object> objs = map.get(key);
                for (Object obj : objs) {
                    if (obj instanceof BaseDO) {
                        if (SqlCommandType.INSERT == sqlCommandType) {
                            Date insertTime = new Date();
                            BeanUtils.setProperty(obj, "insertedBy", userName);
                            BeanUtils.setProperty(obj, "insertTimestamp", insertTime);
                            BeanUtils.setProperty(obj, "updatedBy", userName);
                            BeanUtils.setProperty(obj, "updateTimestamp", insertTime);
                        }
                        if (SqlCommandType.UPDATE == sqlCommandType) {
                            Date updateTime = new Date();
                            BeanUtils.setProperty(obj, "updatedBy", userName);
                            BeanUtils.setProperty(obj, "updateTimestamp", updateTime);
                        }
                    }
                }
            }
        }
        return invocation.proceed();
    }

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

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

Мы можем видеть по приведенному выше коду, мы настраиваем InterceptorIbatisAuditDataInterceptorДостигнутоInterceptorинтерфейс.

на нашем перехватчике@Interceptsаннотация,typeПараметр указывает, что перехваченный классExecutorреализация интерфейса,methodперехват указанного параметраExecutorсерединаupdateметод, потому что добавление, удаление и изменение операций базы данных выполняются черезupdateметод выполняется.

Настройте плагин перехватчика

После определения перехватчика вам нужно указать перехватчик дляSqlSessionFactoryBeanизpluginsдля вступления в силу. Итак, настройте его следующим образом.

<bean id="transSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="transDataSource" />
    <property name="mapperLocations">
        <array>
            <value>classpath:META-INF/mapper/*.xml</value>
        </array>
    </property>
    <property name="plugins">
        <array>
            <!-- 处理审计字段 -->
            <ref bean="ibatisAuditDataInterceptor" />
        </array>
    </property>

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

резюме

В этом выпуске Сяо Хей рассказывает, как изящно обрабатывать операции обновления полей аудита, которые часто используются в нашей повседневной разработке.

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

В конце концов, жизнь коротка, пишите меньше кода и больше ловите рыбу.

Если эта статья была вам полезна, поддержите Сяо Хэя лайком и поддержкой.

Я Сяо Хей, программист, который «выслеживает» Интернет.

Текущая вода не соревнуется за первое, а говорить дорого