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. This 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:
- Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 1)
- Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 2)
Content:
- Creating and connecting a database
- ORM and JPA
- Entity
- Hibernate Properties
- Hibernate configuration
- Data access layer
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 packagejava.sql
has a class Connection
. There are several ways to establish a connection, for example you can use the getConnection
class 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.xml
following 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. Click 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 films
with columns id
, title
etc. 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 emptyDEFAULT
— set the default valueCOLLATE
- encodingCREATE UNIQUE INDEX
— make the field uniqueINSERT INTO
— add a record to the table
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 main
and 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 useSSL
if 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 main
and voila - Connection successful! Well, great, we figured out how to create a connection. The class Main
has 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 topom.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-api
will arrive along with hibernate-core , and spring-jdbc and spring-tx along with spring-orm .
Entity
So we want class objects toFilm
be 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 Film
an 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.
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 property
file. 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.config
Let'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
@Configuration
already@ComponentScan
figured out when we did the classWebConfig
. @EnableTransactionManagement
— allows you to use itTransactionManager
to 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 fromproperty
a file.hibernateProperties
- this method is needed to represent Hibernate properties as a Properties objectDataSource
— used to create a connection to the database. This is an alternative to DriverManager , which we used earlier when we created themain
. The documentation says thatDataSource
it 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.
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.xml
following dependency:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.10</version>
</dependency>
And in the dataSource
class method HibernateConfig
we replace it DriverManagerDataSource
with BasicDataSource
one 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 classFilmDAOImpl
and 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 List
because 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 . When all this was fixed, there was still something left. Let's go to a higher level, to the service. In the class, FilmServiceImpl
we mark the allFilms
spring 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. And 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)
GO TO FULL VERSION