Skip to content
rashansmith edited this page May 14, 2020 · 8 revisions

Welcome to the Spring-To-Quarkus-Spring wiki!

This wiki provides an overview of the process of converting a Spring Application to a Quarkus Application.

PREREQUISITES

Java 11 or higher Maven 3.6.3 or higher

STEP 1: Generate Quarkus Dependencies

In order to convert the Spring Application to a Quarkus application, we will need the necessary Quarkus dependencies:

mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=survey-quarkus-spring-service \
    -DclassName="com.redhat.appdevpractice.samples.survey.controller" \
    -Dextensions="spring-web,spring-di,spring-data-jpa,resteasy-jsonb,jdbc-postgres,hibernate-orm,rest-assured,hibernate-orm,quarkus-agroal" \     
    -Dpath="/hello"

STEP 2: Clone repo of Spring Application

Create a local copy of the AppDev Spring Rest Survey Group repository

git clone https://github.com/redhat-appdev-practice/spring-rest-surveygroups.git

STEP 3: Update Pom.xml

Replace the contents of the Spring Application pom.xml with the contents of the Quarkus Application pom.xml.

STEP 4: Update the SurveyController

Prerequisites: Add the following imports to SurveyController.java

import javax.enterprise.context.ApplicationScoped;
import javax.transaction.Transactional;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;

4.1: Add annotations to SurveyController class

Add the @ApplicationScoped and @RegisteredClient annotations to the top of the SurveyController class:

@CrossOrigin
@RestController
@ApplicationScoped
@RegisterRestClient
public class SurveyController { ... }

4.2: Add UriInfo Context

Quarkus does not support the ServletUriComponentsBuilder. Instead we will use the Javax UriInfo and Context. Declare the following:

@Context
private UriInfo info;

4.3: Edit the POST createSurvey

First, add the @Transactional annotation to the createSurvey method. Quarkus contains a Transactional manager that that coordinates and exposes transactions to your applications. Each extension dealing with persistence will integrate with it for you. To learn more: https://quarkus.io/guides/transaction

@PostMapping("/surveygroups")
@Transactional
public ResponseEntity<String> createSurvey(@RequestBody NewSurveyGroupResource newSurveyGroup) { .. }

Secondly, replace the URI URI location = line to the following:

URI location = info.getAbsolutePathBuilder().path("/{surveyGroupId}").build(surveyGroup.getGuid());

4.4: Edit the GET getSurveyGroup

Add the @Transactional annotation.

@GetMapping("/surveygroups/{surveyGroupId}")
@Transactional
public ResponseEntity<SurveyGroupResource> getSurveyGroup(@PathVariable("surveyGroupId") String surveyGroupId) { ... }

4.5: Edit the GET getSurveyGroups

Add the @Transactional annotation.

@GetMapping("/surveygroups/{surveyGroupId}")
@Transactional
public ResponseEntity<SurveyGroupResource> getSurveyGroups() { ... }

4.6: Edit the PUT saveSurveyGroup

Add the @Transactional annotation.

@PutMapping("/surveygroups/{surveyGroupId}")
@Transactional
public ResponseEntity<SurveyGroupResource> saveSurveyGroup(@PathVariable String surveyGroupId,
	@RequestBody SurveyGroupResource surveyGroupResource) { ... }

STEP 5: Update the Repository to use PanacheRepository

The current Spring Service uses a JPA Repository. Quarkus offers Panache, a Hibernate ORM with a JPA implementation. It makes complex mappings possible, but it does not make simple and common mappings trivial. To learn more: https://quarkus.io/guides/hibernate-orm-panache

STEP 5.1: Remove the following import statements:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

STEP 5.2: Add the following import statements:

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import javax.enterprise.context.ApplicationScoped;

STEP 5.3: Replace the SurveyGroupRepository class with the following:

@ApplicationScoped
public class SurveyGroupRepository implements PanacheRepository<SurveyGroup> {

	public SurveyGroup findByGuid(String surveyGroupGuid) {
		return find("guid", surveyGroupGuid).singleResult();
	}

	public void deleteByGuid(String string) {

		delete(findByGuid(string));
	}

}

STEP 6: Update the Models to extend the PanacheEntity

STEP 6.1: Edit EmployeeAssignment.java

Not only do we need to extend the PanacheEntity, but we need to also format the dates with @JsonFormat so that they can be deserialized by the database.

Add the following import statements:

import com.fasterxml.jackson.annotation.JsonFormat;
import io.quarkus.hibernate.orm.panache.PanacheEntity;

Extend the PanacheEntity on the EmployeeAssignment class:

@Entity
public class EmployeeAssignment extends PanacheEntity { ... }

Add the following @JSONFormat annotations to the LocalDate fields (allow for deserialization):

    @JsonFormat(pattern = "YYYY-MM-dd")
    private LocalDate startProjectDate;
    @JsonFormat(pattern = "YYYY-MM-dd")
    private LocalDate endProjectDate;

We must also configure the fetch type to be LAZY for surveyGroup:

@ManyToOne(fetch = FetchType.LAZY)
@JoinTable(name = "surveygroup_employee", 
    joinColumns = { @JoinColumn(name = "employee_id") }, 
    inverseJoinColumns = { @JoinColumn(name = "id") })
private SurveyGroup surveyGroup;

STEP 6.2: Edit Skill.java

Add the following import:

import io.quarkus.hibernate.orm.panache.PanacheEntity;

Extend the PanacheEntity:

@Entity
public class Skill extends PanacheEntity { ... }

STEP 6.3: Edit SurveyGroup.java

Add the following import:

import io.quarkus.hibernate.orm.panache.PanacheEntity;

Extend the PanacheEntity:

@Entity
public class SurveyGroup extends PanacheEntity { ... }

In employeeAssignments @OneToMany annotion, assign the fetch type to LAZY

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "surveygroup_employee_assignments", 
    joinColumns = { @JoinColumn(name = "id") }, 
    inverseJoinColumns = { @JoinColumn(name = "employee_assignment_id") })
private List<EmployeeAssignment> employeeAssignments;

STEP 7: Update the Service

Remove the following import:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

Add the following imports:

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.NoResultException;
import javax.transaction.Transactional;

Replace the @Service annotation on the SurveyServiceImpl with @ApplicationScoped:

@ApplicationScoped
public class SurveyServiceImpl implements SurveyService { ... }

Add an @Inject annotation to the surveyGroupRespository declaration:

@Inject
private SurveyGroupRepository surveyGroupRepository;

Edit createSurveyGroup by:

  • Adding @Transactional annotation
  • changing .saveandflush (JPA method) to .persistandflush (Panache method)
  • Because the .persistAndFlush method does not return anything, we return the result of .findByGuid(guid)

Replace method body with the following:

@Transactional
public SurveyGroup createSurveyGroup(SurveyGroup surveyGroup) {
    String guid = UUID.randomUUID().toString();
    surveyGroup.setGuid(guid);
    surveyGroupRepository.persistAndFlush(surveyGroup);
    return surveyGroupRepository.findByGuid(guid);
}

Edit getSurveyGroup by:

  • Adding @Transactional annotation
  • adding the .list() to return statement which allows Panache to return a list
@Transactional
public List<SurveyGroup> getSurveyGroups() {
    return surveyGroupRepository.findAll().list();
}

Edit updateSurveyGroup by:

  • Adding @Transactional annotation
@Transactional
public SurveyGroup getSurveyGroup(String surveyGroupGuid) {
    SurveyGroup surveyGroup = surveyGroupRepository.findByGuid(surveyGroupGuid);
    if( surveyGroup == null){
        throw new ResourceNotFoundException("The survey group does not exist.");
    }
    return surveyGroup;
}

Edit updateSurveyGroup by:

  • Adding @Transactional annotation
  • change .saveAndFlush to .persistAndFlush
@Override
@Transactional
public SurveyGroup updateSurveyGroup(SurveyGroup oldSurveyGroup, SurveyGroup newSurveyGroup) {
    oldSurveyGroup.updateWith(newSurveyGroup);
    surveyGroupRepository.persistAndFlush(oldSurveyGroup);
    return oldSurveyGroup;
}

STEP 8: Add an application.properties file

Springboot currently uses an application-local.yml to setup its connection to the database. Instead create an application.properties file in src/main/resources with the following:

---
# datasource configuration
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_db
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = hibernate
quarkus.datasource.password = hibernate


# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

To learn more : https://quarkus.io/guides/hibernate-orm

STEP 9: Update JUnit Tests

Coming Soon..