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
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
Hibernate will create the sql command based the object, do the work of JDBC to connect to database
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 :
Transient State
Persistent State
Detached State
Removed State
Transient State
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.
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.
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.
Setup a modal of the java object which will be used to map to its relative table in database
@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
Identity: The primary key of SQL have set the default value or auto-increment
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
Repository
An interface which contains the method of JPA to access the table , we can also define the custom method on there.
@Override
public void deleteStudentId(String id){
studentRepository.deleteById(id);
}
One-To-One Relationship
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;
Since Student is a entry point, so cascade must be added to Student class
One-To-Many Relationship
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)
)
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:
Lazy -> SELECT(default)
EAGER -> JOIN(default), SUB-SELECT
Many-To-Many Relationship
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)
)
@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);
}
@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
Transaction
Introduction
Spring creates proxies for all the classes annotated with @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
@Service
@Transactional
public class FooService {
//...
}
Apart from that, we can also use programatic approach to implement by using transactional template
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.
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.
/**
* 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.
/**
* 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.
The method should not run within a transaction, but if a transaction is already active, it will be suspended until the method is complete
/**
* 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
/**
* 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();
}
The transient state is the first state of an entity object. When we instantiate an object of a using the new operator then the object is in the transient state. This object is not connected with any hibernate session.
@DynamicInsert / DynamicUpdate: If the field is null, the field will not be inserted or updated , in order to improve the efficiency, detail: