The source for this project can be found on GitHub.

I recently became a big fan of JHipster. For those of you not familiar, JHipster is a development platform for generating Spring Boot and Angular Web applications. With JHipster you can set up a robust production-ready Web application with a sleek, modern UI in the matter of minutes. If you aren’t familiar, I strongly recommend you check out the site to learn more. The documentation is really good and there are a some great working examples as well.

One of the initial challenges I had with JHipster is that the generated interface did not support a true master-detail UI. The good news is that JHipster supports relationships in your entities very well, it just takes a few minor tweaks to get the UI to display the relationship on a single page. The purpose of this blog post is to show you the changes I made to display a one-to-many relationship on a single page, with a master-detail UI.

What Is A Master Detail Relationship?

For the sake of this tutorial, I am going to use a very simple relationship between an Owner and a Pet. An Owner is a simple entity with only one property, a name. A Pet is a slightly more complex (although not really) entity that takes a name and species. These two entities have a one-to-many relationship from Owner to Pet. That is an Owner may have more than one Pet, and a Pet must have one, and only one Owner. In this case, the Owner is the master record or entity, and Pet is the detail record or entity.

Generating The Initial Application

So to get started, I’m going to walk you through the process of generating the application in JHipster. This is by no means an exhaustive JHipster tutorial, so for more information on generating the application, see the JHipster documentation. I’m also assuming you have JHipster installed already.

Run JHipster to Generate the App

This step is easy enough, from the command line run the JHipster command:

Andrews-MBP:master-detail andrew$ jhipster
JHipster Command
Generating an application with the jhipster command.

This command will walk you through the process of selecting the technologies you want to use in your application stack. For the sake of this tutorial generate a monolith with a relational database using AngularJS. You can use the defaults for the rest of the options for this demo case, although you may want to chose a persistent h2 database for your development environment.

Generate The Entities

After you’ve generated the application the next step is adding the entities. Entities are your database object, in this case the Owner and Pet objects that are the subject of our master-detail relationship.

Using JDL

There are several ways to generate entities and they are documented well. You can use the jhipster entity command to generate them from the command line but I’ve found the online JDL-Studio tool to be a very fast and easy way to generate your entities and relationships.

I used the following JDL to define my entities:

entity Owner {
    name String required
}

entity Pet {
    name String required,
    species String required
}

relationship OneToMany {
    Owner{pet} to Pet{owner}
}

Notice the OneToMany relationship in the above JDL. This is what defines the underlying relationship and is key to getting the master-detail relationship to work quickly. The above example states that a Owner has many Pets and a Pet has a single Owner.

Importing JDL

You can download your JDL file from JDL-Studio (or create your own locally). Once this is done you will need to import the entities into your JHipster application. This is done with the jhipstor import-jdl command. Running this command will ask you to overwrite your existing entity definitions. In this case it is safe to overwrite them.

Run The Application For The First Time

At this point you have a fully functioning app. Go ahead and start it up for the first time to see how cool JHipster is. The default address will be http://localhost:8080. You’ve already got a completely working CRUD interface for your entities!

The JHipster default home page
The JHipster default home page.

Take some time to move around and get comfortable with the standard interface. Once you log in you will have access to the entites menu. In here you can find pages to list, add, edit, and delete your entities.

The Default Owner Listing.
The default Owner listing.

The above screenshot is an example of the default owner listing. In the default app, clicking on the view or edit button would only show details on the Owner entity. To create Pets for this Owner you would need to go to the Pets entity and select the correct Owner as you create new ones as in the image below.

UI For Creating A Pet Entity
UI for creating a Pet entity.

The next step is to start making modifications to the code to create your master-detail UI.

Modifying The Generated Code

JHipster uses Spring Boot on the backend, automatically creating JPA repositories to talk to our data source and setting up repositories to establish our REST API that is used by the AngularJS frontend. Most of the framework is there so we need to make a few minor modifications to get our master-detail relationship working.

Updating The Repository

Check out the Swagger API documentation under the Administration -> API menu item. You can see in the pet-resource that we have no way to easily query Pet entities by Owner. This is a key component in getting this relationship to work.

An image of the Pet API.
The default REST endpoints for the Pet API.

So, the first thing we need to do is make the Pet data easily available based on the Owner. There are a few different ways to go about this but I decided to add a new REST end point called /api/pets/owners/{ownerId}. I chose this route because I wanted to keep the Owner and Pet data sources separate in my Resource classes.

Make The Data Available From The Repository

So first we need to provide a way to query the data source for all Pets given a specific Owner. Fortunately for us JPA makes this very easy. So load up the generated application in your favorite editor (I’m using IntelliJ Community here). We are going to modify the PetRepository class to expose the data we need.

Image of the PetRepository class listing.
PetRepository class with newly added findByOwnerId method.

So a couple of notes here. First, JHipster uses a specific directory structure so you will find the repositories under src/main/java//repository. Second, JPA uses a very specific syntax in order to query the data source without us having to write any SQL. In the following method specification we are adding:

List<Pet> findOwnerById(Long Id);

The “Owner” part in this line must match the parent entity property name in the Pet class. The “Id” part must match the parent entity unique identifier property name in the Owner class. This is how JPA is able to query the underlying data source correctly without us adding any SQL code. Once you’ve made the change, save and compile your code. Note that you don’t have to restart your Spring Boot app. JHipster’s included development tools will automatically apply your changes when using the development profile.

Adding The New End Point To The Resource

So now that we’ve created a way to query for the data we need, we need to actually add the new rest endpoint that I mentioned above. We will do this by adding some code to the PetResource class.

An image of the PetResource class listing.
The new getAllPetsForOwner method in the PetResource class

The PetResource class can be found in the src/main/java//web.rest directory. We add the following code to add our new endpoint:

/**
* GET  /pets/owners/{ownerId} : get all the pets by owner id.
*
* @return the ResponseEntity with status 200 (OK) and the list of actions in body
*/
@GetMapping("/pets/owners/{ownerId}")
@Timed
public List<Pet> getAllPetsForOwner(@PathVariable Long ownerId) {
log.debug("REST request to get all pets for owner : {}", ownerId);

List<Pet> actions = petRepository.findByOwnerId(ownerId);
return actions;
}

The important things to note about the addition of code are the fact that we are passing in a PathVariable of ownerId and that we are calling the findByOwnerId method we created in the previous step with that value. So go ahead and save and compile the changes we’ve added here. Let’s go check out the online Swagger documentation again and see what we find!

An image of the available Pet REST API resources.
A listing of our available Pet REST API.

Check it out! There is our new endpoint. And thanks to Swagger, you can actually check it out from the interface right here. If you’ve already added some data to your application, try it out, you should be able to query for all Pets given an Owner’s id number.

Updating The AngularJS Interface

Believe it or not, we’ve made all the backend changes we need to make. At this point the rest of our changes are related to the frontend.

Just a word of caution, I am not a master AngularJS developer, nor an expert JHipster user. there is likely a “more Angular” and “more JHipster” way to do this, but this method worked for me. If you have a better way, please let me know. I’d be happy to share it.

If you haven’t done so already, go ahead and fire up gulp from the command line in your JHipster project directory. This should launch your application on port 9000. This gives us an interface that updates live as we make changes to the frontend. This is a very helpful tool for making fast changes.

Updating The Master (Owner) View

JHipster generates a very slick looking UI. The AngularJS code is broken up in a manner consistent with John Papa’s Angular 1 Style Guide. Understanding this style guide is a must if you want to understand what is going on with the AngularJS code.

An image of the Pet entity directory listing.
The expanded directory listing of the Pet entity.

JHipster creates several controller, service, and html files for your entity as well as a state file. At a high level, the controller files should contain logic to render the view. The service file contains logic for communicating with the back-end. The html files contain the code for rendering the display. Finally, the state file contains routing information for the entity.

JHipster also provides the following standard actions for each entity. They are:

  1. The ability to list all entity items.
  2. Create a new entity item
  3. View the details of a given entity item.
  4. Edit the details of a given entity item.
  5. Delete an entity item.

For this particular tutorial I am only going to set up the master-detail relationship on the Owner entity details

An image of the default owner-detail HTML file.
The default Owner detail HTML code.

So we need to add the Pet entity “list” html code to the owner-detail.html file. This is actually as easy as taking the code from the pets.html code and inserting it into the owner-detail.html code at the location you want it to display. Note that if you are using multiple language support you will have to make some additional changes for your translations to be found.

Since you are using gulp, the second you save your changes to owner-detail.html you can check out the changes live. At this point you won’t actually see any Pets displayed in the view. This is because we need to update the owner-detail.controller.js code to actually query for them.

Updating The Detail (Pet) Service Code

So in order to actually query for Pets and restrict to an Owner we need to update the service code, which contains the logic for querying the backend. To do this we will be making some changes to the pet.service.js file.

(function() {
    'use strict';
    angular
        .module('masterdetailApp')
        .factory('Pet', Pet);

    Pet.$inject = ['$resource'];

    function Pet ($resource) {
        var resourceUrl =  'api/pets/:id';

        return $resource(resourceUrl, {}, {
            'queryByOwner': {
                url: 'api/pets/owners/:id',
                method: 'GET',
                isArray: true
            },
            'query': { method: 'GET', isArray: true},
            'get': {
                method: 'GET',
                transformResponse: function (data) {
                    if (data) {
                        data = angular.fromJson(data);
                    }
                    return data;
                }
            },
            'update': { method:'PUT' }
        });
    }
})();

The important part of the code above is lines 13-17. We have added a query method that allows us to hit our new REST endpoint we created back in the beginning. By using this method I could reuse the existing Pet service. I imagine that there is a better way and that this method could cause issues with JHipster code generation in the long run, but it works.

In line 14 above, we override the resourceUrl to hit a different endpoint. Note that we will be passing a parameter in the URL. In this case it will be the Owner Id.

Lines 15 and 16 are copied from the “query” method that already existed.

Updating The Master (Owner) Controller

Now that we’ve added code to query for the data we want, we need to call that code from a controller. Since we are modifying the Owner detail view in the UI we want to update the owner-detail.controller.js file.

(function() {
    'use strict';

    angular
        .module('masterdetailApp')
        .controller('OwnerDetailController', OwnerDetailController);

    OwnerDetailController.$inject = ['$scope', '$rootScope', '$stateParams', 'previousState', 'entity', 'Owner', 'Pet'];

    function OwnerDetailController($scope, $rootScope, $stateParams, previousState, entity, Owner, Pet) {
        var vm = this;

        vm.owner = entity;
        vm.previousState = previousState.name;

        vm.pets = [];

        loadAll();

        function loadAll() {
            Pet.queryByOwner({id: vm.owner.id}, function(result) {
                vm.pets = result;
                vm.searchQuery = null;
            });
        }

        var unsubscribe = $rootScope.$on('masterdetailApp:ownerUpdate', function(event, result) {
            vm.owner = result;
        });
        $scope.$on('$destroy', unsubscribe);
    }
})();

We’ve added lines 16-25 to the controller and they are responsible for:

  1. Initializing an empty pets list in the view model.
  2. Calling a loadAll function that is responsible for querying the backend data source.
  3. Defining the loadAll function to access the detail (Pet) service and use the queryByOwner logic we added in the previous step.
  4. Pass the Owner Id as a parameter in the queryByOwner call. Note that this is the current Owner Id that already exists in the view model.

After this change you can check out the UI again to see the changes we’ve made.

The Finished Product

At this point you can create a few Owner entity items. In this example I have defined the owners Andrew and Bob.

An image of the owner listing.
The owner listing with two owners defined.

I have also defined a Pet item for each owner. Note in listing below the different Owner Ids for each pet that correspond to the master Owner record.

An image of the pet listing.
The pet listing with two pets defined.

Now it’s time to see the final product. Go ahead and check the details view (click the View button) for each of your Owners. If you’ve followed everything right you should see only the associated Pets in the Owner details!

An image of the owner details.
Details for the owner Andrew with owned pets displayed.
An image of the owner details.
Details for the owner Andrew with owned pets displayed.

Now you have a working master-detail UI. Go ahead and try to add some more Pets. You can even do it from the Owner details screen.

A Few Things To Be Aware Of

  1. As mentioned already if you are using multilanguage support you will need to move some of your translations around.
  2. We didn’t talk about state or routing at all in this example. You will actually have to make quite a few changes to get routing to behave as you would expect in your application. For example if you create a new Pet from the Owner detail view you will be forward to the Pet listing afterward in the current example.
  3. We only implemented this from the Owner detail view. If you have other entities, or other places where you want to display this info you will need to make additional changes.
  4. There is probably a better way to do this with “good AngularJS” and “good JHipster” coding practices. Please reach out if you know what they are.

Give It A Shot

JHipster is a great tool for creating fast apps. I’m just getting in to everything you can do with it but I can already see how it saves a ton of setup and configuration time.

If you have any comments or suggestions on how I went about solving for this problem please let me know and I will be happy to update the blog. I created this entry myself as I was struggling to find the answer in one spot. Hopefully this will help somebody out.

You can find the source for this project on my github:
https://github.com/andrewehale/jhipster-master-detail

8 thoughts on “Creating A Master Detail Relationship With JHipster

  1. Great post! Can this be used for implementing entities that are only shown for a specific User? It would be a lot of code for a larger data model.

    Like

    1. If you want to use this to tie data to the logged in user this may help but you need to be concerned about security. The way we went about this feature (and there may be a better way) was to add a user id column to each entity that we needed to tie a user. We then pulled the user id from the logged in session using the built in security tools that come with jhipster. We then modified all of our hibernate functions to utilize the user id. I actually have a stackoverlfow post on it somewhere you may be able to dig up…

      Like

  2. Thanks for the post. It helped a lot. There is typo in the method specification “List findOwnerById(Long Id);” under the subheading “Make The Data Available From The Repository”.

    Like

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 )

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