19. Веб-додаток MVC у трирівневій архітектурі — Приклад 5, MySQL
19.1. База даних MySQL
У цій версії ми розмістимо список осіб у таблиці бази даних MySQL 4.x. Ми використовували пакет [Apache – MySQL – PHP], доступний за посиланням [http://www.easyphp.org]. Наведені нижче знімки екрана взяті з клієнта EMS MySQL Manager Lite [http://www.sqlmanager.net/fr/products/mysql/manager], безкоштовного клієнта для адміністрування SGBD MySQL.
База даних має назву [dbpersonnes]. Вона містить таблицю [PERSONNES]:

Таблиця [PERSONNES] міститиме список осіб, якими керує веб-додаток. Вона була створена за допомогою таких команд SQL:
MySQL 4.x виглядає біднішим за два попередні SGBD. Мені не вдалося встановити обмеження (checks) для таблиці.
- рядок 10: таблиця повинна мати тип [InnoDB], а не тип [MyISAM], який не підтримує транзакції.
- рядок 2: первинний ключ має тип auto_increment. Якщо вставити рядок без значення для стовпця ID таблиці, MySQL автоматично згенерує ціле число для цього стовпця. Це дозволить нам уникнути необхідності самостійно генерувати первинні ключі.
Таблиця [PERSONNES] може мати такий вміст:

Ми знаємо, що під час вставки об’єкта [Personne] нашим шаром [dao] поле [id] цього об’єкта до вставки дорівнює -1, а після вставки має значення, відмінне від -1, причому це значення є первинним ключем, присвоєним новому рядку, вставленому в таблицю [PERSONNES]. Давайте на прикладі розглянемо, як ми зможемо дізнатися це значення.
![]() |
![]() |
Замовлення SQL
дозволяє дізнатися останнє значення, вставлене в поле ID таблиці. Його слід відправити після вставки. Це відрізняється від запитів SGBD, [Firebird] та [Postgres], де запитувалося значення первинного ключа доданої особи ще до введення даних. Ми будемо використовувати його у файлі [personnes-mysql.xml], який об’єднує команди SQL, що генеруються у базі даних.
19.2. Проєкт Eclipse для шарів [dao] та [service]
Для розробки шарів [dao] та [service] нашого додатка з базою даних MySQL ми будемо використовувати такий проект Eclipse [mvc-personnes-05]:

Цей проєкт є простим Java-проєктом, а не веб-проєктом Tomcat.
Папка [src]
Ця папка містить вихідний код шарів [dao] та [service], а також файли конфігурації цих двох шарів:

Усі файли, у назві яких міститься [mysql], могли зазнати змін або залишитися без змін у порівнянні з версіями для Firebird та Postgres. Далі ми опишемо ті, що зазнали змін.
Папка [database]
Ця папка містить скрипт для створення бази даних MySQL з даними про осіб:
![]()
Папка [lib]
Ця папка містить архіви, необхідні для роботи програми:
![]() |
Зверніть увагу на наявність драйвера JDBC SGBD MySQL. Усі ці архіви входять до складу Classpath проекту Eclipse.
19.3. Рівень [dao]
Рівень [dao] виглядає наступним чином:

Ми наводимо лише зміни порівняно з версією [Firebird].
Файл маппінгу [personne-mysql.xml] має такий вигляд:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap>
<!-- псевдонім класу [Personne] -->
<typeAlias alias="Personne.classe"
type="istia.st.mvc.personnes.entites.Personne"/>
<!-- таблиця відповідності [PERSONNES] — об’єкт [Personne] -->
<resultMap id="Personne.map"
class="istia.st.mvc.personnes.entites.Personne">
<result property="id" column="ID" />
<result property="version" column="VERSION" />
<result property="nom" column="NOM"/>
<result property="prenom" column="PRENOM"/>
<result property="dateNaissance" column="DATENAISSANCE"/>
<result property="marie" column="MARIE"/>
<result property="nbEnfants" column="NBENFANTS"/>
</resultMap>
<!-- список усіх осіб -->
<select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM,
PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select>
<!-- отримати конкретну особу -->
<select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM,
PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#значення#</select>
<!-- додати особу -->
<insert id="Personne.insertOne" parameterClass="Personne.classe">
insert into
PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS)
VALUES(#версія#, #прізвище#, #ім'я#, #dateNaissance#, #марі#,
#nbEnfants#)
<selectKey keyProperty="id">
select LAST_INSERT_ID() as value
</selectKey>
</insert>
<!-- оновити дані особи -->
<update id="Personne.updateOne" parameterClass="Personne.classe"> update
PERSONNES set VERSION=#версія#+1, NOM=#прізвище#, PRENOM=#ім'я#, DATENAISSANCE=#dateNaissance#,
MARIE=#marie#, NBENFANTS=#nbEnfants# WHERE ID=#id# та
VERSION=#version#</update>
<!-- видалити особу -->
<delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE
ID=#значення# </delete>
<!-- отримати значення первинного ключа [id] останньої доданої особи -->
<select id="Personne.getNextId" resultClass="int">select
LAST_INSERT_ID()</select>
</sqlMap>
Його вміст збігається з [personnes-firebird.xml], за винятком таких деталей:
- послідовність SQL «Personne.insertOne» змінилася у рядках 29–37:
- команда вставки SQL виконується перед командою SELECT, що дозволить отримати значення первинного ключа вставленого рядка
- команда вставки SQL не містить значення для стовпця ID таблиці [PERSONNES]
Це відображає приклад вставки, який ми розглядали в параграфі 19.1.
Слід зауважити, що це може бути потенційним джерелом проблем між паралельними потоками. Уявімо два потоки Th1 і Th2, які одночасно виконують вставку. Загалом потрібно відправити чотири команди SQL. Припустимо, що вони виконуються в такому порядку:
- вставка I1 з боку Th1
- вставка I2 від Th2
- запит S1 від Th1
- вибір S2 з Th2
У кроці 3 Th1 отримує первинний ключ, згенерований під час останнього вставлення, тобто ключ Th2, а не свій власний. Я не знаю, чи метод [insert] класу iBATIS захищений від такого випадку. Припустимо, що він коректно обробляє цю ситуацію. Якщо це не так, нам доведеться похідним класом реалізації [DaoImplCommon] від шару [dao] створити клас [DaoImplMySQL], у якому метод [insertPersonne] буде синхронізовано. Це вирішило б проблему лише для потоків нашої програми. Якщо вищезазначені Th1 і Th2 є потоками двох різних програм, то проблему слід вирішувати одночасно за допомогою транзакцій та відповідного рівня ізоляції (isolation level) між транзакціями. Відповідним був би рівень [serializable], на якому транзакції виконуються так, ніби вони виконуються послідовно.
Слід зазначити, що ця проблема не існує у Firebird та Postgres, які виконують SELECT перед INSERT. Наприклад, якщо маємо таку послідовність:
- select S1 з Th1
- select S2 з Th2
- вставка I1 з Th1
- вставлення I2 з Th2
На етапах 1 і 2 Th1 і Th2 отримують значення первинного ключа від одного й того самого генератора. Ця операція зазвичай є атомарною, і Th1 та Th2 отримають два різні значення. Якби операція не була атомарною і Th1 та Th2 отримали два однакові значення, вставка, виконана на кроці 4 Th2, завершилася б невдачею через дублювання первинного ключа. Це цілком виправна помилка, і Th2 може повторити спробу вставки.
Ми залишимо операцію «Personne.insertOne» такою, якою вона є зараз у файлі [personnes-mysql.xml], але читач повинен усвідомлювати, що тут потенційно існує проблема.
Клас реалізації [DaoImplCommon] шару [dao] відповідає двом попереднім версіям.
Конфігурація шару [dao] була адаптована до SGBD та [MySQL]. Таким чином, файл конфігурації [spring-config-test-dao-mysql.xml] має такий вигляд:
<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- джерело даних DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/dbpersonnes</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value></value>
</property>
</bean>
<!-- SqlMapCllient -->
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource">
<ref local="dataSource"/>
</property>
<property name="configLocation">
<value>classpath:sql-map-config-mysql.xml</value>
</property>
</bean>
<!-- клас доступу до шару [dao] -->
<bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon">
<property name="sqlMapClient">
<ref local="sqlMapClient"/>
</property>
</bean>
</beans>
- рядки 5–19: bean [dataSource] тепер вказує на базу [MySQL] [dbpersonnes], адміністратором якої є [root] без пароля. Користувач повинен змінити цю конфігурацію відповідно до свого середовища.
- рядок 31: клас [DaoImplCommon] є класом реалізації шару [dao]
Після внесення цих змін можна переходити до тестування.
19.4. Тестування шарів [dao] та [service]
Тестування шарів [dao] та [service] є таким самим, як і для версії [Firebird]. Отримано такі результати:
![]() |
Можна констатувати, що тестування було успішно пройдено з реалізацією [DaoImplCommon]. Нам не доведеться створювати похідні класи від цього класу, як це було необхідно зробити для SGBD та [Firebird].
19.5. Тестування додатка [web]
Щоб протестувати веб-додаток із SGBD та [MySQL], ми створюємо проект Eclipse [mvc-personnes-05B] аналогічно до того, як створювався проект [mvc-personnes-03B] з базою даних Firebird (див. розділ 17.7). Однак, як і у випадку з Postgres, нам не потрібно заново створювати архіви [personnes-dao.jar] та [personnes-service.jar], оскільки ми не змінювали жодного класу.
Ми розгортаємо веб-проект [mvc-personnes-05B] у середовищі Tomcat:
![]() | ![]() |
Запускається SGBD MySQL. Вміст таблиці [PERSONNES] тепер такий:

Потім запускається Tomcat. У браузері ми вводимо URL-адресу [http://localhost:8080/mvc-personnes-05B]:

Ми додаємо нову особу за посиланням [Ajout]:
![]() | ![]() |
Перевіряємо, чи додано запис до бази даних:

Користувачеві пропонується провести інші тести [modification, suppression].







