Перехватчик Mybatis — получение и выполнение SQL для реализации многоклиентской синхронизации данных.

база данных сервер MyBatis SQL

0. Введение

Недавний проект заключается в упаковке и установке среды J2EE на стороне клиента (используяnwjs+NSISЗапустите установочный пакет), все бизнес-операции выполняются на стороне клиента, а данные сохраняются в базе данных на стороне клиента. База данных на стороне сервера суммирует данные каждой стороны клиента для анализа. Клиентская ORM использует Mybatis. Получить все выполняемые операции через операторы SQL перехватчика Mybatis, периодически синхронизируемые с сервером.

В этой статье рассказывается об использовании перехватчика Mybatis посредством операции перехвата SQL на стороне клиента.

1. Требования к проекту

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

Создайте веб-проект и его операционную среду с помощью установочного пакета NSIS и установите его в каждой ветке, каждая ветка является независимой веб-службой, чтобы обеспечить возможность нормального использования хранилища даже при отсутствии сети (есть локальный сети, а в Интернет нет доступа).Система.В это время в базе данных каждого филиала хранятся данные о работе своего собственного магазина, а данные между каждым магазином изолированы друг от друга.

Но операторы не могут проанализировать агрегатные данные для всех магазинов (таких как общие продажи товаров и т. Д.), поэтому вам необходимо регулярно хранить данные, каждый из которых синхронизирован на сервере базы данных.

  • Так как в магазине нет сети (недоступна синхронизация данных в онлайне, система должна нормально работать), и схема синхронизации в реальном времени исключена.
  • В целях обеспечения безопасности базы данных серверная база данных не может быть открыта для внешнего мира, а также исключена схема использования механизма синхронизации базы данных.
  • Некоторым предприятиям необходимо записывать журналы изменения данных (данные с 1 на 0 на 1, процесс нужно фиксировать), а схема инкрементной синхронизации исключена.

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

2. Решения

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

Как написать SQL в Mybatis:

<insert id="insert">
    INSERT INTO atd681_mybatis_test ( dv ) VALUES ( #{dv} )
</insert>

SQL, который необходимо синхронизировать с сервером для выполнения:

INSERT INTO atd681_mybatis_test ( dv ) VALUES ( 'aaa' )

3. Перехватчик

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

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

покупка продуктов >> мыть овощи >> нарезать овощи >> готовить >> обслуживать >> вымойте посуду

  • Прежде чем начать мыть овощи, операция по покупке продуктов завершена, и вы можете узнать, какие овощи вы купили.
  • Я не начал готовить, когда я мою овощи, поэтому я не знаю, что такое посуду вкус.
  • Перед подачей на стол (когда приготовление закончено) можно узнать вкус блюда.
  • Не знаю, есть ли остатки при подаче
  • Мы можем узнать, остались ли остатки при мытье посуды.

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

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

3.2 Определение перехватчиков

Перехватчик должен быть реализованorg.apache.ibatis.plugin.Interceptorинтерфейс и указать перехватываемый метод.

// 拦截器
@Intercepts(@Signature(type = StatementHandler.class, 
                       method = "update", 
                       args = Statement.class)
            )
public class SQLInterceptor implements Interceptor {

    // 拦截方法后执行的逻辑
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 继续执行Mybatis原有的逻辑
        // proceed中通过反射执行被拦截的方法
        return invocation.proceed();
    }

    // 返回当前拦截的对象(StatementHandler)的动态代理
    // 当拦截对象的方法被执行时, 动态代理中执行拦截器intercept方法.
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    // 设置属性
    @Override
    public void setProperties(Properties properties) {
    }

}

  • @InterceptsАннотации перехватчиков для Mybatis,@SignatureУказывает метод перехвата.
  • Если перехватчик перехватывает несколько методов, в@Interceptsнастроить несколько@Signature(массив) подойдет.
  • Поскольку методы JAVA могут быть перегружены, для определения единственного метода необходимо указать класс (тип), метод (метод) и параметры (аргументы).
  • Перехватчик может перехватитьExecutor,ParameterHandler,ResultSetHandler,StatementHandlerспособ ниже.

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

В файле конфигурации Spring объявите перехватчик и настройте его наSqlSessionFactoryBeanсерединаpluginsатрибут

// Mybatis拦截器
sqlInterceptor(SQLInterceptor)

// Mybatis配置
sqlSessionFactory(SqlSessionFactoryBean) {
    dataSource = ref("dataSource")
    mapperLocations = "classpath*:/com/atd681/mybatis/interceptor/*_mapper.xml"
    
    // 配置Mybatis拦截器
    plugins = [
        sqlInterceptor
    ] 
}

4. Получите и сохраните SQL

Общий процесс обработки SQL Mybatis выглядит следующим образом:

Загрузить SQL >> Разобрать SQL >> Заменить параметры SQL >> выполнить SQL >> получить результат возврата

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

// Mybatis拦截器:拦截所有的增删改SQL,将SQL保持至数据库
// 拦截StatementHandler.update方法
@Intercepts(@Signature(type = StatementHandler.class, 
                       method = "update", 
                       args = Statement.class)
           )
public class SQLInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // invocation.getArgs()可以获取到被拦截方法的参数
        // StatementHandler.update(Statement s)的参数为Statement
        Statement s = (Statement) invocation.getArgs()[0];

        // 数据源为DRUID, Statement为DRUID的Statement
        Statement stmt = ((DruidPooledPreparedStatement) s).getStatement();

        // 配置druid连接时使用filters: stat配置
        if (stmt instanceof PreparedStatementProxyImpl) {
            stmt = ((PreparedStatementProxyImpl) stmt).getRawObject();
        }

        // 数据库提供的Statement可获取参数替换后的SQL(JDBC和DRUID获取的是带?的)
        // 数据库为MySQL,可以直接强制转换为MySQL的PreparedStatement获取SQL
        // SQL在书写时为了格式容器阅读会有换行符(多个空格)存在
        // 为了保存和查看方便去除SQL中的换行及多个空格
        String sql = ((com.mysql.jdbc.PreparedStatement) stmt).asSql().replaceAll("\\s+", " ");

        // 保存SQL的操作必须和当前执行的SQL在同一事务中
        // 使用当前SQL所在的数据库连接执行保存操作即可
        // 目标sql成功时保存sql的方法也同步成功
        Connection conn = stmt.getConnection();

        // 将SQL保存至数据库中
        PreparedStatement ps = null;

        try {
            ps = conn.prepareStatement("INSERT INTO atd681_mybatis_sql (v_sql) VALUES (?)");
            ps.setString(1, sql);

            // 因为和Mybatis的操作在同一事务中
            // 如果本次操作如果失败, 所有操作都回滚
            ps.execute();
        }
        finally {
            if (ps != null) {
                ps.close();
            }
        }

        // 继续执行StatementHandler.update方法
        return invocation.proceed();

    }

}

  • Окончательный SQL можно получить только из объекта PreparedStatement, предоставленного MySQL.
  • Операция сохранения SQL должна быть в той же транзакции, что и операция Mybatis, и должна быть успешной или неудачной одновременно.

5. Тест

Создайте две таблицы в базе данных:

  • atd681_mybatis_test: хранить данные бизнес-тестов
  • atd681_mybatis_sql: SQL для хранения бизнес-операций

СоздайтеDAOиMapperСоздание добавления, удаления и модификации метода SQL

// 数据DAO
@Repository
public interface DataDAO {

    // 添加数据
    void insert(String dv);

    // 更新数据
    void update(String dv);

    // 删除数据
    void delete();

}
<mapper namespace="com.atd681.mybatis.interceptor.DataDAO">
	
	<!-- 添加数据,内容为参数i的值 -->
	<insert id="insert">
		INSERT INTO atd681_mybatis_test ( dv ) VALUES ( #{dv} )
	</insert>
	
	<!-- 更新数据,更新为参数u的值 -->
	<update id="update">
		UPDATE atd681_mybatis_test1 SET dv = #{dv}
	</update>
	
	<!-- 删除数据 -->
	<delete id="delete">
		DELETE FROM atd681_mybatis_test
	</delete>
	
</mapper> 

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

@RestController
public class DataController {

    // 注入DAO
    @Autowired
    private DataDAO dao;

    // 分别执行删除,插入,更新操作
    // 参数i: 插入时的字符串
    // 参数u: 更新时的字符串
    @GetMapping("/mybatis/test")
    @Transactional
    public String excuteSql(String i, String u) {

        // 删除数据后将参数i的内容插件数据库,将数据更新成参数u的内容
        // 该方法添加了事务,3次数据库操作会在同一个事务中执行.
        // Mybatis拦截器会捕获三次数据库SQL插入至数据库中(详见拦截器)
        dao.delete();
        dao.insert(i);
        dao.update(u);

        return "success";
    }

}

запустить службу, доступhttp://localhost:3456/mybatis/test?i=insert&u=update

Программа последовательно выполняет удаление и добавление (содержимое"insert"), обновить (содержимое"update") три операции, после завершения выполнения есть запись в БД (содержимое"update"Поскольку перехватчик сконфигурирован, SQL сохраняется в базе данных перед выполнением каждой операции, поэтому три SQL-запроса также сохраняются в базе данных.

В приведенном выше процессе, в дополнение к 3 бизнес-операциям, есть 3 операции по обслуживанию SQL, поэтому база данных будет выполнять в общей сложности 6 SQL.

  1. выполнить операцию УДАЛИТЬ
  2. Сохраните SQL операции DELETE в 1
  3. Выполнить ВСТАВИТЬ SQL
  4. Сохраните SQL операции INSERT в 3
  5. Выполнить ОБНОВЛЕНИЕ SQL
  6. Сохраните SQL для операции UPDATE в 5

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

6. Пример кода

Хотя Mybatis не так расширяем, как Spring, появление перехватчиков также может помочь нам решить некоторые распространенные проблемы.С помощью перехватчиков могут быть реализованы общие функции, такие как пейджинг и унифицированная настройка параметров.

  • Пример кода адреса:https://github.com/atd681/alldemo
  • Пример названия проекта:atd681-mybatis-interceptor