Bài viết này sẽ giúp bạn hiểu JPA là gì cách để cài đặt một ví dụ đơn giản bằng việc sử dụng Spring Boot

Bạn sẽ biết được:

  • JPA đã giải vấn đề gì - Object Relational Impedance (tạm dịch là Khác quan hệ của đối tượng)
  • Các lựa chọn thay thế cho JPA
  • Hibernate là gì và nó liên hệ đến JPA ra sao
  • Spring Data JPA là gì?
  • Cách tạo một project JPA đơn giản với Spring Boot Data JPA Starter

Các công cụ cần có:

  • Maven 3.0+
  • JDK 1.8+
  • IDE tùy thích. Ở bài này sẽ dùng Eclipse.

Object Relational Impedance Mismatch là gì? 

Object Relational Impedance Mismatch tạm dịch là bất phù hợp trong kháng quan hệ của đối tượng

Java là một ngôn ngữ lập trình hướng đối  tượng. Tất cả thông tin sẽ được lưu trữ trong các đối tượng

Thông thường, các CSDL quan hệ (relational databases) được dùng để lưu trữ dữ liệu (ngày nay các CSDL phi quan hệ cũng đang dần phổ biến hơn – nhưng chúng ta sẽ không đề cập đến nó ở bài viết này). Và các dữ liệu trong CSDL quan hệ được lưu dưới dạng bảng.

Các đối tượng (object) được thiết kế khác với cách mà CSDL quan hệ được thiết kế. Việc này dẫn đến impedance mismatch (chả biết dịch như thế nào nữa: kháng bất phù hợp chăng)

  • Ngôn ngữ hướng đối tượng bao gồm các ý tưởng như: đóng gói, kế thừa, interfaces và đa hình.
  • CSDL quan hệ được tạo nên từ các bảng với các ý tưởng về chuẩn hóa.

Ví dụ về Object Relational Impedance Mismatch (bất phù hợp trong kháng quan hệ của đối tượng)

Hãy xem xét ví dụ sau - Employees and Tasks.

Mỗi Employee có thể có nhiều Task. Mỗi Task lại có thể cùng thực hiện bời nhiều Employee. Đây là mối quan hệ nhiều-nhiều. Dưới đây sẽ là ví dụ về impedance mismatch (bất phù hợp kháng).

Ví dụ 1: class Task được ánh xạ (map) tới bảng Task. Tuy nhiên, có sự không thống nhất ở tên các cột

Object:

public class Task {
        private int id;
        private String desc;
        private Date targetDate;
        private boolean isDone;
        private List<Employee> employees;
}

Table:

 CREATE TABLE task
  (
     id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
     description VARCHAR(255),
     is_done     BOOLEAN,
     target_date TIMESTAMP,
     PRIMARY KEY (id)
  )

Ví dụ 2: Các  mối quan hệ giữa các object được thể hiện theo một cách khác khi so sánh với mối quan hệ giữa các bảng

Mỗi Employee có thể có nhiều Task. Mỗi Task có thể cùng làm bởi nhiều Employee. Đây là mối quan hệ nhiều-nhiều như ta đã nói ở trên.

Object:

public class Employee {
     //Some other code
        private List<Task> tasks;
}

public class Task {
     //Some other code
        private List<Employee> employees;
}

Table:

CREATE TABLE employee
  (
     id            BIGINT NOT NULL,
     OTHER_COLUMNS
  )

  CREATE TABLE employee_tasks
  (
     employees_id BIGINT NOT NULL,
     tasks_id     INTEGER NOT NULL
  )

  CREATE TABLE task
  (
     id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
     OTHER_COLUMNS
  )

Ví dụ 3: Đôi khi, nhiều class được ánh xạ tới một bảng và ngược lại

Object:

public  class Employee {
    //Other Employee Attributes
}

public class FullTimeEmployee extends Employee {
        protected Integer salary;
}

public class PartTimeEmployee extends Employee {
        protected Float hourlyWage;
}

Table:

CREATE TABLE employee
  (
     employee_type VARCHAR(31) NOT NULL,
     id            BIGINT NOT NULL,
     city          VARCHAR(255),
     state         VARCHAR(255),
     street        VARCHAR(255),
     zip           VARCHAR(255),
     hourly_wage   FLOAT,  --PartTimeEmployee
     salary        INTEGER, --FullTimeEmployee
     PRIMARY KEY (id)
  )

Cách tiếp cận khác trước khi có JPA - JDBC, Spring JDBC & myBatis

Các tiếp cận trước khi có JPA là tập trung vào truy vấn và cách để chuyển hóa kết quả truy vấn đó tới các object.

Bất kỳ cách tiếp cận nào sử dụng truy vấn thường làm 2 việc sau:

  • Thiết lập các tham số cho truy vấn: ta cần đọc các giá trị từ object và gán chúng như là các tham số trong truy vấn
  • Phân chia các kết quả của truy vấn: các kết quả sẽ cần phải được ánh xạ vào các bean.

1. JDBC

  • JDBC là viết tắt của Java Database Connectivity
  • Sử dụng ý tưởng là dùng các class Statement, PreparedStatement và ResultSet
  • Trong ví dụ dưới, câu truy vấn dùng là:Update todo set user=?, desc=?, target_date=?, is_done=? where id=?
  • Các giá trị cần có để thực hiện câu truy vấn được gán vào bằng cách sử dụng các method set trong class PreparedStatement.
  • Kết quả từ câu truy vấn sẽ được lưu vào ResultSet. Ta phải viết các câu lệnh để phân chia ResultSet vào các object.

Ví dụ: Update Todo - dùng JDBC để update CSDL

Kết nối với CSDL (lệnh 1) à Chuẩn bị 1 câu query với tham số (lệnh 2) à set các tham số ( lệnh 3 đến 7) à thực hiện truy vấn (lệnh 8) à đóng câu lệnh truy vấn (lệnh 9) à đóng kết nối với CSDL

Connection connection = datasource.getConnection();
PreparedStatement st = connection.prepareStatement(
        "Update todo set user=?, desc=?, target_date=?, is_done=? where id=?");
st.setString(1, todo.getUser());
st.setString(2, todo.getDesc());
st.setTimestamp(3, new Timestamp(todo.getTargetDate().getTime()));
st.setBoolean(4, todo.isDone());
st.setInt(5, todo.getId());
st.execute();
st.close();
connection.close();

Ví dụ: Retrieve a Todo - lấy dữ liệu từ CSDL

Cũng khá giống ví dụ trên nhưng lần này là ta sẽ lấy dữ liệu từ CSDL và gán nó vào trường của object. Câu lệnh số 4 sẽ lấy thông tin truy vấn gán vào ResultSet. Câu lệnh if sẽ là lấy thông tin vừa truy vấn được gán vào object.

Connection connection = datasource.getConnection();
PreparedStatement st = connection.prepareStatement("SELECT * FROM TODO where id=?");
st.setInt(1, id);
ResultSet resultSet = st.executeQuery();

if (resultSet.next()) {
    Todo todo = new Todo();
        todo.setId(resultSet.getInt("id"));
        todo.setUser(resultSet.getString("user"));
        todo.setDesc(resultSet.getString("desc"));
        todo.setTargetDate(resultSet.getTimestamp("target_date"));
        return todo;
}
st.close();
connection.close();
return null;
  •  

2. Spring JDBC

  • Spring JDBC cung cấp một lớp đặt trên của JDBC
  • Nó sử dụng JDBCTemplate
  • Thường nó sẽ sử dụng ít dòng hơn so với JDBC, được đơn giản hóa như sau:
    • Ánh xạ (mapping) tham số tới câu truy vấn
    • Phân chia các resultset cho các bean

Ví dụ: Thao tác update, lấy dữ liệu bằng spring JDBC

Update Todo

jdbcTemplate
.update("Update todo set user=?, desc=?, target_date=?, is_done=? where id=?",
        todo.getUser(),
        todo.getDesc(),
        new Timestamp(todo.getTargetDate().getTime()),
        todo.isDone(),
        todo.getId());

Retrieve a Todo

@Override
public Todo retrieveTodo(int id) {
        return jdbcTemplate.queryForObject(
                         "SELECT * FROM TODO where id=?",
                         new Object[] { id }, new TodoMapper());
}

Reusable Row Mapper

// new BeanPropertyRowMapper(TodoMapper.class)
class TodoMapper implements RowMapper<Todo> {
        @Override
        public Todo mapRow(ResultSet rs, int rowNum) throws SQLException {
                 Todo todo = new Todo();
                 todo.setId(rs.getInt("id"));
                 todo.setUser(rs.getString("user"));
                 todo.setDesc(rs.getString("desc"));
                 todo.setTargetDate(rs.getTimestamp("target_date"));
                 todo.setDone(rs.getBoolean("is_done"));
                 return todo;
        }
}
  1.  

3. myBatis

MyBatis loại bỏ việc viết code thủ công để set tham số và lấy kết quả. Nó cung cấp file XML đơn giản hoặc các Annotation dựa vào cấu hình để ánh xạ các Java POJO vào CSDL.

So sánh các cách tiếp cận:

  • JDBC or Spring JDBC: Update todo set user=?, desc=?, target_date=?, is_done=? where id=?
  • myBatis: Update todo set user=#{user}, desc=#{desc}, target_date=#{targetDate}, is_done=#{isDone} where id=#{id}

Update Todo and Retrieve Todo

@Mapper
public interface TodoMybatisService extends TodoDataService {
        @Override
        @Update("Update todo set user=#{user}, desc=#{desc}, target_date=#{targetDate}, is_done=#{isDone} where id=#{id}")
        public void updateTodo(Todo todo) throws SQLException;

        @Override
        @Select("SELECT * FROM TODO WHERE id = #{id}")
        public Todo retrieveTodo(int id) throws SQLException;
}

public class Todo {
        private int id;
        private String user;
        private String desc;
        private Date targetDate;
        private boolean isDone;
}
  •  

4. Các tính năng phổ biến của JDBC, Spring JDBC and myBatis

  • Trong một ứng dụng lớn, các câu truy vấn có thể rất phức tạp. Đặc biệt là khi chúng ta lấy dữ liệu từ nhiều bảng.
  • Việc này tạo ra vấn đề khi ta thay đổi cấu trúc của CSDL.

JPA

1. JPA hoạt động ra sao

JPA là kết quả của một lối tư duy khác biệt. Đó là “ánh xạ thẳng từ object tới bảng thì sao?”

  • Entities
  • Attributes
  • Relationships

Việc ánh xạ này gọi là ORM - Object Relational Mapping. Trước khi JPA ra đời, ORM là thuật ngữ thường được dùng để nói đến các framework. Đó là một trong các lý do mà Hibernate được gọi là một ORM framework.

  1.  

2. Khái niệm quan trọng trong JPA

JPA cho phép ánh xạ các class của ứng dụng vào thẳng bảng trong CSDL

  • Entity Manager – khi các ánh xạ được định nghĩa, entity manager có thể quản lý các entity (có thể hiểu là các đối tượng và đối tượng này sẽ được ánh xạ để tạo ra bảng trong CSDL). Entity manager xử lý toàn bộ việc tương tác với CSDL
  • JPQL (Java Persistence Query Language) – cung cấp các cách viết truy vấn để thực thi việc tìm kiếm các entity. Một điều quan trọng cần hiểu đó là nó khác với các câu truy vấn SQL. Các truy vấn của JPQL đã hiểu sẵn sự ánh xạ được định nghĩa ở các entity. Ta có thể thêm các điều kiện nếu cần.
  • Criteria API định nghĩa một API dựa trên Java dùng để thực thi các tìm kiếm trong CSDL.

3. JPA vs Hibernate

Hibernate là một trong những framework ORM thông dụng nhất.

JPA định nghĩa sự chi tiết. Nó là một API

  • Định nghĩa entity như thế nào?
  • Làm sao để ánh xạ các thuộc tính?
  • Làm sao để ánh xạ mối quan hệ giữa các entity?
  • Ai quản lý các entity?

Hibernate là một trong số những triển khai phố biến nhất của JPA.

  • Hibernate hiểu được các ánh xạ mà ta đã thêm giữa bảng và object. Nó bảo đảm dữ liệu được lưu trữ / lấy ra từ CSDL phải dựa trên các ánh xạ đó.
  • Hibernate cũng cung cấp các tính năng cao cấp nhất của JPA. Nhưng phụ thuộc và chúng sẽ dẫn đến việc khóa chặt mình với Hibernate. Bạn không thể chuyển sang dùng những framework triểm khai JPA khác như Toplink.

4. Examples of JPA Mappings

Cùng xem một vài ví dụ để hiểu cách sử dụng JPA để ánh xạ object tới vào bảng.

Ví dụ 1:

Class Task dưới được ánh xạ và bảng Task. Tuy nhiên, có sự bất phù hợp trong tên cột. Chúng ta có thể dùng một số annotation để thực hiện ánh xạ như 4 cái bên dưới.

  • @Table(name = “Task”) - đánh dấu class này sẽ mapping vs bảng trong CSDL, bảng này có tên là Task
  • @Id - đánh dấu trường sẽ là khóa chính
  • @GeneratedValue - giá trị sẽ tự sinh ra
  • @Column(name = “description”) - đánh dấu trường mapping với cột trong bảng, cột có tên description
Object:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "Task")
public class Task {

        @Id
        @GeneratedValue
        private int id;

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

        @Column(name = "target_date")
        private Date targetDate;

        @Column(name = "is_done")
        private boolean isDone;
}

Table:

 CREATE TABLE task
  (
     id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
     description VARCHAR(255),
     is_done     BOOLEAN,
     target_date TIMESTAMP,
     PRIMARY KEY (id)
  )

Ví dụ 2:

Mối quan hệ giữa các object được thể hiện khác so với mối quan hệ giữa các bảng

Mỗi Employee có thể có nhiều Task. Mỗi Task có thể nhiểu Emoloyee cùng làm. Đây là mối quan hệ nhiều – nhiều. Chúng ta sử dụng annotation @ManyToMany để thiết lập mối quan hệ này.

Object:

public class Employee {
     //Some other code
        @ManyToMany
        private List<Task> tasks;
}

public class Task {
     //Some other code
        @ManyToMany(mappedBy = "tasks")
        private List<Employee> employees;
}

Table:

CREATE TABLE employee
  (
     id            BIGINT NOT NULL,
     OTHER_COLUMNS
  )

  CREATE TABLE employee_tasks
  (
     employees_id BIGINT NOT NULL,
     tasks_id     INTEGER NOT NULL
  )

  CREATE TABLE task
  (
     id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
     OTHER_COLUMNS
  )

Ví dụ 3:

Đôi khi nhiều class cùng ánh xạ tới một bảng và ngược lại. Ở trường hợp này, chúng ta định nghĩa một inheritance strategy. Ví dụ này chúng ta sẽ dùng InheritanceType.SINGLE_TABLE.

Objects

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "EMPLOYEE_TYPE")
public  class Employee {
    //Other Employee Attributes
}

public class FullTimeEmployee extends Employee {
        protected Integer salary;
}

public class PartTimeEmployee extends Employee {
        protected Float hourlyWage;
}

Tables

CREATE TABLE employee
  (
     employee_type VARCHAR(31) NOT NULL,
     id            BIGINT NOT NULL,
     city          VARCHAR(255),
     state         VARCHAR(255),
     street        VARCHAR(255),
     zip           VARCHAR(255),
     hourly_wage   FLOAT,  --PartTimeEmployee
     salary        INTEGER, --FullTimeEmployee
     PRIMARY KEY (id)
  )

Trên đây là tổng hợp kiến thức và một vài ví dụ nhỏ giúp hiểu rõ hơn. Ở phần 2, chúng ta sẽ cùng làm một project nhỏ với JPA và Spring Boot.

Bài viết được dịch từ đây. Các bạn có thể vào và tự mình thực hành phần còn lại hoặc chờ phần tiếp theo nhé.