Quarkus Extension for Spring Transaction API
While users are encouraged to use Jakarta @Transactional annotations directly, Quarkus provides a compatibility layer for Spring @Transactional in the form of the spring-tx extension.
This guide explains how a Quarkus application can leverage the Spring @Transactional annotation to manage transactions.
Requisitos previos
To complete this guide, you need:
-
Roughly 15 minutes
-
An IDE
-
JDK 17+ installed with
JAVA_HOMEconfigured appropriately -
Apache Maven 3.9.16
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
-
Some familiarity with the Spring DI extension
Creación del proyecto Maven
En primer lugar, necesitamos un nuevo proyecto. Cree un nuevo proyecto con el siguiente comando:
For Windows users:
-
If using cmd, (don’t use backward slash
\and put everything on the same line) -
If using Powershell, wrap
-Dparameters in double quotes e.g."-DprojectArtifactId=spring-tx-quickstart"
This command generates a project which imports the spring-tx, spring-di, spring-web, and spring-data-jpa extensions.
If you already have your Quarkus project configured, you can add the spring-tx extension
to your project by running the following command in your project base directory:
quarkus extension add spring-tx
./mvnw quarkus:add-extension -Dextensions='spring-tx'
./gradlew addExtension --extensions='spring-tx'
Esto añadirá lo siguiente a su archivo de construcción:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-tx</artifactId>
</dependency>
implementation("io.quarkus:quarkus-spring-tx")
Define the entity
Throughout the course of this guide, the following JPA Entity will be used:
package org.acme.spring.tx;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class Fruit {
@Id
@GeneratedValue
private Long id;
private String name;
public Fruit() {
}
public Fruit(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Define the repository
In a typical Spring Data fashion, create a repository for Fruit:
package org.acme.spring.tx;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface FruitRepository extends CrudRepository<Fruit, Long> {
List<Fruit> findByName(String name);
}
Creating a transactional service
Now let’s create a service that uses Spring’s @Transactional annotation to manage transactions.
Create src/main/java/org/acme/spring/tx/FruitService.java with the following content:
package org.acme.spring.tx;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class FruitService {
@Autowired
FruitRepository fruitRepository;
@Transactional (1)
public Fruit addFruit(String name) {
return fruitRepository.save(new Fruit(name));
}
@Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRED) (2)
public void addMultipleFruits(List<String> names) {
for (String name : names) {
fruitRepository.save(new Fruit(name));
}
}
}
| 1 | The Spring @Transactional annotation is automatically transformed into a Jakarta @Transactional annotation at build time. |
| 2 | You can specify propagation attributes just like in Spring. REQUIRED is the default. |
You can also place @Transactional at the class level to make all public methods transactional:
package org.acme.spring.tx;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional (1)
public class TransactionalService {
public void doSomething() {
// this method is transactional
}
public void doSomethingElse() {
// this method is also transactional
}
}
| 1 | All public methods of this class are transactional. |
Controlling rollback behavior
Spring’s rollbackFor and noRollbackFor attributes are supported:
package org.acme.spring.tx;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RollbackService {
@Transactional(rollbackFor = BusinessException.class) (1)
public void performBusinessOperation() throws BusinessException {
// if BusinessException is thrown, the transaction rolls back
}
@Transactional(noRollbackFor = NonCriticalException.class) (2)
public void performTolerantOperation() throws NonCriticalException {
// if NonCriticalException is thrown, the transaction still commits
}
}
| 1 | The transaction will roll back if BusinessException (or a subclass) is thrown. |
| 2 | The transaction will NOT roll back if NonCriticalException is thrown. |
Update the Jakarta REST resource
With the repository and service in place, create the Jakarta REST resource that will use the FruitService.
Create src/main/java/org/acme/spring/tx/FruitResource.java with the following content:
package org.acme.spring.tx;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FruitResource {
@Autowired
FruitService fruitService;
@Autowired
FruitRepository fruitRepository;
@GetMapping("/fruits")
public List<Fruit> list() {
return (List<Fruit>) fruitRepository.findAll();
}
@PostMapping("/fruits")
public Fruit add(@RequestBody Fruit fruit) {
return fruitService.addFruit(fruit.getName());
}
}
Empaquetar y ejecutar la aplicación
| Quarkus Dev Services will automatically start a PostgreSQL container in dev and test modes, so no manual database configuration is needed. |
Ejecuta la aplicación con:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
In another terminal, add a fruit:
curl -X POST -H "Content-Type: application/json" -d '{"name": "Apple"}' http://localhost:8080/fruits
Then list all fruits:
curl http://localhost:8080/fruits
As usual, the application can be packaged using:
quarkus build
./mvnw install
./gradlew build
And executed using java -jar target/quarkus-app/quarkus-run.jar.
You can also generate the native executable with:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
Supported Spring @Transactional features
The table below summarizes the supported attributes:
| Attribute | Estatus | Comments |
|---|---|---|
|
Supported |
All values except |
|
Supported |
|
|
Supported |
|
|
Supported |
|
|
Supported |
|
|
Unsupported |
Ignored with a warning |
|
Unsupported |
Ignored with a warning |
|
Unsupported |
Only |
|
Unsupported |
Quarkus manages the transaction manager |
|
Unsupported |
Ignored with a warning |
Important Technical Note
Please note that the Spring support in Quarkus does not start a Spring Application Context nor are any Spring infrastructure classes run.
Spring classes and annotations are only used for reading metadata and / or are used as user code method return types or parameter types.
What that means for end users, is that adding arbitrary Spring libraries will not have any effect. Moreover, Spring infrastructure
classes (like org.springframework.beans.factory.config.BeanPostProcessor for example) will not be executed.
Más guías de Spring
Quarkus tiene más características de compatibilidad con Spring. Consulte las siguientes guías para obtener más detalles: