Aula 04 — Programação para Web I
No encontro 3 modelamos o domínio do Pet Bem-Estar a partir do e-mail da Marlene, e cada um resolveu, sozinho, um problema de modelagem: quais entidades existem, o que cada uma guarda, e como elas se relacionam.
Uma pergunta fica em aberto: a lista de animais dentro de Tutor e o tutor dentro de Animal não são redundantes?
Hoje essa pergunta tem resposta — mas a resposta é construída sobre outro domínio, para que o Pet Bem-Estar continue sendo território de cada um.
Vamos trabalhar num domínio pequeno e já pronto: uma professora, as turmas que ela leciona, e os alunos de cada turma.
O projeto já está no Moodle. Abram e rodem antes de seguir.
Professor ana = new Professor("Ana", "ana@ifrs.edu.br"); Turma turmaA = new Turma("Programacao para Web I - Turma A"); Turma turmaB = new Turma("Programacao para Web I - Turma B"); ana.adicionarTurma(turmaA); ana.adicionarTurma(turmaB); turmaA.adicionarAluno(new Aluno("Bruno", "2026001")); turmaA.adicionarAluno(new Aluno("Carla", "2026002")); turmaB.adicionarAluno(new Aluno("Diego", "2026003"));
Rodamos o projeto e vimos a professora, as turmas e os alunos impressos no console. Agora rodem de novo.
O que sobrou da execução anterior?
Nada. Os objetos existiam só enquanto o programa estava de pé, e cada execução começa do zero.
Precisamos de um lugar que continue existindo depois que o programa termina, e que guarde os dados em linhas de tabela.
Isso já é conhecido de Banco de Dados I. O que falta é a ponte entre um objeto Java e uma linha de tabela.
Essa ponte tem duas metades: uma que descreve o mapeamento, e outra que efetivamente conversa com o banco.
JPA (Jakarta Persistence API) é uma especificação: um conjunto de regras que diz como um objeto Java deve se transformar numa linha de tabela, e que anotações usar para isso.
@Entity e @Id, do encontro 2, já são JPA — só que sozinhas elas não fazem nada, porque uma especificação não roda: ela só define um contrato.
Falta quem cumpra esse contrato de verdade.
Hibernate é uma implementação de JPA: a biblioteca que lê @Entity e @Id, gera o SQL correspondente, e conversa de fato com o banco.
É essa dupla — especificação e implementação — que faz o @Entity deixar de ser só uma marca e passar a valer alguma coisa.
É também o Hibernate que o Spring Data vai usar por baixo, hoje escrevemos à mão o que ele vai automatizar depois
A mesma regra do Maven do encontro 2: declaramos no pom.xml, e o Maven baixa sozinho.
Nenhum .jar é adicionado à mão ao projeto.
<dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId> <version>6.5.2.Final</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.4.0</version> </dependency>
O Hibernate procura um arquivo num caminho fixo, sem que a gente precise apontar para ele em lugar nenhum do código:
É essa convenção que faz o Hibernate encontrar a configuração sozinho assim que o projeto sobe.
driver e url dizem com qual banco e em qual endereço conversar. user e password são as credenciais do laboratório.
Escrevemos isso uma vez, e todo o resto do projeto passa a persistir sem repetir essa configuração.
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.1"> <persistence-unit name="turmas-pu"> <properties> <property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/> <property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/turmas_exemplo"/> <property name="jakarta.persistence.jdbc.user" value="root"/> <property name="jakarta.persistence.jdbc.password" value="SENHA_DO_LAB"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.show_sql" value="true"/> </properties> </persistence-unit> </persistence>
hbm2ddl.auto=update é conveniente para estudar: ninguém escreve DDL manual hoje, e o banco acompanha as entidades.
Em produção isso não se faz assim. Um update automático em cima de um banco real é risco de perda de dados, porque o Hibernate decide sozinho como alterar tabelas que já têm informação de verdade.
Hoje usamos por conveniência pedagógica.
A EntityManagerFactory lê o persistence.xml uma única vez e sabe montar conexões. É como o setor que emite as autorizações: existe uma vez só.
O EntityManager é um atendimento aberto para uma tarefa e fechado no final, como um caixa de banco: abrimos, resolvemos, fechamos, e não ficamos com o mesmo caixa aberto o dia inteiro.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("turmas-pu"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); em.persist(ana); em.getTransaction().commit(); em.close(); emf.close();
No projeto que vocês abriram, a classe Professor tem @Entity e @Id, mas o id ainda não sabe se gerar sozinho.
Desafio: por que o erro fala em id nulo, e não em nenhum outro atributo?
@GeneratedValue não tem um único comportamento: ele delega a geração do id para uma entre quatro estratégias, declaradas em GenerationType.
Hoje usamos IDENTITY porque é a que casa direto com o auto-incremento nativo do MySQL — o mesmo que apareceu no CREATE TABLE que o Hibernate gerou na Prática 1.
Professor.turmas e Turma.professor apontam um para o outro, mas o banco só entende uma tabela como dona da chave estrangeira.
É como a carteira de identidade: a cidade de nascimento fica registrada no documento da pessoa, e não existe, na prefeitura, uma lista viva de todo mundo que nasceu ali.
Turma é o lado "muitos" da relação com Professor, e por isso é quem leva a chave estrangeira: @ManyToOne, sem mappedBy.
Do outro lado, Professor.turmas só navega, e por isso leva @OneToMany(mappedBy = "professor").
A mesma forma vale para Turma e Aluno, e é isso que a Prática 2 pede.
@Entity public class Turma { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne private Professor professor; // lado dono @OneToMany(mappedBy = "turma") private List<Aluno> alunos = new ArrayList<>(); // ... }
Repitam sozinhos, agora entre Turma e Aluno, o que acabamos de fazer entre Professor e Turma.
persist grava; find recupera pelo id.
Repare que persistimos só a Professor. Gravar uma árvore inteira de uma vez (professor, turmas e alunos, tudo junto) é um assunto de cascata que não é hoje — cada objeto é persistido explicitamente.
em.getTransaction().begin(); em.persist(ana); // em cascata? ainda nao -- so o Professor em.getTransaction().commit(); Professor encontrada = em.find(Professor.class, ana.getId());
Desafio: rodem o programa uma segunda vez, sem apagar o banco. O que aconteceu com a quantidade de linhas, e por quê?
A pergunta era: a lista de animais dentro de Tutor e o tutor dentro de Animal não são redundantes?
Agora tem resposta: não são redundantes, são os dois lados do mesmo relacionamento, e um deles precisa ser o dono.
No Pet Bem-Estar, Animal é quem leva a chave estrangeira para Tutor, e Atendimento leva a chave estrangeira para Animal — a mesma forma que acabamos de aplicar em Turma e em Aluno.
Apliquem tudo isso ao domínio do Pet Bem-Estar, o que entregaram na tarefa do encontro 3.
Hoje o que era objeto em memória passou a ser linha de tabela, primeiro num domínio de treino, depois no projeto de verdade.
Guardem o persistence.xml de hoje: no encontro 7 vamos comparar com o que o Spring faz sozinho, sem escrever nada disso à mão.
No encontro 5 entram o DAO e as interfaces.

Quer trocar mais uma ideia? Me chama!