JPA and Hibernate basics tutorial
2 |
3 | This is the tutorial on JPA and Hibernate configurations and basics features
4 | ### Pre-conditions :heavy_exclamation_mark:
5 | You're supposed to have basic knowledge of ORM, and be able to write Java code.
6 | ##
7 | ### Best practices
8 | * avoid auto-commit mode
9 | * prefer *session-per-request* (*entity-manager-per-request*) transaction pattern
10 |
11 |
--------------------------------------------------------------------------------
/jpa-hibernate-tutorial-model/src/main/java/com/bobocode/model/advanced/Customer.java:
--------------------------------------------------------------------------------
1 | package com.bobocode.model.advanced;
2 |
3 | import com.bobocode.model.basic.User;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | import javax.persistence.Entity;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | @NoArgsConstructor
14 | @Getter
15 | @Setter
16 | @ToString
17 | @Entity
18 | public class Customer extends User {
19 | private List14 | * {@link EntityManager} allows to perform database operations with JPA entities. It represents a db session. E.g. each 15 | * user should get a new instance of {@link EntityManager} each time to perform db operations on JPA entity. 16 | *
17 | * {@link EntityManagerFactory} is a thread-safe factory that allow to create {@link EntityManager} instances.
18 | */
19 | public class JpaEntryPoint {
20 | public static void main(String[] args) {
21 | EntityManagerFactory entityManagerFactory = createEntityManagerFactory();
22 | EntityManager entityManager = entityManagerFactory.createEntityManager();
23 |
24 | Account account = TestDataGenerator.generateAccount();
25 |
26 | entityManager.getTransaction().begin();
27 | entityManager.persist(account);
28 | entityManager.getTransaction().commit();
29 |
30 | System.out.println(account);
31 |
32 | entityManager.close();
33 | entityManagerFactory.close();
34 | }
35 |
36 | /**
37 | * Creates an instance of {@link EntityManagerFactory}. It uses JPA util class {@link Persistence} that allows
38 | * to create an instance by its name. All JPA configuration required to create {@link EntityManagerFactory} are
39 | * located in resources /META-INF/persistence.xml which is a default location for JPA configs.
40 | *
41 | * @return instance of entity manager factory
42 | */
43 | private static EntityManagerFactory createEntityManagerFactory() {
44 | return Persistence.createEntityManagerFactory("BasicEntitiesH2");
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/jpa-hibernate-tutorial-util/src/main/java/com/bobocode/util/JpaUtil.java:
--------------------------------------------------------------------------------
1 | package com.bobocode.util;
2 |
3 | import com.bobocode.util.exception.JpaUtilException;
4 |
5 | import javax.persistence.EntityManager;
6 | import javax.persistence.EntityManagerFactory;
7 | import javax.persistence.Persistence;
8 | import java.util.function.Consumer;
9 | import java.util.function.Function;
10 |
11 | public class JpaUtil {
12 |
13 | private static EntityManagerFactory emf;
14 |
15 | public static void init(String persistenceUnitName) {
16 | emf = Persistence.createEntityManagerFactory(persistenceUnitName);
17 | }
18 |
19 | public static EntityManagerFactory getEntityManagerFactory() {
20 | return emf;
21 | }
22 |
23 | public static void close() {
24 | emf.close();
25 | }
26 |
27 | public static void performWithinPersistenceContext(Consumer
Hibernate "Dirty checking" tutorial
2 | This is the tutorial well-known Hibernate feature called "Dirty checking"
3 |
4 | ### Pre-conditions :heavy_exclamation_mark:
5 | You're supposed to have basic knowledge of ORM, JPA and Hibernate.
6 | ##
7 | **Dirty checking mechanism** is a *Hibernate* feature that **transforms entity field changes into SQL `UPDATE`** statements in the
8 | scope of *Persistence Context (PC)*.
9 |
10 | **By default, dirty checking is enabled.** For each managed(persistent) entity it Hibernate creates a snapshot of persisted
11 | data (entity copy). That data is stored in the scope of *PC* as an array of `Object` elements. That array basically holds
12 | the same data as corresponding database row. At flush time, every entity is compared with its snapshot copy, if some changes detected, Hibernate generates appropriate
13 | `UPDATE` statement.
14 |
15 | ```java
16 | performWithinPersistenceContext(entityManager -> { // Persistence Context begins
17 | Account account = entityManager.find(Account.class, accountId); // account is managed by Hibernate
18 | account.setLastName(newLastName); // this changes will be detected by Dirty Checking mechanism
19 | // on flush dirty checking will generate UPDATE statement and will send it to the database
20 | });// Persistence Context ends
21 | ```
22 |
23 | Please note, that this mechanism has **huge performance impact**, because it **doubles the size of the *PC***, requires
24 | **additional CPU resources** to check each attribute of each manges entity, and causes **additional Garbage Collection.**
25 | In order to **turn of dirty checking**, you should set default read-only for the `Session`, or specify a hint when creating
26 | new select query.
27 |
28 | ```java
29 | performWithinPersistenceContext(entityManager -> {
30 | Session session = entityManager.unwrap(Session.class);
31 | session.setDefaultReadOnly(true); // turns off dirty checking for this session (for this entityManager)
32 | Account managedAccount = entityManager.find(Account.class, accountId);
33 | managedAccount.setFirstName("XXX"); // won't cause SQL UPDATE statement since dirty checking is disabled
34 | });
35 | ```
36 |
37 | In order to disable dirty checking for a particular entity on select use query hints:
38 |
39 | ```java
40 | performWithinPersistenceContext(entityManager -> {
41 | Account managedAccount = entityManager.createQuery("select a from Account a where a.email = :email", Account.class)
42 | .setParameter("email", email)
43 | .setHint(QueryHints.HINT_READONLY, true)// turns off dirty checking for this particular entity
44 | .getSingleResult();
45 | managedAccount.setFirstName("XXX"); // won't cause SQL UPDATE statement since dirty checking is disabled for this entity
46 | });
47 | ```
48 |
49 | ### Best practices
50 | * always use **read-only** queries for all `SELECT` statements
51 | * keep *Persistence Context* size as small as possible
52 |
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
JPA and Hibernate tutorial
2 | The list of tutorials on ORM, JPA and Hibernate features and best practices
3 |
4 | ### Pre-conditions :heavy_exclamation_mark:
5 | You're supposed to be familiar with OOP, have basic knowledge of JDK, and be able to write Java code.
6 | ### Related information :information_source:
7 | #### Overview
8 | * [Java Persistence with Hibernate](https://www.amazon.com/Java-Persistence-Hibernate-Christian-Bauer/dp/1617290459/ref=sr_1_1?s=books&ie=UTF8&qid=1538386848&sr=1-1&keywords=java+persistence+with+hibernate%2C+second+edition) :green_book:
9 | * **1.1.3** Using SQL in Java. *(JDBC API disadvantages)*
10 | * **1.2** The paradigm mismatch. *(Object-oriented vs Relational model)*
11 | * **1.3** ORM and JPA. *(What are ORM advantages?)*
12 | * **3.1.1** A layered architecture. *(What is a persistence (DAO) layer?)*
13 | * **3.1.2** Analyzing the business domain. *(What is a domain model?)*
14 | ### JPA and Hibernate basics
15 | * [JPA and Hibernate ORM basics tutorial](https://github.com/bobocode-projects/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics)
16 | * [A beginner’s guide to entity state transitions with JPA and Hibernate](https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/)
17 | * [Java Persistence with Hibernate](https://www.amazon.com/Java-Persistence-Hibernate-Christian-Bauer/dp/1617290459/ref=sr_1_1?s=books&ie=UTF8&qid=1538386848&sr=1-1&keywords=java+persistence+with+hibernate%2C+second+edition) :green_book:
18 | * **10.1.1** Entity instance states
19 | * **10.1.2** The persistence context
20 | * **10.2.1 - 10.2.6** The EntityManager interface. *(Entity Create Read Update Remove (CRUD) operations)*
21 | * **10.3.4** Merging entity instances. *(What's happening on merge?)*
22 | ### Dirty checking
23 | * ["Dirty checking" tutorial](https://github.com/bobocode-projects/jpa-hibernate-tutorial/tree/master/dirty-checking-mechanism)
24 | * [The anatomy of Hibernate dirty checking mechanism](https://vladmihalcea.com/the-anatomy-of-hibernate-dirty-checking/)
25 | * [Java Persistence with Hibernate](https://www.amazon.com/Java-Persistence-Hibernate-Christian-Bauer/dp/1617290459/ref=sr_1_1?s=books&ie=UTF8&qid=1538386848&sr=1-1&keywords=java+persistence+with+hibernate%2C+second+edition) :green_book:
26 | * **10.2.3** Retrieving and modifying persistent data. *(Entity update)*
27 | ### Lazy loading
28 | * [Java Persistence with Hibernate](https://www.amazon.com/Java-Persistence-Hibernate-Christian-Bauer/dp/1617290459/ref=sr_1_1?s=books&ie=UTF8&qid=1538386848&sr=1-1&keywords=java+persistence+with+hibernate%2C+second+edition) :green_book:
29 | * **12.1.1** Understanding entity proxies. *(One entity lazy loading)*
30 | * **12.1.2** Lazy persistent collections. *(Collection lazy loading)*
31 | * **12.2.1** The n+1 selects problem
32 |
33 |
--------------------------------------------------------------------------------
/jpa-hibernate-basics/src/main/java/com/bobocode/JpaEntityStates.java:
--------------------------------------------------------------------------------
1 | package com.bobocode;
2 |
3 |
4 | import com.bobocode.model.Account;
5 | import com.bobocode.util.TestDataGenerator;
6 |
7 | import static com.bobocode.util.JpaUtil.*;
8 |
9 | /**
10 | * This tutorial provides examples of different JPA entity states. {@link Account} instance, which is a JPA entity
11 | * is moved from state TRANSIENT to state PERSISTENT, then it becomes DETACHED. After it again becomes PERSISTENT
12 | * and soon REMOVED.
13 | */
14 | public class JpaEntityStates {
15 | public static void main(String[] args) {
16 | init("BasicEntitiesH2");
17 |
18 | Account account = TestDataGenerator.generateAccount();
19 |
20 | printAccountInTransientState(account);
21 | printAccountInPersistentState(account);
22 | printAccountInDetachedState(account);
23 | printAccountInRemovedState(account);
24 |
25 | close();
26 | }
27 |
28 | /**
29 | * Receives a new account instance. It doesn't have associated id. It is not stored in the database.
30 | *
31 | * @param account account in transient state
32 | */
33 | private static void printAccountInTransientState(Account account) {
34 | System.out.printf("Account in TRANSIENT state: %s%n", account);
35 | }
36 |
37 | /**
38 | * Receives an account in transient state. Opens a persistence context. Stores account into database, and prints
39 | * an account in persistent state. Persistent account that has associated id, is stored in the database and
40 | * has is associated with a persistence context.
41 | *
42 | * @param account account in transient state
43 | */
44 | private static void printAccountInPersistentState(Account account) {
45 | // performs logic in scope of persistence context
46 | performWithinPersistenceContext(entityManager -> {
47 | entityManager.persist(account); // stores an account in the database (makes it persistent)
48 | System.out.printf("Account in PERSISTENT state: %s%n", account);
49 | });
50 | }
51 |
52 | /**
53 | * Receives an account in DETACHED state and prints it. Detached account is an account that has associated id, has
54 | * corresponding database record, but does not associated with a persistent context.
55 | *
56 | * @param account account in DETACHED state
57 | */
58 | private static void printAccountInDetachedState(Account account) {
59 | System.out.printf("Account in DETACHED state: %s%n", account);
60 | }
61 |
62 | /**
63 | * Receives an account in detached state. Opens a persistence context, merges an account to make it maneged (persistent)
64 | * and then removes an account. Prints account in REMOVED state
65 | *
66 | * @param account account in DETACHED state
67 | */
68 | private static void printAccountInRemovedState(Account account) {
69 | performWithinPersistenceContext(entityManager -> {
70 | Account mergedAccount = entityManager.merge(account);
71 | entityManager.remove(mergedAccount);
72 | System.out.printf("Account in REMOVED state: %s%n", mergedAccount);
73 |
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/persistence-context/src/main/java/com/bobocode/ActionQueue.java:
--------------------------------------------------------------------------------
1 | package com.bobocode;
2 |
3 | import com.bobocode.model.Account;
4 | import com.bobocode.util.JpaUtil;
5 | import com.bobocode.util.TestDataGenerator;
6 |
7 | import static com.bobocode.util.JpaUtil.performWithinPersistenceContext;
8 |
9 | /**
10 | * This code example shows how Hibernate's {@link org.hibernate.engine.spi.ActionQueue} work
11 | */
12 | public class ActionQueue {
13 | public static void main(String[] args) {
14 | JpaUtil.init("BasicEntitiesH2");
15 |
16 | tryToSaveAccountWithSameEmailAfterRemove();
17 | tryToSaveAccountWithSameEmailAfterEmailUpdate();
18 |
19 | JpaUtil.close();
20 | }
21 |
22 | private static void tryToSaveAccountWithSameEmailAfterRemove() {
23 | try {
24 | saveAccountWithSameEmailAfterRemove();
25 | } catch (Exception e) {
26 | System.out.println("Second account wasn't stored, transaction is rolled back");
27 | }
28 |
29 | }
30 |
31 | /**
32 | * Stores and account, then removes it and tries to store the other account with the save email in the scope of the
33 | * same transaction. It does not work because Hibernate operations are sorted according to its priorities.
34 | * E.g. INSERT is done before UPDATE
35 | */
36 | private static void saveAccountWithSameEmailAfterRemove() {
37 | Account account = TestDataGenerator.generateAccount();
38 | performWithinPersistenceContext(entityManager -> entityManager.persist(account));
39 | Account secondAccount = TestDataGenerator.generateAccount();
40 | secondAccount.setEmail(account.getEmail());
41 |
42 | performWithinPersistenceContext(entityManager -> {
43 | Account managedAccount = entityManager.merge(account);
44 | entityManager.remove(managedAccount); // remove first account from the database
45 | // won't work because insert will be performed before remove
46 | entityManager.persist(secondAccount); // store second account with the same email
47 | });
48 | }
49 |
50 |
51 | private static void tryToSaveAccountWithSameEmailAfterEmailUpdate() {
52 | try {
53 | saveAccountWithSameEmailAfterEmailUpdate();
54 | } catch (Exception e) {
55 | System.out.println("Second account wasn't stored, transaction is rolled back");
56 | }
57 |
58 | }
59 |
60 | /**
61 | * Stores and account, then updates its email and tries to store the other account with the previous email value
62 | * in the scope of the same transaction. It does not work because Hibernate operations are sorted according to
63 | * its priorities.
64 | * E.g. INSERT is done before DELETE
65 | */
66 | private static void saveAccountWithSameEmailAfterEmailUpdate() {
67 | Account account = TestDataGenerator.generateAccount();
68 | Account secondAccount = TestDataGenerator.generateAccount();
69 | secondAccount.setEmail(account.getEmail());
70 |
71 | performWithinPersistenceContext(entityManager -> {
72 | entityManager.persist(account);
73 | account.setEmail("UPDATED"); // change the email of the first account
74 | // won't work because insert will be performed before update
75 | entityManager.persist(secondAccount); // store second account with the same email
76 | });
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/entity-relationships-management/src/main/java/com/bobocode/StoringNewRelationship.java:
--------------------------------------------------------------------------------
1 | package com.bobocode;
2 |
3 | import com.bobocode.model.advanced.RoleType;
4 | import com.bobocode.model.basic.Role;
5 | import com.bobocode.model.basic.User;
6 | import com.bobocode.util.JpaUtil;
7 | import com.bobocode.util.TestDataGenerator;
8 |
9 | import static com.bobocode.util.JpaUtil.performWithinPersistenceContext;
10 |
11 | /**
12 | * This example shows different ways of storing new relationship between {@link User} and {@link Role}
13 | */
14 | public class StoringNewRelationship {
15 | public static void main(String[] args) {
16 | JpaUtil.init("BasicEntitiesH2");
17 | User user = TestDataGenerator.generateUser();
18 |
19 | storeNewUserWithRole(user, RoleType.USER);
20 | printUserById(user.getId());
21 |
22 | addNewRole(user, RoleType.OPERATOR);
23 | printUserById(user.getId());
24 |
25 | addNewRole(user.getId(), RoleType.ADMIN);
26 | printUserById(user.getId());
27 |
28 | JpaUtil.close();
29 | }
30 |
31 | /**
32 | * Stores new {@link User} with a specific role. This method uses cascade persist and a helper method
33 | * {@link User#addRole(Role)}, which links {@link User} object with new {@link Role} object
34 | *
35 | * @param user NEW user
36 | * @param roleType a role type of NEW role
37 | */
38 | private static void storeNewUserWithRole(User user, RoleType roleType) {
39 | performWithinPersistenceContext(entityManager -> {
40 | Role role = Role.valueOf(roleType);
41 | user.addRole(role);
42 | entityManager.persist(user); // cascade operation will store also new role records
43 | });
44 | }
45 |
46 | /**
47 | * Loads and prints a {@link User} by id
48 | *
49 | * @param userId
50 | */
51 | private static void printUserById(long userId) {
52 | performWithinPersistenceContext(entityManager -> {
53 | User mangedUser = entityManager.find(User.class, userId);
54 | System.out.println(mangedUser);
55 | });
56 | }
57 |
58 | /**
59 | * Adds a new role to an existing user. This method receives existing user object and uses it helper method
60 | * {@link User#addRole(Role)} to add a new role
61 | *
62 | * @param user DETACHED user
63 | * @param roleType role type of new role
64 | */
65 | private static void addNewRole(User user, RoleType roleType) {
66 | Role role = Role.valueOf(roleType);
67 | user.addRole(role);
68 | performWithinPersistenceContext(entityManager -> entityManager.merge(user));
69 | }
70 |
71 | /**
72 | * Adds a new role to an existing user by its id. This method does not require a full {@link User} object to add
73 | * a new role. It uses a special method {@link javax.persistence.EntityManager#getReference(Class, Object)} that
74 | * allow to get an entity proxy by its id. This proxy is used to store a new relation between {@link Role} and
75 | * {@link User}. User is is enough to store the relation because role store only user id as a foreign key.
76 | *
77 | * @param userId stored user id
78 | * @param roleType role type of new role
79 | */
80 | private static void addNewRole(long userId, RoleType roleType) {
81 | performWithinPersistenceContext(entityManager -> {
82 | Role role = Role.valueOf(roleType);
83 | User userProxy = entityManager.getReference(User.class, userId); // does not call db
84 | role.setUser(userProxy);
85 | entityManager.persist(role);
86 | });
87 | }
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/entity-relationships-management/src/main/java/com/bobocode/CascadeOperations.java:
--------------------------------------------------------------------------------
1 | package com.bobocode;
2 |
3 | import com.bobocode.model.basic.Address;
4 | import com.bobocode.model.basic.Role;
5 | import com.bobocode.model.basic.User;
6 | import com.bobocode.util.JpaUtil;
7 | import com.bobocode.util.TestDataGenerator;
8 |
9 | import javax.persistence.OneToMany;
10 | import java.util.List;
11 |
12 | import static com.bobocode.util.JpaUtil.performWithinPersistenceContext;
13 |
14 | public class CascadeOperations {
15 |
16 | public static void main(String[] args) {
17 | JpaUtil.init("BasicEntitiesH2");
18 |
19 |
20 | User user = TestDataGenerator.generateUser();
21 | List