Configuring Jacoco in a Maven Project

With reference to my earlier post on configuring jacoco in a gradle project, in this post we shall see how to configure the same in a maven project.

To enable jacoco code coverage in a maven project, just add the below configuration to your pom.xml file in the plugins section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.1</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<dataFile>target/jacoco.exec</dataFile>
<!-- Sets the output directory for the code coverage report. -->
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
</systemPropertyVariables>
<excludes>
<exclude>snmaddula/app/domain/*.class</exclude>
<exclude>snmaddula/app/exception/*.class</exclude>
<exclude>snmaddula/app/filter/*.class</exclude>
<exclude>snmaddula/app/App.class</exclude>
</excludes>
</configuration>
</plugin>

Now execute mvn clean test in the project directory. Navigate to target and open jacoco report file in a web browser to view the coverage report.

That’s all you need to configure jacoco in a maven project.

Configuring Jacoco in a Gradle Project

To enable jacoco code coverage in a gradle project, just add the below configuration to your build.gradle file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apply plugin: 'jacoco'

jacoco {
toolVersion = '0.8.1'
}

jacocoTestReport {
reports {
csv.enabled = false
xml.enabled = false
html.enabled = true
}
afterEvaluate { // (optional) : to exclude classes / packages from coverage
classDirectories.from = files(classDirectories.files.collect {
fileTree(
dir: it,
exclude: [ 'snmaddula/sboot/crud/App.class', 'snmaddula/sboot/util/**' ])
})
}
}

build.dependsOn jacocoTestReport

Now execute gradle clean build in the project directory. You’ll see the output similar to the below one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
12:10:08 PM: Executing tasks 'clean build'...

> Task :clean UP-TO-DATE
> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootJar
> Task :jar SKIPPED
> Task :assemble
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
> Task :check
> Task :jacocoTestReport
> Task :build

BUILD SUCCESSFUL in 9s
7 actionable tasks: 6 executed, 1 up-to-date
12:10:18 PM: Tasks execution finished 'clean build'.

Navigate to build > reports > jacoco > test > html and open index.html using a web browser.

You’ll see a report similar to the one shown below.

That’s all you need to configure jacoco in a gradle project.

Spring Boot : Logging Execution Time

Create a Simple Spring Boot App.

As a pre-requisite, we need to have a basic microservice with few endpoints.

In the earlier post Spring Boot JPA Crud Web App, we have implemented a simple microservice with basic CRUD functionality.

In this post, we shall see how to clock the execution time of a request within a microservice application.

Video guide of the process



 

For instance, we have few Controller or RestController classes, each having some RequestMapping methods. Now, how do we track the time taken by a specific endpoint for processing the request.

This can be easily achieved by registering a Filter that intercepts all the desired endpoints or url-patterns and clock the execution time.

Let’s register a OncePerRequestFilter through FilterRegistrationBean.

OncePerRequestFilter is an abstract class with one default method doFilterInternal, where we need to add our clock logic.

Any servlet filter that wraps the request, should be configured with an order that is less than or equal to 0, which is OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER.

Configure the execution time logging filter bean as shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Bean
public FilterRegistrationBean<OncePerRequestFilter> executionTimeLoggingFilter() {
return new FilterRegistrationBean<OncePerRequestFilter>() {{
setOrder(OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER);
setFilter(new OncePerRequestFilter() {

@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws ServletException, IOException {
StopWatch watch = new StopWatch();
watch.start();
try {
chain.doFilter(req, res);
}finally {
watch.stop();
log.info("REQUEST: {} completed within {} ms",
getUriWithMethodAndQuery(req), watch.getTotalTimeMillis());
}
}

private String getUriWithMethodAndQuery(HttpServletRequest req) {
return req.getMethod() + ": " + req.getRequestURI() +
(StringUtils.hasText(req.getQueryString()) ? "?" + req.getQueryString() : "");
}
});
}};
}

With this, you are done with configuring the execution time logging filter bean.

Now, if you access any endpoints in your application, you will get output logging that includes the execution time as follows:

1
2
3
4
5
REQUEST: GET: /products completed within 174 ms
REQUEST: POST: /products completed within 8 ms
REQUEST: GET: /products completed within 4 ms
REQUEST: GET: /products/2 completed within 21 ms
REQUEST: GET: /products/2?alpha=beta&gamma=delta completed within 4 ms

Spring Boot DATA JDBC CRUD Web App

DATA JPA vs DATA JDBC

In the previous post Spring Boot JPA Crud Web App, we have implemented CRUD functionality using spring boot’s data-jpa module.

Spring Data JPA is very rich in features but unfortunately it is not widely preferred due to its internal complexities and the latency costs involved in getting the work done.
If you are looking for an alternative, then you can consider Spring Data JDBC.

Here are some of the cool things about this module:

  • Spring Data JDBC is much simpler, cleaner and a straight-forward model.
  • The SQL commands are executed only when invoked by a repository method.
  • You get a fully loaded object / entity instance as a result for your query – forget about session, proxies, lazy-loading or caching.
  • In order to be simple, it does NOT offer caching, lazy loading, write behind and many other features of JPA
  • All these things make is a simple, limited and better choice compared to JPA for certain use-cases.

01. Add spring-data-jdbc dependency

Remove spring-boot-starter-data-jpa dependency and add the below dependency.

1
2
3
4
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jdbc</artifactId>
</dependency>

02. Update Entity Definition

Remove javax.persistence annotations and update Product.java as show below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package snmaddula.product.crud.entity;

import org.springframework.data.annotation.Id;

import lombok.Data;

@Data
public class Product {

@Id
private Long id;
private String title;
private String description;
private Double price;

}

In case, if your lombok configuration is not working, you can define setters & getters in the above class.

03. Repository Definition

Instead of JpaRepository, our ProductRepository would now extend CrudRepository as below.

1
2
3
4
5
6
7
8
9
10
package snmaddula.product.crud.repo;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import snmaddula.product.crud.entity.Product;

@Repository
public interface ProductRepo extends CrudRepository<Product, Long> {
}

04. Service Implementation

Refer to ProductService.java from previous post and update the below methods accordingly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package snmaddula.product.crud.service;

import java.util.List;
import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import snmaddula.product.crud.entity.Product;
import snmaddula.product.crud.repo.ProductRepo;

@Service
@RequiredArgsConstructor
public class ProductService {

private final ProductRepo productRepo;

public List<Product> fetchAll() {
return stream(productRepo.findAll().spliterator(), false).collect(toList());
}

public void removeAll() {
productRepo.deleteAll();
}

}

05. CREATE schema.sql

As JDBC does not auto create tables, we need to create table on our own.
Add the below CREATE query in your schema.sql file and place this file on the classpath.

1
2
3
4
5
6
CREATE TABLE PRODUCT (
ID INT PRIMARY KEY AUTO_INCREMENT,
TITLE TEXT,
DESCRIPTION TEXT,
PRICE DECIMAL
);