Truncate All Tables in Spring Boot JPA App

When writing integration tests, the common approach to take in a teardown method is to cleanup any data you created for your tests. 9.999 times out of 10 we really just need the database to be empty because the next test is going to create everything it needs. Working with Spring Boot and JPA, the sensible thing to do is rely on your cascades to get rid of child relationships; except when you can't.

I've had a question on Stackoverflow for a while that has yet to be answered, although folks have been nice enough to try. I currently don't know why cascades don't work for integration tests but like any good software engineer, I moved on to get work done.

Originally, I went down the path of deletion services that would manually manage the cascades by calling Repositories to delete child and sibling relationships. Then the "9.999 times out of 10" struck me and I took a different approach; why not just truncate all the tables?

I don't annotate all my entities with @Table or supply the name for @Entity so I needed a way of getting the actual database table names so I could execute some native SQL.

WARNING: If you're not using Hibernate, this won't work.

So here's the service class I created with the all important truncate() method that does the work.

@Service
@Profile("test")
public class TruncateDatabaseService {

    private EntityManager entityManager;

    @Autowired
    public TruncateDatabaseService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Transactional
    public void truncate() throws Exception {
        List<String> tableNames = new ArrayList<>();
        Session session = entityManager.unwrap(Session.class);
        Map<String, ClassMetadata> hibernateMetadata = session.getSessionFactory().getAllClassMetadata();

        for (ClassMetadata classMetadata : hibernateMetadata.values()) {
            AbstractEntityPersister aep = (AbstractEntityPersister) classMetadata;
            tableNames.add(aep.getTableName());
        }

        entityManager.flush();
        entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
        tableNames.forEach(tableName -> entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate());
        entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
    }
}

Now, I can simply inject this service into my test classes and call the truncate() method in whatever teardown method I have. Note that using @Profile("test") ensures that this service is only wired up for DI in the test environment. That makes it a bit safer in case someone accidentally uses it in real code. But definitely a bit risky depending on your dev team and their experience.