JavaRush /Blog Java /Random-VI /Ứng dụng Hibernate đầu tiên của bạn

Ứng dụng Hibernate đầu tiên của bạn

Xuất bản trong nhóm
Trong bài viết này, bạn sẽ làm quen với một trong những framework doanh nghiệp phổ biến nhất dành cho Java và tạo ứng dụng đầu tiên của mình bằng Hibernate. Bạn chưa bao giờ nghe nói về Hibernate? Có thể bạn đã nghe nói về nó nhưng chưa sử dụng? Hoặc đã cố gắng bắt đầu nhưng không thành công? Trong cả ba trường hợp, chào mừng bạn đến với phần cắt :) Ứng dụng Hibernate đầu tiên của bạn - 1Xin chào mọi người! Trong bài viết này, tôi sẽ nói về các tính năng chính của Hibernate framework và giúp bạn viết ứng dụng nhỏ đầu tiên của mình. Đối với điều này chúng ta cần:
  1. Phiên bản cuối cùng của Intellij Idea;
    Tải xuống từ trang web chính thức và kích hoạt phiên bản dùng thử 30 ngày.
  2. PostgeSQL là một trong những hệ thống quản lý cơ sở dữ liệu hiện đại (DBMS) phổ biến nhất;
  3. Maven (đã được tích hợp sẵn trong IDEA);
  4. Một chút kiên nhẫn.
Bài viết chủ yếu hướng tới những người chưa từng làm việc với công nghệ này nên số lượng code đã được giảm thiểu nhiều nhất có thể. Bắt đầu nào!

Ngủ đông là gì?

Đây là một trong những triển khai phổ biến nhất của mô hình ORM. Mô hình quan hệ đối tượng mô tả mối quan hệ giữa các đối tượng phần mềm và các bản ghi trong cơ sở dữ liệu. Tất nhiên, chức năng của Hibernate rất rộng nhưng chúng ta sẽ tập trung vào những chức năng đơn giản nhất. Mục tiêu của chúng tôi: tạo ra một ứng dụng CRUD (Tạo, Đọc, Cập nhật, Xóa) có thể:
  1. Tạo người dùng (Người dùng), cũng như tìm kiếm họ trong cơ sở dữ liệu theo ID, cập nhật dữ liệu của họ trong cơ sở dữ liệu và cũng xóa họ khỏi cơ sở dữ liệu.
  2. Gán đối tượng xe (Auto) cho người dùng. Tạo, chỉnh sửa, tìm và xóa ô tô khỏi cơ sở dữ liệu.
  3. Ngoài ra, ứng dụng sẽ tự động xóa những chiếc xe “mồ côi” khỏi cơ sở dữ liệu. Những thứ kia. Khi một người dùng bị xóa, tất cả xe ô tô của người đó cũng phải bị xóa khỏi cơ sở dữ liệu.
Cấu trúc dự án của chúng ta sẽ như sau: Ứng dụng Hibernate đầu tiên của bạn - 2Như bạn có thể thấy, không có gì phức tạp. 6 lớp + 1 tệp có cấu hình. Đầu tiên, hãy tạo một dự án maven mới trong Intellij Idea. Tệp -> Dự án mới. Từ các loại dự án được đề xuất, chọn Maven và tiếp tục. Ứng dụng Hibernate đầu tiên của bạn - 3Apache Maven là một framework để tự động hóa việc tập hợp các dự án dựa trên mô tả cấu trúc của chúng trong các tệp bằng ngôn ngữ POM. Toàn bộ cấu trúc dự án của bạn sẽ được mô tả trong tệp pom.xml mà chính IDEA sẽ tạo trong thư mục gốc của dự án. Trong cài đặt dự án, bạn sẽ cần chỉ định các tham số Maven - groupId và ArtifactId. Thông thường trong các dự án groupId là tên của tổ chức hoặc bộ phận và tên miền của tổ chức hoặc trang dự án được viết ở đó. Đổi lại, ArtifactId là tên của dự án. Đối với groupdId bạn có thể chỉ định com.вашНикнейм.javarush, điều này sẽ không ảnh hưởng đến hoạt động của ứng dụng dưới bất kỳ hình thức nào. Đối với ArtifactId, hãy chọn bất kỳ tên dự án nào bạn thích. Bạn cũng có thể giữ nguyên Phiên bản. Ứng dụng Hibernate đầu tiên của bạn - 4Trên màn hình cuối cùng, chỉ cần xác nhận dữ liệu đã nhập trước đó của bạn. Ứng dụng Hibernate đầu tiên của bạn - 5Vì vậy, chúng tôi đã tạo dự án, tất cả những gì còn lại là viết mã và làm cho nó hoạt động :) Trước hết, nếu chúng tôi muốn tạo một ứng dụng hoạt động với cơ sở dữ liệu, chúng tôi chắc chắn không thể làm gì nếu không có cơ sở dữ liệu! Tải xuống PostgreSQL từ đây (tôi sử dụng phiên bản 9). PostgreSQL có người dùng mặc định là 'postgres', bạn sẽ cần tạo mật khẩu cho nó trong quá trình cài đặt. Đừng quên mật khẩu của bạn, chúng tôi sẽ cần nó sau! (Nói chung, sử dụng cơ sở dữ liệu mặc định trong các ứng dụng là một phương pháp không tốt, nhưng để giảm số lượng bệnh trĩ, chúng tôi sẽ thực hiện bằng cách tạo cơ sở dữ liệu của riêng mình). Nếu bạn không thoải mái với dòng lệnh và truy vấn SQL thì có tin tốt. Intellij IDEA cung cấp giao diện người dùng khá phù hợp để làm việc với cơ sở dữ liệu. Nó trông như thế này: Ứng dụng Hibernate đầu tiên của bạn - 6(nằm trên thanh bên phải của IDEA, tab Cơ sở dữ liệu) Để tạo kết nối, hãy nhấp vào “+”, chọn nhà cung cấp của chúng tôi (PostgeSQL). Điền vào các trường người dùng, tên cơ sở dữ liệu (cả hai đều là postgres) và nhập mật khẩu bạn đã đặt khi cài đặt PostgreSQL. Nếu cần, hãy tải xuống trình điều khiển Postgres, việc này có thể được thực hiện trên cùng trang này. Nhấp vào "Kiểm tra kết nối" để kiểm tra xem kết nối với cơ sở dữ liệu đã được thiết lập chưa. Nếu bạn thấy dòng chữ “Thành công”, chúng ta sẽ tiếp tục. Bây giờ hãy tạo các bảng chúng ta cần. Sẽ có hai trong số họ - người dùng và ô tô. Tham số cho bảng người dùng: Ứng dụng đầu tiên của bạn trên Hibernate - 7Xin lưu ý rằng id là khóa chính. Nếu bạn không biết khóa chính trong SQL là gì, hãy tìm trên Google, điều đó rất quan trọng. Cài đặt cho bảng ô tô: Ứng dụng Hibernate đầu tiên của bạn - 8Đối với ô tô bạn cần cấu hình Khóa ngoại - khóa ngoại. Nó sẽ liên kết các bảng của chúng tôi. Tôi khuyên bạn nên đọc thêm về anh ấy; Nói một cách rất đơn giản, nó đề cập đến một bảng bên ngoài, trong trường hợp của chúng tôi là người dùng. Nếu ô tô thuộc về người dùng có id=1 thì trong trường user_id của bảng ô tô sẽ có 1. Đây là cách chúng tôi kết nối người dùng với ô tô của họ trong ứng dụng của mình. Trong bảng ô tô của chúng tôi, trường user_id sẽ đóng vai trò là khóa ngoại. Nó sẽ đề cập đến trường id của bảng người dùng. Ứng dụng Hibernate đầu tiên của bạn - 9Như vậy, chúng ta đã tạo được một cơ sở dữ liệu có hai bảng. Vẫn còn phải hiểu cách quản lý nó từ mã Java. Chúng ta sẽ bắt đầu với tệp pom.xml, trong đó chúng ta cần bao gồm các thư viện cần thiết (trong ngôn ngữ Maven chúng được gọi là các thư viện phụ thuộc). Tất cả các thư viện được lưu trữ trong kho lưu trữ trung tâm của Maven. Những cái mà bạn chỉ định trong pom.xml, bạn có thể sử dụng trong dự án. Pom.xml của bạn sẽ trông như thế này: Ứng dụng Hibernate đầu tiên của bạn - 10Không có gì phức tạp như bạn thấy. Chúng tôi chỉ thêm 2 phần phụ thuộc - để sử dụng PostgreSQL và Hibernate. Bây giờ hãy chuyển sang mã Java. Tạo tất cả các gói và lớp cần thiết cho dự án. Để bắt đầu, chúng ta sẽ cần các mô hình dữ liệu - các lớp UserAuto.
package models;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table (name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    @Column(name = "name")
    private String name;
    //you can not specify Column name if it matches the name of the column in the table
    private int age;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Auto> autos;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
        autos = new ArrayList<>();
    }

    public void addAuto(Auto auto) {
        auto.setUser(this);
        autos.add(auto);
    }

    public void removeAuto(Auto auto) {
        autos.remove(auto);
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<Auto> getAutos() {
        return autos;
    }

    public void setAutos(List<Auto> autos) {
        this.autos = autos;
    }

    @Override
    public String toString() {
        return "models.User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package models;

import javax.persistence.*;

@Entity
@Table(name = "autos")
public class Auto {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

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

    //you can not specify Column name if it matches the name of the column in the table
    private String color;


    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public Auto() {
    }

    public Auto(String model, String color) {
        this.model = model;
        this.color = color;
    }

    public int getId() {
        return id;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return color + " " + model;
    }
}
Như bạn có thể thấy, các lớp được trang bị một loạt chú thích vẫn chưa rõ ràng. Hãy bắt đầu đối phó với họ. Chú thích chính đối với chúng tôi là @Entity. Đọc về nó trên Wikipedia và ghi nhớ mọi thứ, đây là nền tảng của những điều cơ bản. Chú thích này cho phép các đối tượng Java trong lớp của bạn được liên kết với cơ sở dữ liệu. Để một lớp trở thành một thực thể, nó phải đáp ứng các yêu cầu sau:
  • Phải có hàm tạo trống ( publichoặc protected);
  • Không thể lồng nhau, giao diện hoặc enum;
  • Không thể finalvà không thể chứa final-fields/properties;
  • Phải chứa ít nhất một trường @Id.
Hãy kiểm tra các lớp thực thể của bạn, đây là nơi rất phổ biến để bạn tự bắn vào chân mình. Rất dễ dàng để quên một cái gì đó. Trong trường hợp này, đơn vị có thể:
  • Chứa các hàm tạo không trống;
  • Được thừa kế và được thừa kế;
  • Chứa các phương thức khác và thực hiện các giao diện.
Như bạn có thể thấy, lớp này Userrất giống với bảng người dùng. Có các trường id, name, age. Các chú thích nằm phía trên chúng không cần phải giải thích nhiều: rõ ràng @Id là dấu hiệu cho thấy trường này là mã định danh của các đối tượng thuộc lớp này. Chú thích @Table phía trên lớp chỉ định tên của bảng mà các đối tượng được viết vào đó. Hãy chú ý đến chú thích phía trên trường tuổi: nếu tên trường trong lớp và bảng giống nhau thì bạn không cần phải thêm chú thích @Column, nó sẽ hoạt động như vậy. Về “chiến lược = GenerationType.IDENTITY” được chỉ ra trong ngoặc đơn: có một số chiến lược tạo ID. Bạn có thể google nó, nhưng trong khuôn khổ ứng dụng của chúng tôi, bạn không cần phải bận tâm. Điều chính là id cho các đối tượng của chúng ta sẽ được tạo tự động, do đó không có setter cho id và chúng ta cũng không chỉ định nó trong hàm tạo. UserTuy nhiên, lớp vẫn nổi bật ở một số mặt . Anh ấy có một danh sách những chiếc xe hơi! Chú thích @OneToMany xuất hiện phía trên danh sách. Điều đó có nghĩa là một đối tượng của lớp người dùng có thể tương ứng với một số máy. Cài đặt "mappedBY" trỏ đến trường người dùng của lớp Auto. Bằng cách này, máy và người dùng được kết nối với nhau. Cài đặt orphanRemoval dịch khá tốt từ tiếng Anh - “xóa trẻ mồ côi”. Nếu chúng tôi xóa một người dùng khỏi cơ sở dữ liệu, tất cả những chiếc xe liên quan đến người đó cũng sẽ bị xóa. Đổi lại, trong lớp, Autobạn sẽ thấy trường người dùng có chú thích @ManyToOne (nhiều Ô tô có thể tương ứng với một Người dùng) và chú thích @JoinColumn. Nó cho biết thông qua cột nào trong bảng autos sẽ xảy ra kết nối với bảng người dùng (cùng một khóa ngoại mà chúng ta đã nói đến trước đó). Sau khi tạo mô hình dữ liệu, đã đến lúc hướng dẫn chương trình của chúng ta thực hiện các thao tác trên dữ liệu này trong cơ sở dữ liệu. Hãy bắt đầu với lớp tiện ích HibernateSessionFactoryUtil. Nó chỉ có một nhiệm vụ - tạo một nhà máy phiên để ứng dụng của chúng tôi hoạt động với cơ sở dữ liệu (xin chào, mẫu "Nhà máy!"). Anh ấy không thể làm gì khác.
package utils;

import models.Auto;
import models.User;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class HibernateSessionFactoryUtil {
    private static SessionFactory sessionFactory;

    private HibernateSessionFactoryUtil() {}

    public static SessionFactory getSessionFactory() {
        if (sessionFactory == null) {
            try {
                Configuration configuration = new Configuration().configure();
                configuration.addAnnotatedClass(User.class);
                configuration.addAnnotatedClass(Auto.class);
                StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
                sessionFactory = configuration.buildSessionFactory(builder.build());

            } catch (Exception e) {
                System.out.println("Exception!" + e);
            }
        }
        return sessionFactory;
    }
}
Trong lớp này, chúng tôi tạo một đối tượng cấu hình mới, Cấu hình và chuyển cho nó các lớp mà nó sẽ coi là các thực thể - UserAuto. Hãy chú ý đến phương pháp configuration.getProperties(). Những tài sản nào khác? Ở đâu? Thuộc tính là các tham số về cách thức hoạt động của chế độ ngủ đông, được chỉ định trong một tệp đặc biệt hibernate.cfg.xml. Ứng dụng Hibernate đầu tiên của bạn - 11Hibernate.cfg.xml được đọc ở đây: new Configuration().configure(); Như bạn có thể thấy, không có gì đặc biệt trong đó - các tham số để kết nối với cơ sở dữ liệu và một tham số đặc biệt show_sql. Điều này là cần thiết để tất cả các truy vấn SQL mà chế độ ngủ đông sẽ thực thi đối với cơ sở dữ liệu của chúng tôi đều được xuất ra bảng điều khiển. Bằng cách này, bạn sẽ thấy chính xác những gì Hibernate đang làm tại từng thời điểm và loại bỏ hiệu ứng “ma thuật”. Tiếp theo chúng ta cần lớp UserDAO. (Nói một cách hay, bạn cần lập trình thông qua các giao diện - tạo một giao diện UserDAOvà triển khai nó một cách riêng biệt UserDAOImpl, nhưng để giảm số lượng mã tôi sẽ bỏ qua phần này. Đừng làm điều này trong các dự án thực tế!). DAO (đối tượng truy cập dữ liệu) là một trong những mẫu thiết kế phổ biến nhất, “Truy cập dữ liệu”. Ý nghĩa của nó rất đơn giản - tạo một lớp trong ứng dụng chỉ chịu trách nhiệm truy cập dữ liệu và không có gì khác. Lấy dữ liệu từ cơ sở dữ liệu, cập nhật dữ liệu, xóa dữ liệu - và thế là xong. Đọc thêm về DAO; bạn sẽ sử dụng chúng liên tục trong công việc của mình. Lớp chúng ta có thể làm gì UserDao? Trên thực tế, giống như tất cả các DAO, nó chỉ có thể hoạt động với dữ liệu. Tìm người dùng theo id, cập nhật dữ liệu của anh ta, xóa anh ta, lấy danh sách tất cả người dùng khỏi cơ sở dữ liệu hoặc lưu người dùng mới vào cơ sở dữ liệu - đó là tất cả chức năng của nó.
package dao;

import models.Auto;
import models.User;
import org.hibernate.Session;
import org.hibernate.Transaction;
import utils.HibernateSessionFactoryUtil;
import java.util.List;

public class UserDao {

    public User findById(int id) {
        return HibernateSessionFactoryUtil.getSessionFactory().openSession().get(User.class, id);
    }

    public void save(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.save(user);
        tx1.commit();
        session.close();
    }

    public void update(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.update(user);
        tx1.commit();
        session.close();
    }

    public void delete(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.delete(user);
        tx1.commit();
        session.close();
    }

    public Auto findAutoById(int id) {
        return HibernateSessionFactoryUtil.getSessionFactory().openSession().get(Auto.class, id);
    }

    public List<User> findAll() {
        List<User> users = (List<User>)  HibernateSessionFactoryUtil.getSessionFactory().openSession().createQuery("From User").list();
        return users;
    }
}
Các phương pháp UserDaotương tự như nhau. Trong hầu hết trong số đó, chúng tôi nhận được một đối tượng Phiên (một phiên kết nối với cơ sở dữ liệu của chúng tôi) bằng cách sử dụng Session Factory, tạo một giao dịch duy nhất trong phiên này, thực hiện các chuyển đổi dữ liệu cần thiết, lưu kết quả giao dịch vào cơ sở dữ liệu và đóng phiên. Bản thân các phương pháp, như bạn có thể thấy, khá đơn giản. DAO là “trái tim” của ứng dụng của chúng tôi. Tuy nhiên, chúng tôi sẽ không trực tiếp tạo DAO và gọi các phương thức của nó trong main(). Tất cả logic sẽ được chuyển sang tệp UserService.
package services;

import dao.UserDao;
import models.Auto;
import models.User;

import java.util.List;

public class UserService {

    private UserDao usersDao = new UserDao();

    public UserService() {
    }

    public User findUser(int id) {
        return usersDao.findById(id);
    }

    public void saveUser(User user) {
        usersDao.save(user);
    }

    public void deleteUser(User user) {
        usersDao.delete(user);
    }

    public void updateUser(User user) {
        usersDao.update(user);
    }

    public List<User> findAllUsers() {
        return usersDao.findAll();
    }

    public Auto findAutoById(int id) {
        return usersDao.findAutoById(id);
    }


}
Dịch vụ là một lớp dữ liệu trong ứng dụng chịu trách nhiệm thực thi logic nghiệp vụ. Nếu chương trình của bạn cần thực hiện một số logic nghiệp vụ, nó sẽ thực hiện việc đó thông qua các dịch vụ. Dịch vụ chứa bên trong chính nó UserDaovà gọi các phương thức DAO trong các phương thức của nó. Đối với bạn, điều này có vẻ giống như sự trùng lặp các hàm (tại sao không chỉ gọi các phương thức từ một đối tượng dao), nhưng với số lượng lớn các đối tượng và logic phức tạp, việc chia ứng dụng thành các lớp mang lại lợi ích rất lớn (đây là cách thực hành tốt, hãy nhớ thông tin này cho tương lai và đọc về “các lớp ứng dụng” "). Trong dịch vụ của chúng tôi, logic rất đơn giản, nhưng trong các dự án thực tế, các phương thức dịch vụ sẽ chứa nhiều hơn một dòng mã :) Bây giờ chúng tôi có mọi thứ cần thiết để ứng dụng hoạt động! Hãy tạo main()một người dùng và máy móc cho anh ta trong phương thức, kết nối chúng với nhau và lưu chúng vào cơ sở dữ liệu.
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User("Masha",26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        ferrari.setUser(user);
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
    }
}
Như bạn có thể thấy, bảng user có mục nhập riêng và bảng autos có mục nhập riêng. Ứng dụng Hibernate đầu tiên của bạn - 13Ứng dụng Hibernate đầu tiên của bạn - 14Hãy thử đổi tên người dùng của chúng tôi. Hãy xóa bảng người dùng và chạy mã
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User("Masha",26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
        user.setName("Sasha");
        userService.updateUser(user);
    }
}
Làm! Ứng dụng Hibernate đầu tiên của bạn - 15Điều gì sẽ xảy ra nếu bạn xóa một người dùng? Hãy xóa bảng người dùng (ô tô sẽ tự xóa) và thực thi mã
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User("Masha",26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
        user.setName("Sasha");
        userService.updateUser(user);
        userService.deleteUser(user);
    }
}
Và các bảng của chúng ta hoàn toàn trống rỗng (chú ý đến console, tất cả các truy vấn mà Hibernate đã thực thi sẽ được hiển thị ở đó). Bạn có thể thử nghiệm ứng dụng và thử tất cả các tính năng của nó. Ví dụ: tạo một người dùng có máy, lưu nó vào cơ sở dữ liệu, xem ID nào được gán cho nó và thử sử dụng phương thức main()để “kéo” người dùng ra khỏi cơ sở dữ liệu theo id này và hiển thị danh sách các máy của anh ta trong bảng điều khiển . Tất nhiên, chúng ta chỉ thấy được một phần nhỏ chức năng của Hibernate. Khả năng của nó rất rộng và từ lâu nó đã trở thành một trong những tiêu chuẩn công nghiệp để phát triển Java. Nếu bạn muốn nghiên cứu chi tiết về nó, tôi có thể giới thiệu cuốn sách “Java Persistence API và Hibernate” mà tôi đã xem xét trong một trong những bài viết trước của mình. Tôi hy vọng bài viết này hữu ích cho người đọc. Nếu bạn có bất kỳ câu hỏi nào, hãy hỏi họ trong phần bình luận, tôi sẽ sẵn lòng trả lời :) Ngoài ra, đừng quên ủng hộ tác giả trong cuộc thi bằng cách "Thích" anh ấy. Hoặc tốt hơn nữa - “Tôi rất thích nó” :) Chúc bạn học tập may mắn!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION