JavaRush /Java Blog /Random EN /JPA : Introduction to Technology
Viacheslav
Level 3

JPA : Introduction to Technology

Published in the Random EN group
The modern development world is full of various specifications designed to make life easier. Knowing the tools, you can choose the right one. Not knowing can make life difficult for yourself. This review will slightly open the veil of secrecy over the concept of JPA - Java Persistence API. I hope, after reading it, you will want to plunge into this mysterious world even deeper.
JPA : Introduction to Technology - 1

Introduction

As we know, one of the main tasks of programs is the storage and processing of data. In the good old days, people simply stored data in files. But as soon as simultaneous read and edit access is needed, when there is a load (that is, several accesses are received simultaneously), storing data simply in files becomes a problem. For more information about what problems databases solve and how, I advise you to read the article " How databases are arranged ". So, we decide to store our data in the database. For a long time, Java has been able to work with databases using the JDBC API (The Java Database Connectivity). You can read more about JDBC here: " JDBC or how it all starts". But time passed and developers each time faced with the need to write the same type and unnecessary "serving" code (the so-called Boilerplate code) for trivial operations of saving Java objects in the database and vice versa, creating Java objects according to data from the database. And then to solve These problems gave rise to such a thing as ORM.ORM - Object-Relational Mapping or translated into Russian object-relational mapping.This is a programming technology that connects databases with the concepts of object-oriented programming languages.To simplify, ORM is a connection Java objects and records in the database: JPA : Introduction to Technology - 2ORM is essentially the concept that a Java object can be represented as data in a database (and vice versa). It was embodied in the form of the JPA specification - Java Persistence API. The specification is already a description of the Java API that expresses this concept. The specification tells what means we must be provided with (i.e. through what interfaces we can work) in order to work according to the ORM concept. And how to use these tools. The specification does not describe the implementation of the means. This makes it possible to use different implementations for the same specification. You can simplify and say that the specification is a description of the API. The text of the JPA specification can be found on the Oracle website: " JSR 338: JavaTM Persistence API". Therefore, in order to use JPA, we need some implementation with which we will use the technology. JPA implementations are also called JPA Providers. One of the most notable JPA implementations is Hibernate. Therefore, I propose to consider it .
JPA : Introduction to Technology - 3

Create a project

Since JPA is about Java, we need a Java project. We could manually create the directory structure ourselves, add the necessary libraries ourselves. But it is much more convenient and correct to use project build automation systems (that is, in fact, it is just a program that will manage the assembly of projects for us. Create directories, put the necessary libraries in the classpath, etc.). One such system is Gradle. You can read more about Gradle here: " Getting Started with Gradle ". As we know, the functionality of Gradle (i.e. the actions it can do) is implemented using various Gradle Plugins. Let's use Gradle and the Gradle Build Init Plugin . Let's execute the command:

gradle init --type java-application
Gradle will make the necessary directory structure for us, create a basic declarative description of the project in the build script build.gradle. So, we have an application. We need to think about what we want to describe or model with our application. Let's use some modeling tool, for example: app.quickdatabasediagrams.com JPA : Introduction to Technology - 4Here it is worth saying that what we have described is our "domain model". A domain is some "subject area". In general, the domain is "possession" in Latin. In the Middle Ages, this was the name of the areas owned by kings or feudal lords. And in French it became the word "domaine", which translates simply as "region". Thus we have described our "domain model" = "subject model". Each element of this model is some "essence", something from real life. In our case, these are entities: Category ( Category), Topic ( Topic). Let's create a separate package for the entities, for example, with the name model. And add there Java classes that describe entities. In Java code, such entities are a regular POJO ,
public class Category {
    private Long id;
    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
Let's copy the contents of the class and make the class by analogy Topic. He will differ only in that he knows about the category to which he belongs. Therefore, let's add Topica category field and methods for working with it to the class:
private Category category;

public Category getCategory() {
	return category;
}

public void setCategory(Category category) {
	this.category = category;
}
Now we have a Java application that has its own domain model. It's time to start connecting to the JPA project now.
JPA : Introduction to Technology - 5

Adding JPA

So, as we remember, JPA is about the fact that we will save something in the database. Therefore, we need a database. To use a database connection in our project, we need to add a library for connecting to the database as a dependency. As we remember, we used Gradle, which created a build script for us build.gradle. In it, we will describe the dependencies that our project needs. Dependencies are those libraries without which our code cannot work. Let's start with a description of the dependence on the connection to the database. We do this in the same way as we would do, working simply with JDBC:

dependencies {
	implementation 'com.h2database:h2:1.4.199'
Now we have a DB. We can now add a layer or layer to our application that is responsible for mapping our Java objects to database concepts (from Java to SQL). As we remember, we are going to use an implementation of the JPA specification called Hibernate for this:

dependencies {
	implementation 'com.h2database:h2:1.4.199'
	implementation 'org.hibernate:hibernate-core:5.4.2.Final'
Now we need to configure JPA. If we read the specification and section "8.1 Persistence Unit", then we will know that the Persistence Unit is some kind of union of configurations, metadata and entities. And for JPA to work, you need to describe at least one Persistence Unit in the configuration file, which has the name persistence.xml. Its location is described in the specification chapter "8.2 Persistence Unit Packaging". According to this section, if we have a Java SE environment, then we must put it in the root of the META-INF directory.
JPA : Introduction to Technology - 6
The content is copied from the example given in the JPA specification in the chapter " 8.2.1 persistence.xml file":
<persistence>
	<persistence-unit name="CodeGym">
        <description>Persistence Unit For test</description>
        <class>hibernate.model.Category</class>
        <class>hibernate.model.Topic</class>
    </persistence-unit>
</persistence>
But this is not enough. We need to tell who our JPA Provider is, i.e. one who implements the JPA specification:
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
And now let's add settings ( properties). Some of them (beginning with javax.persistence) are standard JPA configurations and are described in the JPA specification in section "8.2.1.9 properties". Some of the configurations are provider-specific (in our case, they affect Hibernate as a Jpa Provider. Our settings block will look like this:
<properties>
    <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
    <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />
    <property name="javax.persistence.jdbc.user" value="sa" />
    <property name="javax.persistence.jdbc.password" value="" />
    <property name="hibernate.show_sql" value="true" />
    <property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
Now we have a JPA-compatible config persistence.xml, we have a Hibernate JPA provider, we have an H2 database, and we have 2 classes that are our domain model. Let's finally make this work. In the directory /test/java, our Gradle kindly generated a template for Unit tests and called it AppTest. Let's use it. As the "7.1 Persistence Contexts" chapter of the JPA specification says, entities in the JPA world live in a space called a "Persistence Context" (or Persistence Context). But we do not work directly with Persistence Context. For this we use Entity Manageror "entity manager". It is he who knows about the context and about what entities live there. We interact with Entity Manager'om. Then it remains only to understandEntity Manager? According to chapter "7.2.2 Obtaining an Application-managed Entity Manager" of the JPA specification, we should use EntityManagerFactory. Therefore, we arm ourselves with the JPA specification and take an example from the chapter "7.3.2 Obtaining an Entity Manager Factory in a Java SE Environment" and arrange it in the form of a simple Unit test:
@Test
public void shouldStartHibernate() {
	EntityManagerFactory emf = Persistence.createEntityManagerFactory( "CodeGym" );
	EntityManager entityManager = emf.createEntityManager();
}
Already this test will show "Unrecognized JPA persistence.xml XSD version" error. The reason is that persistence.xmlyou need to correctly specify the schema used, as stated in the JPA specification in section "8.3 persistence.xml Schema":
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">
Also, the order of the elements is important. Therefore, providermust be specified before class enumeration. After that, the test will run successfully. We have done the JPA direct connection. Before we move on, let's think about the rest of the tests. Each of our tests will require EntityManager. Let's make sure that each test has its own EntityManagerat the start of execution. In addition, we want the database to be new each time. Due to the fact that we use inmemorythe variant, it is enough to close the EntityManagerFactory. Creation Factoryis an expensive operation. But for tests - it's justified. JUnit allows you to specify methods that will be executed before (Before) and after (After) the execution of each test:
public class AppTest {
    private EntityManager em;

    @Before
    public void init() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory( "CodeGym" );
        em = emf.createEntityManager();
    }

    @After
    public void close() {
        em.getEntityManagerFactory().close();
        em.close();
    }
Now, before executing any test, a new one will be created EntityManagerFactory, which will entail the creation of a new database, because. hibernate.hbm2ddl.automatters create. And from the new factory we will get a new one EntityManager.
JPA : Introduction to Technology - 7

Entities

As we remember, we created earlier classes that describe our domain model. We have already said that these are our "essences". This is the Entity that we will manage with EntityManager. Let's write a simple test to save the category entity:
@Test
public void shouldPersistCategory() {
	Category cat = new Category();
	cat.setTitle("new category");
	// JUnit обеспечит тест свежим EntityManager'ом
	em.persist(cat);
}
But this test will not work right away, because we will get various errors that will help us understand what entities are:
  • Unknown entity: hibernate.model.Category
    Why doesn't Hibernate understand what Categoryit is entity? The thing is that entities must be described according to the JPA standard.
    Entity classes must be annotated with the annotation @Entityas stated in chapter "2.1 The Entity Class" of the JPA specification.

  • No identifier specified for entity: hibernate.model.Category
    Entities must have a unique ID that can be used to distinguish one record from another.
    According to the chapter "2.4 Primary Keys and Entity Identity" of the JPA specification "Every entity must have a primary key", i.e. each entity must have a "primary key". Such a primary key must be specified with an annotation@Id

  • ids for this class must be manually assigned before calling save()
    The ID has to come from somewhere. It can be specified manually, or it can be obtained automatically.
    Therefore, as mentioned in the chapters "11.2.3.3 GeneratedValue" and "11.1.20 GeneratedValue Annotation", we can specify the annotation @GeneratedValue.

Thus, in order for the category class to become an entity, we must make the following changes:
@Entity
public class Category {
    @Id
    @GeneratedValue
    private Long id;
In addition, the annotation @Idspecifies which Access Type. You can read more about the access type in the JPA specification, in section "2.3 Access Type". If very briefly, then because. we have indicated @Idabove the field ( field), then the access type will default field-basedto , not property-based. Therefore, the JPA provider will read and store values ​​directly from the fields. If we placed @Idabove the getter, then property-basedaccess would be used, i.e. via getter and setter. When executing the test, we see, among other things, which requests are sent to the database (thanks to the option hibernate.show_sql). But when saving, we do not see any insert's. It turns out that we actually did not save anything? JPA allows you to synchronize the persistence context and the database using the method flush:
entityManager.flush();
But if we execute it now, we will get an error: no transaction is in progress . And here comes the time to learn about how JPA uses transactions.
JPA : Introduction to Technology - 8

JPA Transactions

As we remember, JPA is based on the concept of Persistence Context. This is the place where entities live. And we manage entities through EntityManager. When we execute a command persist, we place the entity in the context. More precisely, we say EntityManager'y that it needs to be done. But this context is just some storage area. It is even sometimes referred to as a "first level cache". But it needs to be connected to the database. The command flushthat we previously failed with an error synchronizes data from the persistence context with the database. But this requires a transport, and that transport is a transaction. Transactions in JPA are described in the specification section "7.5 Controlling Transactions". There is a special API for using transactions in JPA:
entityManager.getTransaction().begin();
entityManager.getTransaction().commit();
We need to add transaction management to our code that runs before and after the tests:
@Before
public void init() {
	EntityManagerFactory emf = Persistence.createEntityManagerFactory( "CodeGym" );
	em = emf.createEntityManager();
	em.getTransaction().begin();
}
@After
public void close() {
	if (em.getTransaction().isActive()) {
		em.getTransaction().commit();
        }
	em.getEntityManagerFactory().close();
	em.close();
}
After adding, we will see an expression in the SQL language in the insert log, which did not exist before:
JPA : Introduction to Technology - 9
The changes accumulated in EntityManagerwere committed (confirmed and saved) to the database with the help of a transaction. Let's try to find our essence now. Let's create a test to search for an entity by its ID:
@Test
public void shouldFindCategory() {
	Category cat = new Category();
	cat.setTitle("test");
	em.persist(cat);
	Category result = em.find(Category.class, 1L);
	assertNotNull(result);
}
In this case, we will get the entity we saved earlier, but we will not see SELECT queries in the log. And all according to what we say: "Entity manager, please find me an entity Category with ID=1". And the entity manager first looks in its own context (it uses a kind of cache), and only if it doesn’t find it, it goes to look in the database. It is worth changing the ID to 2 (there is no such thing, we only saved 1 instance), as we will see that SELECTthe request appears. Because no entities were found in the context and EntityManagerit is trying to find the database entity. There are different commands that we can use to control the state of the entity in the context. The transition of an entity from one state to another is called the life cycle of an entity - lifecycle.
JPA : Introduction to Technology - 10

Entity Lifecycle

The life cycle of entities is described in the JPA specification in chapter "3.2 Entity Instance's Life Cycle". Because entities live in a context and are controlled by EntityManager, then they say that entities are managed, i.e. managed. Let's look at the life stages of an entity:
// 1. New or Transient (временный)
Category cat = new Category();
cat.setTitle("new category");
// 2. Managed or Persistent
entityManager.persist(cat);
// 3. Транзакция завершена, все сущности в контексте detached
entityManager.getTransaction().begin();
entityManager.getTransaction().commit();
// 4. Сущность изымаем из контекста, она становится detached
entityManager.detach(cat);
// 5. Сущность из detached можно снова сделать managed
Category managed = entityManager.merge(cat);
// 6. И можно сделать Removed. Интересно, что cat всё равно detached
entityManager.remove(managed);
And here's the schematic for proof:
JPA : Introduction to Technology - 11
JPA : Introduction to Technology - 12

Mapping

In JPA, we can describe the relationship of entities between each other. Recall that we already dealt with entity relationships between each other when we dealt with our domain model. Then we used the resource quickdatabasediagrams.com :
JPA : Introduction to Technology - 13
Establishing relationships between entities is called mapping or association (Association Mappings). The kinds of associations that can be established using JPA are listed below:
JPA : Introduction to Technology - 14
Let's look at an entity Topicthat describes a topic. What can we say about the relationship Topicto Category? Many Topicwill belong to the same category. Therefore, we need an association ManyToOne. Let's express this relationship in JPA:
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
To remember which annotations to put, you can remember that the last part is responsible for the field over which the annotation is indicated. ToOne- specific instance. ToMany- collections. Now we have a one-way connection. Let's make it a two-way link. Let's add to Categorythe knowledge about all Topicthat fall into this category. It must end with ToMany, because we have a list Topic. That is, the relation "To many" topics. The question remains - OneToManyeither ManyToMany:
JPA : Introduction to Technology - 15
On the same topic, a good answer can be found here: " Explain ORM oneToMany, manyToMany relation like I'm five ". If a category has a connection with ToManytopics, then each of these topics can have only one category, then it will be One, otherwise Many. So the Categorylist of all topics will look like this:
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "topic_id")
private Set<Topic> topics = new HashSet<>();
And let's not forget to actually Categorydescribe a getter to get a list of all topics:
public Set<Topic> getTopics() {
	return this.topics;
}
Bidirectional relationships are a very tricky thing to track automatically. Therefore, JPA shifts this responsibility to the developer. For us, this means that when we establish Topica relationship with an entity Category, we must ensure the consistency of the data ourselves. This is done simply:
public void setCategory(Category category) {
	category.getTopics().add(this);
	this.category = category;
}
Let's write a simple test to check:
@Test
public void shouldPersistCategoryAndTopics() {
	Category cat = new Category();
	cat.setTitle("test");
	Topic topic = new Topic();
	topic.setTitle("topic");
	topic.setCategory(cat);
 	em.persist(cat);
}
Mapping is a whole separate topic. Within the framework of this review, it should be understood by what means this is achieved. You can read more about mapping here:
JPA : Introduction to Technology - 16

JPQL

JPA introduces an interesting tool - queries in the Java Persistence Query Language. This language is similar to SQL but uses the Java object model rather than SQL tables. Consider an example:
@Test
public void shouldPerformQuery() {
	Category cat = new Category();
	cat.setTitle("query");
	em.persist(cat);
	Query query = em.createQuery("SELECT c from Category c WHERE c.title = 'query'");
 	assertNotNull(query.getSingleResult());
}
As we can see, in the query we used an indication of the entity Category, and not the table. And also on the field of this entity title. JPQL provides many useful features and claims to be a separate article. More details can be found in the review:
JPA : Introduction to Technology - 17

Criteria API

And finally, I would like to touch on the Criteria API. JPA introduces a dynamic query building tool. An example of using the Criteria API:
@Test
public void shouldFindWithCriteriaAPI() {
	Category cat = new Category();
	em.persist(cat);
	CriteriaBuilder cb = em.getCriteriaBuilder();
	CriteriaQuery<Category> query = cb.createQuery(Category.class);
	Root<Category> c = query.from(Category.class);
	query.select(c);
	List<Category> resultList = em.createQuery(query).getResultList();
	assertEquals(1, resultList.size());
}
This example is equivalent to executing the query " SELECT c FROM Category c". The Criteria API is a powerful tool. You can read more about it here:

Conclusion

As we can see, JPA provides a huge number of features and tools. Each of them requires experience and knowledge. Even as part of the JPA review, it turned out not to mention everything, not to mention a detailed dive. But I hope that after reading it, it became clearer what ORM and JPA are in general, how it works and what can be done with it. Well, for a snack I offer various materials: #Viacheslav
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION