Baixe o app para aproveitar ainda mais
Prévia do material em texto
PROGRAMAÇÃO ORIENTADA A OBJETOS III PROGRAMAÇÃO ORIENTADA A OBJETOS III Copyright © UVA 2020 Nenhuma parte desta publicação pode ser reproduzida por qualquer meio sem a prévia autorização desta instituição. Texto de acordo com as normas do Novo Acordo Ortográfico da Língua Portuguesa. AUTORIA DO CONTEÚDO Carlos Frederico Motta Vasconcelos REVISÃO Janaina Vieira Theo Cavalcanti PROJETO GRÁFICO UVA DIAGRAMAÇÃO UVA V331 Vasconcelos, Carlos Frederico Motta. Programação orientada a objetos III [recurso eletrônico] / Carlos Frederico Motta Vasconcelos. – Rio de Janeiro: UVA, 2021. 1 recurso digital (6474 KB) Formato: PDF ISBN 978-65-5700-091-5 1. Programação orientada a objetos (Computação). 2. Banco de dados relacionais. 3. Java (Linguagem de programação de computador). I. Universidade Veiga de Almeida. II. Título. CDD – 005.11 Bibliotecária Adriana R. C. de Sá CRB 7 – 4049. Ficha Catalográfica elaborada pelo Sistema de Bibliotecas da UVA. SUMÁRIO Apresentação Autor 6 7 Acesso a bancos de dados relacionais via JDBC utilizando diferentes padrões de projeto 37 • Acesso remoto a gerenciadores de bancos de dados relacionais com uso do JDBC • Técnicas de acesso concorrente a bancos de dados cliente-servidor • Tratamento de erros em bancos de dados UNIDADE 2 8 • Padrões de arquitetura para desenvolvimento de sistemas Orientados a Objetos – OO • Padrões de projeto de sistemas OO: Modelo-Visão-Controle – MVC, DAO, Factory e Singleton • Acesso e conexão a gerenciadores de banco de dados com uso do JDBC Padrões de arquitetura e padrões de projetos Orientados a Objetos para desenvolvimento de sistemas Java UNIDADE 1 SUMÁRIO JavaServer Faces 120 • Visão geral do JavaServer Faces – JSF • Managed Beans, requisições e navegação • Componentes básicos, validação de campos, PrimeFaces e Ajax JSF UNIDADE 4 81 • Ferramentas de mapeamento objeto-relacional – MOR (JPA e Hibernate) • Pool de Conexões, Lazy Load e Relacionamentos • Gerenciador de Estados, Caching e consultas JPQL Persistência de dados via JPA e Hibernate (MOR automático) UNIDADE 3 6 Nesta disciplina, serão abordadas técnicas para o desenvolvimento de sistemas compu- tacionais Web usando a linguagem de programação Java e para realizar conexão com gerenciadores de banco de dados relacionais via Java DataBase Connectivity – JDBC. Além da biblioteca JBDC para armazenamento (persistência) em bancos de dados re- lacionais, nesta disciplina serão apresentadas ferramentas do tipo object relational ma- pping – ORM, que facilitam o uso do JDBC para acesso a bancos de dados relacionais. Além disso, serão abordadas ferramentas (frameworks) para facilitar o desenvolvimento e a manutenção de aplicações Web. Para auxiliar o desenvolvimento de sistemas com maior complexidade, também será ne- cessário conhecer alguns padrões de projeto de Orientação a Objeto específicos. Serão apresentados padrões de projeto que possuem caraterísticas próprias que podem faci- litar a implementação e a manutenção de sistemas de informação, tais como: padrões de projeto de arquitetura em multicamadas, Model-View-Controller – MVC, Data Access Objects – DAO, Construtor Virtual – Factory Method, e Singleton. Por fim, apresentaremos ferramentas de desenvolvimento de sistemas computacionais orientados a objetos com persistência de dados via Hibernate (ORM Automático) e tam- bém será abordada a utilização do framework JavaServer Faces – JSF para adicionar comportamento dinâmico às páginas das aplicações Web. Atualmente o mercado apresenta inúmeras oportunidades aos profissionais de Tecnolo- gia da Informação que possuem conhecimentos sobre o desenvolvimento de sistemas Web com a utilização da tecnologia Java e seus respectivos componentes. Por sua vez, o Java possui inúmeras vantagens, como maior liberdade de desenvolvimento de sistemas de forma independente de fabricantes de softwares, sistemas operacionais, banco de dados, servidores de aplicação e containers. APRESENTAÇÃO 7 CARLOS FREDERICO MOTTA VASCONCELOS Possui graduação em Engenharia Eletrônica pela Universidade Federal do Rio de Janei- ro – UFRJ e mestrado em Engenharia Biomédica pelo Instituto Alberto Luiz Coimbra de Pós-Graduação e Pesquisa de Engenharia – COPPE/UFRJ na área de Processamento de Sinais. Possui MBA em Sistemas de Telecomunicações pelo IBMEC-RJ. Experiência de 17 anos como professor do curso de Ciência da Computação na Univer- sidade Veiga de Almeida – UVA nas áreas de Computação Gráfica, Processamento de Sinais e Imagens, Interfaces Homem-Máquina, Multimídia e Programação de Disposi- tivos Móveis. Possui experiência de 18 anos no planejamento e otimização de sistemas usados em redes celulares de telecomunicações com tecnologias LTE, UMTS, GSM e CDMA. Expe- riência no desenvolvimento de sistemas de geoprocessamento (GIS) e de simulação de sinais de radiofrequência. Atualmente trabalha como analista em Ciência e Tecnologia na área de Engenharia Clíni- ca do Instituto Nacional do Câncer, do Ministério da Saúde, exercendo atividades técnicas especializadas na área de gerenciamento do parque de equipamentos médico-hospita- lares de média e alta complexidades, além da fiscalização dos contratos de manutenção dos equipamentos e sistemas de informação. Experiência em elaboração de especifica- ções e documentação técnicas necessárias aos processos de aquisição, recebimento e aceitação de novos equipamentos e sistemas de informação. AUTOR Padrões de arquitetura e padrões de projetos Orientados a Objetos para desenvolvimento de sistemas Java UNIDADE 1 9 Para o desenvolvimento de sistemas computacionais de maior complexidade torna-se necessário conhecer e aplicar padrões de projeto específicos para auxiliar as etapas ini- ciais de especificação, análise e design de tais sistemas, com o objetivo de organizar as etapas seguintes de implementação do código. Nesta unidade serão abordados alguns exemplos de padrões de projeto e de arquitetura bastante utilizados no desenvolvimento de sistemas Orientados a Objetos – OO envolvendo armazenamento em banco de dados e aplicações Web. INTRODUÇÃO Nesta unidade você será capaz de: • Compreender diferentes padrões de arquitetura e projeto (MVC, DAO, Factory e Singleton) para desenvolvimento de sistemas OO com acesso a bancos de dados e aplicações Web. OBJETIVO 10 Padrões de arquitetura para desenvolvimen- to de sistemas Orientados a Objetos – OO A construção de sistemas computacionais complexos exige a implementação de uma quantidade muito grande de componentes. Antes de iniciar as etapas de programação desses componentes é necessário definir as fases iniciais para descrição dos requisitos do cliente, especificações do sistema, juntamente com a definição dos modelos de pro- jeto e arquitetura a serem usados no desenvolvimento do sistema computacional. Observe a figura a seguir. A imagem mostra um exemplo de um modelo básico de proje- to de um sistema computacional até a etapa de implementação de código: Figura: Modelo básico de projeto de um sistema computacional. Fonte: O autor (2020). Um dos desafios das equipes de projeto de sistemas computacionais complexos é con- seguir uma forma de organizar os diversos componentes paraque seja possível: • Atender aos requisitos funcionais e não funcionais definidos na especificação. • Reusar componentes de forma a não implementar o mesmo serviço mais de uma vez. Mundo real Mundo computacional Requisitos do sistemaRequisitos do cliente Especificação técnica Modelos de arquitetura Modelos de projeto Modelos de design Implementação de código 11 • Facilitar a manutenção do sistema (troca de componentes, mudança de regras, entre outros). • Trazer portabilidade para o sistema (execução em plataformas de hardware/ software distintas). Nesse contexto, a elaboração de sistemas de informação passa pela compreensão e pela aplicação adequada de modelos de arquitetura e padrões de projeto. O domínio des- ses conceitos e de sua aplicação desempenha um papel fundamental no gerenciamento da complexidade inerente ao software a ser desenvolvido. Podemos exemplificar o ciclo de vida de um sistema por meio do modelo em cascata, inicialmente proposto por W. W. Royce em 1970. As etapas do modelo em cascata são mostradas na figura a seguir. Figura: Etapas do modelo em cascata. Fonte: O autor (2020). Definição dos requisitos Análise Implementação Implantação Projeto Teste/Avaliação Manutenção 12 Apesar de simplificado, o modelo em cascata nos mostra que, antes da etapa de im- plementação do código do sistema, é necessário que as etapas iniciais de definição de requisitos, análise e design do projeto sejam cuidadosamente elaboradas. Dessa forma: A escolha de padrões de arquitetura e de design de projeto adequados facilitará as etapas seguintes de implementação da programação, testes, alterações e manuten- ções necessárias no sistema durante sua implantação. Existem diferentes modelos propostos para auxiliar as etapas de análise e projeto, como modelos em cascata, modelos incrementais, prototipagem, entre outros. Atualmente um dos modelos mais difundidos para desenvolvimento de sistemas OO é o Processo Unifi- cado (Unified Process – UP). O Processo Unificado define as atividades específicas voltadas para análise, arquitetura e design de sistemas. Agora, antes de continuarmos, vamos refletir: Por que devemos nos preocupar em definir um modelo de arquitetura adequado? A definição do modelo de arquitetura é importante para: APOIAR o planejamento do desenvolvimento do sistema. PERMITIR melhor tratamento dos atributos de qualidade de software (requisitos não funcionais). AJUDAR no gerenciamento dos riscos técnicos e da complexidade da solução. APOIAR a análise de impacto de mudanças tecnológicas e de negócio. ORIENTAR as atividades de projeto e implementação. 13 O que são componentes arquiteturais? São elementos que executam ou apoiam a execução das funcionalidades do sistema computacional, ou seja, são responsáveis pela execução dos serviços propriamente ditos. Componentes também podem ser definidos como abstrações que representam o hardware e o software (desenvolvido pela equipe ou por terceiros), que fazem parte de um sistema computacional. Outra definição importante são os Conectores Arquiteturais, que definem a interação entre os componentes da arquitetura, permitindo representar a comunicação, coordena- ção e cooperação entre eles. Conectores podem representar a conexão entre componen- tes de hardware (equipamentos) ou entre componentes de software. Os conectores podem ser usados para representar: • Comunicação de dados (HTTP, HTTPS, TCP/IP, SSL etc.). • Chamada de funções, procedimentos ou métodos. Exemplos de componentes de software: • Desenvolvidos pela equipe (aplicativos, pacotes, módulos, bibliotecas, web services etc.). • Sistemas Gerenciadores de Banco de Dados – SGBD (SQL Server, MySQL, Oracle etc.). • Servidores de aplicação (Apache Tomcat, Zend Server, IIS etc.). • Containers (Docker, Kubernetes, Google Cloud, Microsoft Azure etc.). • Sistemas operacionais (Windows, Linux, Android, iOS etc.). • Bibliotecas de terceiros (JQuery, Apache Commons, OpenGL etc.). Exemplos de componentes de hardware: • Servidores. • Computadores pessoais (desktop, notebook etc.). • Dispositivos móveis (smartphone, tablet, smartwatch etc.). • Sensores/Leitores. • Atuadores (equipamentos diversos controlados por software). Exemplo 14 • Invocação de serviços. • Acesso a dados. • Conexão com banco de dados (ODBC, JDBC, ADO.NET etc.). • Conexão com arquivos (file input/output). • Streamings, eventos etc. Existem diversos modelos arquiteturais propostos na literatura. Contudo, nesta disci- plina vamos apresentar os modelos de camadas (layers), cliente-servidor (2-tier) e de multicamadas N-Tier. Modelo em camadas O modelo de arquitetura em camadas caracteriza-se pela organização do sistema em um conjunto de camadas lógicas, em que cada camada (“layer”) oferece um conjunto de serviços e possui uma função bem definida no sistema. Outro conceito importante é que uma camada somente solicita serviços da camada inferior e fornece serviços para a camada superior. Figura: Exemplo de modelo em camadas de um sistema computacional de vendas com persistência de dados. Fonte: O autor (2020). Apresentação Controle Negócio ou Domínio Persistência de dados Formulário de Pedido Controle do Pedido PedidoDAO Pedido ClienteDAO Cliente BD 15 O modelo em camadas oferece vantagens como: • Possibilidade de desenvolver cada camada de forma independente (paralelismo). • As camadas podem ser facilmente substituídas por equivalentes. • Mudanças em uma camada só impactam a camada imediatamente superior. Por outro lado, o modelo em camadas possui as seguintes desvantagens: • Cuidados extras devem ser tomados para garantir a integridade entre as cama- das; por exemplo, o estado de um objeto na camada de persistência deve ser refleti- do na camada de domínio e também na camada de apresentação. • Muitas camadas podem comprometer o desempenho do sistema, pois a requisi- ção precisa trafegar pelas várias camadas até ser atendida. Modelo cliente-servidor (2-tier) O modelo arquitetural cliente-servidor possui as seguintes características: • Ser organizado como um conjunto de serviços (servidores e clientes dos serviços). • Cliente e servidor rodam em máquinas distintas. • Requer uma estrutura de rede para clientes acessarem os servidores remotamente. • Clientes devem saber quais serviços e servidores estão disponíveis, mas os servi- dores não conhecem seus clientes. • Baseado no protocolo “pergunta-resposta” (request-reply), quando um cliente faz um pedido ao servidor e espera pela resposta. O servidor executa o serviço e res- ponde ao cliente. É importante destacar que “tiers” se referem a camadas físicas. E oferece vantagens como: • A distribuição de dados é fácil e direta. • Possibilita hardware mais barato (clientes leves). • Facilidade de adicionar novos servidores ou atualizar servidores existentes. 16 E possui as seguintes desvantagens: • Pode haver redundância de serviços em diferentes servidores. • Não prevê um registro central de serviços, ou seja, os clientes devem saber onde estão os servidores e quais serviços eles disponibilizam. • Precisa organizar três camadas lógicas (apresentação, negócio e dados) em duas camadas físicas (cliente e servidor). Modelo multicamadas (N-tier) O modelo multicamadas (N-tier) pode ser considerado uma evolução da arquitetura cliente-servidor porque consegue separar as lógicas de apresentação, negócio e dados em máquinas distintas. E oferece vantagens como: • Melhor balanceamento de carga entre as diversas camadas. • Aumenta a escalabilidade: é fácil adicionar novos servidores de aplicação quando o número de usuários aumenta. E possui as seguintes desvantagens: • Maior complexidade no desenvolvimento para um número maior de camadas (quatro ou mais). • Quanto maior o número de camadas, maior o overhead de comunicação entre elas. Figura: Exemplos de modelo de arquitetura 3-Tier. Fonte: O autor (2020). Servidor Processamento de Aplicações Servidor Gerenciamentode dados Servidor Web Processamento de Aplicações Servidor de BD Gerenciamento de dados Apresentação HTTP Cliente Cliente Cliente Cliente 17 Figura: Exemplo de modelo de arquitetura 4-Tier. Fonte: O autor (2020). Arquitetura de Microsserviços O conceito de Arquitetura de Microsserviços surgiu há poucos anos como uma forma de desenvolver aplicações de software. Apesar de não existir ainda uma definição clara do padrão de Microsserviços, existem algumas características comuns em torno da ca- pacidade de negócio, distribuição automatizada, inteligência nos pontos de integração e controle descentralizado das linguagens e dados. Segundo Martin Fowler (2015), o padrão arquitetural baseado em Microsserviços corres- ponde ao modo de desenvolver uma única aplicação como um conjunto de pequenas aplicações, cada uma em seu próprio contexto e com processos independentes que se comunicam por meio de mecanismos simples, tradicionalmente por requisições a uma API utilizando o protocolo HTTP. Uma das principais razões para utilizar serviços como componentes é que serviços po- dem ser implantados independentemente uns dos outros. Assim, a partir do baixo aco- Camada do cliente (browser) Camada da aplicação (Servlet/JSP) Camada do negócio (Casses Java/Java Beans) Camada de dados (SGBD) 18 plamento e da decomposição da aplicação em vários serviços, a manutenção ou alte- ração de um serviço não atinge outras partes da aplicação no que em geral ocupa um tempo de indisponibilidade menor. 19 Padrões de projeto de sistemas OO: Modelo- -Visão-Controle – MVC, DAO, Factory e Singleton Neste tópico serão abordados alguns padrões de projeto (ou padrões de design) para orientar o desenvolvimento de sistemas computacionais Orientados a Objetos – OO complexos com acesso a banco de dados e aplicações Web. O paradigma da Programação Orientada a Objetos tenta resolver alguns dos problemas existentes na programação procedural, como permitir maior reutilização de código e fa- cilitar a manutenção, a modificação e a evolução dos sistemas. Como definir as responsabilidades de cada componente ou classe de um sistema OO para alcançar uma alta coesão? É necessário definir componentes com atribuições bem delimitadas a fim de facilitar seu gerenciamento. Caso contrário, as classes serão difíceis de compreender, reutilizar e rea- lizar manutenções ou modificações durante o ciclo de vida do sistema. Vamos pensar! Como implementar os diversos componentes do sistema OO para que se tenha baixo acoplamento? Como projetar componentes, classes e sub- sistemas para que as variações nesses elementos não tenham um im- pacto indesejável no restante do sistema? O conceito de coesão está relacionado ao princípio da Responsabilidade Única usado em projeto de sistema OO, em que idealmente uma classe deve ter ape- nas uma responsabilidade e realizá-la de maneira adequada. Uma classe com baixa coesão assume muitas responsabilidades, realiza muitas coisas não re- lacionadas e trabalha demais. Como consequência, classes com baixa coesão apresentam maior dificuldade de manutenção, modificação e reúso. Importante 20 De forma geral, a área de Projeto de Sistemas OO possui alguns princípios e recomen- dações para a organização de componentes, suas responsabilidades e relacionamentos, de forma que tais componentes sejam mais flexíveis e que as mudanças em um deles tenham menor impacto possível nos demais. Além disso, que sejam mais fáceis de en- tender e à base de componentes que possam ser usados em vários sistemas, facilitando o reúso do código. Padrão Model-View-Controller – MVC O padrão MVC pode ser considerado tanto um padrão de arquitetura como de projeto. Ele possui como características a divisão da aplicação interativa em três partes: • Model: encapsula os dados e as funcionalidades do negócio e é independente de uma apresentação específica. • View: apresenta informações do Model ao usuário. • Controller: trata a entrada do usuário. Além disso, no padrão MVC podem existir múltiplas Views para um mesmo Model e cada View está associada a um único Controller. Contudo, os dois juntos, Views e Controllers, formam a interface com o usuário. O padrão MVC é normalmente utilizado em sistemas interativos quando existem várias maneiras de visualizar e interagir com os dados. Outro uso importante do MVC é quando os requisitos de interação com os usuários são voláteis ou em aplicações que requerem interfaces em diversas plataformas distintas de hardware/software. O MVC estabelece um mecanismo para propagação das mudanças do projeto de forma a manter a consistência entre Model e View. A saber: • Os Controllers recebem a entrada do usuário (geralmente eventos). O termo “acoplamento” é uma medida de quanto um elemento está conectado a ou depende de outros elementos. Um componente com acoplamento forte depende de muitos outros componentes, o que dificulta a reutilização, a manu- tenção e as modificações no código. Ampliando o foco 21 • Eventos são traduzidos em requisições de serviço que são enviadas para o Model ou para a View. • Após receber a requisição e alterar seu estado interno o Model notifica todas as Views a ele conectadas. • As Views notificadas recuperam os dados do Model e atualizam as informações para o usuário. Figura: Modelo MVC para desenvolvimento de aplicações Web. Fonte: O autor (2020). Podemos citar como vantagens do padrão MVC: • Permite diferentes padrões/estilos de interação sem alterar o núcleo da aplicação. • Permite a visualização da mesma informação em formatos distintos, porém sin- cronizados. • Permite a alteração de Views/Controllers em “tempo de execução”. E como desvantagens: • Maior complexidade quando o modelo de dados e de interações é muito simples. • Views e Controllers fortemente interconectados. Model Enviar requisição Apresentar dados forward model.alterarEstado() model.getDados() 1 2a 2b 4 3 View Controller 22 Figura: Exemplo do MVC usado no desenvolvimento de aplicações Web com JSP/Servlet/EJB/JPA. Fonte: O autor (2020). Padrão Factory Method Muitas vezes o processo de criação de um objeto exige condições específicas que não são apropriadas para serem incluídas dentro do método construtor do objeto. Pode ocor- rer que para a criação de um objeto sejam necessárias informações externas, respon- sabilidades adicionais, duplicação indesejada de código, gerando maior acoplamento e menor coesão na classe. O padrão Factory Method pode definir uma interface para criação de um objeto, mas deixar que as subclasses decidam qual classe instanciar. Esse padrão permite que uma classe delegue a responsabilidade de instan- ciação às subclasses. Dada uma hierarquia de classe, temos um ponto do código em que gostaríamos de cons- truir um objeto dessa hierarquia. Entretanto, o objeto a ser construído dependerá de algu- ma condição. Model Sessions Beans (EJB) Entity Classes (JPA) Request Response Database Controller (servlet)Client (browser) View (JSP pages) 23 Figura: Exemplo do padrão Factory Method. Fonte: O autor (2020). Padrão de projeto Data Access Objects – DAO Grandes sistemas computacionais normalmente precisam armazenar suas informações de trabalho de forma organizada para permitir consultas, alterações de registros ou geração de relatórios de maneira eficiente. Dessa forma, geralmente é necessário usar uma solução de banco de dados, que armazena as informações de forma organizada e prontas para consultas. Grande parte dos bancos de dados comerciais são relacionais, compostos por uma es- trutura de tabelas e atributos que possuem relacionamentos entre si. Exemplo 1 Precisamos criar um objeto que armazene a forma de pagamento de um pedi- do, que pode ser cartão de crédito, débito, boleto ou PayPal, a depender do tipo desejado pelo cliente. Exemplo 2 Precisamos criar um objeto que armazene a forma de conexão em um banco de dados, que pode ser MySQL, SQLServer, Oracle. Exemplo 24 O modelo debanco de dados relacionais possui um padrão de trabalho bem diferente do paradigma da Orientação a Objetos. Por exemplo, para começar a trabalhar com banco de dados relacionais é necessário conhecer a linguagem SQL (Structured Query Language), que é usada para a criação de tabelas, conexões aos bancos, realização de consultas, entre outras ações. Dessa maneira será necessário: Desenvolver classes para trocar informações com os Sistemas Gerenciadores dos Bancos de Dados – SGBD e implementar operações CRUD (Create, Read, Update e Delete). Essas classes devem ser capazes de: • Ler e escrever nas tabelas do banco de dados. • Transformar esses dados em objetos ou lista de objetos. • Realizar operações usando instruções SQL e outras funcionalidades. Porém, teremos um problema. Quando é preciso colocar código SQL dentro das classes Java para incluir funcionalidades de acesso e consulta aos bancos de dados, as reco- mendações de alta coesão e baixo acoplamento não são mais respeitadas. O código fica menos legível, mais confuso e apresenta maior dificuldade para alterações e manutenção. Como podemos solucionar esse problema? A solução para esse problema é tentar separar o código SQL de acesso ao banco de dados das demais classes de lógica e colocá-lo em uma classe responsável apenas pelo acesso aos dados. Dessa forma, o código de acesso ao banco de dados fica concentra- do em apenas um local, tornando mais fácil a manutenção, além de tornar as classes mais legíveis. Dessa forma, o padrão de projeto Data Access Object – DAO permite que, por meio de uma única classe, seja realizada toda a lógica de controle de acesso ao banco de dados, separando a lógica de negócio das outras classes. 25 Somente a partir das classes DAO é que será possível: 1. Conectar e acessar o banco de dados. 2. Manter os princípios de alta coesão e baixo acoplamento do código. O padrão DAO permite que seja possível alterar a forma de persistência de dados sem in- fluenciar a lógica de negócio. Nas próximas unidades serão abordados maiores detalhes sobre a implementação das classes DAO. Figura: Exemplo de Diagrama de Classes sem DAO – Objeto está com responsabilidades demais. Fonte: O autor (2020). Figura: Exemplo de Diagrama de Classes com DAO – Agora apenas o ObjetoDAO possui responsabilida- des CRUD – Maior coesão e menor acoplamento. Fonte: O autor (2020). 26 Padrão Singleton Em diversas situações é necessário que apenas uma instância de uma classe seja cria- da, independentemente de quantas requisições de criação possam ser feitas. Veja alguns exemplos. O padrão Singleton é definido de forma a garantir que uma classe só tenha uma única instância e prover um ponto de acesso global a ela. Figura: Exemplo de Diagrama de classe do Padrão Singleton. Fonte: O autor (2020). Observe o exemplo a seguir de implementação Java do padrão Singleton com instancia- mento direto. • Uma única conexão com o banco de dados. • Um único arquivo de log de erros. • Uma única fila de impressão. • Um único arquivo de configuração. Exemplos 27 No exemplo anterior o construtor é definido como “private” para impedir que a classe Singleton seja instanciada fora dela. Foi criado um atributo público e estático (da classe) que retorna a partir de um método estático (getInstancia), uma única instância dessa classe. Como o método “getInstancia()” é estático ele pode ser chamado de outra classe sem precisar instanciar a classe Singleton. É importante notar que neste exemplo a ins- tância da classe será sempre criada mesmo antes de chamar o método “getInstancia()”. public class Singleton { private static Singleton instancia = new Singleton(); private Singleton() { } public static synchronized Singleton getInstancia() { return instancia; } } Exemplos 28 Acesso e conexão a gerenciadores de banco de dados com uso do JDBC O desenvolvimento de sistemas de informação para empresas exige funcionalidades de armazenamento de dados, também conhecidas como persistência de dados. Em geral, as atividades de controle e gestão de um sistema empresarial necessitam de acesso às informações armazenadas para geração de relatórios e gráficos, histórico de problemas, análise de tendências, planejamento, tomada de decisões, entre outras diversas atividades. Dessa forma, uma aplicação Java precisa realizar uma conexão com um Sistema Geren- ciador de Banco de Dados – SGBD para poder implementar as operações necessárias para a manutenção de informações, tais como: inclusão, leitura, alteração e exclusão de dados. Essas operações também são conhecidas pelo acrônimo: C reate R ead U pdate D elete Para realizar a conexão devemos inicialmente identificar o gerenciador de banco de da- dos que será utilizado pelo projeto, já que para cada tipo de gerenciador existe um driver (modo de acesso próprio). Logo, para cada gerenciador de banco de dados devemos utilizar uma classe específica para esse fim. Conexão a banco de dados em Java A biblioteca de persistência em banco de dados relacionais do Java é chamada Java DataBase Connectivity – JDBC e fica dentro do pacote java.sql. A biblioteca JDBC consiste em um único conjunto de interfaces muito bem definidas e cujos métodos e classes devem ser implementados para permitir a conexão e o acesso ao banco de dados. Essa configuração com uma API única padroniza a forma de acesso aos dados armaze- nados, evitando que cada banco de dados tenha sua própria interface. 29 Entre as diversas interfaces do JDBC, podemos citar a interface Connection, na qual são definidos métodos padrões para trabalhar com banco de dados, tais como: • Executar consultas. • Inserir dados. • “Comitar” uma transação. • Fechar a conexão, entre outros. Caso seja necessário trabalhar com algum banco de dados específico, como o MySQL, precisaremos de classes concretas que implementem essas interfaces do pacote java.sql. Esse conjunto de classes concretas recebe o nome de driver e fará a ligação entre a apli- cação que usa a interface JDBC e o banco de dados. Esse conjunto de classes é o que implementa a comunicação a partir do protocolo proprietário do banco de dados. Vamos entender melhor o assunto! Veja o exemplo a seguir. Para abrir uma conexão com um determinado banco de dados precisamos utilizar sem- pre o driver apropriado. A classe DriverManager do JDBC é a responsável por comunicar- -se com os drivers disponíveis para conexão, como mostrado na figura a seguir: Todos os principais bancos de dados possuem drivers JDBC para utilização com Java. O nome “driver” é análogo ao utilizado para periféricos em geral. Como um sistema operacional poderia comunicar-se com as diversas impresso- ras disponíveis no mercado, cada uma com características específicas? Neste caso, é necessário que cada impressora disponibilize um driver com as interfaces necessárias para realizar a comunicação com o sistema operacional. Exemplo 30 Figura: Modelo de conexão a diferentes banco de dados com JDBC API e Driver Manager. Fonte: tutorialspoint.com. Adaptado. Conexão implementada por meio do método estático getConnection com uma String que indica a qual banco desejamos nos conectar. Essa String é chamada de string de conexão JDBC, conforme mostra o exemplo a seguir: DriverManager.getConnection(string_de_conexao); Para conexão ao banco de dados MySQL a string de conexão tem o seguinte formato: jdbc:mysql://ip/nome_do_banco MySQL SQL Server Oracle Java Application JDBC API JDBC Driver Manager Drivers específicos JDBC DriverJDBC Driver JDBC Driver https://www.tutorialspoint.com/jdbc/jdbc-introduction.htm 31 Devemos substituir: • “ip” pelo código IP da máquina do servidor. • “nome_do_banco” pelo nome do banco de dados a ser utilizado. Na string de conexão também podem ser incluídos o login e a senha do usuário. Agora, veja um exemplo prático. Preste atenção! Utilizando as informações anteriores podemos usar o código a seguir para implemen- tar a conexão paraum banco de teste MySQL (“BD_teste”), caso ele esteja rodando na mesma máquina: public class JDBCExemplo { public static void main(String[] args) throws SQLException { Connection conexao = DriverManager.getConnection(“jdbc:mysql://localhost/BD_teste”); System.out.println(“Conectado!”); conexao.close(); } } No exemplo anterior o tratamento de exceções não está sendo realizado de forma ade- quada. Estamos deixando passar a SQLException, uma “exception checked”, que pode ser lançada por muitos dos métodos da API de JDBC. Em uma aplicação real é necessário fazer um tratamento de exceções mais específico utilizando a estrutura “try/catch” nos lugares em que há possibilidade de recuperação de alguma falha com o banco de dados. Vale lembrar também que é necessário fechar todas as conexões que foram abertas. 32 Ao testar esse código, será recebida uma “exception” informando que a conexão não pôde ser aberta, de acordo com a mensagem a seguir: java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost/BD_teste Esse problema ocorreu porque o sistema ainda não achou uma implementa- ção de driver JDBC que possa ser usada para abrir a conexão indicada pela URL jdbc:mysql://localhost/BD_teste. O próximo passo é adicionar o driver do MySQL ao “classpath”, ou seja, o arquivo “.jar” contendo a implementação JDBC do MySQL (MySQL Connector) precisa ser colocado em um lugar visível pelo seu projeto ou adicionado à variável de ambiente CLASSPATH. Class.forName Até a versão 3 do JDBC, antes de chamar o DriverManager.getConnection() era necessá- rio registrar o driver JDBC que iria ser utilizado a partir do método: Class.forName(“com.mysql.jdbc.Driver”) No caso do MySQL, que carregava essa classe e se comunicava com o DriverManager, a partir do JDBC 4 (presente no Java 6) esse registro passou a não ser mais necessário. Vale lembrar que, se o JDBC for usado em um projeto com Java 5 ou anterior, será ne- cessário fazer o registro do driver JDBC carregando sua classe, que se registrará no DriverManager. Alterando o banco de dados Inicialmente, para trocar de um banco de dados para outro, bastaria modificar a string de conexão e a string de registro do driver JDBC. O problema é que os códigos implementa- dos das classes JDBC utilizam código SQL para acesso aos bancos de dados. 33 Um banco de dados diferente pode utilizar uma implementação da linguagem SQL não totalmente compatível com o padrão ANSI SQL. Dessa forma, apenas alterar as strings de conexão não garante o total funcionamento das classes no novo banco de dados. Nas próximas unidades serão abordadas soluções para esse problema, como é o caso do Hibernate (www.hibernate.org) e da especificação Java Persistence API – JPA. Para ampliar o seu conhecimento veja o material complementar da Unidade 1, disponível na midiateca. MIDIATECA Algumas vezes é necessário realizar conexões com diferentes bancos de dados, cada um com seu driver específico. Segue exemplo de método com uma confi- guração básica para a conexão com um servidor de banco de dados MySQL. O método retornará a conexão com o banco MySQL por meio do objeto con: • Biblioteca: mysql-connector-java-5.1.6-bin.jar. NA PRÁTICA Os drivers de outros bancos de dados podem ser baixados normalmente nos sites dos respectivos fabricantes. Em alguns casos, como no Microsoft SQL Server, existem grupos alternativos que desenvolvem o driver em http://jtds.sourceforge.net. O driver do MySQL (chamado de MySQL Connector) pode ser baixado no site http://www.mysql.org. Ampliando o foco http://www.hibernate.org http://jtds.sourceforge.net http://www.mysql.org 34 public static Connection acessoMySQL() { Connection con = null; // definição do objeto de conexão com o banco try { // definição do driver de conexão para o MySQL String driver = "com.mysql.jdbc.Driver"; // registro da classe de acesso ao banco não é necessário a partir // do JDBC 4, presente na versão Java 6, versões anteriores poderão // exigir esse registro Class.forName(driver); // determinar o caminho de acesso ao servidor // para acesso local, ou seja, a própria máquina, você pode usar // localhost ou 127.0.0.1 // ou você pode indicar o endereço IP do servidor String nomeServidor = "localhost"; // nome do banco de dados que será aberto // um servidor pode ter mais de um banco de dados registrado String nomeBanco = "BD_teste"; // definição da configuração de acesso ao servidor String url = "jdbc:mysql://" + nomeServidor + "/" + nomeBanco; // executa a conexão com o banco de dados // caso não consiga realizar a conexão, uma exceção será lançada con = DriverManager.getConnection(url); // testa se sua conexão foi realizada// if (con != null) { System.out.println("Banco conectado com sucesso!"); } else { System.out.println("Banco não conectado!"); } } // caso seja lançada uma exceção de driver não encontrado, // provavelmente a biblioteca de acesso do driver // não foi incluída no projeto catch (Exception e) { e.printStackTrace(); } return con; } • Driver: com.mysql.jdbc.Driver. • Exemplo de método de conexão. 35 Resumo da Unidade 1 Nesta unidade foram abordados conceitos de diferentes modelos de arquitetura de sis- temas computacionais (modelo em camadas, cliente-servidor e multicamadas), além de alguns padrões de projeto (MVC, DAO, Factory e Singleton) para desenvolvimento de sis- temas OO com acesso a bancos de dados e aplicações Web. Também foram apresenta- das técnicas de acesso e conexão a sistemas gerenciadores de banco de dados – SGBD com uso da biblioteca JDBC. 36 Referências DEITEL, P.; DEITEL, H. Java: como programar. 10. ed. São Paulo: Pearson Education do Brasil, 2017. Cap. 24, p. 13-860. Biblioteca Virtual. FOWLER, M. Analysis Patterns: Reusable Object Models. Londres: Addison-Wesley Reading, 1996. FREEMAN, E.; FREEMAN, E.; SIERRA, K.; BATES, B. Use a cabeça: padrões e projetos. 2. ed. rev. Rio de Janeiro: Alta Books, 2009. xxiv, 478 p. ISBN 9788576081746. FURGERI, S. Java 8 – Ensino didático: desenvolvimento e implementação de aplicações. São Paulo: Érica, 2015. Cap. 12, p. 232-256. Minha Biblioteca GAMMA, E. et al. Padrões de projeto: soluções reutilizáveis de software orientado a ob- jetos. Porto Alegre: Bookman, 2000. xii, 364 p. ISBN 9788573076103. LARMAN, C. Utilizando UML e padrões: uma introdução à análise e ao projeto orienta- dos a objetos e ao desenvolvimento iterativo. Porto Alegre: Bookman, 2000. 492 p. Acesso a bancos de dados relacionais via JDBC utilizando diferentes padrões de projeto UNIDADE 2 38 Nesta unidade serão abordadas técnicas para desenvolver aplicações Java com persis- tência de dados com uso da biblioteca JDBC por meio de mapeamento objeto-relacio- nal. O aluno terá oportunidade de aprender com exemplos de acesso a banco de dados utilizando os modelos de projeto DAO e “Factory” para implementar operações CRUD e mapeamento objeto-relacional não automático. Como estamos tratando de sistemas computacionais multiusuários com acessos con- correntes ao mesmo banco de dados, é necessário que o SGBD implemente algum tipo de controle de acesso para que cada transação seja atendida de forma justa e eficiente. Assim, serão abordadas diferentes técnicas de controle concorrente de transações em banco de dados. Caso haja falha em uma transação, seus efeitos deverão ser revertidos para garantir a integridade e a consistência do banco de dados. Também serão abordadas recomendações para o tratamento adequado de erros e exce- ções em sistemas Java com acesso a banco de dados. INTRODUÇÃO Nesta unidade você será capaz de: • Desenvolver aplicações em Java com persistência de dados por meio de mapeamento objeto-relacional manual. OBJETIVO 39 Acesso remoto a gerenciadores de bancos de dados relacionais com usodo JDBC Neste tópico serão abordados alguns exemplos práticos de programação Java para uti- lização das classes da biblioteca JDBC para conexão e implementação das operações CRUD em banco de dados. Utilizaremos como SGBD o MySQL Server, que é mantido pela Oracle e amplamente uti- lizado em aplicações comerciais. Em seguida, apresentaremos o modelo de conexão ao MySQL usando JDBC API e Driver Manager, como mostra a figura a seguir. Figura: Modelo de conexão ao MySQL usando JDBC API e Driver Manager. Fonte: Notas de aula do professor (2020). Fábrica de Conexões (“Connection Factory”) Como já vimos muitas vezes, o processo de criação de um objeto pode exigir critérios específicos que não são adequados para incluir dentro de seu próprio método construtor. No caso específico de criação de uma conexão ao banco de dados, são necessárias in- MySQL Java Application JDBC API JDBC Driver Manager JDBC Driver 40 formações específicas dos SGBD, diferentes strings de conexão, o que pode gerar maior acoplamento e menor coesão na classe que está sendo desenvolvida. Dessa forma, o padrão “Factory Method” pode ser usado para definir uma interface para a criação das conexões do banco de dados a fim de controlar melhor esse processo repetitivo e trabalhoso, que consome muitos recursos do SGBD. Veja um exemplo de classe responsável por criar uma nova conexão com o banco de dados. No exemplo anterior, o tratamento das exceções não está sendo realizado de forma ade- quada, pois a SQLException é uma “exception checked”, que pode ser lançada por muitos dos métodos da API de JDBC. É necessário fazer um tratamento de exceções específico, usando a estrutura “try/catch”, quando há possibilidade de recuperação do sistema caso ocorra alguma falha. Dessa forma, para obter uma nova conexão com o banco de dados basta usar o seguinte comando: Para criar uma conexão JDBC, é necessário usar a classe DriverManager pre- sente no pacote java.sql. A string de conexão contendo o endereço URL do banco de dados, o usuário e a senha deve ser repassada ao método estáti- co getConnection() da classe DriverManager para que ela possa criar uma conexão JDBC: Exemplo public class ConnectionFactory { public Connection criaConexao() throws SQLException{ return DriverManager.getConnection("jdbc:mysql://localhost/BD_teste,"ro ot","senha"); } } Connection conexao = ConnectionFactory.criaConexao(); 41 O método “criaConexao()” pode ser considerado uma fábrica de conexões, pois, ao ser executado, retorna um objeto “Connection” representando a criação de uma nova cone- xão pronta para uso, independentemente de detalhes da codificação. Apagando uma base de dados Depois de implementar uma conexão JDBC, podemos começar a executar opera- ções no banco de dados. O primeiro exemplo de operação será apagar uma base de dados “bdteste”. Inicialmente, para executar uma operação deve-se definir o código SQL correspondente. O código SQL que corresponde à operação a ser executada deve ser usado como parâ- metro para a interface “prepareStatement()” de uma conexão JDBC. Essa interface cria um objeto do tipo “PreparedStatement”, que representa a operação que será executada. Em seguida, a operação SQL poderá ser realizada por meio do método “execute()”. No final, deve-se chamar o método “close()” do objeto “PrepareStatment” para liberação de recursos de memória. String sql = "DROP DATABASE IF EXISTS bdteste"; PreparedStatement stm = conexao.prepareStatement(sql); stm.execute(); stm.close(); Uma mesma conexão pode ser reaproveitada para executar outras operações do banco de dados. A conexão JDBC deverá ser finalizada a partir do método “close()” quando não houver mais operações a serem executadas, a fim de libe- rar recursos no SGBD: Importante conexao.close(); 42 Criando uma base de dados O procedimento de criação de uma base de dados é similar ao procedimento anterior para apagar uma base de dados: Criando uma tabela de dados O código a seguir mostra o procedimento de criação de uma tabela a partir de uma conexão JDBC. JavaBeans JavaBeans são classes que possuem o construtor “default” sem argumentos e apenas os métodos básicos de acesso “get” e “set”, cuja especificação é a base dos componen- tes escritos em Java. Neste tópico usaremos o conceito JavaBeans nas classes que representam nosso modelo de dados. String sql = "CREATE DATABASE dbpoo3" PreparedStatement stm = conexao.prepareStatement(sql); stm.execute(); stm.close(); String sql = "CREATE TABLE alunos (" + ; " idaluno INT NOT NULL AUTO_INCREMENT ," + " matricula INT NOT NULL" + " nome VARCHAR(45) NOT NULL ," + " PRIMARY KEY (idaluno))" ; PreparedStatement stm = conexao.prepareStatement(sql); stm.execute(); stm.close(); 43 Veja um exemplo de uma classe “Aluno” no padrão “JavaBeans”, que seria equivalente ao nosso modelo de entidade do banco de dados da tabela “Alunos”: Exemplo public class Aluno { private int idaluno; private int matricula; private String nome; // métodos get e set da classe Aluno public String getNome() { return this.nome; } public void setNome(String novo_nome) { this.nome = novo_nome; } public int getId() { return this.idaluno; } public void setId(int novo_id) { this.idaluno = novo_id; } public int getMatricula() { return this.matricula; } public void setMatricula(int novo_mat) { this.matricula = novo_mat; } } A especificação JavaBeans é a base dos componentes escritos em Java e sua documentação completa pode ser encontrada em http://docs.oracle.com/java- se/tutorial/javabeans/. Ampliando o foco http://docs.oracle.com/javase/tutorial/javabeans/ http://docs.oracle.com/javase/tutorial/javabeans/ 44 Inserindo dados em uma tabela Agora, veremos um exemplo de como adicionar dados nas tabelas utilizando conexões JDBC. Normalmente, as chaves primárias dos registros inseridos em uma tabela são geradas pelos SGBDs. Caso necessário os valores da chave primária podem ser obtidos a partir dos métodos definidos pela especificação JDBC. O exemplo anterior possui alguns problemas. São eles: • A mistura das sintaxes das linguagens Java e SQL torna o código confuso e de difícil entendimento, especialmente para operações de inserção e atualização de dados. • A necessidade de concatenação de strings longas para implementar os comandos SQL, principalmente com tabelas de dados grandes com várias colunas. • O tratamento de caracteres especiais nas strings e o risco de alteração do código SQL por usuários mal-intencionados (SQL Injection). Não confunda JavaBeans com outro componente chamado de Enterprise JavaBeans – EJB. Os EJBs são componentes da plataforma Java Enterprise Edition – JEE, que roda em um “container” de um servidor de aplicação. Seu objetivo é fornecer um desenvolvimento de aplicações Java de forma mais simples e rápida, baseado em componentes distribuídos, transacionais, segu- ros e com portabilidade. Importante String sql = "INSERT INTO alunos (matricula , nome)” + “VALUES ( ’202001’,’João da Silva’)"; PreparedStatement stm = conexao.prepareStatement(sql); stm.execute(); stm.close(); 45 Por esses motivos a forma de entrada do código SQL será atualizada da seguinte maneira: Nesta sintaxe, o comando SQL será executado, mas não sabemos os parâmetros que utilizaremos no código SQL. As cláusulas são executadas em um banco de dados por meio da interface PreparedStatement. Para receber uma PreparedStatement relativa à conexão, basta chamar o método prepareStatement, passando como argumento o co- mando SQL com os valores oriundos de variáveis preenchidos com uma interrogação. Logo, chamamos os métodos setLong e setString do PreparedStatementpara preen- cher os valores, que são do tipo “int” e “String”, passando a posição (começando em 1) da interrogação no SQL e o valor que deve ser colocado: Por fim, uma chamada ao método “execute()”para executar o comando SQL: String sql = "INSERT INTO alunos (matricula, nome) VALUES (?,?)"; String sql = "INSERT INTO alunos (matricula, nome) VALUES (?,?)"; PreparedStatement stm = conexao.prepareStatement(sql); // preenche os valores stm.setInt(1, 202001); stm.setString(2, "João da Silva"); stm.execute(); 46 O exemplo a seguir abre uma conexão e adiciona um aluno ao banco de dados: public class JDBCadiciona { public static void main(String[] args) throws SQLException { // conectando Connection conexao = ConnectionFactory.criaConexao(); // cria um preparedStatement String sql = "INSERT INTO alunos (matricula,nome) VALUES (?,?)"; PreparedStatement stm = conexao.prepareStatement(sql); // preenche os valores stm.setInt(1, 202001); stm.setString(2, "João da Silva"); // executa stmt.execute(); stmt.close(); System.out.println("Gravado!"); conexao.close(); } Sobre a interface Statement, em vez de se usar o PreparedStatement é possível utilizar uma interface mais simples chamada Statement, que simplesmente executa uma cláusula SQL no método “execute”: O problema é que, apesar de ser mais simples, a interface Statment é mais len- ta e será necessário implementar muitas concatenações de strings, tornando o código mais difícil de entender. Dessa forma, é recomendável utilizar apenas a classe PreparedStatement. Statement stm = conexao.createStatement(); stm.execute("INSERT INTO ..."); stm.close(); Importante 47 Classe Data Access Object – DAO A separação do código de acesso ao banco de dados em uma classe DAO, com apenas essa responsabilidade, aumenta a coesão e diminui o acoplamento do código, facilitan- do sua manutenção e sua atualização. Da responsabilidade desse objeto surgiu o nome Data Access Object, ou simplesmente DAO, um dos mais utilizados padrões de projeto (design patterns). Podemos implementar a classe “AlunoDAO” com um método construtor, que cria uma conexão com banco de dados por meio de uma chamada ao método “criaConexao()” da classe ConnectionFactory. Então, uma instância de AlunoDAO já possui uma conexão com o banco de dados e podemos implementar o método “adicionaAluno”, que recebe um objeto “Aluno” como argumento e é responsável por adicioná-lo a partir de código SQL: public class AlunoDAO { // abre uma conexão com o banco de dados private Connection conexao; public AlunoDAO() { this.conexao = ConnectionFactory.criaConexao(); } } public void adicionaAluno(Aluno aluno) throws SQLException{ String sql = "INSERT INTO alunos (matricula,nome) VALUES (?,?)"; // comando prepared statement para inserção de dados PreparedStatement stm = conexao.prepareStatement(sql); // entra com os valores de inserção stm.setInt(1,aluno.getMatricula()); stm.setString(2,aluno.getNome()); // executa stm.execute(); stm.close(); } 48 Fazendo pesquisas no banco de dados A implementação de pesquisas no banco de dados também utiliza a interface “preparedStatement” para montar o comando SQL. Entretanto, como uma pesquisa no banco possui valores de retorno (diferentemente do comando de inserção), será utilizado o método “executeQuery”, que retorna todos os registros de uma determinada consulta. O objeto retornado é do tipo “ResultSet” do JDBC, que possibilita navegar por seus re- gistros por meio do método “next”. Quando chega ao fim da pesquisa, o método “next” retorna um valor “false” e dessa forma pode ser utilizado para fazer um laço (“loop”) nos registros. Para retornar o valor de uma coluna no banco de dados, basta chamar os mé- todos “get” do “ResultSet”, como “getString”, “getInt”, “getLong”, entre outros. Aplicando o conceito DAO, podemos criar um método “getListaAlunos()” na nossa classe AlunoDAO retornando uma lista de objetos “Aluno”: // exemplo de consulta simples // inicia o prepareStatement PreparedStatement stm = conexao.prepareStatement("SELECT * FROM alunos"); // executa uma consulta select ResultSet rs = stm.executeQuery(); // iteração no ResultSet while (rs.next()) { int matricula = rs.getInt("matricula"); String nome = rs.getString("nome"); System.out.println(nome + " - " + matricula); } rs.close(); stm.close(); conexao.close(); public List<aluno> getListaAlunos() throws SQLException { String sql = "SELECT * FROM alunos" PreparedStatement stm = this.conexao.prepareStatement(sql); ResultSet rs = stm.executeQuery(); List<Aluno> alunos = new ArrayList<Aluno>(); while (rs.next()) { // criando o objeto Aluno Aluno aluno = new Aluno(); aluno.setMatricula(rs.getInt("matricula")); aluno.setNome(rs.getString("nome")); // adicionando o objeto à lista alunos.add(aluno); } rs.close(); stm.close(); return alunos; } 49 Métodos para alteração e remoção de registros Nos métodos que apresentaremos a seguir também vamos utilizar a interface “preparedStatement” para executar os códigos SQL de alteração (“update”) e remo- ção (“delete”). O método “ResultSet” será usado para receber os dados retornados de cada pesquisa. A seguir é mostrado o método “modificaAluno”, que recebe um objeto do tipo “Aluno”, cujos valores serão alterados na tabela do banco de dados: O código seguinte implementa a remoção de um registro de aluno com uma consulta baseada na chave primária “idaluno” a fim de executar o comando SQL para deleção do registro na tabela “Alunos”: public List<aluno> getListaAlunos() throws SQLException { String sql = "SELECT * FROM alunos" PreparedStatement stm = this.conexao.prepareStatement(sql); ResultSet rs = stm.executeQuery(); List<Aluno> alunos = new ArrayList<Aluno>(); while (rs.next()) { // criando o objeto Aluno Aluno aluno = new Aluno(); aluno.setMatricula(rs.getInt("matricula")); aluno.setNome(rs.getString("nome")); // adicionando o objeto à lista alunos.add(aluno); } rs.close(); stm.close(); return alunos; } public void modificaAluno(Aluno aluno) throws SQLException{ String sql = "UPDATE alunos SET matricula=?, nome=? WHERE id=?"; PreparedStatement stm = conexao.prepareStatement(sql); stm.setInt(1, aluno.getMatricula()); stm.setString(2, aluno.getNome()); stm.setInt(3, aluno.getIdaluno()); stm.execute(); stm.close(); } public void removeAluno(Aluno aluno) throws SQLException{ String sql = "DELETE FROM alunos WHERE idaluno=?"; PreparedStatement stm = conexao.prepareStatement(sql); stm.setLong(1, aluno.getIdaluno()); stm.execute(); stm.close(); } 50 Técnicas de acesso concorrente a bancos de dados cliente-servidor Quando o acesso a um determinado item do banco de dados é realizado por apenas um usuário de cada vez, o funcionamento do Sistema Gerenciador do Banco de Dados é bem simples, sem a necessidade de métodos complexos de controle de acesso. O problema começa quando existem acessos concorrentes a um mesmo item do banco de dados por mais de um usuário e de forma simultânea. Nestes casos, é necessário que o SGBD implemente algum tipo de controle de acesso para que cada transação concor- rente seja atendida de forma justa e eficiente. Para a implementação de um sistema computacional multiusuárioscom acesso concor- rente às informações do banco de dados é necessário que o SGBD controle a execução das transações de cada usuário. Se houver algum problema ou falha em uma transação, seus efeitos deverão ser revertidos (“rollout”) para manter a integridade e a consistência do banco de dados. O que é uma transação em um banco de dados? É qualquer conjunto de operações de acesso a uma base de dados, formando uma uni- dade lógica de processamento. Pode incluir uma ou mais operações de acesso, criação, consulta, atualização ou remoção de itens em uma base de dados. Transações concorrentes em bancos de dados Quando diferentes usuários tentam acessar um mesmo item de base de dados de forma concorrente é necessário que o sistema computacional realize um controle de acesso entre essas transações. O Sistema Gerenciador de Banco de Dados – SGBD deve garantir que todas as operações completadas com sucesso em uma transação sejam gravadas de forma permanente no banco de dados. Por outro lado, se houver alguma falha durante a execução de uma transação, ela não deverá alterar o banco de dados ou outras transações concorrentes. Existem várias técnicas de controle de concorrência para garantir que não haja interfe- rência e se mantenha o isolamento entre as transações concorrentes na base de dados. 51 Algumas dessas técnicas, como controle por escalonamento e controle por bloqueio, serão mostradas neste tópico. A finalização de uma transação pode ocorrer de duas formas: • Committ Transaction: a transação terminou com sucesso e será gravada de for- ma permanente na base de dados. • Rollback Transaction: houve erro durante a transação e a base de dados deve retornar ao estado anterior à transação. Dessa forma, para evitar falhas catastróficas na base de dados com perda de informa- ções é necessário desenvolver planos de contingência. Conheça alguns: • Verificação de falhas durante e após as transações. • Procedimentos de restauração após as falhas (“abort”). • Retorno do histórico de transações (“rollback ”). • Realização de backups de acordo com estratégias de armazenamento (periodici- dade de backups totais, incrementais etc.). • Planos de recuperação e verificação de backups. Veja alguns exemplos de falhas que podem acontecer durante uma transação. • Falha durante a execução da transação. • Condições de exceção detectadas pela transação. • Problemas de infraestrutura: queda de energia, rede, falhas de componentes de hardware. • Erros humanos, sabotagem. Exemplo 52 Figura: Estados de uma transação em banco de dados. Fonte: Notas de aula do professor (2020). Propriedades de uma transação Para conseguir gerenciar as transações de forma adequada é necessário entender suas propriedades conhecidas pelo acrônimo Acid (atomicidade, consistência, isolamento e durabilidade). Os métodos de controle de concorrência das transações e recuperação do SGBD devem utilizar essas propriedades para manter a integridade dos dados. Vamos conhecer com mais detalhes cada uma delas. 1. Atomicidade O conceito de atomicidade considera cada transação como uma unidade de proces- samento individual, sem separações. A transação somente será considerada realizada quando todas as operações da unidade lógica de processamento forem executadas sem erros. Caso contrário, toda a transação deverá ser cancelada, retornando ao estado ante- rior dos dados (“rollback”) para garantir a consistência e a integridade do banco. Apenas no caso de todas as operações serem executadas de forma adequada é que a transação será persistida no banco de dados (“commit”). 2. Consistência A propriedade consistência considera que uma transação somente poderá ser efetivada no banco de dados quando todas as suas regras, condições e restrições predefinidas forem atendidas. Por exemplo, devem ser atendidas as regras de relacionamento por chaves estrangeiras e verificação de valores permitidos para campos restritos. Essa pro- priedade garante a execução de uma transação sem a interferência de outras, mantendo a consistência do banco de dados. Transação ativa em execução read, write,... begin end abort abort commit rollback Efetivação parcial Transação efetivada Operação abortada Falha 53 3. Isolamento Devido à propriedade de isolamento, mesmo que existam transações concorrentes e si- multâneas, cada uma delas deverá funcionar de modo independente. Durante a execução de uma transação, nenhuma outra transação concorrente poderá interferir no funciona- mento da primeira. Essa propriedade também garante que os resultados parciais de uma transação em execução não possam ser acessados por outras transações concorrentes. 4. Durabilidade A propriedade durabilidade garante que somente uma nova transação pode alterar os resultados de uma transação anterior que foram armazenados de forma permanente no banco de dados. Mesmo em caso de alguma falha no sistema, todas as operações de uma transação finalizada devem ser armazenadas no banco de dados. Figura: Propriedades de uma Transação em banco de dados. Fonte: Notas de aula do professor (2020). É importante notar que o SGBD geralmente armazena um registro das transações exe- cutadas pelo usuário (arquivo de “log”) para que essas ações possam ser desfeitas caso ocorra alguma falha. Esse arquivo de registros também pode ser usado para garantir a durabilidade. Assim, no caso de falhas no sistema antes da execução de alguma transa- ção, o arquivo de “log” pode ser usado para restaurar (“rollback”) o estado do banco de dados quando o sistema for reiniciado. Propriedades de uma transação ATOMICIDADE CONSISTÊNCIA ISOLAMENTO DURABILIDADE 54 Técnicas de controle de concorrência É comum que existam acessos simultâneos a um mesmo banco de dados em siste- mas multiusuários. Dessa forma, é necessário que os SGBDs implementem técnicas de controle de concorrência entre as transações no banco de dados que estiverem sendo executadas de forma simultânea. O controle de concorrência é necessário para manter a integridade e a consistência das informações e pode ser utilizado para garantir as pro- priedades Acid de uma transação. As técnicas de controle de concorrência precisam detalhar todas as operações execu- tadas no banco de dados entre o início e o fim de cada transação. Para que transações concorrentes mantenham a consistência e a integridade dos dados, é necessário ga- rantir que a sequência de operações dessas transações tenha o mesmo resultado de outra transação qualquer que foi executada sem nenhuma concorrência. Este é o conceito da serialização, quando a ordem de execução das operações das tran- sações concorrentes deve ser equivalente a uma transação sequencial sem concorrên- cia. A serialização visa garantir que as transações concorrentes sejam executadas de forma adequada e que o estado final do banco de dados mantenha sua consistência. Controle por escalonamento (“scheduler”) Este método de controle de concorrência utiliza um escalonador (“Scheduler”), que visa ordenar de forma sequencial as ações que seriam executadas por uma ou mais transa- ções em um banco dados. • Escalonamento serial: as transações concorrentes são executadas no banco de dados de forma sequencial, isto é, uma após a outra. • Escalonamento não serial: transações executadas de forma simultânea. - Escalonamento serializável: as ações de transações concorrentes simultâ- neas podem ser executadas de forma serial ou sequencial, atingindo o mesmo resultado final. - Escalonamento não serializável: o resultado da execução das transações de forma concorrente é diferente da execução serial. Neste caso, não há garantia de que o estado final do banco de dados seja consistente. 55 Figura: Tipos de Escalonamento. Fonte: Notas de aula do professor (2020). Controle por bloqueios A implementação de bloqueios sobre elementos do banco de dados é uma técnica para evitar comportamento não serializável das transações. As técnicas de bloqueio para controle de concorrênciasão baseadas em mecanismos que permitem a uma transação impedir que outras acessem ou atualizem registros do banco de dados. Dessa forma, é possível evitar problemas de concorrência e inconsistência nos dados. O bloqueio pode ser implementado por meio de uma variável que fica atrelada ao item de dados envolvido na transação que está sendo realizada. Esse método de bloqueio pode ser implementado de forma binária (“booleana”) com dois estados possíveis determina- dos pela variável: • Estado “bloqueado” com valor “1” ou “True”. • Estado “desbloqueado” com valor “0” ou “False”. Assim, se o item do banco de dados for acessado por alguma transação, o item estará bloqueado, pois a variável possuirá valor “1” (“True”). Caso contrário, se o item estiver desbloqueado, terá valor “0” ou “False”. Normalmente, são usadas as operações “lock” e “unlock” para o bloqueio binário. A ope- ração “lock” configura a variável de bloqueio (“True”) quando o item de dados está sendo acessado por alguma transação. Logo que a transação encerra a utilização do item, é lan- çada a operação “unlock” para “resetar” a variável (“False”), e o item já estará desbloqueado. Serializável Não Serializável Serial Não Serial Escalonamento 56 Existem duas formas de bloquear os dados: • Bloqueio Compartilhado: nesse tipo de bloqueio, somente quando a transação possui operações de apenas leitura, outras transações concorrentes podem aces- sar o mesmo dado. Se a transação possuir operações de gravação, não poderá realizar bloqueio compartilhado. • Bloqueio Exclusivo: para esse tipo de bloqueio, o item do banco fica reservado para a operação que compõe a transação. Assim, outras transações concorrentes não poderão acessar o item do banco de dados que está sendo utilizado. Geral- mente, se um item do banco está sofrendo uma operação de gravação, a transação corrente deve receber um bloqueio exclusivo para evitar que outras transações si- multâneas causem falhas ou interferências no item. É comum que uma transação mantenha o bloqueio ao item durante todo o tempo em que estiver realizando o acesso ao banco de dados. Em alguns casos, o desbloqueio imediato após terminar um acesso ao item do banco não é recomendado, uma vez que pode prejudicar o processo de serialização das transações concorrentes. Bloqueio em duas fases Esse tipo de bloqueio pode ser usado para manter um escalonamento de forma serial. Para isso, antes de poder liberar qualquer bloqueio, cada transação deve primeiro realizar todos os seus bloqueios. Dessa forma, uma transação não pode liberar o bloqueio de um item e em seguida realizar bloqueio de outro. O bloqueio em duas fases é composto pelas seguintes fases: • Fase de crescimento, em que todos os bloqueios necessários são realizados. • Fase de encolhimento, em que há a liberação dos bloqueios. Existe uma variante dessa técnica de bloqueio em duas fases denominada “Strict”, em que a fase de encolhimento é iniciada apenas quando toda a transação termina. A van- tagem da técnica “Strict” é que uma transação sempre lê valores escritos por uma outra transação já executada e finalizada. Por exemplo, se houve necessidade de recuperação de dados (“rollback”) durante uma transação, não haverá inconsistência de dados propa- gando-se para uma transação concorrente. 57 Problemas de concorrência entre transações Caso as propriedades Acid não sejam respeitadas pelos SGBDs, podem acontecer al- guns problemas durante a execução de transações concorrentes. Vejamos alguns deles: • Atualização perdida (“lost update”) Esse problema pode ocorrer quando duas transações concorrentes T1 e T2 leem os mesmos dados do banco e tentam atualizar os dados com base no que foi lido antes que uma das atualizações seja realizada com sucesso. Figura: Exemplo de atualização perdida. Tempo T1 T2 Descrição t0 Início: registro A = 10 t1 read (A,x) leitura do registro A na variável x t2 read (A, y) leitura do registro A na variável y t3 x = x + 1 x = 11 t4 y = y + 5 y = 15 t5 write (A, x) escrita da variável x = 11 no registro A t6 write (A, y) escrita da variável y = 15 no registro A Resultado: A = 15 (atualização da transação T1 é perdida devido à T2). Fonte: Notas de aula do professor (2020). • Leitura suja (“dirty read”) Ocorre quando uma transação está tentando atualizar um item do banco de dados e ou- tra transação concorrente lê esse item que ainda não foi atualizado. 58 Figura: Exemplo de leitura suja. Tempo T1 T2 Descrição t0 Início: registro A = 10 t1 read (A, x) leitura do registro A na variável x t2 x = x + 1 x = 10 + 1 = 11 t3 write (A, x) escrita da variável x = 11 no registro A t4 read (A, y) leitura do registro A = 11 na variável y t5 rollback T1 ocorreu uma falha. desfaz T1 e retoma a = 10 t6 y = y + 5 y = 11 + 5 = 16 t7 write (A, y) escrita da variável y no registro A Resultado: A = 16 (T2 fez uma leitura “suja” do registro A porque depois foi alterado). Fonte: Notas de aula do professor (2020). • Leitura fantasma (“ghost read”) Uma transação está realizando uma segunda consulta consecutiva ao banco, que re- torna um resultado que atende a uma certa condição de procura. O problema é que o segundo resultado é diferente da primeira consulta, pois no intervalo entre as consultas houve a execução de uma transação concorrente. Figura: Exemplo de leitura fantasma. Tempo T1 T2 Descrição t0 Início: tabela A com 2 registros (a, b) t1 select * from A Retorna 2 registros t2 insert intoA valeus (“c”) tabela A com 3 registros (a, b, c) t3 select * from A Retorna 3 registros Resultado: Leituras repetidas de T1 na tabela A retornam diferentes registros. Fonte: Notas de aula do professor (2020). 59 Problemas com as técnicas de bloqueio Sempre haverá necessidade de bloqueio e desbloqueio dos itens de dados com acesso compartilhado, mas existem algumas situações em que a combinação desses estados pode gerar problemas no banco dados. Veremos alguns desses problemas a seguir: • Impasse (“deadlock”) O impasse (“deadlock”) pode ocorrer se não for realizado o desbloqueio do item do banco antes da solicitação de um bloqueio a outro item. Por exemplo, existem duas transações concorrentes T1 e T2. A primeira transação, T1, está esperando por um item de dados que está bloqueado pela transação T2. Simultaneamente, a transação T2 está esperando por outro item, que está bloqueado por T1. Neste caso, as duas transações ficam conge- ladas, esperando indefinidamente que algum dos itens bloqueados seja liberado. Como isso não ocorre, as duas transações não conseguem ser concluídas. Existem algumas formas de tratar o problema de impasse. Por exemplo, alguns sistemas conseguem detectar um “deadlock” e podem escolher uma transação do conjunto para abortar a operação (“rollback”), eliminando o ciclo de espera. O problema de “deadlock” pode ser resolvido pela técnica de controle por rótulo de tempo, que será vista a seguir. Figura: Deadlock. Transação T1 Transação T2 Tabela A Tabela B Fonte: Notas de aula do professor (2020). T1 quer acesso em B mas T2 está bloqueando B T2 quer acesso em A mas T1 está bloqueando A Deadlock 60 • Estagnação (“starvation”) O problema de estagnação pode ocorrer quando uma transação de alteração a um de- terminado item não consegue ser executada devido a outras transações concorrentes de leitura ao mesmo item do banco de dados. Por exemplo, uma transação de leitura de um item está sendo executada e faz uma re- quisição de bloqueio compartilhado. Se uma transação concorrente de alteração tentar acesso ao mesmo item não conseguirá obter a requisição de bloqueio exclusivo e ficará aguardando o término da primeira transação. Se outras transações concorrentes de leitura ao mesmo item do banco chegarem ao SGBD e não houver um escalonador com controle de tempo, a transação de alteração nunca será executada, pois não conseguirá implementar o bloqueio exclusivo. Como as transações de leitura mantêm o bloqueio compartilhado do item,a transação de altera- ção fica em “estagnação”, pois não consegue obter o acesso e fica esperando a liberação indefinidamente. Controle por rótulo de tempo (“TimeStamp”) Nesse tipo de controle de concorrência, para cada transação iniciada é associado um rótulo de tempo (“TimeStamp”) fixo e exclusivo. Assim, antes que uma transação inicie sua execução, o SGBD fornecerá um rótulo de tempo exclusivo para identificar essa transação. Por exemplo, temos duas transações, T1 e T2; a transação T1 iniciou-se no tempo “n”, e a transação T2 teve início no tempo “n+1”. Logo, a transação T1 será executada primeiro do que a transação T2, pois seu tempo de início é mais antigo. Existem duas formas para implementação desse controle, usando como rótulo de tempo: • Hora do relógio do sistema computacional: neste caso, o horário de início da transação será igual à hora em que a transação entrar no sistema. • Contador lógico incrementado sempre que houver um novo “TimeStamp”: o rótulo de tempo da transação é igual ao valor do contador quando a transação entra no sistema. 61 Esse controle precisa garantir que a ordem em que o item do banco de dados está sendo acessado não viola a ordem do “TimeStamp”. Dessa forma, o controle associa a cada item “X” do banco de dados dois valores de rótulo de tempo (“TimeStamp” — TS): • read_TS(X) - “TimeStamp” de leitura do item “X”. • write_TS(X) - “TimeStamp” de gravação do item “X”. Esses rótulos de tempo devem ser atualizados sempre que uma nova instrução de leitura ou escrita é executada. A todo momento em que uma transação de leitura ou escrita é desfeita pelo esquema de controle de concorrência, essa transação é reiniciada com um novo “TimeStamp”. Tal controle de concorrência garante que, se houver operações de leitura ou escrita em conflito, essas operações serão executadas por ordem de “TimeStamp”. O controle de “TimeStamp” pode prevenir o problema de impasse (“deadlock”) visto an- teriormente. Caso duas transações estejam envolvidas em um “deadlock”, a transação mais nova será abortada pela transação mais antiga (a que entrou primeiro). Nesse tipo de controle também existe a possibilidade de paralisação de transações lon- gas, caso uma série de transações curtas causem o reinício da transação longa. Nesse caso, as transações curtas podem ser suspensas temporariamente para permitir que a transação longa seja concluída. Como é possível ver, existem várias técnicas que os SGBDs devem implementar para evitar os problemas de acesso concorrente e manter a integridade dos bancos de da- dos multiusuários. 62 Tratamento de erros em bancos de dados Neste tópico abordaremos as diferentes formas de tratamento de erros em aplicativos de bancos de dados e também como tratar, em Java, algumas situações. Conheça algumas delas: • Divisão por zero. • Erro na conversão de tipos (ex.: converter uma string de letras em um número inteiro). • Erro na abertura ou na transmissão de um arquivo. • Erro de impressão. • Acesso a um vetor com índice inválido. • Erro de conexão a um banco de dados, entre outros. Todas as situações listadas acima em Java são chamadas de exceções e existe um me- canismo específico para tratá-las, que chamamos de tratamento de exceções. As exceções em Java estão organizadas em uma hierarquia de classes, como mostra a figura a seguir. Figura: Hierarquia de classes em Java. Fonte: Notas de aula do professor (2020). 63 Um “Error” pode ocorrer devido a problemas no sistema operacional, na Java Virtual Machine – JVM ou mesmo no hardware. Nesse caso, o melhor a fazer é deixar a JVM encerrar o programa. A classe “Exception” é a classe-mãe de todas as exceções que nossos programas po- dem tratar. Ela está subdividida em dois ramos: RuntimeException Ocorrem devido a um erro de programação, como divisão por zero, índice inválido do vetor, acesso a objeto nulo etc. Também são chamadas de exceções não verificadas (unchecked). Demais exceções Ocorrem devido a um erro no programa causado por fatores externos, como erro na abertura de um arquivo, erro na impres- são, erro na conexão a um banco de dados etc. Também são chamadas de exceções verificadas (checked). Quando executamos o programa abaixo: Podemos observar a seguinte mensagem: public class DividePorZero { public static void main(String args[]) { System.out.println(3/0); System.out.println("imprime"); } } Exception in thread "main" java.lang.ArithmeticException: / by zero at DividePorZero.main(DividePorZero.java:3) 64 Como o nosso programa não está tratando dessa exceção (divisão por zero), o tratador padrão do Java executa as seguintes tarefas: • Imprime o nome da exceção e a mensagem de erro. • Imprime a pilha de execução (sequência de chamadas dos métodos). • Termina o programa. O tratamento de exceções é um mecanismo que permite que o programa defina como as situações inesperadas serão tratadas. Existem três comandos relacionados ao tratamento de exceções. São eles: 1. Blocos try...catch...finally. 2. Comando throws. 3. Comando throw. E alguns métodos que a classe Exception pode implementar: Exception(String mensagemErro) getMessage() printStackTrace() Construtor que permite criar uma exceção e armazenar nesse objeto uma mensagem de erro. Retorna a mensagem de erro armazenada na exceção. Imprime a pilha de execu- ção no mesmo formato da JVM. É possível tratar várias exceções associando vários catchs ao mesmo try. Nesse caso, a ordem dos tratadores é importante: eles devem estar ordenados das subclasses para a superclasse. try { // Código a ser tratado } catch (ArithmeticException e3){ //Primeiro o catch da exceção mais específica. System.out.printf("Erro de aritmetica: %s\n",e3.getMessage()); } catch(IOException e2) { System.out.printf("Erro de E/S: %s\n", e2.getMessage()); } catch(Exception e1) { //Por último o catch da exceção mais geral. System.out.printf("Erro desconhecido: %s\n", e1.getMessage()); } 65 Quando ocorre uma exceção o método cria um objeto do tipo “Exception” e o “dispara” (throw) para a JVM. O objeto “Exception” criado contém todas as informações sobre o erro: seu tipo, o local onde ocorreu, uma mensagem de descrição, a pilha de chamadas, entre outros. Em seguida a JVM procura um bloco “try...catch” para tratar a exceção no método que a gerou. • Encontrou: desvia a execução para o bloco “catch”. • Não encontrou: procura outro bloco “try...catch” para tratar a exceção na pilha de execução, ou seja, nos métodos que chamaram o método que gerou a exceção. - Encontrou: desvia a execução para o primeiro “catch” que encontrar. - Não encontrou: nenhum tratador na pilha de execução, desvia para o tratador padrão da JVM, que interrompe a execução do programa. O tratamento de exceções também pode ser implementado por um bloco “try…catch...finally”. O bloco “finally” indica um trecho de código que sempre será execu- tado se uma exceção ocorrer ou não. O bloco “finally” é muito utilizado quando é neces- sário liberar algum recurso importante do sistema, como uma conexão com o banco de dados, um arquivo de dados ou memória. Em seguida, veremos exemplos de código com uso do bloco “finally” para este objetivo. Figura: Bloco “try … catch … finally”. Fonte: Notas de aula do professor (2020). 66 As exceções do Java são classificadas como “checked” ou “unchecked”. Para as exce- ções “checked” (verificadas), o Java nos obriga a usar uma das soluções a seguir. 1. Tratar as exceções no método em que elas podem ocorrer, implementando o blo- co “try...catch” visto anteriormente. 2. Utilizar o comando “throws” para avisar que estamos cientes de que aquela exce- ção pode ocorrer, mas não desejamos tratá-la. 3. Utilizar o comando “throw” para disparar uma exceção customizada, ou seja, que nós mesmos criamos. - Neste caso, criamos um objeto da classe “Exception” ou de uma de suas subclas- ses com o operador
Compartilhar