source: other-projects/the-macronizer/trunk/src/java/util/MySQLAccess.java@ 35719

Last change on this file since 35719 was 35719, checked in by cstephen, 2 years ago

Add support for JSON response to direct input queries. Cleanup other components.

File size: 13.3 KB
Line 
1package util;
2
3import java.io.BufferedReader;
4import java.io.FileReader;
5import java.sql.Connection;
6import java.sql.Date;
7import java.sql.DriverManager;
8import java.sql.PreparedStatement;
9import java.sql.ResultSet;
10import java.sql.SQLException;
11import java.sql.SQLIntegrityConstraintViolationException;
12import java.sql.Statement;
13import java.sql.Time;
14
15import java.time.LocalDate;
16import java.time.LocalTime;
17
18import java.util.Properties;
19
20
21/*
22 * https://www.javatpoint.com/example-to-connect-to-the-mysql-database
23 * http://www.vogella.com/tutorials/MySQLJava/article.html
24 *
25 * 1. tikauka:[119]/Scratch/wjkw1>mkdir sql
26 * 2. tikauka:[120]/Scratch/wjkw1>mv MySQLAccess.java sql/.
27 * 3. tikauka:[122]/Scratch/wjkw1>javac sql/MySQLAccess.java *
28 * 4. tikauka:[144]/Scratch/wjkw1>java -cp "/PATH/TO/mysql-connector-java-8.0.14/mysql-connector-java-8.0.14.jar:/MYSQL.PROPS/PARENTFOLDER:." sql.MySQLAccess
29 * (If no classpath, then: tikauka:[123]/Scratch/wjkw1>java sql.MySQLAccess)
30 * Note that /MYSQL.PROPS/PARENTFOLDER is the parent folder of file mysql.properties, it should have been automatically
31 * created in macronizer's web/WEB-INF/CLASSES after the Macroniser's build.xml has run.
32 * In mysql.properties:
33 * -the db.password has to be changed from CHANGEME. (Can be empty string if your mysql account uses no pwd.)
34 * - can set db.username too if you don't want it to default to "root".
35 */
36public class MySQLAccess {
37 //Tuple to hold data stored in database
38 public static class Tuple {
39 public final String word;
40 public final LocalDate date;
41 public final LocalTime time;
42
43 public Tuple(String line, LocalDate d, LocalTime t) {
44 word = line;
45 date = d;
46 time = t;
47 }
48 }
49
50 private Connection connect = null;
51 private Statement statement = null;
52 private PreparedStatement preparedStatement = null;
53 private ResultSet resultSet = null;
54
55 private final String DB_NAME = "Macroniser";
56 // obtained from Properties file:
57 private String USERNAME; //= "root"; by default
58 private String PASSWORD; //= "pinky";
59
60 private final String PROPS_FILENAME = "mysql.properties"; // set to "classes/mysql.properties" for IntelliJ
61
62 //Makes a connection to the server
63 public boolean makeConnection() throws Exception {
64 boolean success = false;
65 try {
66 // Read in the username and password from our props file
67 Properties props = new Properties();
68 try(BufferedReader reader = new BufferedReader(new FileReader(PROPS_FILENAME));
69
70 ) {
71
72 props.load(reader);
73
74 } catch(Exception e) {
75 e.printStackTrace();
76 }
77
78
79 USERNAME = props.getProperty("db.username", "");
80 if(USERNAME.equals("")) {
81 USERNAME = "root";
82 System.err.println("WARNING: No sensible value for db.username specified in " + PROPS_FILENAME + " defaulting to: " + USERNAME);
83 }
84 PASSWORD = props.getProperty("db.password");
85
86 if(PASSWORD != null && PASSWORD.equals("CHANGEME")) {
87 success = false;
88 throw new Exception("************ FATAL ERROR: Change DB password in properties file " + PROPS_FILENAME);
89
90 }
91 //System.out.println("*** Found user: |" + USERNAME + "|");
92 //System.out.println("*** Found pwd: " + PASSWORD);
93
94 // This will load the MySQL driver, each DB has its own driver Class.forName("com.mysql.jdbc.Driver");
95 // Setup the connection with the DB
96 connect = DriverManager.getConnection("jdbc:mysql://localhost/"+DB_NAME, USERNAME, PASSWORD);
97 statement = connect.createStatement();
98
99 statement.executeUpdate("set names utf8mb4"); // should return 0 for SQL stmts that return nothing
100 System.out.println("Connected to MySQL DB " + DB_NAME);
101 success = true;
102 } catch (SQLException e) {
103 //connect may be non-null if we've already made the connection but "set names utf8mb4" failed
104 //closeConnection(); // overkill, due to how we call makeConnection + closeConnection ALWAYS TOGETHER
105 success = false;
106 throw e;
107 }
108 return success;
109 }
110
111
112
113 // You need to close the resultSet
114 public void closeConnection() {
115
116 System.out.println("Closing connection...");
117
118 closeResource(resultSet);
119 closeResource(statement);
120 closeResource(preparedStatement);
121
122 // Finally close the actual connection
123 closeResource(connect);
124
125 System.out.println("Connection successfully closed");
126 }
127
128 /**
129 * mysql> set names utf8mb4;
130 * <p>
131 * mysql> SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';
132 * +--------------------------+--------------------+
133 * | Variable_name | Value |
134 * +--------------------------+--------------------+
135 * | character_set_client | utf8mb4 |
136 * | character_set_connection | utf8mb4 |
137 * | character_set_database | utf8mb4 |
138 * | character_set_filesystem | binary |
139 * | character_set_results | utf8mb4 |
140 * | character_set_server | utf8mb4 |
141 * | character_set_system | utf8 |
142 * | collation_connection | utf8mb4_general_ci |
143 * | collation_database | utf8mb4_general_ci |
144 * | collation_server | utf8mb4_general_ci |
145 * +--------------------------+--------------------+
146 * <p>
147 * The values of the final 2 lines are different from https://medium.com/@manish_demblani/breaking-out-from-the-mysql-character-set-hell-24c6a306e1e5
148 * (in case this matters) but both utf8mb4_general_ci and utf8mb4_unicode_ci perform case-INsensitive comparisons
149 * see https://stackoverflow.com/questions/2344118/utf-8-general-bin-unicode
150 * which is fine by us, because we only care about the macrons.
151 */
152 /*
153 Select mw.marked_word, ed.entry_date, et.entry_time
154 FROM Occurrence o, MarkedWords mw, EntryDates ed, EntryTimes et
155 WHERE o.word_id = mw.word_id
156 AND o.date_id = ed.date_id
157 AND o.time_id = et.time_id;
158
159 SELECT word_id FROM MarkedWords WHERE marked_word like ?
160
161 */
162 public boolean addNewEntry(Tuple t) throws SQLException {
163 int word_id = -1, date_id = -1, time_id = -1;
164 boolean success = false;
165 try {
166
167 // Commits happen on a per-Tuple bases: either all update together (commit)
168 // or none of them happen (rollback)
169 //sets a transaction so if an exception happens the partially data entered is not saved
170 //must remember to commit prepared statement after executeUpdate to save data
171 connect.setAutoCommit(false);
172
173 //get ID, if not available then insert word
174 word_id = getMarkedWordID(t.word);
175 if (word_id == -1) {
176 word_id = insertMarkedWord(t.word);
177 }
178 //get ID, if not available then insert date
179 date_id = getEntryDateID(t.date);
180 if (date_id == -1) {
181 date_id = insertEntryDate(t.date);
182 }
183 //get ID, if not available then insert time
184 time_id = getEntryTimeID(t.time);
185 if (time_id == -1) {
186 time_id = insertEntryTime(t.time);
187 }
188
189 insertOccurrence(word_id, time_id, date_id);
190
191 if(MacroniserLogFileProcessor.debug) {
192 System.out.println("The IDs are:(word, date, time) (" + word_id + "," + date_id + "," + time_id + ")");
193 }
194
195 connect.commit();
196 success = true;
197
198 } catch (Exception e) {
199 e.printStackTrace();
200 connect.rollback();
201 throw e;
202
203 } finally {
204 connect.setAutoCommit(true);
205 }
206 return success;
207 }
208
209 //Occurrence into
210 private int insertOccurrence(int wordID, int timeID, int dateID)
211 throws SQLException
212 {
213 String query = "INSERT INTO Occurrences (word_id, time_id, date_id) VALUES (?,?,?)";
214 preparedStatement = connect.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
215 preparedStatement.setInt(1, wordID);
216 preparedStatement.setInt(2, timeID);
217 preparedStatement.setInt(3, dateID);
218
219 int occurrence_id = -1;
220
221 try
222 {
223 preparedStatement.executeUpdate();
224 resultSet = preparedStatement.getGeneratedKeys();
225
226 if (resultSet.next())
227 {
228 occurrence_id = resultSet.getInt(1);
229 }
230
231 }
232 catch (SQLException e)
233 {
234 throw e; // any other SQLException is a bad case, so we'll handle it in the caller
235 }
236
237 return occurrence_id;
238 }
239
240 //gets the specified marked word's id
241 private int getMarkedWordID(String marked_word) throws SQLException {
242 // in cases where the user didn't enter any input str, marked_word will be null. Still of interest, store to db as string "NULL"
243 if(marked_word == null) {
244 marked_word = "NULL";
245 }
246
247 // Warning: "select * from table WHERE str_field LIKE ?" does not work when we want exact matches (or matches featuring macrons)
248 // Use "WHERE BINARY str_filed = ?" instead
249 // See https://stackoverflow.com/questions/7604082/mysql-exact-match
250 String query = "SELECT word_id FROM MarkedWords WHERE BINARY marked_word = ?";
251 preparedStatement = connect.prepareStatement(query);
252 preparedStatement.setString(1, marked_word);
253
254 resultSet = preparedStatement.executeQuery();
255
256 int word_id = -1;
257 while (resultSet.next()) {
258 word_id = resultSet.getInt("word_id");
259 }
260 return word_id;
261 }
262
263 //inserts the marked word into the database, uses transactions to rollback if necessary
264 private int insertMarkedWord(String word) throws SQLException {
265
266 if(word == null) {
267 word = "NULL";
268 }
269
270 String query = "INSERT INTO MarkedWords (marked_word) VALUES (?)";
271 preparedStatement = connect.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
272 preparedStatement.setString(1, word);
273
274
275 int word_id = -1;
276
277 try {
278 preparedStatement.executeUpdate();
279
280 resultSet = preparedStatement.getGeneratedKeys();
281
282 if (resultSet.next()) {
283 word_id = resultSet.getInt(1);
284 }
285 //System.err.println("Word ID: "+word_id);
286
287 } catch (SQLIntegrityConstraintViolationException e) {
288 //This exception happens when an insert statement breaks UNIQUE constraint
289 //Should not be called, but added for robustness
290 // this exception is fine, it means the word was already in the MarkedWords table
291 System.err.println("The word " + word + " was already in MarkedWords table");
292 // this error is not fatal
293 } catch (SQLException e) {
294 throw e; // any other SQLException is a bad case, so we'll handle it in the caller
295 }
296
297 return word_id;
298 }
299
300 //gets the specified date's id
301 private int getEntryDateID(LocalDate date) throws SQLException {
302 String query = "SELECT date_id FROM EntryDates WHERE entry_date = ?";
303 preparedStatement = connect.prepareStatement(query);
304 Date sqlDate = Date.valueOf(date);
305 preparedStatement.setDate(1, sqlDate);
306
307 resultSet = preparedStatement.executeQuery();
308
309 int date_id = -1;
310 while (resultSet.next()) {
311 date_id = resultSet.getInt("date_id");
312 if(MacroniserLogFileProcessor.debug) System.out.println(date_id + " - " + date.toString());
313 }
314 return date_id;
315 }
316
317 //inserts the entry time date into the database
318 private int insertEntryDate(LocalDate date) throws SQLException {
319
320 String query = "INSERT INTO EntryDates (entry_date) VALUES (?)";
321 preparedStatement = connect.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
322 Date sqlDate = Date.valueOf(date);
323 preparedStatement.setDate(1, sqlDate);
324
325 int date_id = -1;
326
327 try {
328 preparedStatement.executeUpdate();
329 resultSet = preparedStatement.getGeneratedKeys();
330
331 if (resultSet.next()) {
332 date_id = resultSet.getInt(1);
333 }
334 //System.err.println("Date ID: "+ date_id);
335
336 } catch (SQLIntegrityConstraintViolationException e) {
337 // this exception is fine, it means the time was already in the EntryTimes table
338 System.err.println("The date " + date + " was already in EntryDates table");
339 // this error is not fatal
340 } catch (SQLException e) {
341 throw e; // any other SQLException is a bad case, so we'll handle it in the caller
342 }
343
344 return date_id;
345 }
346
347 //gets the specified time's id
348 private int getEntryTimeID(LocalTime time) throws SQLException {
349 String query = "SELECT time_id FROM EntryTimes WHERE entry_time = ?";
350 preparedStatement = connect.prepareStatement(query);
351 Time sqlTime = Time.valueOf(time);
352 preparedStatement.setTime(1, sqlTime);
353
354 resultSet = preparedStatement.executeQuery();
355
356 int time_id = -1;
357 while (resultSet.next()) {
358 time_id = resultSet.getInt("time_id");
359 }
360 return time_id;
361 }
362
363 //inserts the entry time into the database
364 private int insertEntryTime(LocalTime time) throws SQLException {
365
366 String query = "INSERT INTO EntryTimes (entry_time) VALUES (?)";
367 preparedStatement = connect.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
368 Time sqlTime = Time.valueOf(time);
369 preparedStatement.setTime(1, sqlTime);
370
371 int time_id = -1;
372 try {
373 preparedStatement.executeUpdate();
374 resultSet = preparedStatement.getGeneratedKeys();
375
376 if (resultSet.next()) {
377 time_id = resultSet.getInt(1);
378 }
379 //System.err.println("Time ID: "+ time_id);
380
381 } catch (SQLIntegrityConstraintViolationException e) {
382 // this exception is fine, it means the time was already in the EntryTimes table
383 System.err.println("The time " + time + " was already in EntryTimes table");
384 // this error is not fatal
385 } catch (SQLException e) {
386 throw e; // any other SQLException is a bad case, so we'll handle it in the caller
387 }
388
389 return time_id;
390 }
391
392
393 public void closeResource(AutoCloseable resource) {
394 try {
395 if(resource != null) resource.close();
396 } catch(Exception e) {
397 e.printStackTrace();
398 }
399 }
400
401 public static void main(String[] args) {
402 MySQLAccess mysql = new MySQLAccess();
403
404 try {
405
406 mysql.makeConnection();
407 String s = "Hello World";
408 LocalTime time = LocalTime.of(10, 10, 15);
409 LocalDate date = LocalDate.of(2018, 04, 21);
410 Tuple t = new Tuple(s, date, time);
411
412 mysql.addNewEntry(t);
413
414 } catch (Exception e) {
415 e.printStackTrace();
416 } finally {
417 mysql.closeConnection();
418 }
419 }
420
421}
Note: See TracBrowser for help on using the repository browser.