1
+ /*
2
+ Исследование транзакций SQL операторы:
3
+ - COMMIT - фиксирует все изменения для текущей транзакции,
4
+ как только COMMIT выполнится, остальным
5
+ пользователям будут доступны внесенные изменения.
6
+ - ROLLBACK - используется для отмены работы, выполняемой текущей
7
+ транзакцией или транзакции, которая сомнительна.
8
+
9
+ В данном примере мы создадим ситуацию при которой попробуем
10
+ удалить связные между таблицами данные и с имитируем проблему,
11
+ чтобы отменить изменения.
12
+
13
+ Не забываем подключить в настройках
14
+ драйвер соединения с базой данных (*.jar),
15
+ который лежит в папке 'lib' проекта 'JDBCLessonOne',
16
+ иначе словим исключение. Не забываем пометить
17
+ папку 'resources', как ресурсную.
18
+ */
19
+
20
+ import connection_util .ConnectionManager ;
21
+
22
+ import java .sql .*;
23
+
24
+ public class SQLQueryApp_11 {
25
+
26
+ public static void main (String [] args ) throws SQLException {
27
+
28
+ int del_flightId = load_data_to_base ();
29
+
30
+ /*
31
+ Номер рейса для удаления, предварительно
32
+ созданный методом *.load_data_to_base()
33
+ */
34
+ long flightId = del_flightId ;
35
+ /* Готовим запросы для удаления данных в формате PreparedStatement */
36
+ var deleteFlightSql = "DELETE FROM flight_repository.flight WHERE id = ?" ;
37
+ var deleteTicketsSql = "DELETE FROM flight_repository.ticket WHERE flight_id = ?" ;
38
+ /*
39
+ Создаем нулевые соединения и запросы,
40
+ которые должны быть доступны в любом
41
+ куске кода.
42
+ */
43
+ Connection connection = null ;
44
+ PreparedStatement deleteFlightStatement = null ;
45
+ PreparedStatement deleteTicketsStatement = null ;
46
+
47
+ try {
48
+ /* Инициализация */
49
+ connection = ConnectionManager .getBaseConnection ();
50
+ deleteFlightStatement = connection .prepareStatement (deleteFlightSql );
51
+ deleteTicketsStatement = connection .prepareStatement (deleteTicketsSql );
52
+ /*
53
+ Отключаем в базе AutoCommit, т.е. теперь
54
+ все подтверждения запросов вручную.
55
+ */
56
+ connection .setAutoCommit (false );
57
+ /* Передаем параметры в наши PreparedStatement запросы */
58
+ deleteFlightStatement .setLong (1 , flightId );
59
+ deleteTicketsStatement .setLong (1 , flightId );
60
+
61
+ /*
62
+ Когда мы создавали таблицы см. папку base в ConnectLessOne.
63
+ Мы таблицу 'flight':
64
+ **********************************************************************
65
+ CREATE TABLE flight
66
+ (
67
+ id BIGSERIAL PRIMARY KEY ,
68
+ flight_no VARCHAR(16) NOT NULL ,
69
+ departure_date TIMESTAMP NOT NULL ,
70
+ departure_airport_code CHAR(3) REFERENCES airport(code) NOT NULL ,
71
+ arrival_date TIMESTAMP NOT NULL ,
72
+ arrival_airport_code CHAR(3) REFERENCES airport(code) NOT NULL ,
73
+ aircraft_id INT REFERENCES aircraft (id) NOT NULL ,
74
+ status VARCHAR(32) NOT NULL
75
+ );
76
+ **********************************************************************
77
+ привязали к таблице 'ticket', через внешний ключ 'flight_id'
78
+ **********************************************************************
79
+ CREATE TABLE ticket
80
+ (
81
+ id BIGSERIAL PRIMARY KEY ,
82
+ passenger_no VARCHAR(32) NOT NULL ,
83
+ passenger_name VARCHAR(128) NOT NULL ,
84
+ flight_id BIGINT REFERENCES flight (id) NOT NULL ,
85
+ seat_no VARCHAR(4) NOT NULL,
86
+ cost NUMERIC(8, 2) NOT NULL
87
+ );
88
+ **********************************************************************
89
+ И теперь мы можем безболезненно удалить данные из таблицы 'ticket',
90
+ а вот данные связные с этой таблицей из таблицы flight мы не можем.
91
+
92
+ Не сложно заметить, что внешний ключ не имеет дополнительного
93
+ ограничения, например:
94
+ - ON DELETE CASCADE - означает, что если удаляется запись в
95
+ родительской таблице, то соответствующие
96
+ записи в дочерней таблице будут удалены
97
+ автоматически - каскадное удаление.
98
+ - ON DELETE SET NULL - означает, что если запись в родительской
99
+ таблице удаляется, то соответствующие поля
100
+ в записи дочерней таблице, имеющие foreign
101
+ key станут NULL.
102
+ */
103
+ deleteTicketsStatement .executeUpdate ();
104
+ /*
105
+ Предыдущий запрос отправил базе команду на удаление
106
+ билетов с 10 рейса, текущая строка выводит количество
107
+ удаленных строк.
108
+ */
109
+ System .out .println ("Удалили билетов: " + deleteTicketsStatement .getUpdateCount ());
110
+ /* Имитируем проблему т.е. ниже программа не пойдет*/
111
+ if (true ) {
112
+ throw new RuntimeException ("Имитируем проблему! После которой, вроде бы удаленные, билеты останутся в базе" );
113
+ }
114
+ /*
115
+ Пытаемся удалить рейс с номером 10, но вылетит ошибка,
116
+ т.к. таблицы связны и данная операция без доп. инструкций
117
+ не допустима.
118
+ */
119
+ deleteFlightStatement .executeUpdate ();
120
+ /*
121
+ АвтоКоммит отключен и мы делаем подтверждение операций
122
+ руками, однако до сюда программа не дойдет
123
+ */
124
+ connection .commit ();
125
+
126
+ } catch (Exception e ) {
127
+ /*
128
+ Поскольку мы словили исключение, то нужно отменить
129
+ удаление билетов, предварительно проверив соединение
130
+ на NULL
131
+ */
132
+ if (connection != null ) {
133
+ connection .rollback ();
134
+ }
135
+ throw e ;
136
+ } finally {
137
+ /*
138
+ Поскольку у нас нет блока try-with-resources,
139
+ то закрываем все соединения и стайтменты руками.
140
+ */
141
+ if (connection != null ) {
142
+ connection .close ();
143
+ }
144
+ if (deleteFlightStatement != null ) {
145
+ deleteFlightStatement .close ();
146
+ }
147
+ if (deleteTicketsStatement != null ) {
148
+ deleteTicketsStatement .close ();
149
+ }
150
+ }
151
+ }
152
+ /*
153
+ Предварительно загрузим в базу немного данных, которые
154
+ потом в методе MAIN попробуем удалить.
155
+ */
156
+ private static int load_data_to_base (){
157
+ int auto_gen_key = 0 ;
158
+ String sql_query_add_flight = """
159
+ INSERT INTO flight_repository.flight (flight_no,
160
+ departure_date,
161
+ departure_airport_code,
162
+ arrival_date,
163
+ arrival_airport_code,
164
+ aircraft_id,
165
+ status)
166
+ VALUES
167
+ ('KQ1202', '2021年03月14日T14:30', 'MNK', '2020年06月14日T18:07', 'LDN', 3, 'DEPARTED');
168
+ """ ;
169
+ try (Connection my_connect = ConnectionManager .getBaseConnection ();
170
+ Statement my_statement = my_connect .createStatement ()){
171
+ my_statement .executeUpdate (sql_query_add_flight , Statement .RETURN_GENERATED_KEYS );
172
+ System .out .println ("Добавили рейсов: " + my_statement .getUpdateCount ());
173
+ var generatedKeys = my_statement .getGeneratedKeys ();
174
+ if (generatedKeys .next ()) {
175
+ var generatedId = generatedKeys .getInt ("id" );
176
+ System .out .println ("Только что добавили рейс с id:" + generatedId );
177
+ auto_gen_key = generatedId ;
178
+ }
179
+ String sql_query_add_ticket =
180
+ "INSERT INTO flight_repository.ticket " +
181
+ "(passenger_no, passenger_name, flight_id, seat_no, cost) " +
182
+ "VALUES ('134533', 'Малкольм Стоун', " + auto_gen_key + ", 'A1', 200)," +
183
+ "('12434A', 'Санара Куэста', " + auto_gen_key + ", 'B1', 180)," +
184
+ "('QQ138D', 'Дуглас Линд', " + auto_gen_key + ", 'B2', 175)," +
185
+ "('QY184E', 'Таймус Роддерик', " + auto_gen_key + ", 'C2', 175), " +
186
+ "('1OQ2A4', 'Говард Аддингтон', " + auto_gen_key + ", 'D1', 160)," +
187
+ " ('SS81M3', 'Амир Ахди', " + auto_gen_key + ", 'A2', 198);" ;
188
+
189
+ my_statement .executeUpdate (sql_query_add_ticket );
190
+ System .out .println ("Добавили билетов: " + my_statement .getUpdateCount ());
191
+
192
+ } catch (SQLException e ) {
193
+ throw new RuntimeException (e );
194
+ }
195
+ return auto_gen_key ;
196
+ }
197
+ }
0 commit comments