post-image

Giới thiệu về Spring Data JDBC

Tổng quan

Spring Data JDBC là một thành viên mới được thêm vào trong Spring Data. Nó là sự kết hợp các thành phần của Spring Data JPA (Java Persistence API) và Spring JDBC. Bài viết này sẽ giới thiệu tới các bạn về khái niệm này và lý do tại sao chúng ta nên sử dụng nó trong dự án Spring Boot.

Giới thiệu

Spring Data JDBC là một thành viên mới trong gia đình Spring Data. Nó được tạo ra để khắc phục những điểm yếu của Spring JDBC và Spring Data JPA. Chúng ta thử cùng xem xét về Spring JDBC, khả năng làm việc của nó thì khá là thấp chủ yếu là được sử dụng để kết nối với cơ sở dữ liệu. Còn Spring Data JPA thì lại quá phức tạp vì nó cho chúng ta quá nhiều sự lựa chọn điều đó làm cho chúng ta rất khó để làm chủ được tất cả các lựa chọn này. Spring Data JDBC là một framework cung cấp cho chúng ta những chức năng giống như chúng ta đang sử dụng Spring Data JPA nhưng  lại dễ hiểu hơn rất nhiều bơi nó sử dụng nguyên tắc DDD. Nó còn giúp chúng ta được quyền kiểm soát nhiều hơn bằng cách làm việc ở cấp độ thấp hơn nó cho phép ta được quyết định khi các tương tác với cơ sở dữ liệu được thực hiện như Spring JDBC nhưng lại theo một cách dễ dàng hơn.

Spring Data JDBC vs Spring JDBC vs Spring Data JPA

Spring JDBC

Spring JDBC cung cấp cho chúng ta một framework để thực thi các câu lệnh SQL. Nó xử lý việc kết nối với cơ sở dữ liệu và giúp chúng ta thực thi các câu lệnh SQL bằng cách sử dụng JdbcTemplate. Nhờ vậy mà nó rất là linh hoạt vì chúng ta hoàn toàn có quyền được kiểm soát việc các câu lệnh SQL được thực thi.

Spring Data JPA

Spring Data JPA sử dụng các entity nên do đó cấu trúc của các class cần phải giống với cấu trúc của các bảng trong cơ sở dữ liệu. Ở dạng đơn giản nhất, mỗi bảng cơ sở dữ liệu sẽ đại diện cho một thực thể và có thể được ánh xạ gần như trực tiếp trên một entity class.

Sử dụng @Entity để đánh dấu một Entity class, sử dụng @OneToMany, @ManyToOne, @ManyToMany để tạo kết nối giữa các bảng.

Chúng ta cùng xem xét ví dụ sau để hiểu cách sử dụng @OneToMany và @ManyToOne trong các entity

@Entity
    public class Rental {
     
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
     
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "company_id")
        private RentalCompany company;
     
        // ...
     
    }
    
    @Entity
    public class RentalCompany {
     
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
     
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
        private List<Rental> rentals;
         
        // ...
    }

Spring Data JDBC

Khi mà chúng ta sử dụng đến Spring Data JDBC, ta sẽ cần tạo các entity class được kết nối với cơ sở dữ liệu. Nhưng điểm khác biệt lớn đó là nó sẽ có nhiều quy tắc hơn và chúng ta cần phải tuân theo khi tạo cấu trúc một class. Cấu trúc của class cần phải tuân theo luật lệ của mẫu thiết kể tổng hợp DDD. Spring Data thực thi điều này vì điều này sẽ dẫn đến việc tạo ra các dự án đơn giản và dễ hiểu hơn. Đây là một số luật mà chúng ta cần phải tuân theo:

  • Một entity có thể là một phẩn của aggregate
  • Tất các các mối quan hệ bên trong aggregate phải là một chiều
  • root aggregate cần được quản lý top relation

Điều này có nghĩa là bằng cách đi theo các liên kết bắt đầu từ root aggregate, mọi thực thể bên trong tập hợp đều có thể được tìm thấy. Do đó, chúng ta không cần một kho lưu trữ cho mỗi thực thể như trong Spring Data JPA, mà chỉ cho các gốc tổng hợp. Để liên kết các lớp thực thể với nhau để tạo thành một tập hợp, ta cần sử dụng tham chiếu đối tượng. Các lớp thực thể bên trong một tập hợp chỉ có thể có mối quan hệ một-một và một-nhiều. Nếu ta có mối quan hệ một – một, thực thể của ta chỉ cần một tham chiếu đối tượng đến đối tượng kia. Khi có mối quan hệ một-nhiều, thực thể cần chứa một tập hợp các tham chiếu đối tượng. Để tạo quan hệ với các thực thể bên ngoài tập hợp, id cần được sử dụng để có được sự kết hợp thấp giữa các lớp này.

Một sự khác biệt lớn trong việc tạo các lớp được sử dụng bởi Spring Data JDBC so với Spring Data JPA là không cần sử dụng @Entity và không có annotation quan hệ như @OneToMany. Spring JDBC biết một lớp là một root aggregate khi nó chứa một kho lưu trữ cho lớp đó. Và do các quy tắc mà các thực thể tổng hợp được kết nối thông qua các tham chiếu đối tượng, Spring Data JDBC cũng biết các tập hợp là gì và có thể chuyển dữ liệu đến cơ sở dữ liệu dưới dạng các tập hợp.

Ví dụ:

public class RentalCompany {
    
        @Id
        private Long id;
    
        private String name;
        private Set<Rental> rentals;
    
    }
    
    public class Rental {
    
        @Id
        private Long id;
        
        private String renter;
        private Long carId;
        private LocalDate startDate;
        private LocalDate endDate;
    }
    
    public class Car {
    
        @Id
        private Long id;
        
        private String color;
        private String brand;
        private String model;
        private String licensePlate;
        private CarType type;
    }

Chèn dữ liệu (Insert)

Spring JDBC

Với Spring JDBC chúng ta sẽ viết câu lệnh thêm bản ghi của chúng ta vào và thực thi chúng bằng cách sử dụng JDBC Template. Lợi ích của việc tự viết tất cả các câu Query đó là chúng ta có toàn quyền kiểm soát chúng.

SingleConnectionDataSource dataSource = new SingleConnectionDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:data/jdbcexample");
dataSource.setUsername("sa");
dataSource.setPassword("");
        
JdbcTemplate template = new JdbcTemplate(ds);
template.execute("create table car (id int, model varchar)");
template.execute("insert into car (id, model) values (1, 'Volkswagen Beetle')");
    
 dataSource.destroy();

Spring Data JPA

Còn với Spring Data JPA, chúng ta muốn thêm mới một bản ghi vào trong cơ sở dữ liệu thì chúng ta đơn giản chỉ cần kế thừa lại Interface có sẵn của Spring JPA và gọi phương thức save()

 @Service
 public class RentalService{
        
        private RentalRepository rentalRepository;
        
        public RentalService(RentalRepository rentalRepository){
            this.rentalRepository = rentalRepository;
        }
        
        public Rental create(Rental rental){
            return rentalRepository.save(rental);
        }
 }

Spring Data JDBC

Spring Data JDBC sử dụng cú pháp có thể tương tự với Spring Data JPA. Sự khác biệt lớn nhất đó là việc quản lý persistence được xử lý bởi kho lưu trữ giống như trong Spring Data JPA, nhưng chỉ root aggregate mới có repository. Điều này có nghĩa là nếu ta muốn thêm hoặc cập nhật dữ liệu, toàn bộ tổng thể cần được lưu. Chúng ta sẽ cần gọi phương thức save của repository của root aggregate và điều này trước tiên sẽ save root aggregate và sau đó tất cả các thực thể được tham chiếu sẽ được lưu lại. Nếu bạn chỉ muốn chèn một phần của tổng hợp, chẳng hạn như chỉ tạo một Rental, thì toàn bộ aggregate sẽ được cập nhật và các thực thể được tham chiếu sẽ bị xóa và chèn lại.

@Service
public class RentalCompanyService{
        
        private RentalCompanyRepository rentalCompanyRepository;
        
        public RentalCompanyService(RentalCompanyRepository rentalCompanyRepository){
            this.rentalCompanyRepository = rentalCompanyRepository;
        }
        
        public RentalCompany addRental(Rental rental, Long rentalCompanyId){
            RentalCompany rentalCompany = rentalRepository.findById(rentalCompanyId);
            rentalCompany.getRentals().add(rental);
            return rentalRepository.save(rentalCompany);
        }
}

Thực thi các câu lệnh query

Spring JDBC

Để thực thi các câu lệnh query chúng ta vẫn sử dụng đến JdbcTemplate. Nhược điểm của nó là chỉ cung cấp cho việc kết nối còn lại chúng ta sẽ phải tự làm tất cả mọi thứ. Nếu như cần tìm kiếm một đối tượng, ta sẽ cần ánh xạ kết quả tới các đối tượng Java bằng cách implement lại interface RowMapper.

Cùng xem xét ví dụ sau:

public class CarRowMapper implements RowMapper<Car> {
    
        @Override
        public Car mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
            Car car = new Car();
     
            car.setId(resultSet.getInt("ID"));
            car.setColor(resultSet.getString("COLOR"));
            car.setBrand(resultSet.getString("BRAND"));
            car.setModel(resultSet.getString("MODEL"));
     
            return car;
        }
}

Mapper này sẽ được chuyển đến cho JdbcTemplate và nó sẽ sử dụng để tạo các đối tượng Java.

 List<Car> cars = jdbcTemplate.queryForObject(
        "SELECT * FROM CAR WHERE ID = ?", new Object[] {id}, new CarRowMapper());

Spring Data JPA

Nếu chúng ta sử dụng JPA thì nó đã cung cấp sẫn cho chúng ta các phương thức tìm kiếm như làm findById, hay là findByName, việc của chúng ta đó là chỉ cần gọi lại các phương thức đó, JPA sẽ tự thực thi các câu lệnh cho chúng ta.

@Service
public class RentalService {
    
        private RentalRepository rentalRepository;
    
        public RentalService(RentalRepository rentalRepository){
            this.rentalRepository = rentalRepository;
        }
    
        public List<Rental> getRentalsByCarType(Long rentalCompanyId, CarType carType) {
            return rentalRepository.findByCompanyIdAndCarType(rentalCompanyId, carType);
        }
}

Repository của chúng ta sẽ khai báo một phương thức như sau

public interface RentalRepository extends PagingAndSortingRepository<Rental, Long> {
    
        List<Rental> findByCompanyIdAndCarType(Long rentalCompanyId, CarType carType);
}
    

Spring Data JDBC

Spring Data JDBC có ít trừu tượng hơn Spring Data JPA, nhưng sử dụng các khái niệm Spring Data để giúp dễ dàng thực hiện các câu lệnh CRUD hơn Spring JDBC.

Khi muốn thêm một phương thức vào repository của mình trong Spring Data JDBC, chúng ta sẽ cần thêm annotation @Query chứa truy vấn. Chúng ta sẽ phải sử dụng câu lệnh SQL thuần túy thay vì JPQL được sử dụng trong Spring Data JPA.

Ví dụ

 @Service
 public class RentalCompanyService {
    
        public RentalCompanyRepository rentalCompanyRepository;
    
        public RentalCompanyService(RentalCompanyRepository rentalCompanyRepository){
            this.rentalCompanyRepository = rentalCompanyRepository;
        }
    
        public List<Rental> getRentalsByCarType(Long rentalCompanyId, CarType carType) {
            return rentalRepository.findByIdAndCarType(rentalCompanyId, carType);
        }
}

Điểm khác biệt với JPA đó là chúng ta sẽ sử dụng @Query như sau:

public interface RentalCompanyRepository extends CrudRepository<RentalCompany, Long> {
    
        @Query(value = "SELECT * " +
                "FROM Rental rental " +
                "JOIN Car car ON car.id = rental.car_id " +
                "WHERE rental.rental_company = :companyId " +
                "AND car.type = :carType")
        List<Rental> findRentalsByIdAndCarType(@Param("companyId") Long companyId, @Param("carType")String carType);
}

Lợi ích của việc sử dụng Spring Data JDBC

  • Một trong những lợi ích lớn nhất của Spring Data JDBC đó là nó sử dụng luật của DDD design như sử dụng các aggregate.
  • Nó rất dễ để có thể hiểu
  • Với Spring Data JDBC tất cả các câu query đều là eager vì vậy sẽ có ít câu query được gửi về database hơn nhờ đó tăng thêm năng suất cho hệ thống.

Leave a Reply

Your email address will not be published. Required fields are marked *