Spring Data JPAを使用するとデフォルトのJPA実装がHibernateになるのですが、
Hibernateって(Spring Bootとの兼ね合いかもしれませんが)クラスローダーのメモリリーク起こすわ起動遅いわで運用上支障が出るレベルだったので、
思い切ってEclipseLinkへの鞍替えをやってみました。
開発環境でとりあえず動くようになったので、設定変更内容をメモしておきます。
- gradleの設定変更
- propertiesよりdialect設定削除
- PersistenceConfigをJpaBaseConfigurationを継承して設定
- CamelCaseSessionCustomizerを作成
- PersistenceContextでEntityManagerを呼び出している部分をPersisteceUnitでEntityManagerFactoryを呼び出すよう変更
- 各Entityでカラムをintで定義しているものをIntegerに変更
- 結果
- 旧設定
- JpaVendorAdapterをEclipseLinkJpaVendorAdapterに変更
- @EntityScanを削除
- EntityManagerFactoryの設定変更
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();
}
 
       
  
  
  
  

コメント