How to structure your Java apps using JHipster

How to structure your Java apps using JHipster

·

7 min read

7 years ago, JHipster had its initial release. It allowed developers to generate code for modern web applications that utilize the microservice architecture. Today, it has grown so much with its main focus being developer productivity.

This article was originally posted at https://www.quod.ai/post/how-to-structure-your-java-apps-using-jhipster

Introduction to JHipster

JHipster, in simple terms, is a code generator that can get you up and started with building applications very quickly. But the simplicity of JHipster ends with that sentence there. It can be used for building the most modern applications following cutting-edge patterns and technologies. The fact that they make it so easy to get started is their ultimate selling point. The best part is that it is all open source. The ‘J’ in JHipster stands for Java and it is directed at Java applications. Today we will take a look at some of the core components of a Java Spring Boot application that is generated by JHipster for you.

RESTful APIs using Spring Web

Spring Boot makes it very straightforward to implement complex concepts through the use of annotations. That is one of the biggest advantages of Spring Boot, that a lot of the configuration is hidden from the user and it takes an opinionated approach on what is the minimum that needs to be there for the application to run. Below is an example of a GET request to activate a particular user from the JHipster repository.

@GetMapping("/activate")
public void activateAccount(@RequestParam(value = "key") String key) {
    Optional user = userService.activateRegistration(key);
    if (!user.isPresent()) {
        throw new AccountResourceException("No user was found for this activation key");
    }
}

View UserController.java in context at Quod AI

Line 1: The @GetMapping annotation marks the method to be executed when the exact URL path is hit from the browser or some tool like Postman. It tells that it is a GET request and the path is “/activate”. Assume you run the application on your localhost on port 8080, the URL would be localhost:8080/activate.

Line 2: This line shows the method name and the arguments that it accepts. We have another annotation here called @RequestParam, with the value as ‘key’. This tells that the request will have a parameter in the URL called key, with its associated value. The URL would look like this: localhost:8080/activate?key=_some_value_.

Line 3-5: The next three lines implement the logic for activating a user. We call the activate method on the userService, passing in the key that we got as a request parameter. It returns an Optional value of User and this is checked to determine whether the user is activated or not.

Database entities and executing queries

Repositories are used to query the database and, in this example, we will take a look at the UserRepository and a few methods associated with it. But before that, we need to understand what the User entity consists of.

@Entity
@Table(name = "jhi_user")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User extends AbstractAuditingEntity implements Serializable {


    private static final long serialVersionUID = 1L;


    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;


    @NotNull
    @Pattern(regexp = Constants.LOGIN_REGEX)
    @Size(min = 1, max = 50)
    @Column(length = 50, unique = true, nullable = false)
    private String login;


    @JsonIgnore
    @NotNull
    @Size(min = 60, max = 60)
    @Column(name = "password_hash", length = 60, nullable = false)
    private String password;

View User.java in context at Quod AI

Line 1-3: The @Entity annotation marks this class as a database entity and the @Table annotation denotes that it represents a table with the name ‘jhi_user’. The @Cache annotation is used to denote that this entity will be part of the cache and the strategy used for caching is NONSTRICT_READ_WRITE. This is used for entities that are updated less frequently and also where two transactions would try to update the same item.

Line 5-53: These are the different fields present inside the user table. There are quite a few annotations to understand here. The @Id annotation tells us that this is the primary key of the table. It has a generated value, meaning we don’t have to explicitly set it, which is done through a sequence generator. The @Column annotation depicts different columns of the table. @Size specifies the max size whereas @NotNull denotes that it cannot be null.

Next, we will execute a database query using Spring Data JPA. Spring uses the @Repository annotation to abstract out a lot of the boilerplate code that was earlier required to make database queries. The below example is to execute a query to return a single user with a particular activation key. Remember the REST API we looked at before that checked whether a user is activated or not? This query is executed as a part of that operation.

@Repository
public interface UserRepository extends JpaRepository {
    Optional findOneByActivationKey(String activationKey);  
 }

View UserRepository.java in context at Quod AI

Line 1-2: The first line denotes that the following interface is a repository. We extend the JpaRepository with the entity class name and the type of its primary key, which is Long. This gives us access to a lot of predefined methods that perform queries behind the scenes.

Line 3: The query that we want to execute here is to find one user from that table with a specific activation key. In traditional SQL, it would be SELECT * FROM USER WHERE ACTIVATION_KEY = “key_value”. But with Spring Data JPA, we can write it as findOneByActivationKey(String activationKey). How does this work? Remember when we defined the columns, we specified the activation key as String. This allows Spring Data JPA to enable this method for us. One calling this method with a key as an argument, it will return a User entity if they exist otherwise null.

Testing using jUnit5

If you’re a Java developer, you must be familiar with jUnit testing. With jUnit 5, they’ve brought about some good additions to make it easier for testing your code. A new addition is the @BeforeEach annotation which you can see in action below.

@BeforeEach
public void initTest() {
    user = initTestUser(userRepository, em);
}

View UserTest.java in context at Quod AI

Line 1-3: The first line marks the following method to be done before each test case. The initTest() method calls another method initTestUser() in the test file passing in the repository object and the entity manager object. This is then used to create a mock object for the user for all the test cases.

Another great new addition is that of lambda functions within test cases. Below is an example of using lambda functions in our test cases. Don’t worry about the logic present here. It simply gets the size of the database before the user is deleted, performs the deletion operation, and checks whether the size has decreased by 1.

@Test
@Transactional
void deleteUser() throws Exception {
    // Initialize the database
    userRepository.saveAndFlush(user);
    int databaseSizeBeforeDelete = userRepository.findAll().size();


    // Delete the user
    restUserMockMvc
        .perform(delete("/api/admin/users/{login}", user.getLogin()).accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isNoContent());


    assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNull();


    // Validate the database is empty
    assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeDelete - 1));
}

View DeleteUserTest.java in context at Quod AI

Line 1-2: Denotes that this is a test case. @Transactional specifies that the following method will perform a database transaction and it will need to satisfy certain rules like not allowing updates if it gets cancelled in between, and ensuring that the database state is stable before and after the operation.

Line 5-6: We initialize the database here and then get the number of users present in the database. This is stored in the variable databaseSizeBeforeDelete.

Line 9-12: We then perform the delete operation on the user which accepts JSON input and the expected status is that of no content.

Line 13-16: Here we check two things. One is that we assert that the user is not present in the cache. Second, we assert that the database size has decreased by 1. If you see Line 16, you can also see the use of a lambda function to do the same, which was previously not possible with jUnit.

Conclusion

We hope that this post gave you an insight into JHipster and some of the concepts behind building a Spring Boot application. Don’t stop here, the repository contains a lot more than you can explore and use to evolve your application.

Quod AI is code search and navigation on steroids. We turn git repositories into documentation that developers actually use. Do follow us on Twitter @quod_ai for updates on our product and DEVs community content. Check our app at: beta.quod.ai.