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();
}
コメント