Разлика между изявление и подготвено изявление

Java Top

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Общ преглед

В този урок ще изследваме разликите между интерфейсите Statement на JDBC и PreparedStatement . Няма да покриваме CallableStatement , интерфейс на JDBC API, който се използва за изпълнение на съхранени процедури.

2. Интерфейс на JDBC API

Както Statement, така и PreparedStatement могат да се използват за изпълнение на SQL заявки. Тези интерфейси изглеждат много сходни. Те обаче се различават значително един от друг по характеристики и производителност:

  • Изявление - Използва се за изпълнение на базирани на низове SQL заявки
  • PreparedStatement - Използва се за изпълнение на параметризирани SQL заявки

За да можем да използваме Statement и PreparedStatement в нашите примери, ще декларираме h2 JDBC конектора като зависимост в нашия файл pom.xml :

 com.h2database h2 1.4.200 

Нека дефинираме обект, който ще използваме в тази статия:

public class PersonEntity { private int id; private String name; // standard setters and getters }

3. Изявление

Първо, интерфейсът Statement приема низове като SQL заявки. По този начин кодът става по-малко четим, когато обединим SQL низовете:

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES(" + personEntity.getId() + ", '" + personEntity.getName() + "')"; Statement statement = connection.createStatement(); statement.executeUpdate(query); }

На второ място, той е уязвим за SQL инжектиране . Следващите примери илюстрират тази слабост.

В първия ред актуализацията ще зададе колоната „ име “ на всички редове на „ хакер “, тъй като всичко след „-“ се интерпретира като коментар в SQL и условията на изявлението за актуализация ще бъдат игнорирани. Във втория ред вмъкването ще се провали, тъй като кавичката в колоната „ име “ не е избягвана:

dao.update(new PersonEntity(1, "hacker' --")); dao.insert(new PersonEntity(1, "O'Brien"))

На трето място, JDBC предава заявката с вградени стойности в базата данни . Следователно няма оптимизация на заявките и най-важното е, че двигателят на базата данни трябва да осигури всички проверки . Също така заявката няма да се появи като същата за базата данни и ще попречи на използването на кеша . По същия начин пакетните актуализации трябва да се изпълняват отделно:

public void insert(List personEntities) { for (PersonEntity personEntity: personEntities) { insert(personEntity); } }

Четвърто, интерфейсът на Statement е подходящ за DDL заявки като CREATE, ALTER и DROP :

public void createTables() { String query = "create table if not exists PERSONS (ID INT, NAME VARCHAR(45))"; connection.createStatement().executeUpdate(query); }

И накрая, интерфейсът на Statement не може да се използва за съхранение и извличане на файлове и масиви .

4. Подготвена декларация

Първо, PreparedStatement разширява интерфейса Statement . Той има методи за обвързване на различни типове обекти , включително файлове и масиви. Следователно кодът става лесен за разбиране :

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.executeUpdate(); }

На второ място, той предпазва от SQL инжектиране , като избягва текста за всички предоставени стойности на параметрите:

@Test void whenInsertAPersonWithQuoteInText_thenItNeverThrowsAnException() { assertDoesNotThrow(() -> dao.insert(new PersonEntity(1, "O'Brien"))); } @Test void whenAHackerUpdateAPerson_thenItUpdatesTheTargetedPerson() throws SQLException { dao.insert(Arrays.asList(new PersonEntity(1, "john"), new PersonEntity(2, "skeet"))); dao.update(new PersonEntity(1, "hacker' --")); List result = dao.getAll(); assertEquals(Arrays.asList( new PersonEntity(1, "hacker' --"), new PersonEntity(2, "skeet")), result); }

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

Освен това тази функция ускорява комуникацията между базата данни и JVM чрез двоичен протокол, различен от SQL. Това означава, че в пакетите има по-малко данни, така че комуникацията между сървърите става по-бърза.

Четвърто, PreparedStatement осигурява пакетно изпълнение по време на единична връзка с база данни . Нека да видим това в действие:

public void insert(List personEntities) throws SQLException { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); for (PersonEntity personEntity: personEntities) { preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.addBatch(); } preparedStatement.executeBatch(); }

След това PreparedStatement предоставя лесен начин за съхраняване и извличане на файлове с помощта на BLOB и CLOB типове данни . В същия дух помага за съхраняване на списъци чрез преобразуване на java.sql.Array в SQL масив.

И накрая, PreparedStatement реализира методи като getMetadata (), които съдържат информация за върнатия резултат.

5. Заключение

В този урок представихме основните разлики между PreparedStatement и Statement . И двата интерфейса предлагат методи за изпълнение на SQL заявки, но е по-подходящо да се използва Statement за DDL заявки и PreparedStatement за DML заявки.

Както обикновено, всички примери за кодове са достъпни в GitHub.

Дъно на Java

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА