Crear un REST API con Spring Boot, Hibernate y MySQL
Tabla de contenido
- Descripción del proyecto
- Preparar la base de datos con Docker
- Modelo Entidad Relación
- Estructura de archivos
- Configuración de base de datos
- Modelo
- El acceso a datos con el patron DAO (Data Access Object)
- El servicio
- El controlador
Descripción del proyecto
En esta ocación crearemos una REST API con Hibernate MySQL
- Spring Boot : v1.5.18.RELEASE
- Hibernate : v5.0.12
- JDK : v1.8
- Mysql : v5.7
Preparar la base de datos con Docker
Para la base de datos vamos a utilizar un contenedor que he creado en mi repositorio personal que puedes ver aqui
Ejecutar la imagen saidmlx/mysql-5-courses
Donde:
-p 33060:3306
Indicamos que el puerto a exponer sera el 33060
name mysql-db
Indicamos que la imagen se creara como mysql-db
-e MYSQL_ROOT_PASSWORD=secret
Indicamos que la contraseña para root sera secret
-e MYSQL_DATABASE=courses
Indicamos que cree la base de datos con nombre courses
$ docker run -d -p 33060:3306 --name mysql-db -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=courses saidmlx/mysql-5-courses
Entrar en modo interactivo
Con el siguiente comando entramos de forma interactiva al contenedor y ejecutamos mysql
$ docker exec -it mysql-db mysql -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.24 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> use courses;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+----------------------+
| Tables_in_courses |
+----------------------+
| course |
| social_media |
| teacher |
| teacher_social_media |
+----------------------+
4 rows in set (0.00 sec)
mysql>
Cargar el script en tu MySQL Local
Si aun le tienes miedo a Docker puedes cargar el siguiente escript de base de datos database.sql in you local MySQL 5
Solo recuerda que debe ser la version 5 de lo contrario puede causarte problemas
Modelo Entidad Relación
La acción anterior creara una base de datos lista para operar
Estructura de archivos
La estructura de carpetas y archivos
.
├── pom.xml
└── src
└── main
└── java
└── online
└── saidmlx
├── AppApplication.java
└── app
├── configuration
│ └── DataBaseConfiguration.java
├── controller
│ ├── CourseController.java
│ ├── MainController.java
│ ├── SocialMediaController.java
│ └── TeacherController.java
├── dao
│ ├── AbstractSession.java
│ ├── CourseDao.java
│ ├── CourseDaoImp.java
│ ├── SocialMediaDao.java
│ ├── SocialMediaDaoImp.java
│ ├── TeacherDao.java
│ ├── TeacherDaoImp.java
│ ├── TeacherSocialMediaDao.java
│ └── TeacherSocialMediaDaoImp.java
├── model
│ ├── Course.java
│ ├── SocialMedia.java
│ ├── Teacher.java
│ └── TeacherSocialMedia.java
├── service
│ ├── CourseService.java
│ ├── CourseServiceImp.java
│ ├── SocialMediaService.java
│ ├── SocialMediaServiceImp.java
│ ├── TeacherService.java
│ ├── TeacherServiceImp.java
│ ├── TeacherSocialMediaService.java
│ └── TeacherSocialMediaServiceImp.java
└── util
└── CustomErrorType.java
Configuración de base de datos
Dentro del archivo online.saidmlx.app.configuration.DataBaseConfiguration
Creamos propiedades adicionales
public Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect","org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.show_sql", "true");
return properties;
}
Definimos la fuente de datos donde indicamos tolos los datos de la base de datos
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource= new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("secret");
dataSource.setUrl("jdbc:mysql://localhost:33060/courses?autoReconnect=true&useSSL=false");
return dataSource;
}
Creamos el Session factory e indicamos que donde tiene que buscar los paquetes es online.saidmlx.app.model
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource());
localSessionFactoryBean.setPackagesToScan("online.saidmlx.app.model");
localSessionFactoryBean.setHibernateProperties(hibernateProperties());
return localSessionFactoryBean;
}
Definimos el administrador de transacciones
@Bean
@Autowired
public HibernateTransactionManager transactionManager () {
HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager();
hibernateTransactionManager.setSessionFactory(sessionFactory().getObject());
return hibernateTransactionManager;
}
Modelo
Todo parte del modelo y aqui tenemos cuatro clases que modelaran el acceso a datos
Cursos
online.saidmlx.app.model.Course
Deficición de la entidad
- @Entity indicamos que va ser una clase de tipo entidad
- @Table(name=”course”) aqui conectamos esta clase con la tabla de base de datos llamada course
- @Column(name=”****“) para cada columna de la tabla se crea una propiedad dentro de la clase
- @Id en caso de que la columna sea una PRIMARY KEY
- @GeneratedValue(strategy=GenerationType.IDENTITY) en caso de que el ID se necesite generar de forma automatica
Relaciones Un curso es impartido por un profesor pero un profesor puede dar varios cursos por lo cual la relacion en de muchos a uno y eso se define con:
- @ManyToOne( fetch=FetchType.EAGER) indicamos que la relación va de muchos a uno con acceso inmediato(Eager)
- @JoinColumn(name=”id_teacher”) indicamos que la columna por la cual uniremos los profesores sera *id_teacher*
@Entity
@Table(name="course")
public class Course implements Serializable {
@Id
@Column(name="id_course")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idCourse;
@Column(name="name")
private String name;
@Column(name="themes")
private String themes;
@Column(name="project")
private String project;
@ManyToOne( fetch=FetchType.EAGER)
@JoinColumn(name="id_teacher")
private Teacher teacher;
}
Profesores
online.saidmlx.app.model.Teacher
Deficición de la entidad
- @Entity indicamos que va ser una clase de tipo entidad
- @Table(name=”teacher”) aqui conectamos esta clase con la tabla de base de datos llamada course
- @Column(name=”****“) para cada columna de la tabla se crea una propiedad dentro de la clase
- @Id en caso de que la columna sea una PRIMARY KEY
- @GeneratedValue(strategy=GenerationType.IDENTITY) en caso de que el ID se necesite generar de forma automatica
Relaciones
- @OneToMany(cascade=CascadeType.ALL, mappedBy=”teacher”)
- @JsonIgnore
- @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
- @JoinColumn(name=”id_teacher”)
@Entity
@Table(name="teacher")
public class Teacher implements Serializable {
@Id
@Column(name="id_teacher")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idTeacher;
@Column(name="name")
private String name;
@Column(name="avatar")
private String avatar;
@OneToMany(cascade=CascadeType.ALL, mappedBy="teacher")
@JsonIgnore
private Set<Course> courses;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="id_teacher")
private Set<TeacherSocialMedia> teacherSocialMedia;
}
### Social Media
online.saidmlx.app.model.SocialMedia
Deficición de la entidad
- @Entity
- @Table(name=”social_media”)
- @Id
- @Column(name=”***“)
- @GeneratedValue(strategy=GenerationType.IDENTITY)
Relaciones
- @OneToMany
- @JoinColumn(name=”id_social_media”)
- @JsonIgnore
@Entity
@Table(name="social_media")
public class SocialMedia implements Serializable {
@Id
@Column(name="id_social_media")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idSocialmedia;
@Column(name="name")
private String name;
@Column(name="icon")
private String icon;
@OneToMany
@JoinColumn(name="id_social_media")
@JsonIgnore
private Set<TeacherSocialMedia> teacherSocialMedia;
}
Teacher Social Media
online.saidmlx.app.model.TeacherSocialMedia
Deficición de la entidad
- @Entity
- @Table(name=”teacher_social_media”)
- @Id
- @Column(name=”***“)
- @GeneratedValue(strategy=GenerationType.IDENTITY)
Relaciones
- @ManyToOne(fetch=FetchType.EAGER)
- @JoinColumn(name=”id_social_media”)
@Entity
@Table(name="teacher_social_media")
public class TeacherSocialMedia implements Serializable {
@Id
@Column(name="id_teacher_social_media")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idTeacherSocialMedia;
@Column(name="nickname")
private String nickname;
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="id_teacher")
@JsonIgnore
private Teacher teacher;
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="id_social_media")
private SocialMedia socialMedia;
}
El acceso a datos con el patron DAO (Data Access Object)
Para crear nuestro DAO creamos una clase abstracta que nos provea de un objeto de tipo Session
online.saidmlx.app.dao.AbstractSession
public abstract class AbstractSession {
@Autowired
SessionFactory sessionFactory;
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
}
Ahora para cada uno de los DAO heredamos de la clase AbstractSession
Para la generación de un DAO es necesario generar su interfaz y su implementación, donde la implementación hereda de la clase bastracta AbstractSession para aceder al objeto de tipo Session y dentro de cada clase de implementación hacemos uso del modelo definido en un paso anterior
public interface CourseDao {
public void saveCourse(Course course);
public void deleteCourseById(Long idCourse);
public void updateCourse(Course course);
public List<Course> findAllCourse();
public Course findById(Long idCourse);
public Course findByName(String name);
public List<Course> findByIdTeacher(Long idTeacher);
}
- @Repository indicamos que es una clase de tipo repositorio
- @Transactional con perfil transaccional
@Repository
@Transactional
public class CourseDaoImp extends AbstractSession implements CourseDao {
@Override
public void saveCourse(Course course) {
getSession().persist(course);
}
@Override
public void deleteCourseById(Long idCourse) {
Course course = findById(idCourse);
if(course != null) {
getSession().delete(course);
}
}
@Override
public void updateCourse(Course course) {
getSession().update(course);
}
@Override
public List<Course> findAllCourse() {
return getSession().createQuery("from Course").list();
}
@Override
public Course findById(Long idCourse) {
return (Course)getSession().get(Course.class, idCourse);
}
@Override
public Course findByName(String name) {
return (Course)getSession().createQuery("from Course where name = :name").setParameter("name", name).uniqueResult();
}
@Override
public List<Course> findByIdTeacher(Long idTeacher) {
return (List<Course>) getSession().createQuery(" from Course c join c.teacher where idteacher = : idTeacher")
.setParameter("idTeacher", idTeacher).list();
}
}
El servicio
Para cada entidad es necesario agregar un servicio definiendo interfaz e implementación y para cada clase de implementacion hacemos uso del DAO generado. Es por eso que injectamos via @Autowired
public interface CourseService {
public void saveCourse(Course course);
public void deleteCourseById(Long idCourse);
public void updateCourse(Course course);
public List<Course> findAllCourse();
public Course findById(Long idCourse);
public Course findByName(String name);
public List<Course> findByIdTeacher(Long idTeacher);
}
@Service("courseService")
@Transactional
public class CourseServiceImp implements CourseService {
@Autowired
CourseDao courseDao;
@Override
public void saveCourse(Course course) {
courseDao.saveCourse(course);
}
@Override
public void deleteCourseById(Long idCourse) {
courseDao.deleteCourseById(idCourse);
}
@Override
public void updateCourse(Course course) {
courseDao.updateCourse(course);
}
@Override
public List<Course> findAllCourse() {
return courseDao.findAllCourse();
}
@Override
public Course findById(Long idCourse) {
return courseDao.findById(idCourse);
}
@Override
public Course findByName(String name) {
return courseDao.findByName(name);
}
@Override
public List<Course> findByIdTeacher(Long idTeacher) {
return courseDao.findByIdTeacher(idTeacher);
}
}
El controlador
El controlador es el que expondra nuestro API
Para cada clase de tipo controller tenemos:
- @Controller indicamos que es un controlador
- @RequestMapping(“/v1”) con una peticion de entrada llamada v1
- @Autowired CourseService courseService; y usamos el servicio Curso
e internamente exponemos varias entradas
{[/v1/courses],methods=[GET]}
Obtenemos todos los cursos{[/v1/course/{id}],methods=[GET]}
Obtenemos un curso pasando su ID{[/v1/course],methods=[POST]}
Guardamos un curso pasando un objeto de tipo Course{[/v1/course/{id}],methods=[PATCH]}
Actualizamos un Curso{[/v1/course/teacher],methods=[PATCH]}
Asignamos un profesor a un curso{[/v1/course/{id}],methods=[DELETE]}
Eliminamos un curso
@Controller
@RequestMapping("/v1")
public class CourseController {
@Autowired
CourseService courseService;
@Autowired
TeacherService teacherService;
//- courses
@RequestMapping(value="/courses", method= RequestMethod.GET, headers="Accept= application/json")
public ResponseEntity<List<Course>> getCourses( ) {
}
//-- GET course
@RequestMapping(value="/course/{id}", method= RequestMethod.GET, headers="Accept= application/json")
public ResponseEntity<Course> getCourse(@PathVariable("id") Long idCourse ) {
}
//-- POST course
@RequestMapping(value="/course", method=RequestMethod.POST, headers="Accept= application/json")
public ResponseEntity<String> saveCourse(@RequestBody Course course, UriComponentsBuilder uri) {
}
@RequestMapping(value="/course/teacher", method=RequestMethod.PATCH, headers="Accept= application/json")
public ResponseEntity<?> addTeacher(@RequestBody Course course) {
}
//-- UPDATE course
@RequestMapping(value="/course/{id}", method=RequestMethod.PATCH)
public ResponseEntity<Course> updateCourse(@RequestBody Course course, @PathVariable("id") Long idCourse ) {
}
//-- DELETE course
@RequestMapping(value="/course/{id}", method=RequestMethod.DELETE)
public ResponseEntity<Course> deleteCourse( @PathVariable("id") Long idCourse ) {
}
}
## Probando nuestro API REST
De esta forma con una aplicación REST Cliente podemos ver como funciona en este caso usamo Postman
Si quieres ver el codigo de la API pudes descargarla del repositorio aqui