insert(@NonNull String insertSQL, Object... params) throws SQLException {
158 | // Con return generated keys obtenemos las claves generadas is las claves es autonumerica por ejemplo
159 | preparedStatement = connection.prepareStatement(insertSQL, preparedStatement.RETURN_GENERATED_KEYS);
160 | // Vamos a pasarle los parametros usando preparedStatement
161 | for (int i = 0; i < params.length; i++) {
162 | preparedStatement.setObject(i + 1, params[i]);
163 | }
164 | preparedStatement.executeUpdate();
165 | return Optional.of(preparedStatement.getGeneratedKeys());
166 | }
167 |
168 | /**
169 | * Realiza una consulta de tipo update de manera "preparada" con los parametros opcionales si son encesarios
170 | * @param updateSQL consulta SQL de tipo update
171 | * @param params parámetros de la consulta parametrizada
172 | * @return número de registros actualizados
173 | * @throws SQLException tabla no existe o no se ha podido realizar la operación
174 | */
175 | public int update(@NonNull String updateSQL, Object... params) throws SQLException {
176 | return updateQuery(updateSQL, params);
177 | }
178 |
179 | /**
180 | * Realiza una consulta de tipo delete de manera "preparada" con los parametros opcionales si son encesarios
181 | * @param deleteSQL consulta SQL de tipo delete
182 | * @param params parámetros de la consulta parametrizada
183 | * @return número de registros eliminados
184 | * @throws SQLException tabla no existe o no se ha podido realizar la operación
185 | */
186 | public int delete(@NonNull String deleteSQL, Object... params) throws SQLException {
187 | return updateQuery(deleteSQL, params);
188 | }
189 |
190 | /**
191 | * Realiza una consulta de tipo update, es decir que modifca los datos, de manera "preparada" con los parametros opcionales si son encesarios
192 | * @param genericSQL consulta SQL de tipo update, delete, creted, etc.. que modifica los datos
193 | * @param params parámetros de la consulta parametrizada
194 | * @return número de registros eliminados
195 | * @throws SQLException tabla no existe o no se ha podido realizar la operación
196 | */
197 | private int updateQuery(@NonNull String genericSQL, Object... params) throws SQLException {
198 | // Con return generated keys obtenemos las claves generadas
199 | preparedStatement = connection.prepareStatement(genericSQL);
200 | // Vamos a pasarle los parametros usando preparedStatement
201 | for (int i = 0; i < params.length; i++) {
202 | preparedStatement.setObject(i + 1, params[i]);
203 | }
204 | return preparedStatement.executeUpdate();
205 | }
206 |
207 | public void initData(@NonNull String sqlFile) throws FileNotFoundException {
208 | ScriptRunner sr = new ScriptRunner(connection);
209 | Reader reader = new BufferedReader(new FileReader(sqlFile));
210 | sr.runScript(reader);
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blog-Relacional-AccesoDatos-2021-2022
2 | Ejemplo de desarrollo de un blog (backend básico) para Acceso a Datos, usando una base de datos realacional e implementando distintas técnicas y patrones de Acceso a Datos vistos en clase.
3 |
4 | [](https://www.java.com/es/)
5 | []()
6 | 
7 |
8 | - [Blog-Relacional-AccesoDatos-2021-2022](#blog-relacional-accesodatos-2021-2022)
9 | - [Descripción](#descripción)
10 | - [Tecnologías](#tecnologías)
11 | - [Enunciado](#enunciado)
12 | - [Ejemplo de diagrama](#ejemplo-de-diagrama)
13 | - [Desarrollo](#desarrollo)
14 | - [GitFlow](#gitflow)
15 | - [Maven](#maven)
16 | - [Secretos](#secretos)
17 | - [Lombok](#lombok)
18 | - [Arquitectura](#arquitectura)
19 | - [Patrón DAO](#patrón-dao)
20 | - [Controladores - Servicios - Repositorios](#controladores---servicios---repositorios)
21 | - [Controlador](#controlador)
22 | - [Servicio](#servicio)
23 | - [Repositorio](#repositorio)
24 | - [Repositorio vs DAO](#repositorio-vs-dao)
25 | - [Patrón DTO y Mapper](#patrón-dto-y-mapper)
26 | - [Base de datos](#base-de-datos)
27 | - [Ejecución](#ejecución)
28 | - [Docker](#docker)
29 | - [Adminer o cliente de Bases de Datos](#adminer-o-cliente-de-bases-de-datos)
30 | - [Autor](#autor)
31 | - [Contacto](#contacto)
32 | - [Licencia](#licencia)
33 |
34 | ## Descripción
35 | Se ha implementado el desarrollo del un blog a nivel de backend para el acceso a los datos que se necesiten con fines didácticos para el módulo de Acceso a Datos de 2DAM.
36 | Debes entender que es un ejemplo didáctico para clase, por lo que parte de la solución simplemente es para mostrar distintas técnicas y patrones y por lo tanto
37 | puede que no sea la más óptima o adecuada a niveles de producción o empresarial. Tenlo en cuenta.
38 |
39 | ## Tecnologías
40 | Se han usado las siguientes tecnologías:
41 | - Java 11, como lenguaje de programación.
42 | - MariaDB como motor de base de datos relacional.
43 | - Docker para lanzar la base de datos, así como otras utilidades para manejarla.
44 |
45 | ## Enunciado
46 | Se desea implementar la base de un blog teniendo en cuenta que:
47 | - Un usuario una vez registrado mediante email y password puede hacer login y logout en el sistema.
48 | - El usuario puede escribir varios posts los cuales pertenecen solo a una categoría existente, como general, dudas o evaluación. Se pueden crear nuevas categorías.
49 | - Los usuarios pueden hacer distintos comentarios sobre posts existentes.
50 |
51 | ### Ejemplo de diagrama
52 | 
53 |
54 | ## Desarrollo
55 | ### GitFlow
56 | Se ha usado GitFlow como modelo de flujo de desarrollo y trabajo con el repositorio.
57 |
58 | ### Maven
59 | Apache Maven es un software de gestión de proyectos. Maven aumenta la reutilización y también se encarga de la mayoría
60 | de las tareas relacionadas con la construcción. Funciona en muchos pasos, como agregar archivos jar a la biblioteca del proyecto,
61 | crear informes y ejecutar casos de prueba o crear archivos Jar para el proyecto e incluso muchas más cosas.
62 | La configuración de Maven se guarda en el fichero [pom.xml](./pom.xml).
63 |
64 | ### Secretos
65 | Para trabajar con secretos y o variables globales se han usado dos enfoques en el directorio recursos:
66 | - Ficheros de Properties (Propiedades): con ellos podemos leer propiedades de la manea clave valor.
67 | - Ficheros .env: Mediante ellos leemos las variables de entorno ya sea del sistema o definidas en un fichero .env
68 |
69 | ### Lombok
70 | Se ha usado [Lombok](https://projectlombok.org/features/all) como sistema de anotaciones para aumentar la productividad
71 | y reducir el código repetitivo.
72 |
73 | ## Arquitectura
74 | ### Patrón DAO
75 | Debemos tener en cuenta que la implementación y formato de la información puede variar según la fuente de los datos el patrón DAO propone separar por completo la lógica de negocio de la lógica para acceder a los datos, de esta forma, el DAO proporcionará los métodos necesarios para insertar, actualizar, borrar y consultar la información; por otra parte, la capa de negocio solo se preocupa por lógica de negocio y utiliza el DAO para interactuar con la fuente de datos.
76 |
77 | 
78 |
79 | ### Controladores - Servicios - Repositorios
80 | La arquitectura que seguiremos es tipo CSS (Controladores -> Servicios -> Repositorios) de esta manera cualquier cambio no afectaría a la capa superior, manteniendo nuestra compatibilidad si por ejemplo pasamos de almacenamiento en ficheros XML a bases de datos relacionales o no relacionales.
81 |
82 | 
83 |
84 | #### Controlador
85 | Tiene la lógica de la aplicación y controla y redirige las distintas peticiones que se nos hacen.
86 |
87 | #### Servicio
88 | Tienen la lógica de negocio y procesan las peticiones que se nos hacen accediendo a los recursos necesarios para ello usando los repositorios. Son la capa intermedia.
89 |
90 | #### Repositorio
91 | Implementan la lógica de acceso y manipulación de los datos encapsulando dichas operaciones.
92 |
93 | #### Repositorio vs DAO
94 | - DAO implementa las operaciones a más bajo nivel para persistencia y manipulación de la información. DAO es una abstracción de la persistencia de datos. DAO es un concepto de nivel inferior, más cercano a los sistemas de almacenamiento de datos. DAO funciona como una capa de mapeo/acceso de datos.
95 | - Repositorio encapsula el propio sistema de almacenamiento, pero no suele estar tan ligado a dicho sistema de almacenamiento. Un repositorio es una abstracción de una colección de objetos. El el repositorio es un concepto de nivel superior, más cercano a los objetos de dominio. un repositorio es una capa entre dominios y capas de acceso a datos, que oculta la complejidad de recopilar datos y preparar un objeto de dominio.
96 | - Se puede dar el caso que un mismo repositorio trabaje con distintos DAOS, por ejemplo las operaciones de manejo de datos de usuarios estén en una base de datos relacional (login, password) y en una NoSQL otra información (nombre, apellidos, email, etc). Es por ello que el repositorio para manejo de usuario llamará por debajo a dos DAOS separados. Pero si la correspondencia es 1 a 1, las ideas son muy similares y podemos optar por uno de ellos.
97 |
98 | ### Patrón DTO y Mapper
99 | El patrón DTO tiene como finalidad de crear un objeto plano (POJO) con una serie de atributos que puedan ser enviados o recuperados por nuestro servicio y enviados a capas superiores cone l objetivo de condensar o adaptar la información para disminuir las trasferencia y con ello respetar nuestro modelo de datos, pues es en el objeto DTO donde realizamos operaciones de trasferencia de datos.
100 | Por ejemplo en nuestro modelo tenemos usuarios que escriben post. Podemos de una tacada traernos todos los usuarios y sus post en el DTO de usuarios.
101 |
102 | Por otro lado, los Mapper nos ayuda a ensamblar los DTO o desensamblaralos según el modelo de datos que tenemos. Es decir crear un objeto POJO a partir de un objeto DTO o POJO desde objetos DTO.
103 |
104 | 
105 |
106 | ### Base de datos
107 | Se ha usado MariaDB como motor de base de datos relacional y se ha creado un controlador para su manejo usando PreparedStatements, con ello conseguimos:
108 | - Ahorrar en la construcción de planes de ejecución. Pues la base de estas consultas se repite y tendremos un hash para identificarlas y con ellas podremos aprovechar el plan existente.
109 | - Evitar que nos inyecten SQL ya que al parametrizar la consulta el API de JDBC nos protege contra las este tipo de ataques. Normalmente el uso de consultas parametrizadas mejora el rendimiento entre un 20 y un 30 % a nivel de base de datos.
110 |
111 | 
112 |
113 | ## Ejecución
114 | ### Docker
115 | Entrar en el directorio docker y ejecutar
116 | ```sh
117 | $ docker-compose up -d
118 | ```
119 | Para iniciar la BD con algunos datos modifica el fichero [docker/mariadb/sql/init.sql](docker/mariadb/sql/init-db.sql)
120 |
121 |
122 | ### Adminer o cliente de Bases de Datos
123 | Debes conectarte a http://localhost:8080/
124 | - server: mariadb
125 | - user: blog
126 | - password: blog1234
127 | - base de datos blog
128 |
129 | ## Autor
130 |
131 | Codificado con :sparkling_heart: por [José Luis González Sánchez](https://twitter.com/joseluisgonsan)
132 |
133 | [](https://twitter.com/joseluisgonsan)
134 | [](https://github.com/joseluisgs)
135 |
136 | ### Contacto
137 |
138 | Cualquier cosa que necesites házmelo saber por si puedo ayudarte 💬.
139 |
140 |
141 |
142 |
144 |
145 |
146 |
148 |
149 |
150 |
152 |
153 |
154 |
156 |
157 |
158 |
159 |
160 | ## Licencia
161 |
162 | Este proyecto está licenciado bajo licencia **MIT**, si desea saber más, visite el fichero [LICENSE](./LICENSE) para su uso docente y educativo.
163 |
--------------------------------------------------------------------------------
/src/main/java/es/joseluisgs/dam/blog/Blog.java:
--------------------------------------------------------------------------------
1 | package es.joseluisgs.dam.blog;
2 |
3 | import es.joseluisgs.dam.blog.controller.*;
4 | import es.joseluisgs.dam.blog.database.DataBaseController;
5 | import es.joseluisgs.dam.blog.dto.CategoryDTO;
6 | import es.joseluisgs.dam.blog.dto.CommentDTO;
7 | import es.joseluisgs.dam.blog.dto.PostDTO;
8 | import es.joseluisgs.dam.blog.dto.UserDTO;
9 |
10 | import java.io.File;
11 | import java.io.FileNotFoundException;
12 | import java.sql.ResultSet;
13 | import java.sql.SQLException;
14 | import java.time.Instant;
15 | import java.time.LocalDate;
16 | import java.time.LocalDateTime;
17 | import java.util.Optional;
18 |
19 | public class Blog {
20 | private static Blog instance;
21 |
22 | private Blog() {
23 | }
24 |
25 | public static Blog getInstance() {
26 | if (instance == null) {
27 | instance = new Blog();
28 | }
29 | return instance;
30 | }
31 |
32 | public void checkService() {
33 | DataBaseController controller = DataBaseController.getInstance();
34 | try {
35 | controller.open();
36 | Optional rs = controller.select("SELECT 'Hello World'");
37 | if (rs.isPresent()) {
38 | rs.get().first();
39 | controller.close();
40 | }
41 | } catch (SQLException e) {
42 | System.err.println("Error al conectar al servidor de Base de Datos: " + e.getMessage());
43 | System.exit(1);
44 | }
45 | }
46 |
47 | public void initDataBase() {
48 | String sqlFile = System.getProperty("user.dir") + File.separator + "sql" + File.separator + "blog.sql";
49 | System.out.println(sqlFile);
50 | DataBaseController controller = DataBaseController.getInstance();
51 | try {
52 | controller.open();
53 | controller.initData(sqlFile);
54 | controller.close();
55 | } catch (SQLException e) {
56 | System.err.println("Error al conectar al servidor de Base de Datos: " + e.getMessage());
57 | System.exit(1);
58 | } catch (FileNotFoundException e) {
59 | System.err.println("Error al leer el fichero de datos iniciales: " + e.getMessage());
60 | System.exit(1);
61 | }
62 | }
63 |
64 | public void Categories() {
65 | CategoryController categoryController = CategoryController.getInstance();
66 | // Obtenemos todas las categorías
67 | // List categories = categoryController.getAllCategories();
68 | // categories.forEach(c-> System.out.println(c.toJSON()));
69 |
70 | System.out.println("GET Todas las categorías");
71 | System.out.println(categoryController.getAllCategoriesJSON());
72 |
73 | System.out.println("GET Categoría con ID = 2");
74 | System.out.println(categoryController.getCategoryByIdJSON(2L));
75 |
76 | System.out.println("POST Insertando Categoría");
77 | CategoryDTO categoryDTO = CategoryDTO.builder()
78 | .texto("Prueba " + Instant.now().toString())
79 | .build();
80 | System.out.println(categoryController.postCategoryJSON(categoryDTO));
81 |
82 | System.out.println("UPDATE Categoría con ID = 4");
83 | categoryDTO = CategoryDTO.builder()
84 | .id(4L)
85 | .texto("Prueba Update")
86 | .build();
87 | System.out.println(categoryController.updateCategoryJSON(categoryDTO));
88 |
89 | System.out.println("DELETE Categoría con ID = 4");
90 | categoryDTO = CategoryDTO.builder()
91 | .id(4L)
92 | .build();
93 | System.out.println(categoryController.deleteCategoryJSON(categoryDTO));
94 | }
95 |
96 | public void Users() {
97 | UserController userController = UserController.getInstance();
98 |
99 | System.out.println("GET Todos los usuarios");
100 | System.out.println(userController.getAllUsersJSON());
101 |
102 | System.out.println("GET Usuario con ID = 2");
103 | System.out.println(userController.getUserByIdJSON(2L));
104 |
105 | System.out.println("POST Insertando Usuario");
106 | UserDTO userDTO = UserDTO.builder()
107 | .nombre("Nombre " + Instant.now().toString())
108 | .email("user" + Math.random() + "@mail.com")
109 | .password("1234")
110 | .fechaRegistro(LocalDate.now())
111 | .build();
112 | System.out.println(userController.postUserJSON(userDTO));
113 |
114 | System.out.println("UPDATE Usuario con ID = 5");
115 | userDTO = UserDTO.builder()
116 | .id(5L)
117 | .nombre("Prueba Update")
118 | .email("prueba@update.com")
119 | .build();
120 | System.out.println(userController.updateUserJSON(userDTO));
121 |
122 | System.out.println("DELETE User con ID = 5");
123 | userDTO = UserDTO.builder()
124 | .id(5L)
125 | .build();
126 | System.out.println(userController.deleteUserJSON(userDTO));
127 | }
128 |
129 | public void Posts() {
130 | PostController postController = PostController.getInstance();
131 |
132 | System.out.println("GET Todos los Post");
133 | System.out.println(postController.getAllPostJSON());
134 |
135 | System.out.println("GET Post con ID = 2");
136 | System.out.println(postController.getPostByIdJSON(2L));
137 |
138 | System.out.println("POST Insertando Post");
139 | PostDTO postDTO = PostDTO.builder()
140 | .titulo("Post " + Instant.now().toString())
141 | .url("http://" + Math.random() + ".dominio.com")
142 | .contenido(Instant.now().toString())
143 | .fechaPublicacion(LocalDateTime.now())
144 | .build();
145 | // Asignamos el usuario y categorías que existan
146 | postDTO.setUser_id(1L);
147 | postDTO.setCategory_id(1L);
148 |
149 | System.out.println(postController.postPostJSON(postDTO));
150 |
151 | System.out.println("UPDATE Post con ID = 4");
152 | // Solo dejamos cambiar el tútulo o el contenido
153 | postDTO = PostDTO.builder()
154 | .id(4L)
155 | .titulo("Update " + Instant.now().toString())
156 | .contenido("Update " + Instant.now().toString())
157 | .url("http://" + Math.random() + ".dominio.com")
158 | .fechaPublicacion(LocalDateTime.now())
159 | .build();
160 | // Asignamos el usuario y categorías que existan
161 | postDTO.setUser_id(1L);
162 | postDTO.setCategory_id(1L);
163 | System.out.println(postController.updatePostJSON(postDTO));
164 |
165 | System.out.println("DELETE Post con ID = 5");
166 | postDTO = PostDTO.builder()
167 | .id(5L)
168 | .build();
169 | // Asignamos el usuario y categorías que existan
170 | postDTO.setUser_id(2L);
171 | postDTO.setCategory_id(3L);
172 | System.out.println(postController.deletePostJSON(postDTO));
173 |
174 | System.out.println("DELETE Post con ID = 4, tiene categorias anidadas");
175 | postDTO = PostDTO.builder()
176 | .id(4L)
177 | .build();
178 | // Asignamos el usuario y categorías que existan
179 | postDTO.setUser_id(2L);
180 | postDTO.setCategory_id(3L);
181 | System.out.println(postController.deletePostJSON(postDTO));
182 |
183 | }
184 |
185 | public void Comments() {
186 | CommentController commentController = CommentController.getInstance();
187 |
188 | System.out.println("GET Todos los Comentarios");
189 | System.out.println(commentController.getAllCommentsJSON());
190 |
191 | System.out.println("GET Comentario con ID = 2");
192 | System.out.println(commentController.getCommentByIdJSON(2L));
193 |
194 | System.out.println("POST Insertando Comentario");
195 | CommentDTO commentDTO = CommentDTO.builder()
196 | .texto("Comentario " + Instant.now().toString())
197 | .fechaPublicacion(LocalDateTime.now())
198 | .build();
199 | // Asignamos el usuario y post que existan
200 | commentDTO.setUser_id(1L);
201 | commentDTO.setPost_id(3L);
202 | System.out.println(commentController.postCommentJSON(commentDTO));
203 |
204 | System.out.println("UPDATE Comentario con ID = 4");
205 | // Solo dejamos cambiar el tútulo o el contenido
206 | commentDTO = CommentDTO.builder()
207 | .id(4L)
208 | .texto("Update " + Instant.now().toString())
209 | .fechaPublicacion(LocalDateTime.now())
210 | .build();
211 | // Asignamos el usuario y post que existan
212 | commentDTO.setUser_id(1L);
213 | commentDTO.setPost_id(3L);
214 | System.out.println(commentController.updateCommentJSON(commentDTO));
215 |
216 | System.out.println("DELETE Comentario con ID = 6");
217 | commentDTO = CommentDTO.builder()
218 | .id(6L)
219 | .build();
220 | commentDTO.setUser_id(1L);
221 | commentDTO.setPost_id(3L);
222 | System.out.println(commentController.deleteCommentJSON(commentDTO));
223 | }
224 |
225 | public void Login() {
226 | LoginController loginController = LoginController.getInstance();
227 | System.out.println("Login con un usario que SI existe");
228 | System.out.println(loginController.login("pepe@pepe.es", "1234"));
229 | System.out.println("Login con un usario que SI existe Y mal Password datos correctos");
230 | System.out.println(loginController.login("pepe@pepe.es", "1255"));
231 | System.out.println("Login con un usario que NO existe o mal Password datos correctos");
232 | System.out.println(loginController.login("pepe@pepe.com", "1255"));
233 | System.out.println("Logout de usuario que está logueado");
234 | System.out.println(loginController.logout(1L));
235 | System.out.println("Logout de usuario que no está logueado");
236 | System.out.println(loginController.logout(33L));
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/diagrams/dto.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------