Sử dụng EclipseLink thay thế Hibernate trong Spring Data JPA Starter

89

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh

Mặc định, khi các bạn sử dụng Spring Data JPA Starter trong Spring project của mình, Hibernate sẽ là default implementation được sử dụng. Thế nhưng, trong một số trường hợp, các bạn có thể không muốn sử dụng default implementation này. Ví dụ như mình: mình đang convert open source openid-connect này https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server từ Spring MVC sang Spring Boot https://github.com/huongdanjavacom/openid-connect-spring-boot, opensource này đang sử dụng EclipseLink thay vì Hibernate. Trong trường hợp như mình, cách thay thế EclipseLink bằng Hibernate như thế nào? Mình sẽ hướng dẫn các bạn làm điều đó trong bài viết này các bạn nhé!

  Cài đặt TestNG trong Eclipse
  Hướng dẫn cách kết nối đến Database MySQL trong Eclipse

Đầu tiên, mình sẽ tạo mới một Spring Boot project:

Sử dụng EclipseLink thay thế Hibernate trong Spring Data JPA Starter

với Spring Data JPA Starter và PostgreSQL dependency như sau:

Sử dụng EclipseLink thay thế Hibernate trong Spring Data JPA Starter

Cấu hình thông tin database trong tập tin application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=khanh
spring.datasource.password=1

và chạy ứng dụng Spring Boot này, các bạn sẽ thấy log message như sau:

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.0)

2021-05-26 20:56:58.926 INFO 8449 --- [ main] c.h.s.e.SpringBootEclipselinkApplication : Starting SpringBootEclipselinkApplication using Java 15.0.1 on Khanhs-MacBook-Pro.local with PID 8449 (/Users/khanh/Documents/workspace-spring-tool-suite-4-4.9.0.RELEASE/spring-boot-eclipselink/target/classes started by khanh in /Users/khanh/Documents/workspace-spring-tool-suite-4-4.9.0.RELEASE/spring-boot-eclipselink)
2021-05-26 20:56:58.930 INFO 8449 --- [ main] c.h.s.e.SpringBootEclipselinkApplication : No active profile set, falling back to default profiles: default
2021-05-26 20:56:59.402 INFO 8449 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-05-26 20:56:59.411 INFO 8449 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 3 ms. Found 0 JPA repository interfaces.
2021-05-26 20:56:59.743 INFO 8449 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2021-05-26 20:57:00.182 INFO 8449 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2021-05-26 20:57:00.223 INFO 8449 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-05-26 20:57:00.328 INFO 8449 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.4.31.Final
2021-05-26 20:57:00.562 INFO 8449 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-05-26 20:57:00.709 INFO 8449 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL10Dialect
2021-05-26 20:57:01.094 INFO 8449 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-05-26 20:57:01.105 INFO 8449 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-05-26 20:57:01.437 INFO 8449 --- [ main] c.h.s.e.SpringBootEclipselinkApplication : Started SpringBootEclipselinkApplication in 2.816 seconds (JVM running for 3.734)
2021-05-26 20:57:01.440 INFO 8449 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
2021-05-26 20:57:01.441 INFO 8449 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFICĐ

Đọc kỹ những dòng log trên, các bạn sẽ thấy việc Spring Data JPA Starter sử dụng Hibernate làm default implementation như thế nào.

Bây giờ, để thay thế Hibernate default implementation này bằng EclipseLink, đầu tiên, mình sẽ khai báo EclipseLink dependency như sau:

<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>2.7.8</version>
</dependency>

Sau đó thì exclude Hibernate dependency trong Spring Data JPA Starter dependency như sau:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</exclusion>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
</dependency>

Spring Boot sử dụng class HibernateJpaAutoConfiguration để cấu hình hết tất cả những thông tin cần thiết cho Hibernate default implementation. Nếu các bạn take a look class HibernateJpaAutoConfiguration này, các bạn sẽ thấy, tất cả các thông tin liên quan đến Hibernate được Spring Boot cấu hình trong class HibernateJpaConfiguration:

/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigure.orm.jpa;

import javax.persistence.EntityManager;

import org.hibernate.engine.spi.SessionImplementor;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

/**
* {@link EnableAutoConfiguration Auto-configuration} for Hibernate JPA.
*
* @author Phillip Webb
* @author Josh Long
* @author Manuel Doninger
* @author Andy Wilkinson
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
@EnableConfigurationProperties(JpaProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {

}

Class HibernateJpaConfiguration này extends từ class JpaBaseConfiguration. Đây là base class để chúng ta có thể extends và cấu hình JPA cho từng implementation mà các bạn muốn. Hibernate là một implementation và EclipseLink cũng là một implementation của JPA.

Chúng ta sẽ tạo mới class để cấu hình JPA cho EclipseLink:

package com.huongdanjava.springboot.eclipselink;

import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.transaction.jta.JtaTransactionManager;

@Configuration
public class EclipseLinkJpaConfiguration extends JpaBaseConfiguration {

protected EclipseLinkJpaConfiguration(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
super(dataSource, properties, jtaTransactionManager);
}

@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
// TODO Auto-generated method stub
return null;
}

@Override
protected Map<String, Object> getVendorProperties() {
// TODO Auto-generated method stub
return null;
}

}

2 phương thức mà các bạn cần phải implement trong class EclipseLinkJpaConfiguration là createJpaVendorAdapter() và getVendorProperties().

Phương thức createJpaVendorAdapter() sẽ return lại implementation provider cho JPA. Mặc định thì Spring đã hỗ trợ Hibernate và EclipseLink:

Sử dụng EclipseLink thay thế Hibernate trong Spring Data JPA Starter

nên các bạn chỉ cần return lại đối tượng của class EclipseLinkJpaVendorAdapter là được.

@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter();
}

Phương thức getVendorProperties() giúp chúng ta định nghĩa một số cấu hình cụ thể cho từng implementation. Cho EclipseLink thì các bạn có thể sử dụng class PersistenceUnitProperties để add những properties mà nó hỗ trợ. Ví dụ như sau:

@Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> map = new HashMap<>();
map.put(PersistenceUnitProperties.WEAVING, "false");
map.put(PersistenceUnitProperties.LOGGING_LEVEL, SessionLog.FINER_LABEL);

return map;
}

Bây giờ, nếu chạy lại ứng dụng này, các bạn sẽ thấy log message như sau:

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.0)

2021-05-26 22:04:26.403 INFO 10452 --- [ main] c.h.s.e.SpringBootEclipselinkApplication : Starting SpringBootEclipselinkApplication using Java 15.0.1 on Khanhs-MacBook-Pro.local with PID 10452 (/Users/khanh/Documents/workspace-spring-tool-suite-4-4.9.0.RELEASE/spring-boot-eclipselink/target/classes started by khanh in /Users/khanh/Documents/workspace-spring-tool-suite-4-4.9.0.RELEASE/spring-boot-eclipselink)
2021-05-26 22:04:26.405 INFO 10452 --- [ main] c.h.s.e.SpringBootEclipselinkApplication : No active profile set, falling back to default profiles: default
2021-05-26 22:04:26.747 INFO 10452 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-05-26 22:04:26.756 INFO 10452 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 3 ms. Found 0 JPA repository interfaces.
[EL Fine]: server: 2021-05-26 22:04:27.108--Thread(Thread[main,5,main])--Configured server platform: org.eclipse.persistence.platform.server.NoServerPlatform
[EL Finer]: metadata: 2021-05-26 22:04:27.126--Thread(Thread[main,5,main])--Searching for mapping file: [META-INF/orm.xml] at root URL: [file:/Users/khanh/Documents/workspace-spring-tool-suite-4-4.9.0.RELEASE/spring-boot-eclipselink/target/classes/].
[EL Finer]: metadata: 2021-05-26 22:04:27.13--Thread(Thread[main,5,main])--Searching for mapping file: [META-INF/eclipselink-orm.xml] at root URL: [file:/Users/khanh/Documents/workspace-spring-tool-suite-4-4.9.0.RELEASE/spring-boot-eclipselink/target/classes/].
[EL Finer]: metamodel: 2021-05-26 22:04:27.139--ServerSession(674233333)--Thread(Thread[main,5,main])--metamodel_type_collection_empty (There is no English translation for this message.)
2021-05-26 22:04:27.140 INFO 10452 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-05-26 22:04:27.254 INFO 10452 --- [ main] c.h.s.e.SpringBootEclipselinkApplication : Started SpringBootEclipselinkApplication in 1.08 seconds (JVM running for 2.108)
2021-05-26 22:04:27.255 INFO 10452 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
2021-05-26 22:04:27.257 INFO 10452 --- [ main] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
[EL Finer]: 2021-05-26 22:04:27.268--Thread(Thread[SpringContextShutdownHook,5,main])--initializing session manager

Ứng dụng của chúng ta đang sử dụng EclipseLink rồi đó các bạn!