Spring Data JPAのJPA実装をHibernateからEclipseLinkに変更する

開発

Spring Data JPAを使用するとデフォルトのJPA実装がHibernateになるのですが、
Hibernateって(Spring Bootとの兼ね合いかもしれませんが)クラスローダーのメモリリーク起こすわ起動遅いわで運用上支障が出るレベルだったので、
思い切ってEclipseLinkへの鞍替えをやってみました。

開発環境でとりあえず動くようになったので、設定変更内容をメモしておきます。

gradleの設定変更

spring-data-jpaの依存関係でHibernateを使わないようにexcludeし、代わりにEclipseLinkを依存関係に追加します。

変更前

// for PostgreSQL, Spring Data JPA and Hibernate
compile 'org.springframework:spring-jdbc:' + springVersion + '.RELEASE'
compile 'org.springframework:spring-orm:' + springVersion + '.RELEASE'
compile 'org.springframework.data:spring-data-jpa:1.7.2.RELEASE'
compile 'org.hibernate:hibernate-entitymanager:4.3.8.Final'
compile 'org.hibernate:hibernate-validator:5.1.3.Final'

変更後

// for PostgreSQL, Spring Data JPA and Eclipselink
compile 'org.springframework:spring-jdbc:' + springVersion + '.RELEASE'
compile 'org.springframework:spring-orm:' + springVersion + '.RELEASE'
compile('org.springframework.data:spring-data-jpa:1.8.2.RELEASE') {
    exclude module: 'org.hibernate:hibernate-entitymanager'
}
compile 'org.eclipse.persistence:org.eclipse.persistence.jpa:2.6.2'

propertiesよりdialect設定削除

Hibernateの設定はいらんので消します。

変更前

# JPA/HIbernate
db.platform.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=true

変更後

# JPA
spring.jpa.show-sql=true

PersistenceConfigをJpaBaseConfigurationを継承して設定

本家で使用していたJpaBaseConfigurationをするようにします。

変更前

省略

変更後

@Configuration
@EnableJpaRepositories("com.tononchi.hoge.persistence.repository")
public class PersistenceConfig extends JpaBaseConfiguration {
    @Value("${eclipselink.logging.level.sql}")
    private String sqlLogLevel;
    @Value("${eclipselink.logging.parameters}")
    private String loggingSqlParameters;

    @Override
    protected String[] getPackagesToScan() {
        return new String[]{"com.tononchi.hoge.persistence.domain"};
    }

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

    @Override
    protected Map getVendorProperties() {
        Map jpaProperties = new HashMap<>();
        jpaProperties.put("eclipselink.ddl-generation", "none");
        jpaProperties.put("eclipselink.logging.level.sql", sqlLogLevel);
        jpaProperties.put("eclipselink.logging.parameters", loggingSqlParameters);
        jpaProperties.put("eclipselink.session.customizer", CamelCaseSessionCustomizer.class.getName());
        jpaProperties.put("eclipselink.temporal.mutable", "true");
        jpaProperties.put("eclipselink.cache.shared.default", "false");
        jpaProperties.put("eclipselink.query-results-cache", "false");
        jpaProperties.put("eclipselink.target-database", "PostgreSQL");
        jpaProperties.put("eclipselink.weaving", "false");
        return jpaProperties;
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        EclipseLinkJpaVendorAdapter vendorAdapter = new EclipseLinkJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.POSTGRESQL);
        return vendorAdapter;
    }
}

CamelCaseSessionCustomizerを作成

Entityをキャメルケースで定義していたのに、EclipseLinkはHibernateのようにアンダースコアに変換してくれません。
ここは先人の知恵を借りてさくっと変換定義を加えます。

変更前

なし

変更後

/**
 * @author Ivan Rodriguez Murillo
 */
public class CamelCaseSessionCustomizer implements SessionCustomizer {
@Override
public void customize(Session session) throws SQLException {
    for (ClassDescriptor descriptor : session.getDescriptors().values()) {
        // Only change the table name for non-embedable entities with no
        // @Table already
        if (!descriptor.getTables().isEmpty() && descriptor.getAlias().equalsIgnoreCase(descriptor.getTableName())) {
            String tableName = addUnderscores(descriptor.getTableName());
            descriptor.setTableName(tableName);
            for (IndexDefinition index : descriptor.getTables().get(0).getIndexes()) {
                index.setTargetTable(tableName);
            }
        }
        for (DatabaseMapping mapping : descriptor.getMappings()) {
            // Only change the column name for non-embedable entities with
            // no @Column already
            if (mapping.getField() != null && !mapping.getAttributeName().isEmpty()
                    && mapping.getField().getName().equalsIgnoreCase(mapping.getAttributeName())) {
                mapping.getField().setName(addUnderscores(mapping.getAttributeName()));
            }
        }
    }
}

private static String addUnderscores(String name) {
    StringBuffer buf = new StringBuffer(name.replace('.', '_'));
    for (int i = 1; i < buf.length() - 1; i++) {
        if (Character.isLowerCase(buf.charAt(i - 1)) && Character.isUpperCase(buf.charAt(i))
                && Character.isLowerCase(buf.charAt(i + 1))) {
            buf.insert(i++, '_');
        }
    }
    return buf.toString().toUpperCase();
}

}

PersistenceContextでEntityManagerを呼び出している部分をPersisteceUnitでEntityManagerFactoryを呼び出すよう変更

こちらを参照

変更前

    @PersistenceContext
    private EntityManager entityManager;

    public int getTerminalId() {
        Query query = entityManager.createNativeQuery(SQL);

変更後

    @PersistenceUnit
    private EntityManagerFactory entityManagerFactory;

    public int getHoge() {
        Query query = entityManagerFactory.createEntityManager().createNativeQuery(SQL);

各Entityでカラムをintで定義しているものをIntegerに変更

intは使えないみたいです...

変更前

@Component
@Entity
@Table(name = Hoge.TABLE_NAME)
public class Hoge implements Serializable, Persistable {
public static final String TABLE_NAME = "hoge";
@Id
private int id;

変更後

@Component
@Entity
@Table(name = Hoge.TABLE_NAME)
public class Hoge implements Serializable, Persistable {
public static final String TABLE_NAME = "hoge";
@Id
private Integer id;

結果

クラスローダーのメモリリークは無くなり、deployから20秒くらいかかっていた起動速度も4秒程度におさまりました。
速度に関してはモジュールの大きさ等にもよると思いますが、割と気軽に再起動できるようになりました。
めでたしめでたし。

旧設定

以下はJpaBaseConfigurationを使用する前の設定です。
こちらでも起動時にエラーにはなりませんでしたが、JPARepositoryを使用したコミットができませんでした。

JpaVendorAdapterをEclipseLinkJpaVendorAdapterに変更

VendorAdapterもHibernateからEclipseLinkへ。

変更前

HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

変更後

EclipseLinkJpaVendorAdapter vendorAdapter = new EclipseLinkJpaVendorAdapter();

@EntityScanを削除

@EntityScanを残しておくと

java.lang.IllegalStateException: Unable to configure LocalContainerEntityManagerFactoryBean from @EntityScan, ensure an appropriate bean is registered.

ってエラーが出るので消します。同じ設定をLocalContainerEntityManagerFactoryBeanですればOK。

変更前

@EntityScan("com.tononchi.hoge.persistence.domain")

変更後

下記LocalContainerEntityManagerFactoryBeanへsetPackagesToScanで設定

EntityManagerFactoryの設定変更

ココらへんからややトリッキーで検証不足。
ddl-generationでcreateとかすると勝手にテーブル作り始めるという怖い動きをするので外したり、
後述のCamelCaseSessionCustomizerを設定したり、DBをポスグレに設定したりしています。

変更前

@Autowired
@Bean(name="entityManagerFactory")
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
    LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
    bean.setDataSource(dataSource);
    bean.setJpaVendorAdapter(jpaVendorAdapter);
    bean.setPackagesToScan("com.tononchi.hoge.persistence.domain");
    bean.afterPropertiesSet();
    return bean.getObject();
}

変更後

@Autowired
@Bean(name="entityManagerFactory")
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
    LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
    bean.setDataSource(dataSource);
    bean.setJpaVendorAdapter(jpaVendorAdapter);
    bean.setPackagesToScan("com.tononchi.hoge.persistence.domain");

    Map jpaProperties = new HashMap<>();
    jpaProperties.put("eclipselink.ddl-generation", "none");
    jpaProperties.put("eclipselink.logging.level.sql", "fine");
    jpaProperties.put("eclipselink.logging.parameters", "true");
    jpaProperties.put("eclipselink.session.customizer", CamelCaseSessionCustomizer.class.getName());
    jpaProperties.put("eclipselink.temporal.mutable", "true");
    jpaProperties.put("eclipselink.persistence-context.flush-mode", "commit");
    jpaProperties.put("eclipselink.cache.shared.default", "false");
    jpaProperties.put("eclipselink.query-results-cache", "false");
    jpaProperties.put("eclipselink.target-database", "PostgreSQL");
    jpaProperties.put("eclipselink.weaving", "false");
    bean.setJpaPropertyMap(jpaProperties);
    bean.afterPropertiesSet();
    return bean.getObject();
}

コメント

タイトルとURLをコピーしました