Hỏi - đáp Nơi cung cấp thông tin nghề nghiệp và giải đáp những thắc mắc thường gặp của bạn

Sử dụng Hibernate native API trong SpringBoot

1. Giới thiệu

Hibernate là một ORM được sử dụng phổ biến trong các ứng dụng SpringBoot, nhưng với cách tiếp cận hiện nay trong hầu hết các ứng dụng SpringBoot, mặc định SpingBoot sử dụng Spring data JPA để auto config. Mục đích của Spring Data JPA là giảm thiểu việc thực hiện quá nhiều bước để có thể implement JPA. Nhưng để làm việc trực tiếp với Hibernate Native API trong bài này chúng ta sẽ đi tìm hiểu cách config SessionFactory trong SpringBoot và cách sử dụng các Native API của Hibernate.

2. Hibernate SessionFactory

SessionFactory là một interface giúp tạo ra session kết nối đến database bằng cách đọc các cấu hình trong Hibernate configuration.
Có 2 cách để sử dụng Session của Hibernate native API trong SpringBoot:

- Unwrap từ JPA:

Session session = entityManager.unwrap( Session.class );
SessionImplementor sessionImplementor = entityManager.unwrap( SessionImplementor.class );
SessionFactory sessionFactory = entityManager.getEntityManagerFactory().unwrap( SessionFactory.class );

- Configuration bean:

hibernate-config.properties

cafeit.entity.package=info.cafeit.hibernate-native-api-spring.entity
cafeit.datasource.driver=org.h2.Driver
cafeit.datasource.url=jdbc:h2:mem:testdb
cafeit.datasource.username=root
cafeit.datasource.password=
cafeit.datasource.dialect=org.hibernate.dialect.H2Dialect
cafeit.datasource.hbm2ddl.auto=validate
cafeit.datasource.show-sql=TRUE

HibernateSessionFactoryConfig.java

@Configuration
@EnableTransactionManagement
@PropertySource("classpath:hibernate-config.properties")
public class HibernateSessionFactoryConfig {

    @Value("${cafeit.entity.package}")
    private String entityPackageScan;

    @Value("${cafeit.datasource.driver}")
    private String driverClassName;

    @Value("${cafeit.datasource.url}")
    private String dataSourceUrl;

    @Value("${cafeit.datasource.username}")
    private String userName;

    @Value("${cafeit.datasource.password}")
    private String password;

    @Value("${cafeit.datasource.dialect}")
    private String dialect;

    @Value("${cafeit.datasource.hbm2ddl.auto}")
    private String hbm2ddlAuto;

    @Value("${cafeit.datasource.show-sql}")
    private String showSql;
 
    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(entityPackageScan);
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    }

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setDriverClassName(driverClassName);
        config.setJdbcUrl(dataSourceUrl);
        config.setUsername(userName);
        config.setPassword(password);
        config.setAutoCommit(false);
        return new HikariDataSource(config);

    }
 
    @Bean
    public PlatformTransactionManager hibernateTransactionManager() {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(sessionFactory().getObject());
        return transactionManager;
    }
 
    private Properties hibernateProperties() {
        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty(SHOW_SQL, showSql);
        hibernateProperties.setProperty(
                AvailableSettings.HBM2DDL_AUTO, hbm2ddlAuto);
        hibernateProperties.setProperty(
                AvailableSettings.DIALECT, dialect);
        return hibernateProperties;
    }
}

Trong đó:

  • LocalSessionFactoryBean: Thay vì config các tham số dùng StandardServiceRegistryBuilder như các ứng dụng Java EE để tạo ra SessionFactory thì trong Spring chúng ta sẽ sử dụng LocalSessionFactoryBean để cấu hình và share SessionFactory cho Spring application context. Sau khi share thì chúng ta có thể inject bean SessionFactory như một Spring bean thông thường.
  • dataSource(): Dùng để config thông tin của database như driver, url, username, password... ở đây mình sử dụng HikariCP để config data source, mình sẽ có 1 bài nói về connecttion pool trong Hibernate.
  • PlatformTransactionManager: Dùng để đăng ký Transaction cho SessionFactory, sau khi đăng ký thì chúng ta có thể sữ dụng @Transactional or TransactionTemplate để quản lý transaction.
  • hibernateProperties(): Dùng để config các thuộc tính cho Hibernate như dialect, hbm2ddl...

SessionFactory là một đối tượng luồng an toàn (Thread-safe) và được sử dụng bởi tất cả các luồng trong ứng dụng dẫn đến việc khởi tạo đối tượng SessionFactory mất khá nhiều tài nguyên nên nó thường được tạo ra trong quá trình khởi động ứng dụng và lưu giữ để sử dụng sau này.

Mỗi database cần phải có một session factory, nên nếu chúng ta sử dụng nhiều cơ sở dữ liệu thì bạn cũng phải tạo đối tượng SessionFactory cho từng database tương ứng.

3. Session

Session được sử dụng để tạo ra kết nối vật lý với một cơ sở dữ liệu. Đối tượng Session được thiết kế để được tạo ra thể hiện mỗi khi tương tác với cơ sở dữ liệu, các persistent object được lưu và truy xuất thông qua một đối tượng Session. Các đối tượng Session không nên mở trong thời gian dài bởi vì chúng là unsafe thread nên cần được tạo ra và đóng khi cần thiết.

Chúng ta có thể sử dụng method openSession() hoặc getCurrentSession() để mở một session. Nếu dùng openSession() nó sẽ mở một session mới, còn nếu dùng getCurrentSession() nó sẽ lấy session từ thread context đang tồn tại.

- Method getCurrentSession() 

Để sử dụng thì đầu tiên cần phải cấu hình hibernate.current_session_context_class 

hibernateProperties.setProperty(
        AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "org.hibernate.context.internal.ThreadLocalSessionContext");

Nếu không có cấu hình trên thì bạn sẽ gặp lỗi:

org.hibernate.HibernateException: No CurrentSessionContext configured!

Khi sử dụng getCurrentSession() thì session sẽ tự động đẩy dữ liệu (flush()) và đóng (close()) session sau mỗi lần commit.

Cùng xem ví dụ bên dưới:


@SpringBootApplication
public class HibernateNativeAPIApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(HibernateNativeAPIApplication.class, args);
    }

    @Autowired
    SessionFactory sessionFactory;

    @Override
    @Transactional
    public void run(String... args) throws Exception {
        Session session = sessionFactory.getCurrentSession();
        session.beginTransaction();
        Users u1 = session.load(Users.class, 1l);
        session.getTransaction().commit();
        Users u2 = session.load(Users.class, 2l);
        System.out.println(u1);
        System.out.println(u2);
    }
}

Sau khi kết thúc một phiên thì getCurrentSession() sẽ tự động đóng session nên trường hợp trên nó sẽ bắn ra exception vì session đã bị close.

Caused by: java.lang.IllegalStateException: Session/EntityManager is closed

- Method openSession()

Đối với phương thức openSession(), sau khi truy vấn dữ liệu (thêm, xóa, sửa) thì session vẫn còn giữ và không tự động đẩy (flush()) hay close mà bạn phải tự làm việc này.


@SpringBootApplication
public class HibernateNativeAPIApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(HibernateNativeAPIApplication.class, args);
    }

    @Autowired
    SessionFactory sessionFactory;

    @Override
    @Transactional
    public void run(String... args) throws Exception {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        Users u1 = session.load(Users.class, 1l);
        session.getTransaction().commit();
        Users u2 = session.load(Users.class, 2l);
        System.out.println(u1);
        System.out.println(u2);
    }
}

Vì openSession() sau khi kết thúc một commit thì session vẫn được giữ đến khi chúng ta gọi close nên để tránh memory leak chúng ta cần quản lý close session đúng cách.

4. Tổng kết

Trong bài này chúng ta đã tìm hiểu cách config SessionFactory trong Springboot, bài sau chúng ta sẽ đi tìm hiểu Lifecycle của Entity và các API của Session. Mọi thắc mắc hoắc đóng góp giúp bài viết hoạn thiện hơn vui lòng comment ở bên dưới. Happy learning :D

Nguồn: cafeit.info