Fecha | Versión | Descripción |
---|---|---|
12/03/2025 | 1.0.0 | Operaciones CRUD con el Api de Pokémon |
En el desarrollo de esta actividad vamos a partir de la aplicación previamente desarrollada en la unidad 7 basada en una lucha de Pokémon.
El objetivo es que nuestra aplicación va a almacenar los Pokémon en una base de datos MySQL. No obstante vamos a utilizar el patrón de diseño DAO, implementando operaciones CRUD, de tal manera que si en un futuro cambiaramos de SGBD este realmente sería transparente.
En MySQL crea un Schema de base de datos llamado pokedb. En este esquema vamos a almacenar lo que nos interesa de los Pokémon. Serán las siguientes tablas:
Como podemos observar las relaciones son las siguientes:
Tabla pokemon en la cual almacenamos una serie de datos como id, name, height, weight, type, ability_name y ability_effect.
Un Pokemon puede tener muchas formas, lo podemos ver en la tabla form en la cual está la relación de la tabla pokemon, pokemon_id, así como las url de sus formas.
Cómo un pokemon puede tener muchos movimientos, disponemos de la tabla pokemon_move y de manera análoga con las estadisticas, pokemon_stat.
El script de creación de las tablas es el siguiente:
xCREATE TABLE pokemon (
id INT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
height INT,
weight INT,
type VARCHAR(255),
ability_name VARCHAR(255),
ability_effect TEXT
);
CREATE TABLE form (
id INT AUTO_INCREMENT PRIMARY KEY,
pokemon_id INT,
back_default VARCHAR(255),
back_shiny VARCHAR(255),
front_default VARCHAR(255),
front_shiny VARCHAR(255),
FOREIGN KEY (pokemon_id) REFERENCES pokemon(id)
);
CREATE TABLE crie (
id INT AUTO_INCREMENT PRIMARY KEY,
pokemon_id INT,
latest VARCHAR(255),
legacy VARCHAR(255),
FOREIGN KEY (pokemon_id) REFERENCES pokemon(id)
);
CREATE TABLE stat (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
base_stat INT
);
CREATE TABLE pokemon_stat (
pokemon_id INT,
stat_id INT,
PRIMARY KEY (pokemon_id, stat_id),
FOREIGN KEY (pokemon_id) REFERENCES pokemon(id),
FOREIGN KEY (stat_id) REFERENCES stat(id)
);
CREATE TABLE move (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
type VARCHAR(255),
power INT
);
CREATE TABLE pokemon_move (
pokemon_id INT,
move_id INT,
PRIMARY KEY (pokemon_id, move_id),
FOREIGN KEY (pokemon_id) REFERENCES pokemon(id),
FOREIGN KEY (move_id) REFERENCES move(id)
);
Para el desarrollo de esta actividad deberás hacer uso de las clases con las que ya trabajaste en el ejercicio de la unidad 7, la lucha de Pokémon:
Un package llamado connect, y dentro de este te dejas la clase ApiPokemon, que fué el eje central de esta actividad y se encargaba de conectar y recoger toda la información de los Pokémon con la que trabajabamos.
Package pojo en el cual deberás depositar las clases que se utilizaron en la actividad anterior:
Ability
Crie
LuchaPokemon
Move
Pokemon
Stat
StatDetail
TipoEfectividad
En azul se puede ver esto que se indica:
Se proporciona completado el package dbconnection con la clase ConexionDB. Fíjate que no hace uso del patrón Singleton. Esto es por los autocommit que veremos y porque da errores en la dinámica de funcionamiento de nuestra aplicación.
Se puede ver en verde:
El grueso de la aplicación se va a encontrar en el package PokemonDAO. En este se van a llevar a cabo 5 operaciones CRUD:
Create: Crearemos un Pokémon en la base de datos.
Read: Leeremos un Pokémon de la base de datos.
Update: Actualizaremos un cde la base de datos.
Delete: Borraremos un pokémon de la base de datos.
Obtener todos los pokémon: Obtendremos todos los pokémon de la base de datos.
Puesto que vamos a trabajar con varias tablas a la vez, y puesto que la mayoría de operaciones sobre la base de datos, a excepción de las lecturas, es decir, select, tienen el auto commit implicito, lo desactivaremos y llevaremos a cabo transacciones.
Se proporciona la operación de creación de un pokémon, para que podamos coger la dinámica y podamos implementar el resto.
Además en el Main, deberás realizar una serie de operaciones, de tal manera primero consultemos con una operación CRUD si tenemos pokémon en la base de datos, si no hay, deberemos obtenerlos de la APIrest, como ya hacíamos e insertarlos en la base de datos.
Luego deberemos otener todos los pokemons y listarlos.
Obtendremos uno, le modificaremos el nombre, lo actualizaremos.
Luego borraremos este pokemon.
En Naranja tienes lo que deberás trabajar/modificar:
Veamos el método que nos implementa la operación CRUD de Create:
x
public void createPokemon(Pokemon pokemon) {
String sqlPokemon = "INSERT INTO pokemon (id, name, height, weight, type, ability_name, ability_effect) VALUES (?, ?, ?, ?, ?, ?, ?)";
String sqlCrie = "INSERT INTO crie (pokemon_id, latest, legacy) VALUES (?, ?, ?)";
String sqlForm = "INSERT INTO form (pokemon_id, back_default, back_shiny, front_default, front_shiny) VALUES (?, ?, ?, ?, ?)";
// el id de Move es autoincremental
String sqlMove = "INSERT INTO move (name, type, power) VALUES (?, ?, ?)";
String sqlPokemonMove = "INSERT INTO pokemon_move (pokemon_id, move_id) VALUES (?, ?)";
// el id de Stat es autoincremental
String sqlStat = "INSERT INTO stat (name, base_stat) VALUES (?, ?)";
String sqlPokemonStat = "INSERT INTO pokemon_stat (pokemon_id, stat_id) VALUES (?, ?)";
Connection conn = null;
try {
conn = ConexionBD.getConnection();
conn.setAutoCommit(false); // Se desactiva el auto-commit para usar transacciones
// Insertar Pokémon
try (PreparedStatement psPok = conn.prepareStatement(sqlPokemon)) {
psPok.setInt(1, pokemon.getId());
psPok.setString(2, pokemon.getName());
psPok.setInt(3, pokemon.getHeight());
psPok.setInt(4, pokemon.getWeight());
psPok.setString(5, pokemon.getType());
psPok.setString(6, pokemon.getAbility().getName());
psPok.setString(7, pokemon.getAbility().getEffect());
psPok.executeUpdate();
}
// Insertar cries
for (Crie crie : pokemon.getCrieList()) {
try (PreparedStatement psCrie = conn.prepareStatement(sqlCrie)) {
psCrie.setInt(1, pokemon.getId());
psCrie.setString(2, crie.getLatest());
psCrie.setString(3, crie.getLegacy());
psCrie.executeUpdate();
}
}
// Insertar formas
for (Form form : pokemon.getFormList()) {
try (PreparedStatement psForm = conn.prepareStatement(sqlForm)) {
psForm.setInt(1, pokemon.getId());
psForm.setString(2, form.getBack_default());
psForm.setString(3, form.getBack_shiny());
psForm.setString(4, form.getFront_default());
psForm.setString(5, form.getFront_shiny());
psForm.executeUpdate();
}
}
// Insertar movimientos
for (Move move : pokemon.getMoves()) {
int moveId;
try (PreparedStatement psMove = conn.prepareStatement(sqlMove, Statement.RETURN_GENERATED_KEYS)) {
psMove.setString(1, move.getName());
psMove.setString(2, move.getType());
psMove.setInt(3, move.getPower());
psMove.executeUpdate();
// Obtener el ID generado para el movimiento
try (ResultSet rs = psMove.getGeneratedKeys()) {
if (rs.next()) {
moveId = rs.getInt(1);
} else {
throw new SQLException("No se pudo obtener el ID del movimiento.");
}
}
}
// Insertar relación Pokémon-Movimiento
try (PreparedStatement pstmtPokemonMove = conn.prepareStatement(sqlPokemonMove)) {
pstmtPokemonMove.setInt(1, pokemon.getId());
pstmtPokemonMove.setInt(2, moveId);
pstmtPokemonMove.executeUpdate();
}
}
// Insertar estadísticas
for (Stat stat : pokemon.getStats()) {
int statId;
try (PreparedStatement pstmtStat = conn.prepareStatement(sqlStat, Statement.RETURN_GENERATED_KEYS)) {
pstmtStat.setString(1, stat.getStat().getName());
pstmtStat.setInt(2, stat.getBaseStat());
pstmtStat.executeUpdate();
// Obtener el ID generado para la estadística
try (ResultSet rs = pstmtStat.getGeneratedKeys()) {
if (rs.next()) {
statId = rs.getInt(1);
} else {
throw new SQLException("No se pudo obtener el ID de la estadística.");
}
}
}
// Insertar relación Pokémon-Estadística
try (PreparedStatement pstmtPokemonStat = conn.prepareStatement(sqlPokemonStat)) {
pstmtPokemonStat.setInt(1, pokemon.getId());
pstmtPokemonStat.setInt(2, statId);
pstmtPokemonStat.executeUpdate();
}
}
conn.commit(); // Confirmar la transacción
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // Revertir la transacción en caso de error
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true); // Restaurar auto-commit
conn.close(); // Cerrar la conexión
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Cómo podemos ver en el método anterior, public void createPokemon(Pokemon pokemon) se llevan a cabo las siguientes instrucciones, todas ellas estrechamente relacionadas con las tablas de la base de datos que tenemos:
String sqlPokemon = "INSERT INTO pokemon (id, name, height, weight, type, ability_name, ability_effect) VALUES (?, ?, ?, ?, ?, ?, ?)";
String sqlCrie = "INSERT INTO crie (pokemon_id, latest, legacy) VALUES (?, ?, ?)";
String sqlForm = "INSERT INTO form (pokemon_id, back_default, back_shiny, front_default, front_shiny) VALUES (?, ?, ?, ?, ?)";
// el id de Move es autoincremental
String sqlMove = "INSERT INTO move (name, type, power) VALUES (?, ?, ?)";
String sqlPokemonMove = "INSERT INTO pokemon_move (pokemon_id, move_id) VALUES (?, ?)";
// el id de Stat es autoincremental
String sqlStat = "INSERT INTO stat (name, base_stat) VALUES (?, ?)";
String sqlPokemonStat = "INSERT INTO pokemon_stat (pokemon_id, stat_id) VALUES (?, ?)";
Vemos que preparamos las inserciones a las tablas de la base de datos. Insertar en la tabla pokemon, tabla crie, tabla form, move pokemove, stat y pokemon_stat.
Para obtener toda la información recibimos un objeto de tipo Pokemon. ¿De dónde lo obtenemos? Tratandose de una inserción, lo aplicaremos para cada uno de los Pokemon que hemos obtenido del WebService de PokeApi que obtuvimos en el ejercicio anterior.
Connection conn = null;
Ponemos la conexión a null y todo el código lo metemos dentro de un try ... catch
conn = ConexionBD.getConnection();
conn.setAutoCommit(false); // Se desactiva el auto-commit para usar transacciones
Creamos un objeto de conexión y establecemos el auto commit a false. Esto lo realizamos para tratar todo con una transacción. No se puede dejar la base de datos en estado inconsistente. Por lo tanto en el caso de fallo, se desharan todos los cambios.
// Insertar Pokémon
try (PreparedStatement psPok = conn.prepareStatement(sqlPokemon)) {
psPok.setInt(1, pokemon.getId());
psPok.setString(2, pokemon.getName());
psPok.setInt(3, pokemon.getHeight());
psPok.setInt(4, pokemon.getWeight());
psPok.setString(5, pokemon.getType());
psPok.setString(6, pokemon.getAbility().getName());
psPok.setString(7, pokemon.getAbility().getEffect());
psPok.executeUpdate();
}
Se prepara la sentencia preparada, se proporcionan los parámetros a través de los Getters y se ejecuta la inserción.
Ahora vamos a ver un fragmento de código interesante:
x
// Insertar movimientos
for (Move move : pokemon.getMoves()) {
int moveId;
try (PreparedStatement psMove = conn.prepareStatement(sqlMove, Statement.RETURN_GENERATED_KEYS)) {
psMove.setString(1, move.getName());
psMove.setString(2, move.getType());
psMove.setInt(3, move.getPower());
psMove.executeUpdate();
// Obtener el ID generado para el movimiento
try (ResultSet rs = psMove.getGeneratedKeys()) {
if (rs.next()) {
moveId = rs.getInt(1);
} else {
throw new SQLException("No se pudo obtener el ID del movimiento.");
}
}
}
Si nos fijamos en la sentencia SQL para insertar en la tabla de movimientos:
String sqlMove = "INSERT INTO move (name, type, power) VALUES (?, ?, ?)";
Podemos ver que falta el id, pero este dato está en la tabla:
Como se puede ver el id es de tipo auto_increment, de tal manera que se inserta automaticamente, pero necesitamos poderlo obtener para utilizarlo en otras tablas, es decir, en las relaciones.
Para ello podemos ver que en el insert se hace el uso del siguiente parámetro:
x
PreparedStatement psMove = conn.prepareStatement(sqlMove, Statement.RETURN_GENERATED_KEYS)
Una vez ser realiza la inserción, necesitamos saber el id que se ha generado, de ahí, el RETURN_GENERATED_KEYS
x
try (ResultSet rs = psMove.getGeneratedKeys()) {
if (rs.next()) {
moveId = rs.getInt(1);
} else {
throw new SQLException("No se pudo obtener el ID del movimiento.");
}
}
En el campo moveId obtenemos el id generado de la inserción anterior, que luego utilizaremos en la siguiente inserción, en la tabla del movimiento de los pokemon, pokemon_move:
x
// Insertar relación Pokémon-Movimiento
try (PreparedStatement pstmtPokemonMove = conn.prepareStatement(sqlPokemonMove)) {
pstmtPokemonMove.setInt(1, pokemon.getId());
pstmtPokemonMove.setInt(2, moveId);
pstmtPokemonMove.executeUpdate();
}
Podemos observar en los lugares de las tablas en las que nos haga falta un id autogenerado, lo tendremos que obtener preparando el prepareStatement con Statement.RETURN_GENERATED_KEYS
Una vez hemos llegado al final de toda la operación realizamos el commit. En caso de error, llevaremos a cabo el RollBack:
conn.commit(); // Confirmar la transacción
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // Revertir la transacción en caso de error
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true); // Restaurar auto-commit
conn.close(); // Cerrar la conexión
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Debes tener en cuenta que un rollback() puede provocar una excepción.
En el finally observamos que restauramos el autocommit y cerramos la conexión.
Debes completar los siguientes métodos que tendrás en el proyecto con partes a implementar.
public Pokemon readPokemon(int id)
public void updatePokemon(Pokemon pokemon)
public void deletePokemon(int id)
public List<Pokemon> getAllPokemon()
Fíjate que hay consultas que contienen tres puntos, ..., que deberás terminar de completar.
También en el método Main.
La recomendación es que observes la estrategia que se proporciona en el método implementado, lo entiendas y luego vayas poco a poco.
Observa los comentarios, en los cuales se indican que se debe realizar.
Deberás entregar el proyecto funcionando.