Todo desenvolvedor, mais cedo ou mais tarde, enfrenta a necessidade de migrar dados em um banco de dados. Em nosso projeto, usamos o mongoDB como um banco de dados. Abordamos a migração de dados de diferentes maneiras:
- escreveu scripts js e correu diretamente no banco de dados
Mongobee usado - uma ferramenta para migrações automáticas
Mongobee funcionou bem até encontrarmos uma situação em que queríamos adicionar um novo campo com um índice exclusivo. Digamos que temos uma aula:
@Document @Data public class TestDocument { @Id private String id; private String text; }
Agora adicionamos um novo campo:
@Document @Data public class TestDocument { @Id private String id; private String text; @Indexed(unique = true) private String text2; }
Escrevemos uma migração, que deve preencher o campo text2 em todos os documentos com valores exclusivos:
@ChangeLog public class TestDocumentChangelog { @ChangeSet(order = 1, id = "change1", author = "Stepan") public void changeset(MongoTemplate template) { template.findAll(Document.class, "testDocument").forEach(document -> { document.put("text2", UUID.randomUUID().toString()); template.save(document, "testDocument"); }); } }
Porém, quando iniciamos o aplicativo, vimos que o Spring lançou os componentes do Mongo Data antes do início da migração e, portanto, ocorreu um erro ao criar automaticamente o índice acima do campo text2 , pois esse campo ainda não foi preenchido por nenhum documento.
Após esse incidente, decidimos abandonar o Mongobee e tentar escrever nossa própria ferramenta, o que também facilitaria a migração de migrações, mas além disso, apresentaria recursos como:
- Executar antes do Mongo Data
- Suporte à transação adicionado no MongoDB 4.0
- Injeção de dependência nas classes ChangeLog
O resultado é uma biblioteca chamada Mongration, que suporta todas as funcionalidades descritas acima.
Suporte ao ciclo de vida de inicialização por mola
A primeira função é implementada usando a configuração automática, iniciada após a criação do MongoClient e antes da varredura dos repositórios:
@Configuration @AutoConfigureAfter(MongoAutoConfiguration.class) @AutoConfigureBefore(MongoDataAutoConfiguration.class) @Import(MongrationConfiguration.class) public class MongrationAutoConfiguration { }
Mas o problema com a configuração automática é que, se você usar a anotação @EnableMongoRepositories
para ativar repositórios, os componentes do Mongo Data serão inicializados antes da nossa configuração automática. Para evitar esse problema, você deve usar a anotação @EnableMongration
junto com @EnableMongoRepositories
:
@Configuration @EnableMongoRepositories @EnableMongration public class MongoRepositoriesConfiguration { }
A @EnableMongration
nada mais faz do que iniciar a mesma configuração, apenas permite executá-la mais cedo:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(MongrationConfiguration.class) public @interface EnableMongration { }
Suporte de transação
Para oferecer suporte a transações, você deve configurar o MongoDB do conjunto de réplicas. Também é necessário declarar o bean MongoTransationManager (se o Mongration não encontrar esse bean no contexto, ele o criará por si só). O Mongration permite usar transações de duas maneiras:
- Usando
@Transactional
@Transactional @ChangeSet(order = 1, id = "change1", author = "Stepan") public void changeset(MongoTemplate template) { template.findAll(Document.class, "testDocument").forEach(document -> { document.put("text2", UUID.randomUUID().toString()); template.save(document, "testDocument"); }); }
- Usando
TransactionTemplate
@ChangeSet(order = 1, id = "change1", author = "Stepan") public void migration(MongoTemplate template, TransactionTemplate txTemplate) { template.createCollection("entity"); txTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { template.save(new Document("index", "1").append("text", "1"), "entity"); template.save(new Document("index", "2").append("text", "2"), "entity"); template.save(new Document("index", "3").append("text", "3"), "entity"); } }); }
O segundo método é útil, pois permite o uso de operações DDL que não podem ser iniciadas em uma transação e operações DML iniciadas em uma transação em uma única migração.
Injeção de dependência nas classes ChangeLog
Essa funcionalidade é possível devido ao fato de que cada classe ChangeLog em si é um bean comum:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component public @interface ChangeLog { }
Vale ressaltar que você não pode injetar beans que possuem dependências nos componentes do Mongo Data, porque no momento em que as migrações são concluídas, elas ainda não foram inicializadas.
O código fonte da biblioteca está disponível no Github .