# JPA and Hibernate

## Introduction

### Object-Relational-Mapping (ORM)

* Initially, the property of java object is unmatched with the table of database, and we use JDBC directly to connect to the database which require us to write sql command for many times which will be time-consuming

![The differences between table and java object](https://1374779285-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MFW3x2aqEO8GF2kr3VU%2F-MKh-xKoQEwZhDh27AS0%2F-MKh3XZHv_kSDg7zBvx-%2Fimage.png?alt=media\&token=56e50a84-9a7e-4274-b475-e6987664bb43)

* So , we need Object-Relational-Mapping (ORM) to allow the conversion of data between java object and table of database. We can access to our target java object, and then converse the suitable data format automatically and store into database.

### JPA (Java Persistence API)

* It is a standard that providing method of insert, delete, update

### Hibernate

* Hibernate is one of the solution of ORM and is a hidden implementation of JPA interface, so as to convert the method of JPA into relative SQL command to access the table &#x20;
* Hibernate will create the sql command based the object, do the work of JDBC to connect to database

![](https://1374779285-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MFW3x2aqEO8GF2kr3VU%2F-MKhAaTr2M_WMgC4ncyp%2F-MKhB1UVSDT6jAq6abLH%2Fimage.png?alt=media\&token=599f6bf8-43b2-471e-be34-79cc7a391c2c)

## Entity Manager

### Introduction

* EntityManager is part of the Java Persistence API. Chiefly, it implements the programming interfaces and lifecycle rules defined by the JPA 2.0 specification.
* It implements the hidden logic of JPA repository to manage the state of entity

### Life Cycle

There are mainly four states of the entity :

1. Transient State
2. Persistent State
3. Detached State
4. Removed State

![](https://1374779285-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MFW3x2aqEO8GF2kr3VU%2Fuploads%2FwNE2yLfKh9LfJP9pHRi9%2Fimage.png?alt=media\&token=43f38b28-be26-45f0-b639-dbefb19ee1e5)

#### Transient State

* The transient state is the first state of an entity object. When we instantiate an object of a [POJO class](https://www.geeksforgeeks.org/pojo-vs-java-beans/) using the new operator then the object is in the transient state. This object is not connected with any hibernate session.

```java
Student student = new Student("email@dot.com");
```

#### Persistent State

* Once the object is connected with the Hibernate Session then the object moves into the Persistent State.&#x20;
* In this state. each object represents one row in the database table. Therefore, if we make any changes in the data then hibernate will detect these changes and make changes in the database table.

```java
Student student = new Student("test@email.com");
// Make changes and see if the data is updated automatically
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
entityManager.persist(student);
//purposely made changes - did not manually update
student.setEmail("updated_email@gmail.com");
Long persistedId = student.getId();
tx.commit();
entityManager.close();
```

#### Detached  State

* For converting an object from Persistent State to Detached State, we either have to close the session or we have to clear its cache. As the session is closed here or the cache is cleared, then any changes made to the data will not affect the database table.

```java
tx.begin();
student = entityManager.find(Student.class, persistedId);
tx.commit();
entityManager.close();
System.out.println("Persisted Student: " + student);
student.setEmail("updated_again@gmail.com");
```

#### Removed State

* In the removed state, when the entity object is deleted from the database then the entity object is known to be in the removed state.

```java
//Remove an Object from the database
entityManager = entityManagerFactory.createEntityManager();
tx = entityManager.getTransaction();
tx.begin();
student = entityManager.find(Student.class, persistedId);
entityManager.remove(student);
tx.commit();
entityManager.close();
```

## Pre-Action&#x20;

* Install Dependencies

```markup
	<!-- MySQL Database Driver -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
	</dependency>
	
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	
```

* Enter the configuration of database

```
spring.jpa.hibernate.ddl-auto = update
spring.datasource.url = jdbc:mysql://localhost:3306/test?useUnicode=yes&characterEncoding=UTF-8
spring.datasource.username = root
spring.datasource.password = 1234
```

## Modal

* Setup a modal of the java object which will be used to map to its relative table in database

![The format of relative table ](https://1374779285-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MFW3x2aqEO8GF2kr3VU%2F-MKhFLmM55ECICMsP6ui%2F-MKhGiAWPv2vpxJL_rXJ%2Fimage.png?alt=media\&token=067bbf23-dad0-4518-9295-44fac47c196f)

```java
@Entity(name = "student_info")
@DynamicInsert
@DynamicUpdate
public class Student
{
	@Id
	//@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id")
	private String studentId;

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

	@Column(name = "age")
	private Integer studentAge;

	@Column(name = "class")
	private String studentClass;

	public Student()
	{
	}

	public String getStudentId() {
		return studentId;
	}

	public void setStudentId(String studentId) {
		this.studentId = studentId;
	}

	public String getStudentName() {
		return studentName;
	}

	public void setStudentName(String studentName) {
		this.studentName = studentName;
	}

	public Integer getStudentAge() {
		return studentAge;
	}

	public void setStudentAge(Integer studentAge) {
		this.studentAge = studentAge;
	}

	public String getStudentClass() {
		return studentClass;
	}

	public void setStudentClass(String studentClass) {
		this.studentClass = studentClass;
	}
}
```

* @Entity:  To label a class which will be used to mapped to its relational table, but also occupy columns and row to the table
* @Column: As the name of column of table may not be the same as the class, it should be used top label the variable so as to map to its relational column with the name provided
* @Id: To label the variable as a primary key
* @GeneratedValue : It is mainly divided into 3 different strategies - Identity, Auto, Sequence, Table
* &#x20;Identity:  The primary key of SQL have set the default value or auto-increment
* &#x20;Auto: No need do a setter and getter for primary key, the primary key will increased automatically and make sure the primary key of sql must be integer type
* Not set: The primary key is a string type and set by setter
* @DynamicInsert / DynamicUpdate: If the field is null, the field will not be inserted or updated , in order to improve the efficiency, detail: <https://www.gaoyaxuan.net/blog/425.html>

## Repository

* An interface which contains the method of JPA to access the table , we can also define the custom method on there.

```java
@Repository
public interface StudentRepository extends JpaRepository<Student,String>
{
   Student getStudentByStudentName(String name);
}
```

* 1st parameter is a entity class
* 2nd parameter is the type of primary key

## Basic Operation (CRUD)&#x20;

### Insert

```java
	@Override
	public void insertNewStudent(String name, Integer age, String studentClass){
		Student student = new Student();
		String studentId = UUID.randomUUID().toString();
		student.setStudentId(studentId);
		student.setStudentName(name);
		student.setStudentAge(age);
		student.setStudentClass(studentClass);
		studentRepository.save(student);
	}
```

### Update

```java
@Override
	public void updateStudentName(String oldName, String newName){
		Student student = studentRepository.getStudentByStudentName(oldName);
		student.setStudentName(newName);
		studentRepository.save(student);
	}
```

### Delete

```java
@Override
	public void deleteStudentId(String id){
		studentRepository.deleteById(id);
	}
```

## One-To-One Relationship

### SQL&#x20;

```sql
CREATE TABLE student_info(
	id VARCHAR(100) NOT NULL,
	name VARCHAR(100) NOT NULL,
	age int NOT NULL,
	class VARCHAR(100) NOT NULL,
	PRIMARY KEY (id)
)ENGINE = InnoDB;

CREATE TABLE student_address(
    id int NOT NULL,
    address VARCHAR(100) NOT NULL,
    student_id VARCHAR (100),
    PRIMARY KEY (id)
)ENGINE = InnoDB;
```

### Modals

```java
@Entity(name = "student_info")
@DynamicInsert
@DynamicUpdate
public class Student
{
	@Id
	@Column(name = "id")
	private String studentId;

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

	@Column(name = "age")
	private Integer studentAge;

	@Column(name = "class")
	private String studentClass;

	@OneToOne(mappedBy = "student", cascade = CascadeType.ALL)
	private Address address;
	...
}
```

```java
@Entity(name = "student_address")
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

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

    @OneToOne
    @JoinColumn(name = "student_id")
    private Student student;
    ...
}
```

### Service

```java
	@Override
	public void insertNewStudent(String name, Integer age, 
	String studentClass, String address){
		Student student = new Student();
		Address address1 = new Address();
		String studentId = UUID.randomUUID().toString();
		address1.setAddress("MK");
		address1.setStudent(student);
		student.setStudentId(studentId);
		student.setStudentName(name);
		student.setStudentAge(age);
		student.setStudentClass(studentClass);
		student.setAddress(address1);
		studentRepository.save(student);
	}
```

* Since Student is a entry point, so cascade must be added to Student class

## One-To-Many Relationship

### SQL&#x20;

```sql
CREATE TABLE student_info(
	id VARCHAR(100) NOT NULL,
	name VARCHAR(100) NOT NULL,
	age int NOT NULL,
	class VARCHAR(100) NOT NULL,
	PRIMARY KEY (id)
)ENGINE = InnoDB;

CREATE TABLE student_hobby(
    id int NOT NULL,
    hobby VARCHAR (100) NOT NULL,
    student_id VARCHAR (100),
    PRIMARY KEY (id)
)
```

### Modals

```java
@Entity(name = "student_info")
@DynamicInsert
@DynamicUpdate
public class Student
{
	@Id
	@Column(name = "id")
	private String studentId;

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

	@Column(name = "age")
	private Integer studentAge;

	@Column(name = "class")
	private String studentClass;

	@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
	private List<Hobby> hobbyList;

	...
}
```

```java
@Entity(name = "student_hobby")
public class Hobby {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;
    ...
}
```

### Service

```java
@Override
	public void insertNewStudent(String name, Integer age, String studentClass){
		Student student = new Student();
		List<Hobby> hobbyList = new ArrayList<>() ;
		hobbyList.add(new Hobby("football",student));
		hobbyList.add(new Hobby("basketball",student));
		String studentId = UUID.randomUUID().toString();
		student.setStudentId(studentId);
		student.setStudentName(name);
		student.setStudentAge(age);
		student.setStudentClass(studentClass);
		student.setHobbyList(hobbyList);
		studentRepository.save(student);
	}
```

* Fetch type is divided into 2 types - lazy (default) and eager
* Lazy: the list of hobby will not be obtained immediately when a student is obtained from database, only when the getter of student is called, the list of hobby can be obtained
* Eager: When student is obtained from database, the list of hobby is obtained immediately, even the list will not be used
* Fetch Mode:&#x20;

&#x20;       <https://www.baeldung.com/hibernate-fetchmode>

&#x20;       <https://www.jianshu.com/p/23bd82a7b96e>

* Lazy -> SELECT(default)
* EAGER -> JOIN(default),  SUB-SELECT

## Many-To-Many Relationship

### SQL

```sql
CREATE TABLE student_info(
	id VARCHAR(100) NOT NULL,
	name VARCHAR(100) NOT NULL,
	age int NOT NULL,
	class VARCHAR(100) NOT NULL,
	PRIMARY KEY (id)
)ENGINE = InnoDB;

CREATE TABLE course_info(
    id VARCHAR(100) NOT NULL,
    name VARCHAR(100) NOT NULL,
    PRIMARY KEY(id)
)

CREATE TABLE student_course(
    student_id VARCHAR(100) NOT NULL,
    course_id VARCHAR(100) NOT NULL,
    PRIMARY KEY(student_id, course_id)
)
```

### Modals

```java
@Entity(name = "student_info")
@DynamicInsert
@DynamicUpdate
public class Student
{
	@Id
	@Column(name = "id")
	private String studentId;

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

	@Column(name = "age")
	private Integer studentAge;

	@Column(name = "class")
	private String studentClass;

	@ManyToMany(cascade = CascadeType.ALL, mappedBy = "studentList")
	private List<Course> courseList;
	
	...
}
```

```java
@Entity(name = "course_info")
public class Course {
    @Id
    @Column(name = "id")
    private String id;

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

    @ManyToMany
    @JoinTable(
            name = "student_course",
            joinColumns = @JoinColumn(name = "course_id"),
            inverseJoinColumns = @JoinColumn(name = "student_id")
    )
    private List<Student> studentList;

    ...
}
```

### Service

```sql
@Override
	public void insertNewStudent(){
		List<Student> studentList1 = new ArrayList<>();
		List<Student> studentList2 = new ArrayList<>();
		List<Student> studentList3 = new ArrayList<>();
		List<Course>  courseList1 = new ArrayList<>();
		List<Course>  courseList2 = new ArrayList<>();
		Student peter = new Student("s1","Peter",18,"6D");
		Student tom = new Student("s2","Tom",19,"6C");
		Course chinese = new Course("c1","Chinese");
		Course english = new Course("c2","English");
		Course math = new Course("c3","Math");
		studentList1.add(peter);
		studentList2.add(peter);
		studentList2.add(tom);
		studentList3.add(tom);
		courseList1.add(chinese);
		courseList1.add(english);
		courseList2.add(english);
		courseList2.add(math);
		peter.setCourseList(courseList1);
		tom.setCourseList(courseList2);
		chinese.setStudentList(studentList1);
		english.setStudentList(studentList2);
		math.setStudentList(studentList3);
		studentRepository.saveAll(studentList2);
	}
```

## Customize Repository

### Method 1

#### Custom Interface

```java
public interface CustomStudentRepository {
    List<String> getStudentByClass(String className);
    Student getStudentById(String Id);
}
```

#### Custom Implementation

```java
@Repository
public class CustomStudentRepositoryImpl implements CustomStudentRepository {

    @Autowired
    private EntityManager entityManager;

    private static final String STUDENT_TABLE = "student_info";

    @Override
    public List<String> getStudentByClass(String className){
        String sql = String.format("SELECT name FROM %s WHERE class = :className", STUDENT_TABLE);
        List<String> nameList = entityManager.createNativeQuery(sql).setParameter("className",className).getResultList();
        return nameList;
    }
    
    @Override
    public Student getStudentById(String Id){
        String sql = String.format("SELECT * FROM %s WHERE id = :Id", STUDENT_TABLE);
        Student student = (Student) entityManager.createNativeQuery(sql, Student.class).setParameter("Id", Id).getSingleResult();
        return student;
    }
}
```

#### Original Interface

```java
@Repository
public interface StudentRepository extends JpaRepository<Student,String>, CustomStudentRepository
{
   Student getStudentByStudentName(String name);
   Page<Student> findAll(Pageable pageable);
}
```

### Method 2

```java
@Repository
public interface StudentRepository extends JpaRepository<Student,String>, CustomStudentRepository
{
   Student getStudentByStudentName(String name);

   Page<Student> findAll(Pageable pageable);

   @Query(value = "SELECT name FROM student_info WHERE age = :age", nativeQuery = true)
   List<String> customFindStudentByStudentAge(@Param("age") Integer age);

   @Query(value = "SELECT * FROM student_info WHERE id = :id", nativeQuery = true)
   Student customFindStudentByStudentId(@Param("id") String  id);
}
```

### Reference

{% embed url="<https://www.baeldung.com/spring-data-jpa-query>" %}

## Transaction

### Introduction

* **Spring creates proxies for all the classes annotated with&#x20;*****@Transactional***, either on the class or on any of the methods. The proxy allows the framework to inject transactional logic before and after the running method, mainly for starting and committing the transaction, which applies  AOP on the application

### Implementation

```java
@Service
@Transactional
public class FooService {
    //...
}
```

* Apart from that, we can also use programatic approach to implement by using transactional template

```java
public void saveResult(String input, String input2) {
        TransactionTemplate txTemplate = new TransactionTemplate(txManager);
        txTemplate.execute(status -> {
            try{
                testRepository.save(input);
                testRepository.saveAll(input2);
                status.flush();
                log.info(
                    "Saved {} newRawTransactions for S3Object",
                    rawTransactions.size()
                 );
            }
            catch(Exception e){
                status.setRollbackOnly();
            }
            return null;
        });
    }
```

### Propagation

#### Introduction

* Propagation determines how transactions treats with other transaction
* Spring calls *TransactionManager::getTransaction* to get or create a transaction according to the propagation.

#### Required

* If a transaction is already active when the method is called, the method will run within that transaction. If a transaction is not active, a new transaction will be started.

#### Required\_new

* This option always starts a new transaction, regardless of whether a transaction is already active when the method is called. Any existing transaction will be **suspended** until the new transaction is complete.&#x20;
* If an exception is thrown and caught within the transaction, only the changes made within the current transaction will be rolled back, and the outer transaction will continue unaffected.

```java
/**
* ProductService.java
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateProductQuantityWithRequiresNewPropagation(OrderItem item) {
    // Find the product
    Product product = productRepository.findById(item.getProduct().getId())
            .orElseThrow(() -> new RuntimeException("Product not found"));

    // Update the stock quantity
    int newQuantity = product.getQuantity() - item.getQuantity();
    if (newQuantity < 0) {
        throw new RuntimeException("Insufficient stock");
    }
    product.setQuantity(newQuantity);
    productRepository.save(product);
}

/**
* SpringTransactionTest.java
* Description.    : `updateProductQuantityWithRequiresNewPropagation` was using REQUIRES_NEW propagation, 
*                   if exception happened every action inside this transaction should rollback, but the 
*                   outer transaction should continue without affected.
* Expected Result : Order record is created, product record is rollback.
*/
@Test
@Transactional
public void testUpdateProductQuantityWithRequiresNewPropagationAndInnerException() {
    // Create one order
    Order order = new Order();
    order.setCustomerName("testUpdateProductQuantityWithRequiresNewPropagationAndInnerException");
    order.setStatus(OrderStatus.PENDING);
    orderRepository.save(order);

    try {
        OrderItem item = new OrderItem();
        item.setProduct(new Product()); // Here we passed empty Product, so exception occured.
        productService.updateProductQuantityWithRequiresNewPropagation(item);
    } catch (RuntimeException e) {

    }

   // Verify that the order was created.
   Order savedOrder = orderRepository.findById(order.getId()).get();
   assertEquals(order.getCustomerName(), savedOrder.getCustomerName());
}
```

#### Mandatory

* A transaction is already active when the method is called. If a transaction is not active, an exception will be thrown.

```java
/**
* OrderService.java
*/
@Transactional(propagation = Propagation.MANDATORY)
public void updateOrderStatus(Long orderId, OrderStatus status) {
    Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found"));
    order.setStatus(status);
    orderRepository.save(order);
}

/**
* SpringTransactionTest.java
* Description.    : `updateOrderStatus` was using MANDATORY propagation, 
*                   if the caller method doesn't initiate a transaction, 
*                   an exception will throw.
* Expected Result : IllegalTransactionStateException occured.
*/
@Test
public void testMandatoryPropagationWithoutTransaction() {
    Order order = new Order();
    order.setCustomerName("testMandatoryPropagationWithoutTransaction");
    order.setStatus(OrderStatus.PENDING);
    orderRepository.save(order);
    assertThrows(IllegalTransactionStateException.class, () -> {
            orderService.updateOrderStatus(order.getId(), OrderStatus.COMPLETED);
    });
}
```

#### Never

* A transaction should not be active when the method is called. If a transaction is already active, an exception will be thrown.

```java
@Transactional(propagation = Propagation.NEVER)
public void deleteOrder(Order order) {
    this.orderRepository.delete(order);
    this.orderDetailRepository.delete(order.getDetail());
}

@Test
@Transactional
public void testNeverPropagationWithTransaction() {
    assertThrows(IllegalTransactionStateException.class, () -> {
        orderService.deleteOrder(new Order());
    });
}
```

#### Not Supported

* The method should not run within a transaction, but if a transaction is already active, it will be suspended until the method is complete

```java
/**
* ProductService.java
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void createProductWithNotSupportedPropagationWithException() {
    Product product = new Product();
    product.setName("This product created with NOT_SUPPORTED propagation and exception.");
    product.setQuantity(10);
    product.setPrice(BigDecimal.valueOf(10.0));
    productRepository.save(product);
    throw new RuntimeException("DummyException: Simulating an error");
}

/**
* SpringTransactionTest.java
* Expected Result : product was created, order was rollback.
*/
@Test(expected = RuntimeException.class)
@Transactional
public void testCreateProductWithNotSupportedPropagationWithTransactionAndException() {

    Order order = new Order();
    order.setCustomerName("testCreateProductWithNotSupportedPropagationWithTransactionAndException");
    order.setStatus(OrderStatus.PENDING);
    orderRepository.save(order);

    productService.createProductWithNotSupportedPropagationWithException();
}

/**
* SpringTransactionTest.java
* Expected Result : product was created, order was created.
*/
@Test(expected = RuntimeException.class)
public void testCreateProductWithNotSupportedPropagationWithoutTransactionAndException() {
    Order order = new Order();
    order.setCustomerName("testCreateProductWithNotSupportedPropagationWithoutTransactionAndException");
    order.setStatus(OrderStatus.PENDING);
    orderRepository.save(order);

    productService.createProductWithNotSupportedPropagationWithException();
}
```

#### Supported

* If a transaction is already active when the method is called, the method will run within that transaction. If a transaction is not active, the method will run outside of the transaction

```java
/**
* ProductService.java
*/
@Transactional(propagation = Propagation.SUPPORTS)
public Product createProductWithSupportsPropagationAndException() {
    Product product = new Product();
    product.setName("This product created with SUPPORTS propagation and exception.");
    product.setQuantity(10);
    product.setPrice(BigDecimal.valueOf(10.0));
    productRepository.save(product);
    throw new RuntimeException("DummyException: Simulating an error");
}

/**
* SpringTransactionTest.java
* Expected Result : product was rollback, order was rollback.
*/
@Test(expected = RuntimeException.class)
@Transactional
public void testCreateProductWithSupportPropagationWithTransactionAndException() {
    Order order = new Order();
    order.setCustomerName("testCreateProductWithSupportPropagationWithTransactionAndException");
    order.setStatus(OrderStatus.PENDING);
    orderRepository.save(order);

    productService.createProductWithSupportsPropagationAndException();
}

/**
* SpringTransactionTest.java
* Expected Result : product was created, order was created.
*/
@Test(expected = RuntimeException.class)
public void testCreateProductWithSupportPropagationWithoutTransactionAndException() {
    Order order = new Order();
    order.setCustomerName("testCreateProductWithSupportPropagationWithoutTransactionAndException");
    order.setStatus(OrderStatus.PENDING);
    orderRepository.save(order);

    productService.createProductWithSupportsPropagationAndException();
}
```
