предисловие
В JDBC в основном используются два типа операторов: один из них — PrepareStatement, который поддерживает параметризацию и предварительную компиляцию, который может поддерживать собственный Sql, а также поддерживает способ установки заполнителей, параметризации входных параметров, предотвращения внедрения Sql. Заявление, которое поддерживает собственный Sql, и существует риск внедрения Sql.
В процессе разработки с Mybatis детали того, какой оператор используется на нижнем уровне, скрыты.Мы используем # и $, чтобы сообщить Mybatis, какую операцию мы на самом деле выполняем, следует ли параметризовать оператор или сохранить его напрямую Исходное состояние Это хорошо.
Сегодня мы в основном рассмотрим производительность системы, связанной с внедрением Sql, при использовании двух символов и анализ исходного кода того, как Mybatis обрабатывает их внутри.
Разница между # и $ при работе с Sql-инъекциями
Используя способность существующего приложения внедрять (вредоносные) SQL-команды в выполнение фонового ядра базы данных, оно может получить доступ к базе данных на уязвимом веб-сайте, введя (вредоносные) операторы SQL в веб-форму, а не намеренно. для выполнения оператора SQL.
Например, чтобы проверить информацию об ученике на основе его имени, будет передан параметр имени. Если предположить, что имя ученика — Фангфанг, тогда Sql
SELECT id,name,age FROM student WHERE name = '方方';
Когда нет анти-Sql-инъекций, наш оператор Sql может быть написан следующим образом:
<select id="fetchStudentByName" parameterType="String" resultType="entity.StudentEntity"> SELECT id,name,age FROM student WHERE name = '${value}' </select>
При нормальных обстоятельствах узнайте информацию об учениках, имена которых совпадают с Фангфанг.
Но если мы внесем какие-то изменения в параметр входящего имени, например что-нибудь ИЛИ 'x'='x, то сплайсированный Sql станет
SELECT id,name,age FROM student WHERE name = 'anything' OR 'x'='x'
Вся студенческая информация в библиотеке была вытащена, разве это не ужасно? Причина в том, что входящее ИЛИ 'x'='x и исходные одинарные кавычки просто образуют 'что угодно' OR 'x'='x', а оборотная сторона OR всегда равна 1, поэтому это эквивалентно выполнение этой библиотеки.Проверьте все операции.
Чтобы предотвратить внедрение Sql, необходимо использовать одинарные кавычки во всем, что угодно ИЛИ 'x'='x как часть параметра, вместо соединения с одинарными кавычками в Sql.
Используйте # для экранирования параметров в Mybatis
<select id="fetchStudentByName" parameterType="String" resultType="entity.StudentEntity"> SELECT id,name,age FROM student WHERE name = #{name} </select>
Давайте посмотрим, как выглядит инструкция Sql, отправляемая в базу данных.
SELECT id,name,age FROM student WHERE name = 'anything\' OR \'x\'=\'x'
Из вышеприведенного кода видно, что все одинарные кавычки в параметрах были переданы, что является заслугой PrepareStatement в JDBC.Если прекомпиляция включена на сервере базы данных, это делает сервер.
Подробнее см. в этой статье, которую я написал ранее: JDBC и Mysql, в которой объясняется, почему PrepareStatement может это делать.
исходный код
В предыдущих статьях мы объяснили основные компоненты процесса выполнения Mybatis, SqlSession предоставляет API для пользовательских операций, а Executor конкретно выполняет операции с базой данных, но на самом деле это будет делегировано интерфейсу StatementHandler внутри Executor.
Класс реализации этого обработчика представляет оператор операции в JDBC. CallableStatementHandler, PrepareStatementHandler и SimpleStatementHandler представляют CallableStatement, PrepareStatement и Statement в JDBC. Внутри этих обработчиков будет вызываться соответствующий оператор в JDBC.
Это аналогично процессу выполнения Mybatis и оригинальному методу JDBC, который мы используем.
Mybatis: Sqlsession -> Executor -> StatementHandler -> ResultHandler
JDBC: Connection -> Statement -> Result
Таким образом, мы можем знать, что операции с оператором JDBC будут выполняться внутри статеменсандлера.
В PrepareStatementHandler оператор будет параметризован с помощью paramterize, в котором он будет делегирован DefualtParameterHandler для работы. Давайте посмотрим на разницу этого кода в Debug с помощью двух разных операторов.
Первый заключается в использовании символа $, который будет склеен непосредственно в Sql.Как видно из рисунка ниже, при параметризации оператор Sql склеен и завершен, см. originSql.
Введите DefualtParameterHandler, как показано на рисунке ниже, мы видим, что ParameterMappings ofboundSql здесь не существует, поэтому нет необходимости выполнять операцию установки соответствующего заполнителя во втором красном поле.
Затем посмотрим, какой результат обработки получит тот же код при использовании #. Как видно из рисунка ниже, при использовании # исходное #{значение} заменяется на ? No., который является заполнителем в JDBC, с которым мы знакомы.
При повторном входе в DefualtParameterHandler будут ParameterMappings, value -> any' ИЛИ 'x'='x', найдите подходящий TypeHandler и вставьте его в PrepareStatement.
**Из вышеприведенного анализа мы получаем, что при использовании {value} напрямую заменяется соответствующим значением, сопоставление параметров отсутствует, и операция установки заполнителей не будет выполняться. } будет заменен на знак ?, есть сопоставление параметров, а операция установки плейсхолдеров будет выполняться в DefaultParameterHandler.
вопрос
1 Почему по умолчанию используется оператор PrepareStatementHandler
Когда заменяются 2 и #, почему соответствующий BoundSql, $ не имеет сопоставления, # имеет сопоставление.
С этими двумя вопросами давайте взглянем на фазу инициализации Mybatis.Для экономии места перечислены только приблизительные пути и коды клавиш.
Mybatis построен с помощью SqlSessionFactory и будет анализировать файл сопоставления.Примерный путь:
SqlSessionFactoryBuilder->XmlConfigBuilder->XMLMapperBuilder->XMLStatementBuilder.
ParseStatementNode XMLStatementBuilder отвечает за создание MappedStatement, сначала ответьте на первый вопрос. Если вы не укажете тип оператора, Mybatis по умолчанию использует PrepareStatementHandler, который здесь используется для выбора того, какой оператор StatementHandler использовать в последующем процессе с помощью RoutingStatementHandler.
Затем переходите ко второму вопросу, как заменяются $ и #.
Как мы упоминали ранее, BoundSql содержит тело Sql, и сопоставление параметров в нем определяет, следует ли параметризовать его позже.При использовании $ и # производительность отличается.
BoudSql происходит от MappedStatement, в MappedStatement задача получения BoundSql делегируется интерфейсу SqlSource. Поэтому мы в основном смотрим на то, как генерируется SqlSource.
XMLLandDriver можно понимать как оператор, используемый для разбора XML-символов, настроенных Mybatis. Он передаст ответственность за синтаксический анализ определенных символов методу parseScriptNode XMLScriptBuilder.
В parseDynamicTags оператор будет обернут в TextSql, а затем будет использоваться метод isDynamic, а в методе будет использоваться GerenericTokenParser, чтобы определить, является ли он динамическим оператором. Если он содержит $, он динамический, если # — нединамический, а используемый обработчик — DynamicCheckerTokenParser.
После входа в метод разбора в основном посмотрите на следующий абзац.
Здесь для дальнейшей обработки выражения будут использоваться различные классы реализации TokenHandler.Вот улучшение Sql с тех пор.При оценке isDynamic используется DynamicCheckerTokenParser, что является самой простой реализацией.
После завершения синтаксического анализа, если isDynamic имеет значение true, это динамический оператор, использующий DynamicSqlSource.
Если он нединамический, он обычно ссылается на оператор с использованием #, используя RawSqlSource, в котором он будет далее анализироваться.
Как видно из рисунка ниже, этот TokenParser на этот раз использует #{} и использует ParameterMappingTokenHandler.
В методе handlerToken объекта ParameterMappingTokenHandler добавление сопоставления параметров и замена #{value} на ? обязанности.
Из вышеизложенного мы можем знать, что использование # на этапе инициализации будет заменено на ? числа и одновременно генерировать сопоставление параметров и использовать $ на этапе инициализации, в этом нет ничего особенного, просто суждение о том, является ли это динамическим оператором.
После инициализации входим в метод getBoundSql, чтобы посмотреть, что в этот момент делают DynamicSqlSource и StaticSource, первый — это DynamicSqlSource.
Среди них DynamicContext будет сгенерирован первым, в основном для создания привязок, один из них — «_parameter» -> «что угодно» ИЛИ «x» = «x», другой — «_databaseId» -> «null».
Затем используется метод применения, который, как я понимаю, должен выполнить замену. В частности, используйте ${} для оценки, что согласуется с приведенным выше, за исключением того, что здесь используется BindingTokenParser.
Взгляните на метод HandleToken класса BindingTokenParser.
Результатом приведенного выше кода является использование Ognl, использование значения в Bindings, поиск соответствующего значения и, наконец, возврат, объединение в Sql, поэтому существует риск внедрения Sql. Используйте значение, потому что, когда Ognl ищет его, он будет использовать значение по умолчанию value, поэтому вам нужно добавить такую пару ключ-значение в привязки.Если вам интересно, вы можете продолжить читать вещи, связанные с ONGL.
Следующим шагом является создание SqlSource с использованием метода синтаксического анализа SqlSourceBuilder.
Как упоминалось выше, в этом методе синтаксического анализа для оценки используется #{}, поэтому, если вы не можете получить доступ к методу handlerToken ParameterMappingTokenHandler, вы не можете добавить сопоставление параметров. Это напрямую возвращает StaticSqlSource, что также объясняет, почему $ используется. , карта параметров пуста.
Следующим шагом является получение BoundSql, который использует StaticSqlSource, который создается непосредственно в соответствии с параметрами, а карта параметров пуста.
При использовании # используется StaticSqlSource, экземпляр которого создается напрямую.Поскольку сопоставление параметров также создается на предыдущем этапе инициализации, это очень простой процесс.
Последующий процесс соответствует нормальному процессу Mybatis.
Суммировать
В этой статье в основном анализируется разница в использовании символов $ и # в Mybatis, а также разница в обработке исходного кода при использовании этих двух символов. Всем рекомендуется использовать знак #, чтобы избежать риска внедрения Sql на уровне orm.