Olá Pessoal, tudo bom?
O artigo de hoje é a segunda parte de uma adaptação do artigo de Adam Crume criado em 03 de abril de 2007 para o site Java World (link).
Para essa adaptação eu modifiquei um pouco o código apresentado inserindo o conceito de generics e algumas melhorias no código. Veja abaixo como ficou o resultado.
Iniciando a construção do Parser
Na ultima parte nós explicamos o porque da necessidade de ter um parser para o NamedParameter Statement, pois a construção de novos SQLs será realizada agora com a utilização de labels. Dessa forma nessa parte do artigo iremos detalhar o código desse parser e explicar o porque foi adaptado dessa forma.
SQL Parser – Código
Como descrito acima, o código abaixo é uma adaptação do que foi apresentado no artigo utilizando generics e uma reorganização das validações dos códigos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
//Map para indicar as posicoes dos parametros private final Map<String, List<Integer>> mapPosicoesParametros = new HashMap<String, List<Integer>>(); private String parse(String namedQuery) { int length = namedQuery.length(); //Cria um String Buffer com o tamanho do SQL StringBuilder parsedQuery = new StringBuilder(length); boolean inSingleQuote = false; boolean inDoubleQuote = false; int position = 1; //Percorre todo o SQL for(int i = 0; i < length; i++) { char c = namedQuery.charAt(i); //: Significa inicio de um rotulo de parametro //E nao esta em uma palavra com aspas simples ou duplas if(c == ':' && !inSingleQuote && !inDoubleQuote && i+1 < length && Character.isJavaIdentifierStart(namedQuery.charAt(i+1))) { int j = i+2; while(j < length && Character.isJavaIdentifierPart(namedQuery.charAt(j))) { j++; } String name = namedQuery.substring(i+1, j); c='?'; // substitui o caracter c pelo parametro de index i += name.length(); // pula i ate o fim do nome do parametro List<Integer> indexList = mapPosicoesParametros.get(name); //Se o parametro ainda nao existir no map inicializa-o if(indexList == null) { indexList = new LinkedList<Integer>(); mapPosicoesParametros.put(name, indexList); } indexList.add(position++); } //Adiciona o novo caractere a query passada pelo parser parsedQuery.append(c); if(c == '\'') { inSingleQuote = !inSingleQuote; } else if(c == '"') { inDoubleQuote = !inDoubleQuote; } } return parsedQuery.toString(); } |
Vamos dividir esse código em várias partes, assim podemos explicar os vários aspectos existentes dentro dele. Além disso, vamos relembrar os SQLs de exemplo da parte anterior para auxiliar na explicação do parser. Exemplo do código SQL de position parameter:
1 2 3 4 |
INSERT INTO TB_ENDERECO (RUA, NUMERO, COMPLEMENTO, BAIRRO, CIDADE, ESTADO, PAIS) VALUES (?,?,?,?,?,?,?) |
Exemplo de código SQL de named parameter:
1 2 3 4 |
INSERT INTO TB_ENDERECO (RUA, NUMERO, COMPLEMENTO, BAIRRO, CIDADE, ESTADO, PAIS) VALUES (:rua, :numero, :complemento, :bairro, :cidade, :estado, :pais) |
Map de Posições de Parâmetros
O primeiro ponto a ser apresentado é o atributo mapPosicoesParametros, localizado na linha 02 do código do parser.
1 2 |
//Map para indicar as posicoes dos parametros private final Map<String, List<Integer>> mapPosicoesParametros = new HashMap<String, List<Integer>>(); |
Esse Map é composto por uma String e uma Lista de Inteiros. Essa String irá representar o label dos parâmetros da namedStatement. Já a lista servirá para armazenar a ordem dos parâmetros, que se transformarão em Interrogações, no PreparedStatement convencional.
Preenchimento do Map de Posições
As linhas 32 a 38 são as responsáveis pelo preenchimento do mapPosicoesParametros.
32 33 34 35 36 37 38 |
List<Integer> indexList = mapPosicoesParametros.get(name); //Se o parametro ainda nao existir no map inicializa-o if(indexList == null) { indexList = new LinkedList<Integer>(); mapPosicoesParametros.put(name, indexList); } indexList.add(position++); |
Vamos pegar o exemplo de SQL named parameter acima e verificar como seria a execução dessas linhas sobre esse SQL preenchendo o Map.
1 2 3 4 5 6 7 8 9 |
mapPosicoesParametros = { {"rua", List<Integer> = {1}} {"numero", List<Integer> = {2}} {"complemento", List<Integer> = {3}} {"bairro", List<Integer> = {4}} {"cidade", List<Integer> = {5}} {"estado", List<Integer> = {6}} {"pais", List<Integer> = {7}} }; |
Existem dois pontos aqui a serem explicitados. O primeiro ponto está na remoção dos dois pontos presentes em todos os labels do named SQL, eles não devem existir aqui. A serventia deles no named SQL é somente para identificar quais são as Strings que são parâmetros. Dessa forma é necessário possuir um caractere coringa para identificá-los. Como o símbolo de dois pontos já é utilizado como padrão do JPAQuery, continuaremos a utilizar este caractere.
O segundo ponto é que as posições dos parâmetros, conforme descrito na API da PreparedStatement, iniciam-se em 1. Dessa forma a atribuição das posições deve iniciar na posição 1. Outro detalhe é que se caso não exista uma lista de inteiros instanciada para determinado label, as linhas 35 e 36 serão responsáveis por criar uma nova lista e atribui-la ao Map, setando como chave o label especificado.
É importante frisar também que essa lista de inteiros pode ser utilizada para armazenar mais de uma posição do label caso este se repita dentro do SQL.
Navegação do namedSQL
Após explanar como será realizada armazenada as posições dos labels dentro do Map, agora é necessário entender como será a navegação do namedSQL para que possamos realizar a substituição dos labels por pontos de interrogação. As linhas 15 e 16 nos mostram como será essa navegação, obtendo caractere por caractere da String namedQuery. Para essa navegação é necessário estabelecer dois pontos a serem melhor tratados.
15 16 |
for(int i = 0; i < length; i++) { char c = namedQuery.charAt(i); |
Navegação do namedSQL – Validação das Aspas
O primeiro ponto está relacionado as aspas simples ( ‘ ) e as aspas duplas ( ” ). Quando estão em uma String SQL significam a construção de palavras para a utilização em comparações ou restrições. Dessa forma é necessário tratar ao estabelecer o inicio de uma String, pois caso exista um caractere de dois pontos dentro de uma palavra este não deverá ser considerado pelo parser. Para tanto, existem dois blocos de código para realizar essa tarefa. O primeiro encontra-se nas linhas 10 e 11, declarando as variáveis inSingleQuote e inDoubleQuote.
10 11 |
boolean inSingleQuote = false; boolean inDoubleQuote = false; |
O segundo encontra-se nas linhas 44 a 48 e realizam essa comparação, indicando se o caractere é uma aspas simples ou aspas duplas e invertendo o valor atual do booleano.
44 45 46 47 48 |
if(c == '\'') { inSingleQuote = !inSingleQuote; } else if(c == '"') { inDoubleQuote = !inDoubleQuote; } |
Navegação do namedSQL – Validação do dois pontos
O outro ponto está relacionado ao caractere de dois pontos. Para isso o IF localizado na linha 20-21 retrata o aspecto de validação de um novo label.
20 21 |
if(c == ':' && !inSingleQuote && !inDoubleQuote && i+1 < length && Character.isJavaIdentifierStart(namedQuery.charAt(i+1))) { |
Assim o primeiro aspecto da validação é verificar se o caractere obtido corresponde a um dois pontos. Isso indica que estamos iniciando um novo label do namedQuery. Após isso devemos validar senão estamos em uma palavra do SQL, ao validarmos se as variáveis inSingleQuote e inDoubleQuote estão com o valor falso. Outro ponto a ser validado é se o tamanho total da namedQuery já não foi atingido. Pois caso tenha sido, não existe mais nenhum caractere após os dois pontos.
Por fim, é necessário validar se o caractere do namedQuery é um caractere Java de identificação válido. Para isso vamos utilizar o método isJavaIdentifierPart(char ch), que está descrito em maiores detalhes aqui na API da classe Character.
Se todas essas validações forem verdadeiras então as linhas 22 a 26 serão responsáveis por avançar na String do SQL até chegar a um caractere de controle ou delimitação de palavras, como o espaço, tab ou quebra de linha.
22 23 24 25 26 |
int j = i+2; while(j < length && Character.isJavaIdentifierPart(namedQuery.charAt(j))) { j++; } |
Após chegar a esse caractere de controle o while da linha 24 é interrompido. Assim a linha 28 obtém o label que foi percorrido pelo while, através do método substring. A linha 29 faz com que o caractere c receba um ponto de interrogação, o qual será substituído no lugar do label e por fim a linha 30 avança a variável de posição da String sql até o fim do label, removendo assim qualquer informação sobre o label informado.
28 29 30 |
String name = namedQuery.substring(i+1, j); c='?'; // substitui o caracter c pelo parametro de index i += name.length(); // pula i ate o fim do nome do parametro |
Navegação do namedSQL – Adicionar caractere a nova SQL
A linha 42 retrata a adição do caractere c à nova SQL que sofreu o parser para conter apenas pontos de interrogação, sendo os labels removidos.
42 |
parsedQuery.append(c); |
Com isso finalizamos a explicação do parser. Na próxima parte deste artigo estaremos explicando como funcionará as outras partes dessa nova funcionalidade do NamedParameters.
finnaly{
Duvidas ou sugestões? Deixe seu feedback! Isso ajuda a saber a sua opinião sobre os artigos e melhorá-los para o futuro! Isso é muito importante!
Até um próximo post!
Leave a Reply