An example application with JPA and JavaFX

We are going to create an example application with JPA and JavaFX 2 using Netbeans 7.4 as our IDE. It is assumed that you have installed the JDK 7 and have the Java FX Scene Builder installed and configured in your computer. Our example will be scientifically oriented due to the author’s interests (I know, the content of the example is insubstantial.. but just for the fun of imagination). Let’s assume we are asked to build an application to register the data of an experiment in which some parameters need to be collected from a sample of rats in a laboratory.

So, first of all, Let’s create a Java FX project. We choose it as an FXML application because we want to use the Scene Builder WYSWYG editor to set up the UI. We name the app as RatManager and check the option Create Application Class choosing the name as com.aljiro.ratmanager.RatManager. Delete the default FXML Document and Controller and create the following package strcucture:

  • com.aljiro.ratmanager.model
  • com.aljiro.ratmanager.ui

rm-structure

Now, lets create a JAVA DB Database to store our data. In the services tab, right click in the Java DB item under the Databases category and choose Create Database. Write the name ratmanagerdb and user/password: ratadmin/ratadmin. Finally, choose a location to put the database, for example, the same project directory.

rm-db

We can take the opportunity and create the table we are going to use in our example. First, double click in the item that just appeared below in the databases category with the long connection string: jdbc:derby://localhost:1527/ratmanagerdb [ratadmin on RATADMIN]. There, under the scheme RATADMIN, in the item Tables, right-click and open the Execute Command Sql editor. Finally, write the SQL Query to create the table we need:

CREATE TABLE rat (
  id INT not null
     GENERATED ALWAYS AS IDENTITY
     (START WITH 1, INCREMENT BY 1),
  code VARCHAR(5),
  weight DOUBLE,
  brain_concentration DOUBLE,
  PRIMARY KEY (id)
);

Finally, we execute the query. Take a look at the tables to be sure that everything is alright.

DB

It is time of do the magic. Let’s return to the JavaFX project; we are going to get our entity classes out of the DB in just a few steps. Right click on the model package and choose the option New/Entity Classes from Database. Choose the ratadmin connection and add the available tables to the project (i.e. the only one, the “rat” table). Click next, then finish and let the other options unchanged.

DB

Now, our model package is populated with all the entities corresponding to tables in our database (the so-called ORM). If we take a look to the class Rat, we see the usual JPA syntax with a set of NamedQueries on top and the attributes, setters, getters, etc. We can continue without worrying about such details (although, no serious java developer could go too far without knowing it…). It is a good time to add the JavaDB driver to our project, you can find it if you Right-Click in the Libraries directory of your project and choose Add Library. Maybe you have noted that Netbeans already added a couple of libraries and a Persistence.xml file that we would use later.

Anyway, what about some programming? Until know everything have been about click-typing names; let’s focus on the core of our app. Right click on the “ui” package and choose the option New/Empty FXML (We could make our way without FXML, but that’s not the case this time). Name it RatView, click “next”, and check the Use Java Controller option, click “next” and check the Use CSS option; finally, click “finish”.

We are now ready to specify the user interface. First, change the root pane of the scene by a StackPane (By default it is an AnchoPane). This can be accomplished in many ways, the easier one I find is to add a StackPane as a child of the AnchorPane put by default and right click in the last one selecting the option Unwrap.

fxml1

Now, we need to refer to the StackPane in the code, so we put an attribute in the RatViewController accounting for that element in the code (Don’t forget the import).

 @FXML
 private StackPane ratStackPane;

Note the use of the @FXML annotation; it means, it is an element present in the fxml document, so we must put the same name there as it is in the code. To put a name to an element in the Scene Builder, find the code window in the right side accordion menu and paste the same name of the variable in the fx:id field (making sure the StackPane is selected).

fxml1

Now, following the same procedures, let’s put a Label, a TableView (fx:id: ratTable), and a Button (fx:id: addRatButton) and after a bit of tinkering with the layout and playing with the Containers, get a design like this:

fxml1

Well, let’s put some code in the controller. We need first an ObservableList attribute to put all the rats we are going to manage, and a variable where to hold every new rat we are adding.

private ObservableList rats;
private Rat currentRat;

Now, we are going to put a couple of ugly functions to setup the TableView and the Add Button; it is up to you the refactoring task to come up with something more modular. These functions are called in the initialize method. We will ignore the updateList() method for a while:

   @Override
    public void initialize(URL url, ResourceBundle rb) {
         // Retrieving data from the database
        updateRatList();
        // Setup the tableview
        setupTable();
        // setup the Button add
        setupButton();
    }

So, for the TableView, we need to put some columns and bind the content of that columns with the content of the ObservableList which contains its items. Note how in the fourth column, the optionsColumn, we use the cellFactory property to change its content for a couple of buttons to show and delete; this can be useful (I have to add that I am not sure this is the best way of put those buttons… if someone know something better, please, share it!):

private void setupTable()
{
	 // Code Column
	TableColumn codeColumn = new TableColumn<Rat, String>();
	codeColumn.setText("Code");
	codeColumn.setMinWidth(100);
	codeColumn.setCellValueFactory(new PropertyValueFactory("code"));

	// Weight Column
	TableColumn weightColumn = new TableColumn<Rat, Double>();
	weightColumn.setText("Weight");
	weightColumn.setMinWidth(50);
	weightColumn.setCellValueFactory(new PropertyValueFactory("weight"));

	// Weight Column
	TableColumn concentrationColumn = new TableColumn<Rat, Double>();
	concentrationColumn.setText("Brain Concentration");
	concentrationColumn.setMinWidth(100);
	concentrationColumn.setCellValueFactory(
							  new PropertyValueFactory("brainConcentration"));

	// Options Column
	TableColumn optionsColumn = new TableColumn();
	optionsColumn.setText("Options");
	optionsColumn.setMinWidth(100);
	// Use the CellFactory to customize the TableCell
	optionsColumn.setCellFactory(new Callback<TableColumn, TableCell>(){
		// Let's put a delete button and a display button
		@Override
		public TableCell call(TableColumn p) {

			TableCell tc = new TableCell(){
				@Override
				public void updateItem( Object item, boolean empty )
				{
					super.updateItem(item, empty);
					// Get the cell index
					int i = getIndex();

					if( i >= rats.size() )
						return;
					// Select the cell whose button we clicked
					ratTable.getSelectionModel().select(i);

					final Rat ratItem = (Rat)ratTable
											 .getSelectionModel()
											 .getSelectedItem();
					HBox hb = new HBox();
					Button delButton = new Button("X");
					delButton.setOnAction(new EventHandler(){

						@Override
						public void handle(ActionEvent t) {
							deleteRat(ratItem);
						}

					});

					Button dispButton = new Button("D");
					dispButton.setOnAction(new EventHandler() {

						@Override
						public void handle(ActionEvent t) {
							showRat(ratItem);
						}
					});

					hb.getChildren().addAll( delButton, dispButton );
					hb.setAlignment(Pos.CENTER);
					// Change the graphics of the cell to put the buttons
					setGraphic(hb);
				}
			};

			return tc;
		}
	});

	ratTable.getColumns().addAll( codeColumn,
								  weightColumn,
								  concentrationColumn,
								  optionsColumn );
	// items in the table view
	ratTable.setItems(rats);
}

To set up the button, we are going to take this opportunity and illustrate the use of animations and data binding in javafx. In the initial lines of the function we build a panel that contain the labels, textfields and button, in this case, we use a VBox. To each TextField we a changeListener to the textProperty to update the new Rat object when this property is changed:

codeText.textProperty().addListener(new ChangeListener() {

                    @Override
                    public void changed(ObservableValue<? extends String> ov, String oldV, String newV) {
                        currentRat.setCode(newV);
                    }
                });

Also, after adding it to the container StackPane, we set up a FadeTransition using the FadeTransitionBuilder; this create a fancy entrance of the panel into scene.

FadeTransition tf = FadeTransitionBuilder.create()
                                    .fromValue(0)
                                    .toValue(100)
                                    .node(vb)
                                    .duration(Duration.seconds(.5))
                                    .build();
                tf.play();

The complete code of the setupButton function is:

private void setupButton()
{
	// put the action on the button
	addRatButton.setOnAction(new EventHandler() {

		@Override
		public void handle(ActionEvent t) {
			currentRat = new Rat();
			final VBox vb = new VBox();
			Label codeLabel = new Label("Code: ");
			TextField codeText = new TextField();
			codeLabel.setLabelFor(codeText);
			// Property binding
			codeText.textProperty().addListener(new ChangeListener() {

				@Override
				public void changed(ObservableValue<? extends String> ov, String oldV, String newV) {
					currentRat.setCode(newV);
				}
			});

			Label weightLabel = new Label("Weight: ");
			TextField weightText = new TextField();
			weightLabel.setLabelFor(weightText);
			weightText.textProperty().addListener(new ChangeListener() {

				@Override
				public void changed(ObservableValue<? extends String> ov, String oldV, String newV) {
					currentRat.setWeight(Double.parseDouble(newV));
				}
			});
			Label concLabel = new Label("Concentration: ");
			TextField concText = new TextField();
			concLabel.setLabelFor(concText);
			concText.textProperty().addListener(new ChangeListener() {

				@Override
				public void changed(ObservableValue<? extends String> ov, String oldV, String newV) {
					currentRat.setBrainConcentration(Double.parseDouble(newV));
				}
			});

			Button addButton = new Button("Add");
			addButton.setOnAction(new EventHandler() {

				@Override
				public void handle(ActionEvent t) {
					saveRat();
					// Maybe another animation/fade out
					ratStackPane.getChildren().remove(vb);
				}
			});

			vb.getChildren().addAll(codeLabel,
									codeText,
									weightLabel,
									weightText,
									concLabel,
									concText,
									addButton);
			vb.setFillWidth(false);
			vb.setAlignment(Pos.CENTER);
			vb.getStyleClass().add("add-rat-pane");
			ratStackPane.getChildren().add(vb);

			FadeTransition tf = FadeTransitionBuilder.create()
								.fromValue(0)
								.toValue(100)
								.node(vb)
								.duration(Duration.seconds(.5))
								.build();
			tf.play();
		}
	});
}

Note that we have put a style to the panel, so we can give it a cool look and feel just with the traditional css, great! don’t you think?. Finally, let’s add some code to save/delete/list our rats.

First we need an EntityManager to do the DB thing. We put it as an attribute of the class. The createEntityManagerFactory method receives the Persistence Unit name as a parameter; this name can be found in the persistence.xml:

// Persistence
    final EntityManagerFactory emf = Persistence
                                       .createEntityManagerFactory("RatManagerPU");;
    final EntityManager em = emf.createEntityManager();

The long awaited updateList() method runs a NamedQuery, which is defined in the entity class (you can look for yourself). This is done in two steps: create a query and execute the query; the results are added to the ObservableList that constitute the model of the TableView, any change to this list is reflected immediately in our TableView.

 private void updateRatList()
    {
        Query rq = em.createNamedQuery("Rat.findAll");
        List results = rq.getResultList();

        if( rats == null)
            rats = FXCollections.observableArrayList(results);
        else
        {
            rats.clear();
            rats.addAll(results);
        }
    }

On the other hand, the add button calls the method saveRat() (sadly some rats won’t be saved… specially in a neuro-lab), which use a transaction to persist the new object just created thanks to the binding of the Form displayed to the user; obviously, there is a couple of validations that need to be done before saving, but that’s up to you.

private void saveRat()
    {
        em.getTransaction().begin();
        em.persist(currentRat);
        em.getTransaction().commit();
        updateRatList();
    }

We conclude giving birth to a method to delete entities which is invoked by the delete button (The code of the display button is left as a homework).

private void deleteRat( Rat rat )
    {
        em.getTransaction().begin();
        em.remove(rat);
        em.getTransaction().commit();
        updateRatList();
    }

Before we can run our application, we need to modify the RatManager class to update the scene we want to load, instead of the default one we erased at the beginning.

Parent root = FXMLLoader.load(getClass().getResource("ui/RatView.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Rat Manager");
stage.show();

And that is all.

Advertisements

2 thoughts on “An example application with JPA and JavaFX”

  1. Hi,
    Unfortunately you forgot to post the method code for showRat(ratItem).
    So your tutorial example does not run. It would be so helpful if you could post it.

    And, second, can you post the libraries for persisting? I get constantly compilation errors. looks like some library problem. Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s