· 9 years ago · Dec 07, 2016, 01:02 AM
1package h2app;
2
3import javafx.application.Application;
4import javafx.collections.*;
5import javafx.concurrent.Task;
6import javafx.scene.Scene;
7import javafx.scene.control.*;
8import javafx.scene.layout.*;
9import javafx.stage.Stage;
10
11import java.sql.*;
12import java.util.concurrent.*;
13import java.util.concurrent.atomic.AtomicInteger;
14import java.util.logging.Level;
15import java.util.logging.Logger;
16
17public class H2Tasks extends Application {
18 private static final Logger logger = Logger.getLogger(H2Tasks.class.getName());
19 private static final String[] SAMPLE_NAME_DATA = { "John", "Jill", "Jack", "Jerry" };
20
21 public static void main(String[] args) { launch(args); }
22
23 // executes database operations concurrent to JavaFX operations.
24 private ExecutorService databaseExecutor;
25
26 // the future's data will be available once the database setup has been complete.
27 private Future databaseSetupFuture;
28
29 // initialize the program.
30 // setting the database executor thread pool size to 1 ensures
31 // only one database command is executed at any one time.
32 @Override public void init() throws Exception {
33 databaseExecutor = Executors.newFixedThreadPool(
34 1,
35 new DatabaseThreadFactory()
36 );
37
38 // run the database setup in parallel to the JavaFX application setup.
39 DBSetupTask setup = new DBSetupTask();
40 databaseSetupFuture = databaseExecutor.submit(setup);
41 }
42
43 // shutdown the program.
44 @Override public void stop() throws Exception {
45 databaseExecutor.shutdown();
46 if (!databaseExecutor.awaitTermination(3, TimeUnit.SECONDS)) {
47 logger.info("Database execution thread timed out after 3 seconds rather than shutting down cleanly.");
48 }
49 }
50
51 // start showing the UI.
52 @Override public void start(Stage stage) throws InterruptedException, ExecutionException {
53 // wait for the database setup to complete cleanly before showing any UI.
54 // a real app might use a preloader or show a splash screen if this
55 // was to take a long time rather than just pausing the JavaFX application thread.
56 databaseSetupFuture.get();
57
58 final ListView<String> nameView = new ListView<>();
59 final ProgressIndicator databaseActivityIndicator = new ProgressIndicator();
60 databaseActivityIndicator.setVisible(false);
61
62 final Button fetchNames = new Button("Fetch names from the database");
63 fetchNames.setOnAction(event ->
64 fetchNamesFromDatabaseToListView(
65 fetchNames,
66 databaseActivityIndicator,
67 nameView
68 )
69 );
70
71 final Button clearNameList = new Button("Clear the name list");
72 clearNameList.setOnAction(event -> nameView.getItems().clear());
73
74 VBox layout = new VBox(10);
75 layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 15;");
76 layout.getChildren().setAll(
77 new HBox(10,
78 fetchNames,
79 clearNameList,
80 databaseActivityIndicator
81 ),
82 nameView
83 );
84 layout.setPrefHeight(200);
85
86 stage.setScene(new Scene(layout));
87 stage.show();
88 }
89
90 private void fetchNamesFromDatabaseToListView(
91 final Button triggerButton,
92 final ProgressIndicator databaseActivityIndicator,
93 final ListView<String> listView) {
94 final FetchNamesTask fetchNamesTask = new FetchNamesTask();
95
96 triggerButton.disableProperty().bind(
97 fetchNamesTask.runningProperty()
98 );
99 databaseActivityIndicator.visibleProperty().bind(
100 fetchNamesTask.runningProperty()
101 );
102 databaseActivityIndicator.progressProperty().bind(
103 fetchNamesTask.progressProperty()
104 );
105
106 fetchNamesTask.setOnSucceeded(t ->
107 listView.setItems(fetchNamesTask.getValue())
108 );
109
110 databaseExecutor.submit(fetchNamesTask);
111 }
112
113 abstract class DBTask<T> extends Task<T> {
114 DBTask() {
115 setOnFailed(t -> logger.log(Level.SEVERE, null, getException()));
116 }
117 }
118
119 class FetchNamesTask extends DBTask<ObservableList<String>> {
120 @Override protected ObservableList<String> call() throws Exception {
121 // artificially pause for a while to simulate a long running database connection.
122 Thread.sleep(1000);
123
124 try (Connection con = getConnection()) {
125 return fetchNames(con);
126 }
127 }
128
129 private ObservableList<String> fetchNames(Connection con) throws SQLException {
130 logger.info("Fetching names from database");
131 ObservableList<String> names = FXCollections.observableArrayList();
132
133 Statement st = con.createStatement();
134 ResultSet rs = st.executeQuery("select name from employee");
135 while (rs.next()) {
136 names.add(rs.getString("name"));
137 }
138
139 logger.info("Found " + names.size() + " names");
140
141 return names;
142 }
143 }
144
145 class DBSetupTask extends DBTask {
146 @Override protected Void call() throws Exception {
147 try (Connection con = getConnection()) {
148 if (!schemaExists(con)) {
149 createSchema(con);
150 populateDatabase(con);
151 }
152 }
153
154 return null;
155 }
156
157 private boolean schemaExists(Connection con) {
158 logger.info("Checking for Schema existence");
159 try {
160 Statement st = con.createStatement();
161 st.executeQuery("select count(*) from employee");
162 logger.info("Schema exists");
163 } catch (SQLException ex) {
164 logger.info("Existing DB not found will create a new one");
165 return false;
166 }
167
168 return true;
169 }
170
171 private void createSchema(Connection con) throws SQLException {
172 logger.info("Creating schema");
173 Statement st = con.createStatement();
174 String table = "create table employee(id integer, name varchar(64))";
175 st.executeUpdate(table);
176 logger.info("Created schema");
177 }
178
179 private void populateDatabase(Connection con) throws SQLException {
180 logger.info("Populating database");
181 Statement st = con.createStatement();
182 for (String name: SAMPLE_NAME_DATA) {
183 st.executeUpdate("insert into employee values(1,'" + name + "')");
184 }
185 logger.info("Populated database");
186 }
187 }
188
189 private Connection getConnection() throws ClassNotFoundException, SQLException {
190 logger.info("Getting a database connection");
191 Class.forName("org.h2.Driver");
192 return DriverManager.getConnection("jdbc:h2:~/test", "sa", "");
193 }
194
195 static class DatabaseThreadFactory implements ThreadFactory {
196 static final AtomicInteger poolNumber = new AtomicInteger(1);
197
198 @Override public Thread newThread(Runnable runnable) {
199 Thread thread = new Thread(runnable, "Database-Connection-" + poolNumber.getAndIncrement() + "-thread");
200 thread.setDaemon(true);
201
202 return thread;
203 }
204 }
205}