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