Aula 02 — Programação para Web I
No encontro anterior retomamos três recursos do Java, e cada um deles resolvia uma pergunta diferente sobre os nossos dados:
Hoje usamos os três juntos para transformar o pedido de uma cliente em classes Java — o primeiro passo de qualquer sistema.
Todo projeto Java precisa resolver duas coisas antes de existir uma única linha de código nossa.
Podemos resolver as duas na mão, criando pastas e baixando arquivos — e é exatamente isso que ninguém faz há uns vinte anos.
O Maven é a ferramenta que resolve as duas: ele cria uma estrutura de pastas padronizada, igual em qualquer projeto do mundo, e baixa sozinho as bibliotecas que declaramos precisar.
Ou seja, descrevemos o que queremos e ele vai buscar, em vez de administrarmos arquivos manualmente.
Observe a estrutura ao lado, que o Maven cria para nós:
É por causa dessa padronização que abrimos o projeto de outra pessoa e sabemos onde procurar cada coisa.
pet-bem-estar/
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── br/edu/ifrs/petbemestar/
│ │ ├── Principal.java
│ │ └── dominio/
│ └── resources/
└── test/
└── java/É neste arquivo que declaramos quem o projeto é e do que ele precisa.
Nada aqui é código que roda: é descrição. Quem lê essa descrição e age a partir dela é o Maven.
Ele tem quatro partes, e vamos percorrer uma de cada vez.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.edu.ifrs</groupId> <artifactId>pet-bem-estar</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> </project>
Vamos percorrer o arquivo de cima para baixo.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.edu.ifrs</groupId> <artifactId>pet-bem-estar</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> </project>
O cabeçalho diz em que idioma este arquivo está escrito.
Ou seja, é o que permite ao Eclipse conferir se escrevemos certo e sugerir as tags válidas quando pedimos.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.edu.ifrs</groupId> <artifactId>pet-bem-estar</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> </project>
Estes três campos são a identidade do projeto.
Juntos, eles formam um endereço que não se repete no mundo.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.edu.ifrs</groupId> <artifactId>pet-bem-estar</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> </project>
As propriedades ajustam como o projeto é compilado.
Sem estas linhas o Maven assume uma versão antiga de Java, e código moderno deixa de compilar sem explicação aparente.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.edu.ifrs</groupId> <artifactId>pet-bem-estar</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> </project>
Aqui declaramos as bibliotecas de que precisamos.
Repare que declaramos apenas o nome e a versão, e em nenhum momento dizemos de onde baixar nem onde salvar.
Ou seja, é uma lista de necessidades, não de instruções.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.edu.ifrs</groupId> <artifactId>pet-bem-estar</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> </project>
São os mesmos três campos que identificam o nosso projeto, agora apontando para o projeto de outra pessoa:
É o endereço de um livro numa biblioteca, com sobrenome do autor, título e edição.
<dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency>
Uma biblioteca é código que outra pessoa escreveu, empacotado num arquivo .jar para que o nosso projeto possa usar.
Um .jar é literalmente um .zip com outro nome, e dentro dele vão as classes já compiladas, nas pastas dos seus pacotes.
Ou seja, jakarta.persistence é o pacote, e o arquivo ao lado é a biblioteca que o traz.
jakarta.persistence-api-3.1.0.jar
└── jakarta/
└── persistence/
├── Entity.class
├── Id.class
├── EntityManager.class
├── Table.class
└── ...O Java corporativo era da Oracle, e os pacotes dele começavam em javax. Em 2017 ele foi doado à Eclipse Foundation, que não ficou com o direito de usar a marca Java nem o prefixo javax.
O conjunto passou a se chamar Jakarta EE, e em 2020 todos os pacotes foram renomeados.
Ou seja, quando um tutorial antigo escrever javax.persistence, troque por jakarta.persistence e o resto continua igual.
javax.persistence.Entity → jakarta.persistence.Entity javax.persistence.Id → jakarta.persistence.Id javax.persistence.Table → jakarta.persistence.Table javax.persistence.EntityManager → jakarta.persistence.EntityManager
javax.persistence.Entity → jakarta.persistence.Entity javax.persistence.Id → jakarta.persistence.Id javax.persistence.Table → jakarta.persistence.Table javax.persistence.EntityManager → jakarta.persistence.EntityManager
A biblioteca que declaramos traz só anotações e interfaces. Ela diz o que existe e o que cada coisa significa, sem fazer nenhum trabalho.
Quem faz o trabalho é a implementação, que é outra biblioteca e entra no encontro 4.
Ou seja, hoje temos a norma e ainda não temos o fabricante.
// o que temos hoje — jakarta.persistence-api public @interface Entity { } public interface EntityManager { void persist(Object entidade); <T> T find(Class<T> tipo, Object id); ... }
O projeto do semestre cresce por entregas, e cada entrega precisa de um histórico que possamos consultar e desfazer.
O Git guarda esse histórico em pontos que nós mesmos marcamos, chamados commits, e permite voltar a qualquer um deles.
Hoje usamos o mínimo para colocar o projeto no ar, e no encontro 3 estudamos a ferramenta com calma.
São cinco comandos para começar, e depois deles o dia a dia se resume a três:
Commitar toda vez que algo funciona rende um histórico mais útil do que commitar uma vez só no fim do dia.
# uma vez, no começo git init git add . git commit -m "projeto inicial" git remote add origin <url> git push -u origin main # no dia a dia git add . git commit -m "dominio modelado" git push
O .gitignore é um arquivo de texto simples, um por projeto, com um padrão de nome por linha.
O Git lê essa lista antes de cada commit, e ignora silenciosamente tudo o que casar com ela.
Ele precisa existir antes do primeiro commit: depois que algo já foi commitado, colocar o nome aqui não tira mais o arquivo do histórico.
target/ *.class .idea/ .vscode/ .DS_Store
A Marlene tem um petshop e anota tudo num caderno em cima do balcão. Observe as quatro linhas ao lado.
CADERNO DO BALCÃO — agosto 12/08 9h Mimi banho Rosa 99999-0000 12/08 9h Thor banho Rosa 99999-0000 12/08 14h Frajola tosa Rosa 99999-0000 13/08 10h Nina consulta Rosa 98888-1111
CADERNO DO BALCÃO — agosto 12/08 9h Mimi banho Rosa 99999-0000 12/08 9h Thor banho Rosa 99999-0000 12/08 14h Frajola tosa Rosa 99999-0000 13/08 10h Nina consulta Rosa 98888-1111
Em duplas, a partir do e-mail da Marlene:
Desafio: encontre no e-mail duas coisas que só podem assumir alguns valores fixos.
Nem todo substantivo do e-mail vira uma classe, e três perguntas costumam resolver a dúvida:
Quando as três respostas apontam para o valor sozinho, estamos diante de um atributo, e não de uma entidade.
Aplicando as três perguntas ao e-mail, sobram três coisas que existem por si mesmas:
Chamamos essas classes de entidades do domínio, e é sobre elas que o sistema inteiro vai ser construído até dezembro.
Observe o trecho ao lado, em que a Marlene descreve um relacionamento sem usar o termo.
"A dona Rosa sozinha tem três gatos e um cachorro, e eu acabo anotando o telefone dela quatro vezes, uma pra cada bicho. Aí ela mudou de telefone e eu só corrigi num lugar."
"A dona Rosa sozinha tem três gatos e um cachorro, e eu acabo anotando o telefone dela quatro vezes, uma pra cada bicho. Aí ela mudou de telefone e eu só corrigi num lugar."
Vocês já viram isso em Banco de Dados I, e a leitura é a mesma aqui: a cardinalidade responde quantos de um lado se ligam a quantos do outro.
1 ── 1 uma pessoa tem um CPF,
e um CPF é de uma pessoa
1 ── N um tutor tem vários animais,
e cada animal tem um tutor
N ── N um aluno cursa várias disciplinas,
e cada disciplina tem vários alunosO Pet Bem-Estar tem dois relacionamentos, e os dois são 1 ── N: Tutor 1 ── N Animal 1 ── N Atendimento.
A forma que acabamos de descobrir no petshop aparece em qualquer sistema, então vamos praticá-la em outro lugar.
Retomamos a Biblioteca do encontro anterior, que guarda os jogos de um jogador, e acrescentamos as partidas de cada jogo:
public class Biblioteca { private String nome; private String email; private List<Jogo> jogos = new ArrayList<>(); }
É exatamente a mesma forma do Pet Bem-Estar, em outro mundo.
Em código, o lado que tem muitos vira uma coleção, e o lado que tem um guarda uma referência de volta. Repare no e-mail: ele existe uma vez só, dentro da Biblioteca — se cada jogo o guardasse, teríamos de novo o caderno da Marlene.
public class Biblioteca { private String nome; private String email; private List<Jogo> jogos = new ArrayList<>(); public void adicionarJogo(Jogo jogo) { jogos.add(jogo); jogo.setBiblioteca(this); } } public class Jogo { private String titulo; private Plataforma plataforma; private Classificacao classificacao; private Biblioteca biblioteca; public void setBiblioteca(Biblioteca biblioteca) { this.biblioteca = biblioteca; } }
public class Biblioteca { private String nome; private String email; private List<Jogo> jogos = new ArrayList<>(); public void adicionarJogo(Jogo jogo) { jogos.add(jogo); jogo.setBiblioteca(this); } } public class Jogo { private String titulo; private Plataforma plataforma; private Classificacao classificacao; private Biblioteca biblioteca; public void setBiblioteca(Biblioteca biblioteca) { this.biblioteca = biblioteca; } }
Observe o método ao lado, que faz duas coisas numa chamada só: põe o jogo na lista e diz ao jogo de quem ele é.
Fazemos assim porque, se a lista ficar aberta para quem quiser usar, uma hora alguém adiciona um jogo sem preencher o outro lado — e o mesmo objeto passa a ter duas versões da verdade.
public void adicionarJogo(Jogo jogo) { jogos.add(jogo); jogo.setBiblioteca(this); }
Ou seja, é o encapsulamento resolvendo um problema concreto.
Alguns atributos só podem assumir valores de uma lista conhecida, e para eles usamos enum, pelo mesmo motivo do encontro anterior.
Repare que nenhum deles é uma entidade: não existe nada a guardar sobre CAMPANHA além do próprio nome.
public enum Plataforma { PC, PLAYSTATION, XBOX, SWITCH } public enum Classificacao { LIVRE, DEZ, DOZE, QUATORZE, DEZESSEIS, DEZOITO } public enum Modo { CAMPANHA, MULTIJOGADOR, COOPERATIVO } public enum SituacaoPartida { EM_ANDAMENTO, CONCLUIDA, ABANDONADA }
A Partida fecha o segundo nível do relacionamento e junta os três recursos numa classe só:
Toda partida nasce EM_ANDAMENTO, e é o construtor que garante isso.
public class Partida { private LocalDateTime inicio; private int duracaoMinutos; private Modo modo; private SituacaoPartida situacao; private Jogo jogo; public Partida(LocalDateTime inicio, Modo modo, Jogo jogo) { this.inicio = inicio; this.modo = modo; this.jogo = jogo; this.situacao = SituacaoPartida.EM_ANDAMENTO; } public void concluir(int duracaoMinutos) { this.duracaoMinutos = duracaoMinutos; this.situacao = SituacaoPartida.CONCLUIDA; } }
public class Partida { private LocalDateTime inicio; private int duracaoMinutos; private Modo modo; private SituacaoPartida situacao; private Jogo jogo; public Partida(LocalDateTime inicio, Modo modo, Jogo jogo) { this.inicio = inicio; this.modo = modo; this.jogo = jogo; this.situacao = SituacaoPartida.EM_ANDAMENTO; } public void concluir(int duracaoMinutos) { this.duracaoMinutos = duracaoMinutos; this.situacao = SituacaoPartida.CONCLUIDA; } } public class Jogo { private List<Partida> partidas = new ArrayList<>(); public void adicionarPartida(Partida partida) { partidas.add(partida); } }
Commit ao terminar.
Uma anotação é uma marca que colocamos no código e que não muda o que ele faz.
Ela existe para que alguma ferramenta leia essa marca depois e tome uma decisão a partir dela.
Você já usou pelo menos uma, e ela está no print ao lado.
public class Jogo { private String titulo; @Override public String toString() { return titulo; } }
Queremos que as nossas classes virem tabelas no banco: @Entity marca a classe, @Id marca o identificador — as duas vêm do pom.xml.
public class Biblioteca { private String nome; private String email; private List<Jogo> jogos = new ArrayList<>(); }
public class Jogo { private String titulo; private Plataforma plataforma; private Classificacao classificacao; private Biblioteca biblioteca; private List<Partida> partidas = new ArrayList<>(); }
import jakarta.persistence.Entity; import jakarta.persistence.Id; @Entity public class Biblioteca { @Id private Long id; private String nome; private String email; private List<Jogo> jogos = new ArrayList<>(); }
import jakarta.persistence.Entity; import jakarta.persistence.Id; @Entity public class Jogo { @Id private Long id; private String titulo; private Plataforma plataforma; private Classificacao classificacao; private Biblioteca biblioteca; private List<Partida> partidas = new ArrayList<>(); }
Repare que precisamos criar um atributo id que não estava em nenhum pedido de cliente nenhum.
Ele existe porque o banco precisa identificar cada linha sem ambiguidade, mesmo quando dois jogadores tiverem o mesmo nome ou dois animais o mesmo apelido.
Ou seja, é a primeira vez no semestre em que o modelo ganha uma coisa que vem da tecnologia, e não do mundo do cliente.
Rodamos o projeto e nada é gravado em lugar nenhum.
Temos a especificação, que diz o que as anotações significam, e não temos a implementação, que é quem leria essas marcas e conversaria com o banco.
No encontro 4 chega o fabricante, e o modelo já vai estar pronto para ele.
public class Principal { public static void main(String[] args) { Biblioteca ana = new Biblioteca("Ana", "ana@exemplo.com"); ana.adicionarJogo(new Jogo("Hollow Knight", Plataforma.PC, Classificacao.DEZ)); System.out.println(ana.getJogos().size()); // e o banco de dados? } }
Desafio: por que os enums não recebem @Entity? Explique com as suas palavras, num comentário no topo de um dos arquivos.
Hoje percorremos o caminho inteiro, do texto de uma cliente até classes marcadas para virar tabelas:
No encontro 3, a distância, você faz esse mesmo caminho sozinho no domínio da Marlene, e entrega por push no GitHub.

Quer trocar mais uma ideia? Me chama!