🗃️ A Camada de Repositório (`@Repository`)

⚠️

Acesso Antecipado (Beta)

Esta documentação refere-se a uma versão em acesso antecipado do SDK Sankhya. As funcionalidades e APIs estão sujeitas a modificações. Para obter acesso, envie um e-mail para [email protected] informando a appkey do seu projeto.

🗃️ A Camada de Repositório (@Repository)

O padrão Repository no SDK Sankhya é uma abstração poderosa que simplifica drasticamente o acesso a dados. Ele permite que você defina consultas de forma declarativa, sem escrever uma única linha de implementação, e promove um código limpo, seguro e fácil de manter.

🤔 Por que usar Repositórios?

  • Implementação Automática: Você define a interface, e o SDK escreve o código de acesso a dados para você em tempo de compilação.
  • CRUD Gratuito: Métodos como save(), findById(), findAll() e delete() já vêm prontos para uso.
  • Consultas Declarativas: Escreva consultas complexas com anotações (@Criteria), utilizando parametros nomeados.
  • Segurança de Tipos (Type-Safety): Chega de "strings mágicas". Parâmetros e retornos são fortemente tipados, e erros são pegos em tempo de compilação, não em execução.
  • Código Limpo e Testável: A camada de acesso a dados fica completamente isolada, permitindo que seus serviços sejam testados facilmente com repositórios mock.

📊 Abordagem Antiga vs. Nova

❌ Abordagem Tradicional (DAO com Jape)

O padrão DAO (Data Access Object) com Jape manual é verboso, propenso a erros de digitação e vulnerável a SQL Injection.

// Código legado: verboso, inseguro e difícil de manter
public class VeiculoDAO {
    public Optional<Veiculo> buscarPorPlaca(String placa) throws Exception {
        JapeWrapper dao = JapeFactory.dao("Veiculo");
        // Concatenação de strings é perigosa e propensa a erros
        Collection<DynamicVO> vos = dao.find("PLACA = '" + placa + "'");

        if (vos.isEmpty()) {
            return Optional.empty();
        }
        // Conversão manual de DynamicVO para POJO
        DynamicVO vo = vos.iterator().next();
        Veiculo veiculo = new Veiculo();
        veiculo.setId(vo.asLong("CODVEICULO"));
        veiculo.setPlaca(vo.asString("PLACA"));
        return Optional.of(veiculo);
    }
}

✅ Nova Abordagem com @Repository

Com o SDK, você apenas define a interface. O código é limpo, seguro e declarativo.

// Código moderno: seguro, declarativo e sem boilerplate
@Repository
public interface VeiculoRepository extends JapeRepository<Veiculo, Long> {
    
    @Criteria("ativo = :ativo AND v.marca = :nomeMarca")
    List<Veiculo> findAtivosPorMarca(
        @Parameter("ativo") boolean ativo, 
        @Parameter("nomeMarca") String nomeMarca
    );

    @Criteria(clause = "ATIVO = :ativo")
    Page<Veiculo> findByAtivoPaginado(Boolean ativo, Pageable pageable);
}

// Uso no serviço:
@Service
public class VeiculoService {
    private final VeiculoRepository repository;

    @Inject
    public VeiculoService(VeiculoRepository repository) {
        this.repository = repository;
    }

    public Optional<Veiculo> buscarPorPlaca(String placa) {
        // Simples, direto e seguro.
        return repository.findByPlaca(placa);
    }
    
    public Page<Veiculo> buscarAtivosPaginados(String nomeMarca) {
        //Objeto de paginação número da página, tamanho da página e argumentos para paginação
        PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("NOME", Direction.DESC), Sort.by("PLACA", Direction.DESC));
        return repository.findByAtivoPaginado(nomeMarca, pageRequest);
    }
}

📝 Como Criar um Repositório

Um repositório no SDK é uma interface que estende JapeRepository<T, ID>.

  • T: A classe da sua entidade (ex: Veiculo).
  • ID: O tipo da chave primária da sua entidade (ex: Long).
import br.com.sankhya.sdk.data.repository.JapeRepository;
import br.com.sankhya.studio.stereotypes.Repository;

@Repository
public interface VeiculoRepository extends JapeRepository<Veiculo, Long> {
    // Seus métodos de consulta customizados virão aqui
}

Métodos CRUD Padrão

Ao estender JapeRepository, sua interface herda automaticamente os seguintes métodos:

MétodoDescrição
save(T entity)Salva uma nova entidade ou atualiza uma existente.
findById(ID id)Busca uma entidade pela sua chave primária. Retorna Optional<T>.
findAll()Retorna todas as instâncias da entidade.
findAll(Pageable pageable)Retorna uma página de resultados com ordenação. Retorno Page<T>
delete(T entity)Remove uma entidade do banco de dados.

🔍 Criando Consultas Customizadas

@Criteria

A anotação @Criteria permite definir consultas de busca (SELECT) personalizadas usando condições SQL na cláusula WHERE.

Características Principais

  • Declarativa: Define a consulta através de anotação
  • Parâmetros Nomeados: Vincula parâmetros do método à query
  • Flexível: Suporta funções SQL e macros Sankhya
  • Type-Safe: Retorno tipado (entidade ou lista de entidades)

Sintaxe Básica

@Criteria(clause = "CAMPO = :parametro")
ReturnType nomeDoMetodo(TipoParametro parametro);

@NativeQuery

A anotação @NativeQuery permite executar consultas SQL nativas diretamente em métodos de repositório, oferecendo controle total sobre o SQL utilizado. Ela é ideal para cenários complexos, de alta performance ou que não podem ser representados facilmente por abstrações como @Criteria.

Quando Usar

  • Consultas complexas: JOINs múltiplos, subqueries, window functions e outras operações específicas do banco de dados.
  • Otimização de performance: Quando é necessário escrever uma consulta altamente otimizada.
  • Relatórios e agregações: Consultas que realizam cálculos complexos (GROUP BY, HAVING) e retornam DTOs personalizados.

Características Principais

  • SQL nativo: Permite escrever consultas completas e específicas do banco.
  • Segurança: Suporte a parâmetros nomeados (:nome) para evitar SQL Injection.
  • Mapeamento flexível: Conversão automática para interfaces (DTOs), listas ou tipos primitivos Java (String, Boolean, Long, etc.).
  • Gerenciamento de transações: Integração com JdbcWrapper para reutilização de conexões existentes.
  • Sankhya Macros: É possível utilizar Macros Sankhya (ex: dbDate()) como em qualquer outro lugar.

Sintaxe Básica

@NativeQuery("SELECT CAMPO1, CAMPO2, CAMPO_3, CAMPO4 AS CAMPOCOMALIAS FROM MINHA_TABELA WHERE CAMPO = :parametro")
ReturnType nomeDoMetodo(TipoParametro parametro);

Tipos de Retorno

A anotação @NativeQuery pode retornar tanto interfaces quanto tipos Java simples.

  • Interfaces

Interfaces que representam o resultado da consulta devem ser anotadas com @NativeQuery.Result. Cada método deve seguir o padrão de getter (getCampo) e corresponder exatamente ao nome da coluna ou alias retornado.

@NativeQuery.Result
public interface MinhaInterface {
    String getCampo1();
    String getCampo2();
    String getCampo_3();
    String getCampoComAlias();
}

Consulta correspondente:

@NativeQuery("SELECT CAMPO1, CAMPO2, CAMPO_3, CAMPO4 AS CAMPOCOMALIAS FROM MINHA_TABELA WHERE CAMPO = :parametro")
MinhaInterface buscarUm(TipoParametro parametro);

@NativeQuery("SELECT CAMPO1, CAMPO2, CAMPO_3, CAMPO4 AS CAMPOCOMALIAS FROM MINHA_TABELA")
List<MinhaInterface> buscarTodos();

Atenção: o nome dos getters deve coincidir exatamente com os nomes (ou aliases) das colunas. Qualquer divergência resultará em null.

  • Tipos Java

Atenção: Os tipos de retornos das funções do repositório deve sempre ser os tipos Wrapper Java (ex: Integer, Long, String, Boolean, etc.). Tipos primitivos não são suportados.

Também é possível retornar tipos Java diretamente:

@NativeQuery("SELECT CAMPO1 FROM MINHA_TABELA WHERE CAMPO = :parametro")
String buscarCampo(TipoParametro parametro);

@NativeQuery("SELECT CAMPO1 FROM MINHA_TABELA")
List<String> buscarCampos();

@NativeQuery("SELECT COUNT(1) FROM MINHA_TABELA")
Long contarItens();
⚠️

Consultas que retornam tipos Java simples devem garantir que o resultado contenha exatamente uma (1) coluna.

Caso a consulta retorne mais de uma coluna, será lançada a exceção ResultHasMoreThanOneColumnException.

✅ Exemplo válido: SELECT CAMPO1 FROM MINHA_TABELA

❌ Exemplo inválido: SELECT CAMPO1, CAMPO2 FROM MINHA_TABELA

Métodos Ambíguos

Como internamente o @NativeQuery utiliza java.sql.ResultSet, pode haver ambiguidade entre métodos com o mesmo tipo de retorno (por exemplo, getString() e getNString()). Para resolver, use o atributo method:

@NativeQuery(value = "SELECT CAMPO1 FROM MINHA_TABELA", method = ResultSetMethods.GET_STRING)
String buscarPorString();

@NativeQuery(value = "SELECT CAMPO1 FROM MINHA_TABELA", method = ResultSetMethods.GET_N_STRING)
String buscarPorNString();

Observação: Ambiguidade só ocorre em retornos de tipos Java simples. Interfaces são resolvidas automaticamente.

Recebendo JdbcWrapper como Argumento

O @NativeQuery permite receber um JdbcWrapper como argumento — essencial ao executar dentro de containers Sankhya (ex: @Listener). Reaproveitar o JdbcWrapper existente evita problemas de performance e vazamento de conexões.

@NativeQuery("SELECT COUNT(1) FROM TGFVEI")
Long contarVeiculos(JdbcWrapper jdbcWrapper);

Uso dentro de um listener:

@Listener(instanceNames = {"Veiculo"})
@RequiredArgsConstructor(onConstructor_ = @Inject)
public class ListenerDeVeiculo extends PersistenceEventAdapter {

    private final Repository repository;

    @Override
    public void afterDelete(PersistenceEvent event) throws Exception {
        JdbcWrapper jdbcWrapper = event.getJdbcWrapper();
        Long quantidade = repository.contarVeiculos(jdbcWrapper);
    }
}

Boas Práticas

  • Prefira interfaces para consultas com múltiplas colunas.
  • EviteSELECT * : mapeie apenas os campos necessários.
  • Verifique aliases: garanta que o nome dos getters coincida exatamente com os nomes/aliases retornados.

Exemplos Práticos

1. Consulta Simples por Campo

@Repository
public interface VeiculoRepository extends JapeRepository<Long, Veiculo> {
    
    // Busca por placa específica
    @Criteria(clause = "PLACA = :placa")
    Optional<Veiculo> findByPlaca(String placa);
    
    // Busca por status ativo
    @Criteria(clause = "ATIVO = :ativo")
    List<Veiculo> findByAtivo(Boolean ativo);

    // Busca por status ativo
    @NativeQuery("SELECT CODVEICULO, PLACA FROM TGFVEI WHERE MODELO = :modelo")
    List<InterfaceVeiculoDTO> findByAtivoNativo(String modelo);
}

2. Consultas com Múltiplos Parâmetros

@Repository
public interface PedidoRepository extends JapeRepository<Long, Pedido> {
    
    // Busca por empresa e status
    @Criteria(clause = "CODEMP = :empresa AND STATUS = :status")
    List<Pedido> findByEmpresaAndStatus(Long empresa, String status);
    
    // Busca com parâmetros nomeados explicitamente
    @Criteria(clause = "CODEMP = :COD_EMP AND DTNEG BETWEEN :dataInicio AND :dataFim")
    List<Pedido> findByPeriodo(
        @Parameter(name = "COD_EMP") Long codigoEmpresa,
        @Parameter(name = "dataInicio") LocalDate inicio,
        @Parameter(name = "dataFim") LocalDate fim
    );


    @NativeQuery("SELECT CODEMP, NOME FROM TB_PEDIDO WHERE CODEMP = :empresa AND STATUS = :STATUS_PEDIDO")
    List<InterfacePedidoDTO> findByEmpresaAndStatusNativo(Long empresa, @Parameter(name = "STATUS_PEDIDO") String status);
}

3. Consultas com Operadores SQL

@Repository
public interface ProdutoRepository extends JapeRepository<Long, Produto> {
    
    // Busca com LIKE
    @Criteria(clause = "DESCRPROD LIKE :termo")
    List<Produto> findByDescricaoContaining(String termo);
    
    // Busca com IN
    @Criteria(clause = "CODPROD IN :codigos")
    List<Produto> findByCodigos(List<Long> codigos);
    
    // Busca com comparação numérica
    @Criteria(clause = "VLRUNIT >= :valorMinimo")
    List<Produto> findByValorMinimo(BigDecimal valorMinimo);

    @NativeQuery("SELECT CODPROD, DESCRPROD FROM TGFPRO WHERE VLRUNIT >= :valorMinimo")
    List<InterfaceProdutoDTO> findByValorMinimoNativo(BigDecimal valorMinimo);
}

4. Consultas com Funções SQL

@Repository
public interface NotaRepository extends JapeRepository<Long, Nota> {
    
    // Usando função UPPER
    @Criteria(clause = "UPPER(NOMEPARC) = UPPER(:nome)")
    List<Nota> findByNomeParceiroCaseInsensitive(String nome);
    
    // Usando função de data
    @Criteria(clause = "EXTRACT(YEAR FROM DTNEG) = :ano")
    List<Nota> findByAno(Integer ano);
    
    // Usando CONCAT
    @Criteria(clause = "CONCAT(CODPARC, ' - ', NOMEPARC) LIKE :busca")
    List<Nota> findByCodigoOuNomeParc(String busca);

    @NativeQuery("SELECT NUNOTA, CODTIPOPER FROM TGFCAB WHERE CONCAT(CODPARC, ' - ', NOMEPARC) LIKE :busca")
    List<InterfaceNotaDTO> findByCodigoOuNomeParcNativo(String busca);
}

5. Consultas com Macros Sankhya

@Repository
public interface MovimentacaoRepository extends JapeRepository<Long, Movimentacao> {
    
    // Usando macro dbDate()
    @Criteria(clause = "DTMOV = dbDate()")
    List<Movimentacao> findByDataAtual();
    
    // Usando macro getEmpresa()
    @Criteria(clause = "CODEMP = getEmpresa()")
    List<Movimentacao> findByEmpresaAtual();
    
    // Combinando macros
    @Criteria(clause = "CODEMP = getEmpresa() AND DTMOV >= dbDate() - :diasAtras")
    List<Movimentacao> findUltimosDias(Integer diasAtras);

    @NativeQuery("SELECT NUFIN, NUNOTA FROM TGFFIN WHERE DTMOV = dbDate()")
    List<InterfaceMovimentacaoDTO> findByDataAtualNativo();
}

6. Consultas com Paginação

Suportada apenas para @Criteria. @NativeQuery não suporta paginação.

@Repository
public interface VeiculoRepository extends JapeRepository<Long, Veiculo> {
    
    // Busca páginada por status ativo
    @Criteria(clause = "ATIVO = :ativo")
    Page<Veiculo> findByAtivoPaginado(Boolean ativo, Pageable pageable);
}
@Component
public class VeiculoService {

    private final VeiculoRepository repository;

    @Inject
    public VeiculoService(VeiculoRepository repository) {
        this.repository = repository;
    }

    public Optional<Veiculo> buscarPorAtivoPaginado(Boolean ativo) {
        if (ativo == null) {
            throw new IllegalArgumentException("Ative não pode ser vazia, valores aceitos: true ou false");
        }
        //page: mínimo é 0
        //size: mínimo é 1
        //sort: se não informado, ordena por id de forma crescente. Caso id não esteja incluso no Sort, será 
        //incluído automaticamente ordenando de forma crescente. A ordenação padrão é crescente caso não informado.
        int page = 0;
        int size = 10;
        Pagerequest pageRequest = PageRequest.of(page, size, Sort.by("PLACA", Direction.DESC));
        return repository.findByAtivoPaginado(ativo, pageRequest);
    }
}

7. Entidade Com EmbbededID

@Embeddable
public interface ItemNotaPK {
    
    @Column(name="NUNOTA")
    Long numeroUnico;
    @Column(name="SEQUENCIA")
    Long sequenciaItem;
     
}
@JapeEntity(entity="ItemNota", table="TGFITE")
public interface ItemNotaEntity {
    
    // Usando macro dbDate()
    @Id
    ItemNotaPK id;
    @Column(name="CODPROD")
    Long codProduto;
    @Column(name="CODVOL")
    Long unidadeMedida;
 
}
@Repository
public interface ItemNotaRepository extends JapeRepository<ItemNotaPK, ItemNotaEntity> {
    
    
}

Operações de Exclusão com @Delete

A anotação @Delete permite executar operações de exclusão em massa baseadas em critérios específicos.

Características

  • Exclusão em Massa: Remove múltiplos registros de uma vez
  • Baseada em Critérios: Usa a mesma sintaxe do @Criteria
  • Eficiente: Executa DELETE SQL direto no banco
  • Transacional: Deve ser executada dentro de uma transação

Sintaxe Básica

@Delete(clause = "CAMPO = :parametro")
int nomeDoMetodo(TipoParametro parametro);
// Retorna o número de registros excluídos

Exemplos Práticos

1. Exclusão Simples

@Repository
public interface LogRepository extends JapeRepository<Long, Log> {
    
    // Exclui logs antigos
    @Delete(clause = "DTLOG < :dataLimite")
    int deleteLogsByDataAnterior(LocalDate dataLimite);
    
    // Exclui por tipo de log
    @Delete(clause = "TIPO = :tipo")
    int deleteByTipo(String tipo);
}

2. Exclusão com Múltiplos Critérios

@Repository
public interface TempRepository extends JapeRepository<Long, TabelaTemp> {
    
    // Exclui registros temporários por usuário e sessão
    @Delete(clause = "CODUSU = :usuario AND SESSAO = :sessao")
    int deleteBySessaoUsuario(Long usuario, String sessao);
    
    // Exclui registros não processados antigos
    @Delete(clause = "STATUS = 'PENDENTE' AND DTCRIACAO < :dataLimite")
    int deletePendentesAntigos(LocalDateTime dataLimite);
}

3. Exclusão com Subconsultas

@Repository
public interface ItemRepository extends JapeRepository<Long, Item> {
    
    // Exclui itens de pedidos cancelados
    @Delete(clause = "NUNOTA IN (SELECT NUNOTA FROM TGFCAB WHERE STATUS = 'CANCELADO')")
    int deleteItensPedidosCancelados();
}

Limitações e Comportamento Atual

1. Query Methods Não Suportados

// ❌ NÃO SUPORTADO (ainda) - Query methods do Spring
public interface VeiculoRepository extends JapeRepository<Long, Veiculo> {
    List<Veiculo> findByPlacaStartingWith(String prefix); // Não funciona
    List<Veiculo> findByAtivoTrue(); // Não funciona
}

// ✅ SUPORTADO - Use @Criteria
public interface VeiculoRepository extends JapeRepository<Long, Veiculo> {
    @Criteria(clause = "PLACA LIKE :prefix")
    List<Veiculo> findByPlacaStartingWith(String prefix);
    
    @Criteria(clause = "ATIVO = 'S'")
    List<Veiculo> findByAtivoTrue();
}

2. Controle de Quantidade de Registros

Por padrão, as consultas são limitadas pela configuração de sessão (500 registros):

@Repository
public interface ProdutoRepository extends JapeRepository<Long, Produto> {
    
    // ⚠️ ATENÇÃO: Retorna no máximo 500 registros (configuração padrão da sessão)
    @Criteria(clause = "ATIVO = 'S'")
    List<Produto> findAllAtivos();
}

Nota: Funcionalidades de paginação e streaming serão disponibilizadas em versões futuras.


Boas Práticas

1. Nomenclatura de Métodos

// ✅ BOM: Nomes descritivos e consistentes
@Criteria(clause = "PLACA = :placa")
Optional<Veiculo> findByPlaca(String placa);

@Criteria(clause = "STATUS = :status")
List<Pedido> findByStatus(String status);

// ❌ RUIM: Nomes genéricos ou confusos
@Criteria(clause = "PLACA = :placa")
Optional<Veiculo> buscar(String placa);

@Criteria(clause = "STATUS = :status")
List<Pedido> getList(String status);

2. Uso de Optional para Resultados Únicos

// ✅ BOM: Optional para resultados que podem não existir
@Criteria(clause = "CODPROD = :codigo")
Optional<Produto> findByCodigo(String codigo);

// ❌ RUIM: Pode lançar exceção se não encontrar
@Criteria(clause = "CODPROD = :codigo")
Produto findByCodigo(String codigo);

3. Parâmetros Nomeados Explícitos

// ✅ BOM: Parâmetros explícitos para queries complexas
@Criteria(clause = "DTNEG BETWEEN :DT_INICIO AND :DT_FIM AND CODEMP = :COD_EMPRESA")
List<Nota> findByPeriodoEmpresa(
    @Parameter(name = "DT_INICIO") LocalDate inicio,
    @Parameter(name = "DT_FIM") LocalDate fim,
    @Parameter(name = "COD_EMPRESA") Long empresa
);

// ❌ RUIM: Nomes de parâmetros confusos
@Criteria(clause = "DTNEG BETWEEN :inicio AND :fim AND CODEMP = :empresa")
List<Nota> findByPeriodoEmpresa(LocalDate dataInicial, LocalDate dataFinal, Long codigoEmpresa);

4. Validação de Parâmetros

@Component
public class VeiculoService {
    
    private final VeiculoRepository repository;
    
    @Inject
    public VeiculoService(VeiculoRepository repository) {
        this.repository = repository;
    }
    
    // ✅ BOM: Validação antes da consulta
    public Optional<Veiculo> buscarPorPlaca(String placa) {
        if (placa == null || placa.trim().isEmpty()) {
            throw new IllegalArgumentException("Placa não pode ser vazia");
        }
        return repository.findByPlaca(placa.toUpperCase());
    }
}

Anti-Patterns

1. Consultas Muito Genéricas

// ❌ RUIM: Consulta pode retornar muitos registros
@Criteria(clause = "1 = 1") // Retorna todos os registros
List<Produto> findAll();

// ✅ BOM: Consultas específicas com filtros
@Criteria(clause = "ATIVO = 'S' AND CODEMP = :empresa")
List<Produto> findAtivosByEmpresa(Long empresa);

2. Uso Incorreto de @Delete

// ❌ RUIM: Delete sem transação
public class LogService {
    @Delete(clause = "DTLOG < :data")
    int limparLogsAntigos(LocalDate data); // Pode falhar
}

// ✅ BOM: Delete dentro de transação
@Component
public class LogService {
    
    @Transactional
    public int limparLogsAntigos(LocalDate data) {
        return logRepository.deleteByDataAnterior(data);
    }
}

3. Consultas Complexas Demais

// ❌ RUIM: Query muito complexa em uma linha
@Criteria(clause = "EXISTS (SELECT 1 FROM TABELA2 T2 WHERE T2.ID = T1.ID AND T2.STATUS IN ('A', 'B', 'C')) AND CAMPO1 > :valor AND EXTRACT(MONTH FROM DATA1) = EXTRACT(MONTH FROM dbDate())")
List<Entidade> consultaComplexaDemais(BigDecimal valor);

// ✅ BOM: Divida em métodos menores e mais claros
@Criteria(clause = "STATUS IN ('A', 'B', 'C') AND VALOR > :valor")
List<Entidade> findByStatusAndValor(BigDecimal valor);

@Criteria(clause = "EXTRACT(MONTH FROM DATA1) = EXTRACT(MONTH FROM dbDate())")
List<Entidade> findByMesAtual();

Exemplo Completo: Repository de E-commerce

@Repository
public interface PedidoRepository extends JapeRepository<Long, Pedido> {
    
    // Consultas básicas
    @Criteria(clause = "NUNOTA = :numero")
    Optional<Pedido> findByNumero(Long numero);
    
    @Criteria(clause = "CODPARC = :codigoCliente")
    List<Pedido> findByCliente(Long codigoCliente);
    
    // Consultas por período
    @Criteria(clause = "DTNEG BETWEEN :inicio AND :fim")
    List<Pedido> findByPeriodo(LocalDate inicio, LocalDate fim);
    
    // Consultas com status
    @Criteria(clause = "STATUS = :status AND CODEMP = :empresa")
    List<Pedido> findByStatusEmpresa(String status, Long empresa);
    
    // Consulta com valor
    @Criteria(clause = "VLRNOTA >= :valorMinimo")
    List<Pedido> findByValorMinimo(BigDecimal valorMinimo);
    
    // Consulta usando macros
    @Criteria(clause = "CODEMP = getEmpresa() AND DTNEG = dbDate()")
    List<Pedido> findPedidosHoje();
    
    // Exclusão de rascunhos antigos
    @Delete(clause = "STATUS = 'RASCUNHO' AND DHALTER < :dataLimite")
    int deleteRascunhosAntigos(LocalDateTime dataLimite);
}

// Serviço que utiliza o repository
@Component
public class PedidoService {
    
    private final PedidoRepository pedidoRepository;
    
    @Inject
    public PedidoService(PedidoRepository pedidoRepository) {
        this.pedidoRepository = pedidoRepository;
    }
    
    @Transactional
    public void limparRascunhosAntigos() {
        LocalDateTime dataLimite = LocalDateTime.now().minusDays(30);
        int removidos = pedidoRepository.deleteRascunhosAntigos(dataLimite);
        System.out.println("Removidos " + removidos + " rascunhos antigos");
    }
    
    public List<Pedido> buscarPedidosCliente(Long codigoCliente) {
        if (codigoCliente == null) {
            throw new IllegalArgumentException("Código do cliente é obrigatório");
        }
        return pedidoRepository.findByCliente(codigoCliente);
    }
}

🚀 Recursos Implementados

As seguintes funcionalidades estão disponíveis e testadas:

  • CRUD Automático: save(), findById(), findAll(), delete()
  • Consultas Personalizadas: @Criteria com parâmetros nomeados e múltiplas condições
  • Exclusão em Massa: @Delete para operações eficientes de limpeza
  • Funções SQL: Suporte completo a UPPER(), LOWER(), CONCAT(), etc.
  • Macros Sankhya: dbDate(), getEmpresa(), getUsuario() nativamente suportadas
  • Type Safety: Parâmetros e retornos completamente tipados
  • Optional Support: Optional<T> para resultados únicos que podem não existir
  • Collection Support: List<T> para múltiplos resultados
  • Parameter Binding: @Parameter para nomeação explícita de parâmetros
  • Paginação: Page<T> para resultados paginados

O padrão Repository no SDK Sankhya oferece uma abstração poderosa e flexível para acesso a dados, mantendo a simplicidade e familiaridade com padrões de mercado como Spring Data JPA, enquanto se integra perfeitamente com o ecossistema Sankhya.