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<String, Object> getVendorProperties() { Map<String, Object> 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<String, String> 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(); }
コメントを残す