В университете мы узнали, и я также читал во многих местах, что НИКОГДА не следует объединять строки SQL. Да, я знаю, что это из-за SQL-инъекции, и я это знаю.
Однако что, если строка, которую вы хотите объединить, доступна только в коде? Так что это НЕ пользовательский ввод и не имеет к нему никакого отношения.
Например, у меня есть таблица символов с полемcharacter_id. Этот символ имеет множество атрибутов, поэтому во многих других таблицах символ_символа отображается как внешний ключ. Когда я создаю строку в таблице символов, мне нужно создавать строки в других таблицах, где она отображается как внешний ключ.
Для этого мне пришлось бы писать операторы SQL один за другим, поскольку «НИКОГДА не следует использовать конкатенацию». Однако допустимой функцией здесь будет:
public void insertAttribute(String attributeTable, int characterId) {
String sql = "INSERT INTO "+attributeTable+" (character_id) VALUES (?)";
// execute
}
Это можно вызвать с любой атрибутивной таблицей, если строка атрибутивной таблицы не запрашивается у пользователя, а используется только в конкатенации для сокращения кода.
НИКОГДА не следует объединять пользовательский ввод.
Вы можете выполнить конкатенацию, если источник гарантированно безопасен, но на самом деле для этого нет никаких причин. Использование подготовленных операторов не требует дополнительных усилий, возможно, более читабельно и позволяет обрабатывать экранирование, если attributeTable оказывается зарезервированным словом. И нет риска забыть его изменить, если attributeTable в какой-то момент позже начнет поступать из ненадежного источника.
Чтобы понять суть проблемы, прочтите об атаках с помощью SQL-инъекций: owasp.org/www-community/attacks/SQL_Injection. Проблема не в конкатенации, а в объединении пользовательских данных или переменных, которые могут быть подделаны.
@GuyIncognito Вы не можете связать имя таблицы в подготовленном операторе. По крайней мере, не при выборе или вставке. Вы можете сделать это с помощью хранимой процедуры и CallableStatement. Но я не уверен, что оно того стоит.
Когда вам нужно сделать что-то вроде "INSERT INTO "+attributeTable+", это означает, что у вас неправильная структура базы данных. Таблицы должны быть связаны значениями, а не именами.
@YourCommonSense моя база данных выглядит примерно так: создать табличный символ (первичный ключ id серийного ограниченияcharacter_id, имя varchar(255) не равно нулю, профессия varchar(255), .....); И вот одна таблица атрибутов из многих: создайте таблицу body_skill (целочисленное ограничение символа_id body_skill_id ссылается на символ, целое число телосложения по умолчанию 0, целое число выносливости по умолчанию 0); Что в этом плохого? Почему это неправильная структура?
@GuyIncognito Я не уверен, что решение подготовленных утверждений будет лучше. Можете ли вы привести мне пример?
Проблему вопроса невозможно решить с помощью подготовленного оператора, поскольку параметризовать можно только значения, а не имена объектов, например имя таблицы.
Вы можете использовать подготовленные операторы при вставке значений вместо их объединения, как говорили другие. И, как сказал Ваш здравый смысл, если у вас много разных таблиц атрибутов, значит, ваша база данных плохо спроектирована. Каждый атрибут не должен представлять собой отдельную таблицу.
@GuyIncognito Я также не могу заменить имя таблицы подготовленным оператором, так что это ничего не решает. Я не объединяю значения, я объединяю имя таблицы. Это большая разница. Подготовленный оператор не может быть принят. У меня более 100 атрибутов. Мне нужно разделить их на другие таблицы по логическим причинам.
Да. это то, что я сказал. При вставке значений можно использовать подготовленные операторы. Их нельзя использовать при использовании имен динамических таблиц. А ваши «логические доводы» ошибочны, база данных построена неправильно.
@GuyIncognito Если я сохраню все атрибуты в одной таблице, поиск и сортировка будут очень медленными, и их будет сложнее развернуть. не говоря уже о том, что атрибуты также логически разделены. Я протестировал, и это намного быстрее (у меня запрос примерно в 5 раз быстрее). Но вы заявляете, что концепция ошибочна, не зная назначения базы данных или чего-либо о ней? Только на основе 1 запроса?
«Если я сохраню все атрибуты в одной таблице, поиск и сортировка будут очень медленными, и их будет сложнее развернуть», это неправда. Ваши тесты ошибочны (или, возможно, вы выбираете какой-то странный способ). Иметь все в одной таблице должно быть быстрее.
Таблиц может быть несколько: некоторые содержат структуру различных типов атрибутов, а некоторые содержат фактические данные. Вот здесь-то и вступает в игру дизайн. У вас есть больше вариантов дизайна, чем те два, которые вы придумали.




Объединение строк по своей сути не является плохим. Например, хотя это и глупо, это не представляет угрозы для безопасности:
String sql = "INSERT INTO " + "MyTable" + " (character_id) VALUES (?)";
В дальнейшем, если у вас есть ограниченный набор значений для замены "MyTable" и вы контролируете эти значения, вы можете динамически выбирать из них, не создавая угрозы безопасности.
Проблема возникает не при объединении строк и даже не в том, когда пользователи могут выбирать, какие строки объединять. Проблема возникает, когда вы не контролируете эти строки.
Любое значение, которое может быть изменено пользователем, будь то прямой ввод пользователя или косвенное редактирование пользователем других значений базы данных, или любой другой способ, которым пользователи могут изменять используемые значения... Эти значения не следует объединять в код SQL.
Не из-за конкатенации, а из-за обработки пользовательского ввода как кода и его выполнения.
Как показывает ваш пример, одной из очень распространенных причин сделать это вообще является динамическая замена объектов схемы (например, имен таблиц), что обычно невозможно сделать с помощью параметров.
Обратите внимание, что есть и другие причины избегать динамического объединения строк в код SQL. Вы можете упустить возможность оптимизации производительности базы данных, если сделаете это для значений, управляемых параметрами. Вы можете создать прецедент, который позже, менее осторожные разработчики, расширятся до чего-то, что можно инъецировать с помощью SQL. И т. д.
Часто могут быть лучшие подходы. Использование ORM, использование хранимых процедур или просто разрешение строк кода быть больше, чем вам хотелось бы, просто чтобы сохранить их согласованность/ясность/безопасность. Иногда даже простое избегание прецедента, разрешающего такое объединение в большую кодовую базу, само по себе стоит дополнительных усилий.
Это не только пользовательский ввод, но и любой ненадежный источник контента. Например, получение данных из файла или вызов веб-службы и их выполнение как часть вашего оператора SQL также представляет собой риск. Мы понятия не имеем, что ответит этот веб-сервис.
Если вы подтвердите параметр, вы сможете защитить себя от атак sql-инъекций
private static final Set<String> TABLE_WHITELIST = Set.of("FOO", "BAR", "BAZ");
public void insertAttribute(String table, int characterId) {
if (!TABLE_WHITELIST.contains(table)) {
throw new IllegalArgumentException("Table " + table + " not allowed");
}
String sql = "INSERT INTO "+ table +" (character_id) VALUES (?)";
// execute
}