Em muitos projetos, tabelas de banco de dados frequentemente possuem campos como data de criação, usuário criador, data de atualização e usuário atualizador. Durante operações de inserção ou atualização, a lógica para preencher esses campos de forma consistente pode se tornar repetitiva. A Programação Orientada a Aspectos (AOP) oferece uma solução elegante para automatizar esse processo.
Criação de uma Anotação Personalizada
Primeiro, definimos uma anotação para marcar métodos de Mapper que requerem preenchimento automático. Uma enumeração indica o tipo de operação do banco de dados.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreenchimentoAutomatico {
TipoOperacao value();
}
public enum TipoOperacao {
ATUALIZAR,
INSERIR
}
Implementação do Aspecto
Em seguida, criamos a classe de aspecto. Ela contém um ponto de corte que captura métodos anotados em pacotes específicos e uma notificação anterior para preencher os campos.
@Aspect
@Component
@Slf4j
public class AspectoPreenchimento {
@Pointcut("execution(* com.exemplo.repository.*.*(..)) && @annotation(com.exemplo.annotation.PreenchimentoAutomatico)")
public void pontoDeCorte() {
}
@Before("pontoDeCorte()")
public void preencherCampos(JoinPoint juntaPonto) {
log.info("Iniciando preenchimento automático de campos");
AssinaturaMetodo assinatura = (AssinaturaMetodo) juntaPonto.getSignature();
PreenchimentoAutomatico anotacao = assinatura.getMethod().getAnnotation(PreenchimentoAutomatico.class);
TipoOperacao tipoOp = anotacao.value();
Object[] argumentos = juntaPonto.getArgs();
if (argumentos == null || argumentos.length == 0) {
return;
}
Object entidade = argumentos[0];
Long idUsuario = ContextoUsuario.obterIdAtual();
LocalDateTime momentoAtual = LocalDateTime.now();
try {
if (tipoOp == TipoOperacao.INSERIR) {
executarSetter(entidade, "setDataAtualizacao", LocalDateTime.class, momentoAtual);
executarSetter(entidade, "setUsuarioAtualizador", Long.class, idUsuario);
executarSetter(entidade, "setDataCriacao", LocalDateTime.class, momentoAtual);
executarSetter(entidade, "setUsuarioCriador", Long.class, idUsuario);
} else if (tipoOp == TipoOperacao.ATUALIZAR) {
executarSetter(entidade, "setDataAtualizacao", LocalDateTime.class, momentoAtual);
executarSetter(entidade, "setUsuarioAtualizador", Long.class, idUsuario);
}
} catch (Exception e) {
throw new RuntimeException("Falha ao preencher campos via reflexão", e);
}
}
private void executarSetter(Object obj, String nomeMetodo, Class> tipoParam, Object valor) throws Exception {
Method setter = obj.getClass().getMethod(nomeMetodo, tipoParam);
setter.invoke(obj, valor);
}
}
Utilização no Repositório
A anotação é aplicada diretamente nos métodos do repositório, indicando o tipo da operação para guiar o preenchimento.
@Insert("INSERT INTO usuarios (nome, email, senha, data_criacao, usuario_criador) VALUES (#{nome}, #{email}, #{senha}, #{dataCriacao}, #{usuarioCriador})")
@PreenchimentoAutomatico(value = TipoOperacao.INSERIR)
void inserir(Usuario usuario);
@PreenchimentoAutomatico(value = TipoOperacao.ATUALIZAR)
void atualizar(Usuario usuario);