JavaRush /Java Blog /Random EN /Introduction to Maven, Spring, MySQL, Hibernate and the f...
Макс
Level 41

Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 3)

Published in the Random EN group
Good afternoon. In this article I would like to share my first encounter with things like Maven, Spring, Hibernate, MySQL and Tomcat in the process of creating a simple CRUD application. This is the third part of 4. The article is intended primarily for those who have already completed 30-40 levels here, but have not yet ventured beyond pure Java and are just beginning (or are about to begin) to enter the open world with all these technologies, frameworks and other unfamiliar words. Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 3) - 1This is the third part of the article "Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application." Previous parts can be seen by following the links:

Content:

Creating and connecting a database

Well, it's time to start working on the database. Before connecting Hibernate and thinking about how it should all work there, first let’s look at the database itself, i.e. Let's create it, connect it, make it and fill out the sign. We will use the DBMS (Database Management System) MySQL (of course, you must first download and install). SQL (Structured Query Language) is a declarative programming language used to create, modify, and manipulate data in a relational database. In such databases, data is stored in the form of tables. How does the application communicate with the database (transmitting SQL queries to the database and returning results). For this, Java has such a thing as JDBC (Java DataBase Connectivity) , which, simply put, is a set of interfaces and classes for working with databases. To interact with the database, you need to create a connection; for this, the package java.sqlhas a class Connection. There are several ways to establish a connection, for example you can use the getConnectionclass method DriverManager. However, interaction with the database is not carried out directly, because there are many databases, and they are different. So for each of them there is its own JDBC Driver. Using this driver, a connection to the database is established. Therefore, first of all, so as not to be distracted by this later, let’s install the MySQL driver . Let's add the pom.xmlfollowing dependency:
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>
Now let's create the database. View -> Tool Windows -> Database - the database panel will open. New (green +) -> Data Source -> MySQL - a window will open in which you need to specify the username and password, we set them when installing MySQL (for the example, I used root and root). Port (default for MySQL 3306), name, etc. leave it as is. You can test the connection using the " Test Connection " button. Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 3) - 2Click OK and now we are connected to the MySQL server. Next, let's create a database. To do this, you can write a script in the console that opens:
CREATE DATABASE test
Click Execute and the database is ready, now you can connect it. To do this, go back to Data Source Properties and enter the database name (test) in the Database field, then enter the username and password again and click OK. Now we need to make a table. You can use graphical tools, but for the first time, it’s probably worth writing a script by hand to see what it looks like:
USE test;

CREATE TABLE films
(
  id int(10) PRIMARY KEY AUTO_INCREMENT,
  title VARCHAR(100) NOT NULL,
  year int(4),
  genre VARCHAR(20),
  watched BIT DEFAULT false  NOT NULL
)
COLLATE='utf8_general_ci';
CREATE UNIQUE INDEX films_title_uindex ON films (title);

INSERT INTO `films` (`title`,`year`,`genre`, watched)
VALUES
  ("Inception", 2010, "sci-fi", 1),
  ("The Lord of the Rings: The Fellowship of the Ring", 2001, "fantasy", 1),
  ("Tag", 2018, "comedy", 0),
  ("Gunfight at the O.K. Corral", 1957, "western", 0),
  ("Die Hard", 1988, "action", 1);
A table is created with the name filmswith columns id, titleetc. For each column, the type is indicated (maximum output size in parentheses).
  • PRIMARY KEY- this is the primary key, used to uniquely identify a record in the table (which implies uniqueness)
  • AUTO_INCREMENT— the value will be generated automatically (of course it will be non-zero, so you don’t have to specify this)
  • NOT NULL- here everything is also obvious, it cannot be empty
  • DEFAULT— set the default value
  • COLLATE- encoding
  • CREATE UNIQUE INDEX— make the field unique
  • INSERT INTO— add a record to the table
The result is a sign like this: Perhaps it’s worth trying to connect to it, just for now, separately from our web application. What if some problems arise with this, then we’ll sort it out right away. Otherwise, later we will connect Hibernate , do something, configure, tinker, and if we mess up somewhere, then at least we will know that the problem is not here. Well, to check the connection, let's create a method main, temporarily. In principle, you can put it anywhere, even in the controller class, even in the model or configuration, it doesn’t matter, you just need to use it to make sure that everything is fine with the connection and you can delete it. But to be more careful, let’s create a separate class for it Main:
package testgroup.filmography;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/test";
        String username = "root";
        String password = "root";
        System.out.println("Connecting...");

        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            System.out.println("Connection successful!");
        } catch (SQLException e) {
            System.out.println("Connection failed!");
            e.printStackTrace();
        }
    }
}
Everything is simple here, we set the connection parameters to our database and try to create a connection. Let's launch this one mainand take a look. So, I got an exception, some time zone problems, and some other warning about SSL. After browsing the Internet, you can find out that this is a fairly common problem, and when using different versions of the driver (mysql-connector-java), it can swear differently. For example, I found out experimentally that when using version 5.1.47 there are no exceptions due to the time zone, the connection is created normally, but the SSL warning still pops up. With some other versions it seemed that there was an exception regarding SSL, and not just a warning. Okay, that's not the point. You can try to deal with this issue separately, but we won’t go into it now. The solution to this is all quite simple, you need to specify additional parameters in the url , namely serverTimezone, if the problem is with the time zone, and useSSLif the problem is with SSL:
String url = "jdbc:mysql://localhost:3306/test?serverTimezone=Europe/Minsk&useSSL=false";
Now we have set the time zone and disabled SSL. We launch it again mainand voila - Connection successful! Well, great, we figured out how to create a connection. The class Mainhas basically completed its task, you can delete it.

ORM and JPA

In a good way, for a better understanding, it is better to start getting acquainted with the databases in order, from the very beginning, without any hibernates and other things. Therefore, it would be a good idea to find some guides and first try to work with JDBC classes, write SQL queries manually and so on. Well, let’s move on to the ORM model right away. What does this mean? Of course, it is again advisable to read about this separately, but I will try to briefly describe it. ORM (Object-Relational Mapping or object-relational mapping) is a technology for mapping objects into relational database structures, i.e. to represent our Java object as a table row. Thanks to ORM, you don't have to worry about writing SQL scripts and focus on working with objects. How to use it. Java has another great thing, JPA (Java Persistence API), which implements the ORM concept. JPA is such a specification; it describes the requirements for objects, it defines various interfaces and annotations for working with the database. JPA is essentially a description, a standard. Therefore, there are many specific implementations, one of which (and one of the most popular) is Hibernate, which is the essence of this framework. Hibernate is an implementation of the JPA specification designed to solve object-relational mapping (ORM) problems. We need to connect this whole thing to our project. In addition, in order for our Spring not to stand on the sidelines and also participate in all this movement with databases, we need to connect a couple more modules, because everything we got from the spring-webmvc dependency is no longer enough for this. We will also need spring-jdbc to work with the database, spring-tx to support transactions, and spring-orm to work with Hibernate. Let's add dependencies to pom.xml:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.1.1.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.3.7.Final</version>
</dependency>
These two dependencies are enough. javax.persistence-apiwill arrive along with hibernate-core , and spring-jdbc and spring-tx along with spring-orm .

Entity

So we want class objects to Filmbe able to be stored in the database. To do this, the class must satisfy a number of conditions. In JPA there is such a thing as an Entity for this . An entity class is an ordinary POJO class, with private fields and getters and setters for them. It must have a non-private constructor without parameters (or a default constructor), and it must have a primary key, i.e. something that will uniquely identify each record of this class in the database. You can also read about all the requirements for such a class separately. Let's make our class Filman entity using JPA annotations:
package testgroup.filmography.model;

import javax.persistence.*;

@Entity
@Table(name = "films")
public class Film {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "title")
    private String title;

    @Column(name = "year")
    private int year;

    @Column(name = "genre")
    private String genre;

    @Column(name = "watched")
    private boolean watched;

    // + getters and setters
}
  • @Entity- indicates that this class is an entity.
  • @Table- points to a specific table to display this entity.
  • @Id— indicates that this field is a primary key, i.e. this property will be used to identify each unique entry.
  • @Column— connects a field to a table column. If the field and table column names are the same, you can omit them.
  • @GeneratedValue— the property will be generated automatically; you can specify how in parentheses. We will not now understand how exactly different strategies work. It is enough to know that in this case each new value will increase by 1 from the previous one.
For each property, you can additionally specify many other things, for example, what should be non-zero or unique, specify the default value, maximum size, etc. This will be useful if you need to generate a table based on this class; Hibernate has this option. But we have already created the table ourselves and configured all the properties, so we can do without it. A small note.The Hibernate documentation recommends using annotations not on fields, but on getters. However, the difference between these approaches is quite subtle and in our simple application this will not have any impact. Plus, most people put annotations above the fields anyway. Therefore, let's leave it like this, it looks neater.

Hibernate Properties

Well, let's start setting up our Hibernate. And first of all, let's put some information, such as username and password, url and something else into a separate file. You can, of course, specify them as a regular line directly in the class, as we did when we checked the connection ( String username = "root";and then passed it to the method for creating the connection). But it is still more correct to store such static data in some propertyfile. And if, for example, you need to change the database, then you won’t have to go through all the classes and look for where it is used; it will be enough to change the value in this file once. Let's create a db.properties file in the resources directory :
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=Europe/Minsk&useSSL=false
jdbc.username=root
jdbc.password=root

hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
hibernate.show_sql=true
Well, everything is clear from above, parameters for connecting to the database, i.e. driver class name, url, username and password. hibernate.dialect- this property is needed to indicate to Hibernate which particular version of the SQL language is used. The fact is that in every DBMS, in order to expand capabilities, add some functionality or optimize something, they usually slightly modernize the language. As a result, it turns out that each DBMS has its own SQL dialect. It’s like with English, it seems like the language is the same, but in Australia, the USA or Britain it will be slightly different, and some words may have different meanings. And in order to avoid any problems with understanding, you need to directly tell Hibernate what exactly it has to deal with. hibernate.show_sql— thanks to this property, queries to the database will be displayed in the console. This is not necessary, but with this thing you can at least look at what is happening, otherwise it may seem that Hibernate is doing some kind of magic. Well, of course, it won’t be entirely clear to display; it’s better to use some kind of logger for this, but that’s something for another time, for now it’ll do.

Hibernate configuration

Let's move on to setting up the configuration. configLet's create a class in the package HibernateConfig:
package testgroup.filmography.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@ComponentScan(basePackages = " testgroup.filmography")
@EnableTransactionManagement
@PropertySource(value = "classpath:db.properties")
public class HibernateConfig {
    private Environment environment;

    @Autowired
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
        properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
        return properties;
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
        dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
        dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
        dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
        return dataSource;
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan("testgroup.filmography.model");
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    }

    @Bean
    public HibernateTransactionManager transactionManager() {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(sessionFactory().getObject());
        return transactionManager;
    }
}
There is quite a lot of new stuff here, so it is best to additionally look for information on each item in different sources. Let's go over it briefly here.
  • We @Configurationalready @ComponentScanfigured out when we did the class WebConfig.
  • @EnableTransactionManagement— allows you to use it TransactionManagerto manage transactions. Hibernate works with the database using transactions; they are needed for a certain set of operations to be performed as a single whole, i.e. if the method has problems with any one operation, then all the others will not be executed, so that it does not happen like in the classic example with the transfer of money, when the operation of withdrawing money from one account was completed, but the operation of writing to another did not work, as a result the money disappeared.
  • @PropertySource— connecting the properties file that we recently created.
  • Environment- in order to get properties from propertya file.
  • hibernateProperties- this method is needed to represent Hibernate properties as a Properties object
  • DataSource— used to create a connection to the database. This is an alternative to DriverManager , which we used earlier when we created the main. The documentation says that DataSourceit is preferable to use. That’s what we’ll do, of course, not forgetting to read on the Internet what the difference and advantages are. One benefit in particular is the ability to create a Database Connection Pool (DBCP).
  • sessionFactory— to create sessions with the help of which operations with entity objects are carried out. Here we set the data source, Hibernate properties and in which package we need to look for entity classes.
  • transactionManager— to configure the transaction manager.
A small note about DataSource. The documentation says that using the standard implementation, namely DriverManagerDataSource, is not recommended, because it is only a replacement for normal connection pooling and is generally only suitable for tests and such. For a normal application, it is preferable to use some kind of DBCP library. Well, for our application, of course, what we have is enough, but to complete the picture, perhaps we’ll still use another implementation as advised. Let's add the pom.xmlfollowing dependency:
<dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-dbcp</artifactId>
            <version>9.0.10</version>
</dependency>
And in the dataSourceclass method HibernateConfigwe replace it DriverManagerDataSourcewith BasicDataSourceone from the package org.apache.tomcat.dbcp.dbcp2:
BasicDataSource dataSource = new BasicDataSource();
Well, everything seems to be ready, the configuration is ready, all that remains is to add it to our AppInitializer :
protected Class<?>[] getRootConfigClasses() {
        return new Class[]{HibernateConfig.class};
    }

Data access layer

It's time to finally get started with our DAO. We go to the class FilmDAOImpland first of all we delete the trial list from there, we no longer need it. Let's add a session factory and work through it.
private SessionFactory sessionFactory;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
First, let's create a method for displaying a page with a list of movies, in it we will receive a session and make a request to the database (pull out all records and create a list):
public List<Film> allFilms() {
        Session session = sessionFactory.getCurrentSession();
        return session.createQuery("from Film").list();
    }
There are 2 points here. First, a warning was displayed. This is due to the fact that we want to receive a parameterized List<Film>, but the method returns simply Listbecause at compile time it is not known what type the request will return. So the idea warns us that we are doing an unsafe conversion, which may result in trouble. There are several more correct ways to do this so that such a question does not arise. You can search for information on the Internet. But let's not bother with this now. The fact is that we know exactly what type will be returned, so no problems will arise here, you can simply ignore the warning. But, so that your eyes are not an eyesore, you can hang an annotation above the method @SupressWarning("unchecked"). By doing this, we kind of tell the compiler, thank you, buddy, for your concern, but I know what I’m doing and have everything under control, so you can relax and not worry about this method. Secondly, the idea is underlined in red " from Film". It’s just an HQL (Hibernate Query Language) query and the idea doesn’t understand whether everything is correct or there is an error. You can go to the idea settings and manually adjust everything (look on the Internet if interested). Or you can simply add support for the Hibernate framework, to do this, right-click on the project, select Add Framework Support , check the box for Hibernate and click OK. After this, most likely in the entity class ( Film) a lot of things will also be underlined in red, for example, where the annotation @Table(name = "films")will issue the warning Cannot resolve table 'films' . Again, there is nothing wrong here, this is not a design error, everything will compile and work. The idea is emphasized because it knows nothing about our base. To fix this, let's integrate the idea with the database. View -> Tool Windows -> Persistense (a tab will open) -> right mouse button, select Assign Data Sources -> in Data Source, specify the connection to the database and click OK . Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 3) - 3When all this was fixed, there was still something left. Let's go to a higher level, to the service. In the class, FilmServiceImplwe mark the allFilmsspring method with an annotation @Transactional, which will indicate that the method should be executed in a transaction (without this, Hibernate will refuse to work):
@Transactional
public List<Film> allFilms() {
    return filmDAO.allFilms();
}
So, everything is ready here, you don’t need to touch anything in the controller. Well, it looks like the moment of truth has come, click Run and see what happens. Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 3) - 4And here it is, our sign, and this time it was obtained not from a list that we ourselves made right in class, but from a database. Great, everything seems to be working. Now we do all other CRUD operations in the same way using session methods. The resulting class looks like this:
package testgroup.filmography.dao;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import testgroup.filmography.model.Film;

import java.util.List;

@Repository
public class FilmDAOImpl implements FilmDAO {
    private SessionFactory sessionFactory;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<Film> allFilms() {
        Session session = sessionFactory.getCurrentSession();
        return session.createQuery("from Film").list();
    }

    @Override
    public void add(Film film) {
        Session session = sessionFactory.getCurrentSession();
        session.persist(film);
    }

    @Override
    public void delete(Film film) {
        Session session = sessionFactory.getCurrentSession();
        session.delete(film);
    }

    @Override
    public void edit(Film film) {
        Session session = sessionFactory.getCurrentSession();
        session.update(film);
    }

    @Override
    public Film getById(int id) {
        Session session = sessionFactory.getCurrentSession();
        return session.get(Film.class, id);
    }
}
Now all that remains is not to forget to go to the service and add an annotation to the methods @Transactional. That's it, ready. You can now run and check. Click links and buttons, try to add/delete/edit entries. If everything is done correctly it should work. Now this is a full-fledged CRUD application using Hibernate, Spring, MySQL. To be continued... Introducing Maven, Spring, MySQL, Hibernate and the first CRUD application (part 1) Introducing Maven, Spring, MySQL, Hibernate and the first CRUD application (part 2) Introducing Maven, Spring, MySQL, Hibernate and the first CRUD application (part 3) Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 4)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION