@DataJpaTest
Starts only JPA, your repositories and a test database, wrapping each test in a transaction that rolls back, to test queries and mappings.
@DataJpaTest(showSql = false)- Added in Spring Boot Spring Boot 1.4
Parameters
| Parameter | Type | Description |
|---|---|---|
$propertiesoptional | String[]= {} | key=value pairs added to the Environment for this test. |
$showSqloptional | boolean= true | Maps to spring.jpa.show-sql: whether SQL statements are logged. |
$bootstrapModeoptional | BootstrapMode= DEFAULT | Maps to spring.data.jpa.repositories.bootstrap-mode: DEFAULT, DEFERRED or LAZY repository initialisation. |
$useDefaultFiltersoptional | boolean= true | Whether to use the default filter that limits scanning to JPA types. |
$includeFiltersoptional | ComponentScan.Filter[]= {} | Extra types to add to the slice. |
$excludeFiltersoptional | ComponentScan.Filter[]= {} | Types to remove from the slice. |
$excludeAutoConfigurationoptional | Class<?>[]= {} | Auto-configuration classes to leave out. |
Return value
n/aStarts a JPA test slice: DataSource, Hibernate, Spring Data repositories and TestEntityManager, with each test in a transaction rolled back at the end.
Errors
Constraint violations surface as DataIntegrityViolationException only when the change is flushed.
Changelog
- PHP Spring Boot 4.0Moved to org.springframework.boot.data.jpa.test.autoconfigure in the spring-boot-data-jpa-test module (spring-boot-starter-data-jpa-test).
@DataJpaTest is the persistence slice. It configures a DataSource, Hibernate, Spring Data JPA repositories, Flyway or Liquibase if present, and a TestEntityManager. Controllers, services and other components are not loaded. Every test method runs in a transaction that is rolled back at the end, so tests do not see each other's rows.
In Spring Boot 4 it is org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest, from spring-boot-starter-data-jpa-test. TestEntityManager moved to org.springframework.boot.jpa.test.autoconfigure.
Which database it uses
The slice is meta-annotated with @AutoConfigureTestDatabase, whose default replace mode is NON_TEST. If an embedded database such as H2 is on the classpath, it replaces the DataSource from your configuration. A DataSource that comes from the test itself, such as a Testcontainers database with @ServiceConnection, is kept. Use replace = Replace.NONE to keep the configured database in every case.
Hibernate only creates the schema by itself for an embedded database. Against PostgreSQL the default ddl-auto is none, so the tables must come from your Flyway or Liquibase migrations (which the slice runs) or from spring.jpa.hibernate.ddl-auto=create-drop set for the test. Otherwise the first query fails with relation does not exist.
Prefer the real engine. H2 does not have PostgreSQL's jsonb, its locking or its exact SQL dialect, so a query that passes on H2 can fail in production. A PostgreSQL container started once per test class costs a few seconds and removes that whole class of surprise.
The flush trap
Hibernate writes changes at flush time, usually at commit. Because the test transaction never commits, a plain save() may never send an INSERT: unique constraints, not-null columns and database defaults are not checked, and the test passes when production would fail. Call saveAndFlush() or em.flush() when the test is about what the database accepts. Call em.clear() before reading back, otherwise you get the same Java object from the persistence context rather than what was stored.
What to test here
Derived and @Query methods with non-trivial conditions, projections, fetch joins, mapping of embeddables and converters, and constraints. Do not test that save followed by findById works; Spring Data already does. The rollback also hides LazyInitializationException, because the session stays open for the whole test, so test lazy loading through a service in a @SpringBootTest instead.
Example
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
class Book {
Long id;
String title;
String author;
boolean archived;
protected Book() {}
Book(String title, String author, boolean archived) {
this.title = title;
this.author = author;
this.archived = archived;
}
}
interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findByAuthorAndArchivedFalseOrderByTitle(String author);
}
// JPA, the repositories and an embedded database; each test rolls back.
class BookRepositoryTest {
TestEntityManager em;
BookRepository books;
void findsOnlyLiveBooksByAuthorInTitleOrder() {
em.persist(new Book("Persuasion", "Austen", false));
em.persist(new Book("Emma", "Austen", false));
em.persist(new Book("Lady Susan", "Austen", true));
em.persist(new Book("Middlemarch", "Eliot", false));
em.flush();
em.clear(); // read from the database, not the persistence context
List<Book> found = books.findByAuthorAndArchivedFalseOrderByTitle("Austen");
assertThat(found).extracting(b -> b.title).containsExactly("Emma", "Persuasion");
}
}
When to use it
- Check that a derived query with three conditions and a sort returns exactly the rows a search screen expects.
- Prove that a duplicate email is rejected by the unique index before relying on it in the sign-up service.
- Catch a native query that works on H2 but uses syntax PostgreSQL rejects.
Frequently asked questions
Why does @DataJpaTest use H2 when I configured PostgreSQL?
The default replace mode swaps a configured DataSource for an embedded database when one is on the classpath. Supply the database from the test with a Testcontainers container and @ServiceConnection, or add @AutoConfigureTestDatabase(replace = Replace.NONE). Against the container, let your Flyway migrations create the schema, or set spring.jpa.hibernate.ddl-auto=create-drop for the test, because Hibernate only creates tables automatically for an embedded database.
Can I stop the rollback?
Add @Rollback(false) or @Commit to a test method, or @Transactional(propagation = Propagation.NOT_SUPPORTED) to run without a test transaction. Then clean up yourself, because rows will persist between tests.
Are my services available in a @DataJpaTest?
No. Only JPA infrastructure and repositories are loaded. @Import a service if you need it, or move the test to @SpringBootTest when the service's own transactions are what you are testing.
More examples
Running the slice against real PostgreSQL
The static container starts once for the class. <code>@ServiceConnection</code> supplies the connection details, and because that <code>DataSource</code> comes from the test, the default <code>NON_TEST</code> replace mode leaves it alone instead of swapping in H2. PostgreSQL is not an embedded database, so Hibernate does not create tables unless told to: in a real project Flyway migrations do that, here <code>ddl-auto=create-drop</code> does. <code>@JdbcTypeCode(SqlTypes.JSON)</code> makes Hibernate bind the value as JSON; without it the insert fails because PostgreSQL will not put a <code>varchar</code> into a <code>jsonb</code> column. The <code>jsonb</code> column would not even create on H2, which is the point of using the real engine.
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import org.springframework.data.jpa.repository.JpaRepository;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
class Customer {
@Id
@GeneratedValue
Long id;
// Bind as JSON; a plain String is sent as varchar, which PostgreSQL rejects for jsonb.
@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "jsonb")
String preferences;
protected Customer() {}
Customer(String preferences) {
this.preferences = preferences;
}
}
interface CustomerRepository extends JpaRepository<Customer, Long> {}
// The same database engine as production, started for this class.
// No migrations in this example, so Hibernate creates the tables;
// with Flyway on the classpath, drop the property and let the migrations run.
@DataJpaTest(properties = "spring.jpa.hibernate.ddl-auto=create-drop")
@Testcontainers
class CustomerRepositoryPostgresTest {
@Container
@ServiceConnection
static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:17-alpine");
@Autowired
CustomerRepository customers;
@Test
void storesJsonbColumns() {
Customer saved = customers.saveAndFlush(new Customer("{\"newsletter\": true}"));
assertThat(customers.findById(saved.id)).isPresent();
}
}
Proving a unique constraint with saveAndFlush
The second insert must reach the database for the constraint to fire. With <code>save()</code> the entity would only be queued and the transaction rolled back before flushing, so the test would wrongly pass. Spring translates the driver's error into <code>DataIntegrityViolationException</code>, the same exception your service sees in production.
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.jpa.repository.JpaRepository;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
class Account {
@Id
@GeneratedValue
Long id;
@Column(nullable = false, unique = true)
String email;
protected Account() {}
Account(String email) {
this.email = email;
}
}
interface AccountRepository extends JpaRepository<Account, Long> {}
@DataJpaTest
class AccountConstraintsTest {
@Autowired
AccountRepository accounts;
@Test
void duplicateEmailIsRejectedByTheDatabase() {
accounts.saveAndFlush(new Account("[email protected]"));
// save() alone would pass: the INSERT only happens at flush,
// and the test transaction is rolled back before that.
assertThatThrownBy(() -> accounts.saveAndFlush(new Account("[email protected]")))
.isInstanceOf(DataIntegrityViolationException.class);
}
}
Validating mappings against the configured database
<code>Replace.NONE</code> keeps the <code>DataSource</code> from configuration, for example a database that Flyway has migrated. <code>ddl-auto=validate</code> makes Hibernate compare each entity with the real tables at startup, so a renamed column fails here instead of on the first request in production. <code>showSql = false</code> turns off the SQL logging the slice enables by default.
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace;
import org.springframework.data.jpa.repository.JpaRepository;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
class Country {
@Id
String code;
String name;
protected Country() {}
}
interface CountryRepository extends JpaRepository<Country, String> {}
// Use the DataSource from configuration (for example a local Docker database),
// check the mappings against the migrated schema, and keep the log quiet.
@DataJpaTest(showSql = false, properties = "spring.jpa.hibernate.ddl-auto=validate")
@AutoConfigureTestDatabase(replace = Replace.NONE)
class CountryMappingTest {
@Autowired
CountryRepository countries;
@Test
void schemaMatchesTheEntities() {
assertThat(countries.count()).isGreaterThanOrEqualTo(0);
}
}
Discussion