bd oracle_ UTILIZANDO CÓDIGO JAVA EM BANCO DE DADOS ORACLE Como criar procedimentos e funções em banco de dados Oracle utilizando Java O SGBD (Sistema de Gerenciamento de Banco de Dados) da Oracle possui, a partir da versão 8i, uma JVM (Java Virtual Machine) embutida, tornando-o assim capaz de armazenar e executar códigos Java. Neste artigo, será explicado como funciona esta estrutura e mostrado como criar uma classe Java dentro da base de dados, habilitando assim que seus métodos sejam executados como procedimentos nativos. A Oracle em 1999 lançou a versão 8i de seu gerenciador de banco de dados. A grande novidade em relação às versões anteriores era a implementação de uma máquina virtual Java interna, exceto nas versões Express. Esta mudança tornaria os bancos de dados a partir desta versão capazes de armazenar internamente classes Java. Os métodos públicos de cada classe poderiam ser compatibilizados com o !"#$%&'()*+(&,!-./%0&-%#,!,"%1%2&!34225/4.2&3%6%& se fossem funções ou procedimentos. A implantação de uma máquina virtual Java interna nos bancos de dados Oracle traz alguns recursos interessantes, dentre os quais podemos destacar: opção para o desenvolvedor Java criar soluções nas plataformas da Oracle; possibilidade de integrar no banco de dados aplicações Java já existentes; tirar proveito das medidas de segurança providas pela 6789.,!&/.#-9!:&40& %#&;60&!:%3!<$%&"4&646=#.!&46& separado para cada sessão de usuário. Este texto irá explicar como é a arquitetura da máquina virtual dentro do banco de dados, como é / 58 feito o armazenamento das classes e o gerenciamento da sessão de cada usuário. Será mostrado também um exemplo para criar procedimentos em Java dentro do banco de dados. APLICAÇÕES JAVA EM SERVIDOR BIBLIOTECAS ESPECÍFICAS DA ORACLE BIBLIOTECAS JAVA PADRÃO MÁQUINA VIRTUAL JAVA DO SGBD BIBLIOTECAS NATIVAS DO ORACLE SISTEMA OPERACIONAL Figura 1. Arquitetura Java interna no gerenciador de banco de dados. Edgard Resende Bilharinho | [email protected] Graduado em Ciência de Computação, especializado em administração de banco de dados Oracle e em desenvolvimento Java. Trabalha como desenvolvedor PL/SQL utilizando Oracle Forms/Reports. ARQUIVOS .java ARQUIVOS .jar ARQUIVOS .class CLIENTE 1 LOAD JAVA CLIENTE 2 RBDMS CLIENTE 3 CÓDIGO FONTE CLASSE JAVA CLASSE JAVA RECURSOS EM JAVA schema COMPILADOR JAVA TABELA DE OPÇÕES Figura 2. Gerenciamento dos códigos em Java. Arquitetura Dentro de cada gerenciador de banco de dados Oracle nas versões 8i e posteriores estão implemen-!"!2&!2&>'?2&"!&42 43.;3!<$%&@!/!&*AB&C%6%&6%2-#!& !&;D9#!&E0&!:F6&"!2&>'?2&G72.3!2&4H.2-46&-!6GF6&!2& 42 435;3!2&"!&I#!3:4&894&J%#!6&.,3:95"!2& !#!&.,-4grar o Java ao banco de dados. Esta arquitetura é que possibilita que o Oracle seja capaz de armazenar e executar códigos Java compatibilizando-os com seus 3=".D%2&,!-./%2&46&'()*+(B Pode-se utilizar a máquina virtual Java do banco de dados para compilar códigos. Porém, no Oracle há uma particularidade. Ao contrário das máquinas virtuais que rodam em sistemas operacionais, o Oracle não gera arquivos .jar e nem arquivos .class. Ao 3%6 .:!#&96&3=".D%&@!/!&42-4& !22!&!&;3!#&.,-4D#!"%& dentro do banco de dados como um objeto interno. >&;D9#!&K&.:92-#!&3%6%&24#.!6&!2&489./!:L,3.!2&4,-#4& os objetos em banco e os arquivos geralmente criados em uma máquina virtual padrão. O equivalente ao arquivo executável .jar se torna Para cada sessão de usuário MVJ MEMÓRIA DA SESSÃO MEMÓRIA CHAMADA Figura 3. Tratamento de sessões Java. um objeto que pode ser invocado como um procedi64,-%&,!-./%&46&'()*+(B&>&3!"!& #%34".64,-%&@!/!& acionado por uma conexão no banco, o Oracle cria automaticamente uma instância da máquina virtual dedicada para aquela conexão. Neste caso, cada utilizador do banco possuirá individualmente um coletor de lixo, uma área de memória estática e outra área de memória alocável. Isto garante que caso ocorra um problema em alguma sessão as demais ,$%& 24#$%& !J4-!"!2& %#& 4/4,-9!:& J!:M!B& N4#.;3!#& ,!& ;D9#!&O&3%6%&%&I#!3:4&-#!-!&3!"!&2422$%&894&!3.%,!& um código Java. Implantação Criando a classe Java Este é o primeiro passo para a implantação da rotina Java dentro do banco. Pode-se usar qualquer ambiente de desenvolvimento como Eclipse ou Netbeans. Para a criação da classe será utilizada como base uma regra de negócio simples. A ideia é ilustrar algumas das possibilidades que podem ser exploradas e demonstrar o conteúdo técnico necessário. Serão abordados a criação da classe, seu armazenamento 59 \ no banco de dados e a vinculação de seus métodos Listagem 1. !"#$%$ &'()*(&+, '-.(/#'+01+/#-*(2-)#$3 para que possam ser invocados como procedimentos ou funções. public static String registraAbastecimento(String cpf, String placa, int tipo_combustivel, A regra de negócio que será utilizada neste pe!"#$ quantidade) { queno projeto é algo comum hoje em dia. Atualmente vários postos de combustível possuem programas String sRetorno = null; "4& ;&"4:."!"4B& *4#7& 9-.:.P!"%& 96& 3%,Q9,-%& "4& #4D#!2& !"#$ fPreco = 0f; para gravação dos dados de abastecimento de clien-42B& R4:42& ;&3!#$%& /.,39:!"%2& %& 3:.4,-40& %& /4539:%0& %& 44 5-'(6*+ /- $/ &+'72-#'$/ &+//+%$/ *$)/#+2 3%6G92-5/4:&9-.:.P!"%&4&29!&89!,-."!"40&%& #4<%&;&,!:& // na base de dados e a data da operação. Após o registro dos dados, será sRetorno = buscaPessoa(cpf); enviado um e-mail para o cliente informando sobre o sRetorno = buscaVeiculo(placa); fPreco = getPrecoCombustivel(tipo_combustivel); abastecimento. R!& ;&D9#!& S& 3%,2-!& 96& TAU& VT.!D#!6!& "4& A,-.1 if (sRetorno == null && fPreco > 0) { dades e Relacionamentos) que demonstra a estrutura em que os dados serão gravados. try { Connection conn = DriverManager.getConnection (89%1*:%-;+<,#:*$))-*#($):=); PreparedStatement stm = conn.prepareStatement( “INSERT INTO ABASTECIMENTOS(“ +”Sequencia, Cpf, Placa, Tipo_Combustivel,” +”Quantidade, Valor, Data) VALUES (“ + “SEQ_ABASTECIMENTO.NEXTVAL,?,?,?,?,?,?)” ); stm.setString(1,cpf); stm.setString(2,placa); stm.setInt(3,tipo_combustivel); stm.setFloat(4,quantidade); stm.setFloat(5,quantidade * fPreco); stm.setDate(6,new 9+5+3/>,3Date (System.currentTimeMillis())); Figura 4. Diagrama de entidades e relacionamentos. >,-42&"4&/4#.;&3!#&%&3=".D%1J%,-4&F&.6 %#-!,-4&3.1 tar que ao efetuar a conexão com a base de dados não é necessário informar o usuário e o servidor em que ela está instalada. O próprio Oracle irá prover estas informações, pois o código roda internamente como um objeto de banco. Serão demonstrados primeiro os métodos públicos que posteriormente serão invocados pelos procedimentos de banco de dados. Este é o objetivo. Criar métodos que possam ser invocados como procedimentos ou funções nativos. Os métodos privados se#$%&"423#.-%2& %2-4#.%#64,-4B&R!&;&D9#!&W&F&"46%,21 trado o diagrama da classe que será criada. stm.executeUpdate(); stm.close(); } catch (SQLException e) { sRetorno = “Erro ao registrar o abastecimento” + “ no banco de dados.” + e.getMessage(); } if (sRetorno == null) { disparaEmail(cpf, quantidade * fPreco); sRetorno = “O registro do abastecimento “ + “foi concluído.”; } } return(sRetorno); Figura 5. Diagrama da classe que será criada. O principal método da classe é o registraAbastecimento. Como pode ser visto no código contido na Listagem 1 é a partir dele que é registrado o abastecimento. Antes de se gravar os dados de abasteci64,-%2&F&/4#.;&3!"!&!&4H.2-L,3.!&"%&3:.4,-4&"%&/4539:%B& Após a conclusão do registro, executa-se o procedimento que envia o e-mail. / 60 } O método getPrecoCombustivel serve para consultar o preço atual para um dos combustíveis cadastrados, caso o tipo informado não exista então é retornado valor zero. O código do método pode ser conferido na Listagem 2. Listagem 2. !"#$%$ .-#?'-*$@$21</#(5-,3 public static "#$ getPrecoCombustivel( int tipo_combustivel){ Listagem 4. !"#$%$ /-#?'-*$@$21</#(5-,3 public static void setPrecoCombustivel( int tipoCombustivel, "#$ novoPreco) { "#$ fResultado = 0; String sConsulta = “SELECT SUM(Preco) FROM “ + “ Combustiveis WHERE Tipo_Combustivel = ? “; ResultSet rs = null; try { Connection conn = DriverManager.getConnection (89%1*:%-;+<,#:*$))-*#($):=); PreparedStatement stm = conn.prepareStatement( “UPDATE Combustiveis “ +”SET Preco = ? “ +”WHERE Tipo_Combustivel = ?”); stm.setFloat(1,novoPreco); stm.setInt(2,tipoCombustivel); try { Connection conn = DriverManager.getConnection (89%1*:%-;+<,#:*$))-*#($):=); PreparedStatement stm = conn.prepareStatement(sConsulta); stm.setInt(1,tipo_combustivel); rs = stm.executeQuery(); rs.next(); fResultado = rs.getFloat(1); stm.executeQuery(); stm.close(); stm.close(); } catch(SQLException e) {} return(fResultado); } catch(SQLException e) {} } } Os quatro métodos públicos descritos anteriorO método getAbastecimentosDia é uma simples con29:-!&894&/4#.;3!&89!,-%2&!G!2-43.64,-%2&J%#!6&#4- mente: registraAbastecimento, getPrecoCombustigistrados no dia. O código-fonte está disponível na vel, getAbastecimentosDia e setPrecoCombustivel estarão disponíveis para serem disponibilizados Listagem 3. como funções e procedimentos de banco logo que a Listagem 3. !"#$%$ .-#01+/#-*(2-)#$/A(+3 classe estiver compilada dentro da base de dados. O banco de dados respeita o princípio da classe public static int getAbastecimentosDiaB9+5+3/>,3A+#Java no que diz respeito à política de acesso às clasdata) { ses. Sendo assim, não é possível criar funções ou procedimentos para acessar os métodos privados diResultSet rs = null; retamente. Estes métodos complementares da nossa int iQtde = 0; classe estão contidos na Listagem 5. try { Connection conn = DriverManager.getConnection (89%1*:%-;+<,#:*$))-*#($):=); PreparedStatement stm = conn.prepareStatement( “SELECT COUNT(1) FROM Abastecimentos “ +”WHERE Data = ?”); stm.setDate(1,data); rs = stm.executeQuery(); rs.next(); iQtde = rs.getInt(1); stm.close(); } catch(SQLException e) {} return(iQtde); } R!&(.2-!D46&S& %"46%2&/4#.;3!#&%&6F-%"%&24-'#43%Combustivel. Este método, também muito simples, não retorna valor algum e é responsável por ajustar o preço de algum dos combustíveis cadastrados. Listagem 5. !"#$%$/ &'(5+%$/3 private static String buscaPessoa(String sCpf) { String sResultado = null; String sConsulta = “SELECT COUNT(1) FROM Pessoas WHERE Cpf = ? “; ResultSet rs = null; int iQtde = 0; try { Connection conn = DriverManager.getConnection( 89%1*:%-;+<,#:*$))-*#($):=); PreparedStatement stm = conn.prepareStatement(sConsulta); stm.setString(1,sCpf); rs = stm.executeQuery(); rs.next(); iQtde = rs.getInt(1); stm.close(); 61 \ } catch(SQLException e) { sResultado = “Houve um erro na busca do CPF.” + e.getMessage(); } if (iQtde == 0) { sResultado = “O CPF informado não está adastrado.”; } return(sResultado); } private static String buscaVeiculo(String sPlaca) { String sResultado = null; String sConsulta = “SELECT COUNT(1) FROM Veiculo WHERE Placa = ? “; ResultSet rs = null; int iQtde = 0; //busca o nome e o email da pessoa ResultSet rs = null; String sEmail = null; String sNome = null; try { Connection conn = DriverManager.getConnection( 89%1*:%-;+<,#:*$))-*#($):=); PreparedStatement stm = conn.prepareStatement( “SELECT nome, email FROM Pessoas WHERE Cpf = ?”); stm.setString(1,cpf); rs = stm.executeQuery(); rs.next(); sNome = rs.getString(1); sEmail = rs.getString(2); stm.close(); } catch(SQLException e) {} try { Connection conn = DriverManager.getConnection( 89%1*:%-;+<,#:*$))-*#($):=); PreparedStatement stm = conn.prepareStatement(sConsulta); stm.setString(1,sPlaca); rs = stm.executeQuery(); rs.next(); iQtde = rs.getInt(1); stm.close(); } catch(SQLException e) { sResultado = “Houve um erro na busca do veículo.” + e.getMessage(); } if (iQtde == 0) { sResultado = “O veículo informado não está cadastrado.”; } return(sResultado); } private static void disparaEmail(String cpf, "#$ valor) { / 62 44 +9</#+ + *$)6.<'+CD$ %$ /-'5(%$' %// envio de emails Properties propriedades = System.getProperties(); propriedades.put(“mail.smtp.host”, “smtp.minhaempresa.com.br”); Session session = Session.getDefaultInstance( propriedades, null); try { // cria a mensagem de email MimeMessage msg = new MimeMessage(session); { 44 +9</#+ $ -2+(, %$ '-2-#-)#InternetAddress[] enderecos= InternetAddress.parse( “[email protected]”); msg.addFrom(enderecos); } { 44 +9</#+ $ -2+(, %- %-/#()$ InternetAddress[] enderecos =InternetAddress. parse(sEmail); msg.addRecipients(Message.RecipientType.TO, enderecos); } !"#$"%&'(!)*"!&$&%"+&!(!,(-"!'&!./&##"!.(-!(! "#0&'(! .(-(! 12/3'(4! &##3-! .(-(! &$%"#",0&'(! ,&! 5gura 6. // assunto do email msg.setSubject(“Inclusão de registro.”); // cria o corpo da mensagem de email com o texto 44 %-/-9+%$3 Multipart mp = new MimeMultipart(); MimeBodyPart mbp = new MimeBodyPart(); mbp.setText(sNome + “, foi efetuado um abastecimento em “ + “seu nome no valor de: “ + valor); mp.addBodyPart(mbp); msg.setContent(mp); 44 *$)6.<'+ + %+#+ %- -)5($ msg.setSentDate(new 9+5+3<#(,3Date()); // dispara o email para o servidor informado Transport.send(msg); }catch(Exception e){} } } 3.2. Compilando a classe para o banco de dados Uma vez que o código da classe foi criado agora é necessário compilá-lo para dentro do banco de dados. Para que seja possível efetuar a compilação é necessário que, ao instalar os aplicativos da Oracle na máquina cliente, os pacotes de desenvolvimento tenham sido selecionados. Deve-se procurar por um arquivo executável chamado loadjava no local do disco onde as aplicações estão instaladas. Geralmente o caminho é: C:\Oracle\Dev10g\Bin Ao encontrar o executável deve-se dispará-lo seguindo a seguinte sintaxe: :%!"Q!/!&19&X9297#.%Y)X24,M!YZXG!,3%&"4&"!"%2Y& -resolve Classe.java '!#!&/4#.;3!#&24&%&%GQ4-%&J%.&3%##4-!64,-4&3#.!"%& no banco de dados é só utilizar a consulta contida na Listagem 6. Listagem 6. !"#$%&'( )(*( +,*-./(* ($ /&($$,$ 0(+( registradas na base de dados. SELECT OBJECT_NAME, STATUS FROM USER_OBJECTS WHERE OBJECT_TYPE = ‘JAVA CLASS’; Figura 6. Situação das classes Java compiladas dentro do banco de dados. 3.3. Publicar a classe como procedimento Depois de executar a consulta nos objetos Java '&!6&#"!'"!'&'(#!"!1"%35.&%!)*"!&!./&##"!7(3!.(%%"0&mente compilada, será necessário criar as chamadas de seus métodos. Para cada método público da classe é necessário criar uma função para aqueles que retornam valor, ou então um procedimento, para aqueles que não retornam valor. Assim será possível invocar estes métodos como objetos nativos do banco. Estas funções e procedimentos que serão criados vão servir como uma espécie de intérprete. Constam na Listagem 7 os códigos necessários. Listagem 7. Criação dos procedimentos e funções )(*( -#+"/(* "$ 12'"3"$ 3( /&($$,4 CREATE OR REPLACE FUNCTION FUN_GRAVA_ ABASTECIMENTO (CPF VARCHAR2, PLACA VARCHAR2, TIPO_COMBUSTIVEL NUMBER, QUANTIDADE NUMBER) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 567($',/-1,#'"$!"#'*"&&,*4*,8-$'*(67($',/-1,#'"9:(+(4 lang.String, :(+(4&(#84;'*-#8< -#'< ="('> *,'%*# :(+(4&(#84;'*-#8?@ CREATE OR REPLACE FUNCTION FUN_PRECO_ COMBUSTIVEL (TIPO_COMBUSTIVEL NUMBER) RETURN NUMBER AS LANGUAGE JAVA NAME ‘AbastecimentosController.getPrecoCombustivel(int) *,'%*# ="('?@ CREATE OR REPLACE FUNCTION FUN_ ABASTECIMENTOS_DIA(DATA DATE) RETURN NUMBER AS LANGUAGE JAVA NAME 567($',/-1,#'"$!"#'*"&&,*48,'67($',/-1,#'"$A-(9:(+(4 sql.Date) return int’; CREATE OR REPLACE PROCEDURE PRO_AJUSTA_PRECO_ COMBUSTIVEL 63 \ -&3#!*-&!.&-&'&!$&%&!5/0%&%!(#!&."##(#!&(!.D'3@(? (TIPO_COMBUSTIVEL NUMBER, NOVO_VALOR NUMBER) AS LANGUAGE JAVA NAME 567($',/-1,#'"$!"#'*"&&,*4$,'B*,/"!"17%$'-+,&9-#'< ="('>?@ Os comandos apresentados na Listagem 7 disponibilizam as chamadas para os métodos da classe. A partir daí será possível acessá-los como os demais $%(."'3-",0(#!"!7*,+8"#!9:;<=:!,&031(#!'(!>%&./"?! Um exemplo destas chamadas é mostrado na Listagem 8. Listagem 8. Exemplo de chamada de função no Oracle. DECLARE RETURN_VALUE NUMBER := 0; DATA DATE := TO_DATE(‘2011-01-31’,’ YYYY-MM-DD’); BEGIN RETURN_VALUE := FUN_ABASTECIMENTOS_ DIA(DATA); DBMS_OUTPUT.PUT(‘RETURN_VALUE: ‘); DBMS_OUTPUT.PUTLINE(RETURN_VALUE); DBMS_OUTPUT.PUT(‘DATA: ‘); DBMS_OUTPUT.PUTLINE(DATA); END; !"#$%&'()*&#+,"($# A utilização de código Java dentro do Oracle vem oferecer uma alternativa à programação em banco já $%"13&-",0"! (7"%".3'&! .(-! (! "-$%"@(! '(! 9:;<=:4! que até então era a única linguagem de desenvolvimento disponível nesta plataforma. Adiante serão listadas as características positivas e negativas sobre o uso de Java dentro do banco de dados. Desvantagens As vantagens em transferir parte do código da aplicação para o banco de dados são muitas, entretanto é necessário também considerar os pontos fracos, dentre os quais podemos destacar: Dependência em relação ao administrador do banco de dados. Como o código Java precisará ser compilado internamente e interpretado como procedimentos nativos, haverá a necessidade da intervenção do administrador. E0",+A(!.(-!(#!%")*3#30(#!'"!#"%13'(%?! !,&0*%&/! utilizar um banco de dados como um simples repositório. Ao mudar esta abordagem e agregar necessidade de processamento é necessário ter atenção com os recursos do servidor disponibilizados para o SGBD. Dependência em relação à arquitetura. Atualmente o Oracle é o único gerenciador de banco de dados capaz de rodar nativamente código Java. Ao empregar esta arquitetura será mais difícil trocar de plataforma futuramente. Conclusão Toda tecnologia disponível tem seus pontos fortes e fracos. Neste caso abordado não seria diferente. Em determinados ambientes, nos quais o servidor é potente e diversos softwares compartilham uma mesma base de dados, empregar esta estratégia de desenvolvimento pode ser uma alternativa interessante. Vantagens Menor tráfego de dados na rede. Uma vez que parte do processamento será realizada dentro do servidor que abriga o banco de dados, o software que o usuário opera receberá os dados já processados, diminuindo assim o número de transações. Melhor desempenho. As máquinas virtuais padrão que conhecemos rodam sobre sistemas operacionais, já a dos bancos de dados Oracle é um objeto interno integrado ao núcleo do SGBD. Esta integra+A(!$"%-30"!*-!&."##(!-&3#!"5.3",0"!&(#!%".*%#(#!'"! memória e processamento. Facilidade de manutenção. Como uma base de dados pode ser comum a diversos softwares, a centralização do código no banco de dados pode resultar em ganho de tempo, pois não será necessário atualizar cada um dos softwares e serviços que acessam. Mais segurança. Como o acesso aos objetos em B&1&!"#0&%2!.('35.&'(!.(-(!*-!(6C"0(!'",0%(!'&!6&#"! de dados, o administrador poderá aplicar neles as diretivas de segurança do Oracle. Desta forma, criamos / 64 /referências !"#$%&$'!()*+%$#,!-./%*#$!%&0!1%2%!345/$0!6/5*$07/$89,! :;/)#!0$!<==>,!?)8@5&A2$#!$BC!D+44@CEEFFF,0$2$#5@$/, *5BE0;E%/4)*#$,@+@EGGGH>IIE./%*#$J%&0J1%2%J345/$0J Procedures.htm]. !K5$28L)'!M%0)B,!-1%2%C!NO4$&0)&P!4+$!@5F$/!5Q!6KE3RK9,! <==S,!?)8@5&A2$#!$BC!D+44@CEEFFF,0)B#%/,*5BE@%@$/,@0QT, !65/4Q5#)5'!U5B,!-1%2%!345/$0!6/5*$07/$8!?$2$#5@$/V8! W7)0$'!X$#$%8$!<!YZ,<[9,!./%*#$'!B%/\5!0$!<==<,!?)8@5&A2$#! $BC!D+44@CEE05F&#5%0,5/%*#$,*5BE05*8E*0E]I=S=I^=IE _%2%,Z<=E%Z``SZ,@0QT,! !ab$/'!M$&L%4%87;/%B%&)%B!c!(%/)&P'!3+$/b#!c!3%@)/'! X)*L!c!d)$8$&;$/P'!()*+%$#,!-1%2%!?$2$#5@$/V8!W7)0$!I=P! X$#$%8$!<!YI=,<[9,!./%*#$'!%P5845!0$!<==`,!?)8@5&A2$#!$BC! D+44@CEE05F&#5%0,5/%*#$,*5BE05*8E*0E]IZG=`^=IE_%2%,I=<E b14187.pdf].