diff --git a/hibernate-types-52/pom.xml b/hibernate-types-52/pom.xml index e603650db..b81f94fb6 100644 --- a/hibernate-types-52/pom.xml +++ b/hibernate-types-52/pom.xml @@ -70,6 +70,84 @@ true + + com.zaxxer + HikariCP + ${hikari.version} + + + org.slf4j + slf4j-api + + + provided + true + + + + org.aspectj + aspectjrt + ${aspectj.version} + provided + true + + + + org.aspectj + aspectjweaver + ${aspectj.version} + provided + true + + + + org.springframework + spring-beans + ${spring.version} + provided + true + + + + org.springframework + spring-context + ${spring.version} + provided + true + + + + org.springframework + spring-tx + ${spring.version} + provided + true + + + + org.springframework + spring-orm + ${spring.version} + provided + true + + + + org.springframework.data + spring-data-jpa + ${spring-data.version} + provided + true + + + + org.springframework + spring-test + ${spring.version} + jar + test + + @@ -85,6 +163,11 @@ 31.1-jre 1.4.2 + 4.0.3 + 1.8.7 + 5.3.18 + 2.6.0 + diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java b/hibernate-types-52/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java new file mode 100644 index 000000000..fc0fc44b8 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java @@ -0,0 +1,62 @@ +package com.vladmihalcea.spring.repository; + +import java.util.List; + +/** + * The {@code HibernateRepository} fixes the problems that the default Spring Data {@code JpaRepository} + * suffers from. + * + * For more details about how to use it, check out this article. + * + * @author Vlad Mihalcea + * @version 2.17.0 + */ +public interface HibernateRepository { + + //Save methods will trigger an UnsupportedOperationException + + @Deprecated + S save(S entity); + + @Deprecated + List saveAll(Iterable entities); + + @Deprecated + S saveAndFlush(S entity); + + @Deprecated + List saveAllAndFlush(Iterable entities); + + //Persist methods are meant to save newly created entities + + S persist(S entity); + + S persistAndFlush(S entity); + + List persistAll(Iterable entities); + + List peristAllAndFlush(Iterable entities); + + //Merge methods are meant to propagate detached entity state changes + //if they are really needed + + S merge(S entity); + + S mergeAndFlush(S entity); + + List mergeAll(Iterable entities); + + List mergeAllAndFlush(Iterable entities); + + //Update methods are meant to force the detached entity state changes + + S update(S entity); + + S updateAndFlush(S entity); + + List updateAll(Iterable entities); + + List updateAllAndFlush(Iterable entities); + +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java b/hibernate-types-52/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java new file mode 100644 index 000000000..158c0a944 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java @@ -0,0 +1,167 @@ +package com.vladmihalcea.spring.repository; + +import org.hibernate.Session; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.AbstractSharedSessionContract; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * @author Vlad Mihalcea + */ +public class HibernateRepositoryImpl implements HibernateRepository { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public S save(S entity) { + return unsupported(); + } + + @Override + public List saveAll(Iterable entities) { + return unsupported(); + } + + @Override + public S saveAndFlush(S entity) { + return unsupported(); + } + + @Override + public List saveAllAndFlush(Iterable entities) { + return unsupported(); + } + + public S persist(S entity) { + entityManager.persist(entity); + return entity; + } + + public S persistAndFlush(S entity) { + persist(entity); + entityManager.flush(); + return entity; + } + + public List persistAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(persist(entity)); + } + return result; + } + + public List peristAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(persist(entity)); + } + entityManager.flush(); + return result; + }); + } + + public S merge(S entity) { + return entityManager.merge(entity); + } + + @Override + public S mergeAndFlush(S entity) { + S result = merge(entity); + entityManager.flush(); + return result; + } + + @Override + public List mergeAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(merge(entity)); + } + return result; + } + + @Override + public List mergeAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(merge(entity)); + } + entityManager.flush(); + return result; + }); + } + + public S update(S entity) { + session().update(entity); + return entity; + } + + @Override + public S updateAndFlush(S entity) { + update(entity); + entityManager.flush(); + return entity; + } + + @Override + public List updateAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(update(entity)); + } + return result; + } + + @Override + public List updateAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(update(entity)); + } + entityManager.flush(); + return result; + }); + } + + protected Integer getBatchSize(Session session) { + SessionFactoryImplementor sessionFactory = session.getSessionFactory().unwrap(SessionFactoryImplementor.class); + final JdbcServices jdbcServices = sessionFactory.getServiceRegistry().getService(JdbcServices.class); + if(!jdbcServices.getExtractedMetaDataSupport().supportsBatchUpdates()) { + return Integer.MIN_VALUE; + } + return session.unwrap(AbstractSharedSessionContract.class).getConfiguredJdbcBatchSize(); + } + + protected R executeBatch(Supplier callback) { + Session session = session(); + Integer jdbcBatchSize = getBatchSize(session); + Integer originalSessionBatchSize = session.getJdbcBatchSize(); + try { + if (jdbcBatchSize == null) { + session.setJdbcBatchSize(10); + } + return callback.get(); + } finally { + session.setJdbcBatchSize(originalSessionBatchSize); + } + } + + protected Session session() { + return entityManager.unwrap(Session.class); + } + + protected S unsupported() { + throw new UnsupportedOperationException("There's no such thing as a save method in JPA, so don't use this hack!"); + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/spring/base/config/SpringDataJPABaseConfiguration.java b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/base/config/SpringDataJPABaseConfiguration.java new file mode 100644 index 000000000..808c37e86 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/base/config/SpringDataJPABaseConfiguration.java @@ -0,0 +1,134 @@ +package com.vladmihalcea.spring.base.config; + +import com.vladmihalcea.hibernate.util.logging.InlineQueryLogEntryCreator; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener; +import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import java.util.Properties; + +/** + * + * @author Vlad Mihalcea + */ +@Configuration +@EnableTransactionManagement +@EnableAspectJAutoProxy +public abstract class SpringDataJPABaseConfiguration { + + @Value("${jdbc.dataSourceClassName}") + private String dataSourceClassName; + + @Value("${jdbc.url}") + private String jdbcUrl; + + @Value("${jdbc.username}") + private String jdbcUser; + + @Value("${jdbc.password}") + private String jdbcPassword; + + @Value("${hibernate.dialect}") + private String hibernateDialect; + + @Bean + public static PropertySourcesPlaceholderConfigurer propertySources() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean(destroyMethod = "close") + public HikariDataSource actualDataSource() { + Properties driverProperties = new Properties(); + driverProperties.setProperty("url", jdbcUrl); + driverProperties.setProperty("user", jdbcUser); + driverProperties.setProperty("password", jdbcPassword); + + Properties properties = new Properties(); + properties.put("dataSourceClassName", dataSourceClassName); + properties.put("dataSourceProperties", driverProperties); + properties.setProperty("maximumPoolSize", String.valueOf(3)); + HikariConfig hikariConfig = new HikariConfig(properties); + hikariConfig.setAutoCommit(false); + return new HikariDataSource(hikariConfig); + } + + @Bean + public DataSource dataSource() { + SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener(); + loggingListener.setQueryLogEntryCreator(new InlineQueryLogEntryCreator()); + DataSource dataSource = ProxyDataSourceBuilder + .create(actualDataSource()) + .name("DATA_SOURCE_PROXY") + .listener(loggingListener) + .build(); + return dataSource; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); + entityManagerFactoryBean.setPersistenceUnitName(getClass().getSimpleName()); + entityManagerFactoryBean.setPersistenceProvider(new HibernatePersistenceProvider()); + entityManagerFactoryBean.setDataSource(dataSource()); + entityManagerFactoryBean.setPackagesToScan(packagesToScan()); + + JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter); + entityManagerFactoryBean.setJpaProperties(properties()); + return entityManagerFactoryBean; + } + + @Bean + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){ + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory); + return transactionManager; + } + + @Bean + public TransactionTemplate transactionTemplate(EntityManagerFactory entityManagerFactory) { + return new TransactionTemplate(transactionManager(entityManagerFactory)); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + protected Properties properties() { + Properties properties = new Properties(); + properties.setProperty("hibernate.dialect", hibernateDialect); + properties.setProperty("hibernate.hbm2ddl.auto", "create-drop"); + additionalProperties(properties); + return properties; + } + + protected void additionalProperties(Properties properties) { + } + + protected String[] packagesToScan() { + return new String[]{ + packageToScan() + }; + } + + protected String packageToScan() { + return "com.vladmihalcea.book.hpjp.hibernate.forum"; + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/PostRepository.java b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/PostRepository.java new file mode 100644 index 000000000..93ca9b818 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/PostRepository.java @@ -0,0 +1,13 @@ +package com.vladmihalcea.spring.repository; + +import com.vladmihalcea.spring.repository.domain.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Vlad Mihalcea + */ +@Repository +public interface PostRepository extends JpaRepository, HibernateRepository { + +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/SpringDataJPASaveTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/SpringDataJPASaveTest.java new file mode 100644 index 000000000..9de5b0a53 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/SpringDataJPASaveTest.java @@ -0,0 +1,109 @@ +package com.vladmihalcea.spring.repository; + +import com.vladmihalcea.spring.repository.config.SpringDataJPASaveConfiguration; +import com.vladmihalcea.spring.repository.domain.Post; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SpringDataJPASaveConfiguration.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class SpringDataJPASaveTest { + + protected final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + @Autowired + private TransactionTemplate transactionTemplate; + + @Autowired + private PostRepository postRepository; + + @PersistenceContext + private EntityManager entityManager; + + @Test + public void testPersistAndMerge() { + String slug = "high-performance-java-persistence"; + + transactionTemplate.execute((TransactionCallback) transactionStatus -> { + postRepository.persist( + new Post() + .setId(1L) + .setTitle("High-Performance Java Persistence") + .setSlug("high-performance-java-persistence") + ); + + postRepository.persistAndFlush( + new Post() + .setId(2L) + .setTitle("Hypersistence Optimizer") + .setSlug("hypersistence-optimizer") + ); + + postRepository.peristAllAndFlush( + LongStream.range(3, 1000) + .mapToObj(i -> new Post() + .setId(i) + .setTitle(String.format("Post %d", i)) + .setSlug(String.format("post-%d", i)) + ) + .collect(Collectors.toList()) + ); + + return null; + }); + + List posts = transactionTemplate.execute(transactionStatus -> + entityManager.createQuery( + "select p " + + "from Post p " + + "where p.id < 10", Post.class) + .getResultList() + ); + + posts.forEach(post -> post.setTitle(post.getTitle() + " rocks!")); + + transactionTemplate.execute(transactionStatus -> + postRepository.updateAll(posts) + ); + } + + @Test + public void testSave() { + try { + transactionTemplate.execute((TransactionCallback) transactionStatus -> { + postRepository.save( + new Post() + .setId(1L) + .setTitle("High-Performance Java Persistence") + .setSlug("high-performance-java-persistence") + ); + return null; + }); + + fail("Should throw UnsupportedOperationException!"); + } catch (UnsupportedOperationException expected) { + LOGGER.warn("You shouldn't call the JpaRepository save method!"); + } + } +} + diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/config/SpringDataJPASaveConfiguration.java b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/config/SpringDataJPASaveConfiguration.java new file mode 100644 index 000000000..571340dc7 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/config/SpringDataJPASaveConfiguration.java @@ -0,0 +1,34 @@ +package com.vladmihalcea.spring.repository.config; + +import com.vladmihalcea.spring.base.config.SpringDataJPABaseConfiguration; +import com.vladmihalcea.spring.repository.domain.Post; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import java.util.Properties; + +/** + * + * @author Vlad Mihalcea + */ +@ComponentScan( + basePackages = { + "com.vladmihalcea.spring.repository", + } +) +@EnableJpaRepositories("com.vladmihalcea.spring.repository") +@PropertySource({"/META-INF/jdbc-postgresql.properties"}) +public class SpringDataJPASaveConfiguration extends SpringDataJPABaseConfiguration { + + @Override + protected String packageToScan() { + return Post.class.getPackage().getName(); + } + + @Override + protected void additionalProperties(Properties properties) { + properties.put("hibernate.jdbc.batch_size", "100"); + properties.put("hibernate.order_inserts", "true"); + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/domain/Post.java b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/domain/Post.java new file mode 100644 index 000000000..a9ccb3433 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/spring/repository/domain/Post.java @@ -0,0 +1,50 @@ +package com.vladmihalcea.spring.repository.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * @author Vlad Mihalcea + */ +@Entity +@Table( + name = "post", + uniqueConstraints = @UniqueConstraint( + name = "UK_POST_SLUG", + columnNames = "slug" + ) +) +public class Post { + + @Id + private Long id; + + private String title; + + private String slug; + + public Long getId() { + return id; + } + + public Post setId(Long id) { + this.id = id; + return this; + } + + public String getTitle() { + return title; + } + + public Post setTitle(String title) { + this.title = title; + return this; + } + + public Post setSlug(String slug) { + this.slug = slug; + return this; + } +} diff --git a/hibernate-types-52/src/test/resources/META-INF/jdbc-hsqldb.properties b/hibernate-types-52/src/test/resources/META-INF/jdbc-hsqldb.properties new file mode 100644 index 000000000..54dcea523 --- /dev/null +++ b/hibernate-types-52/src/test/resources/META-INF/jdbc-hsqldb.properties @@ -0,0 +1,6 @@ +hibernate.dialect=org.hibernate.dialect.HSQLDialect +jdbc.driverClassName=org.hsqldb.jdbc.JDBCDriver +jdbc.dataSourceClassName=org.hsqldb.jdbc.JDBCDataSource +jdbc.url=jdbc:hsqldb:mem:test +jdbc.username=sa +jdbc.password= \ No newline at end of file diff --git a/hibernate-types-52/src/test/resources/META-INF/jdbc-mysql.properties b/hibernate-types-52/src/test/resources/META-INF/jdbc-mysql.properties new file mode 100644 index 000000000..a47e83a03 --- /dev/null +++ b/hibernate-types-52/src/test/resources/META-INF/jdbc-mysql.properties @@ -0,0 +1,6 @@ +hibernate.dialect=org.hibernate.dialect.MySQL57Dialect +jdbc.dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource +jdbc.url=jdbc:mysql://localhost/high_performance_java_persistence +jdbc.database=high_performance_java_persistence +jdbc.username=mysql +jdbc.password=admin \ No newline at end of file diff --git a/hibernate-types-52/src/test/resources/META-INF/jdbc-postgresql.properties b/hibernate-types-52/src/test/resources/META-INF/jdbc-postgresql.properties new file mode 100644 index 000000000..cf95c16d5 --- /dev/null +++ b/hibernate-types-52/src/test/resources/META-INF/jdbc-postgresql.properties @@ -0,0 +1,8 @@ +hibernate.dialect=org.hibernate.dialect.PostgreSQL94Dialect +jdbc.dataSourceClassName=org.postgresql.ds.PGSimpleDataSource +jdbc.url=jdbc:postgresql://localhost:5432/high_performance_java_persistence +jdbc.host=localhost +jdbc.port=5432 +jdbc.database=high_performance_java_persistence +jdbc.username=postgres +jdbc.password=admin \ No newline at end of file diff --git a/hibernate-types-55/pom.xml b/hibernate-types-55/pom.xml index 8fba02afb..49d81f1c9 100644 --- a/hibernate-types-55/pom.xml +++ b/hibernate-types-55/pom.xml @@ -70,6 +70,84 @@ true + + com.zaxxer + HikariCP + ${hikari.version} + + + org.slf4j + slf4j-api + + + provided + true + + + + org.aspectj + aspectjrt + ${aspectj.version} + provided + true + + + + org.aspectj + aspectjweaver + ${aspectj.version} + provided + true + + + + org.springframework + spring-beans + ${spring.version} + provided + true + + + + org.springframework + spring-context + ${spring.version} + provided + true + + + + org.springframework + spring-tx + ${spring.version} + provided + true + + + + org.springframework + spring-orm + ${spring.version} + provided + true + + + + org.springframework.data + spring-data-jpa + ${spring-data.version} + provided + true + + + + org.springframework + spring-test + ${spring.version} + jar + test + + @@ -84,6 +162,11 @@ 31.1-jre 1.4.2 + 4.0.3 + 1.8.7 + 5.3.18 + 2.6.0 + diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/array/BooleanArrayType.java b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/array/BooleanArrayType.java index 52e5c4709..ed9cbebca 100644 --- a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/array/BooleanArrayType.java +++ b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/array/BooleanArrayType.java @@ -4,7 +4,6 @@ import com.vladmihalcea.hibernate.type.array.internal.BooleanArrayTypeDescriptor; import com.vladmihalcea.hibernate.type.util.Configuration; import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType; -import org.hibernate.type.spi.TypeBootstrapContext; import org.hibernate.usertype.DynamicParameterizedType; import java.util.Properties; diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java b/hibernate-types-55/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java new file mode 100644 index 000000000..fc0fc44b8 --- /dev/null +++ b/hibernate-types-55/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java @@ -0,0 +1,62 @@ +package com.vladmihalcea.spring.repository; + +import java.util.List; + +/** + * The {@code HibernateRepository} fixes the problems that the default Spring Data {@code JpaRepository} + * suffers from. + * + * For more details about how to use it, check out this article. + * + * @author Vlad Mihalcea + * @version 2.17.0 + */ +public interface HibernateRepository { + + //Save methods will trigger an UnsupportedOperationException + + @Deprecated + S save(S entity); + + @Deprecated + List saveAll(Iterable entities); + + @Deprecated + S saveAndFlush(S entity); + + @Deprecated + List saveAllAndFlush(Iterable entities); + + //Persist methods are meant to save newly created entities + + S persist(S entity); + + S persistAndFlush(S entity); + + List persistAll(Iterable entities); + + List peristAllAndFlush(Iterable entities); + + //Merge methods are meant to propagate detached entity state changes + //if they are really needed + + S merge(S entity); + + S mergeAndFlush(S entity); + + List mergeAll(Iterable entities); + + List mergeAllAndFlush(Iterable entities); + + //Update methods are meant to force the detached entity state changes + + S update(S entity); + + S updateAndFlush(S entity); + + List updateAll(Iterable entities); + + List updateAllAndFlush(Iterable entities); + +} diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java b/hibernate-types-55/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java new file mode 100644 index 000000000..158c0a944 --- /dev/null +++ b/hibernate-types-55/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java @@ -0,0 +1,167 @@ +package com.vladmihalcea.spring.repository; + +import org.hibernate.Session; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.AbstractSharedSessionContract; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * @author Vlad Mihalcea + */ +public class HibernateRepositoryImpl implements HibernateRepository { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public S save(S entity) { + return unsupported(); + } + + @Override + public List saveAll(Iterable entities) { + return unsupported(); + } + + @Override + public S saveAndFlush(S entity) { + return unsupported(); + } + + @Override + public List saveAllAndFlush(Iterable entities) { + return unsupported(); + } + + public S persist(S entity) { + entityManager.persist(entity); + return entity; + } + + public S persistAndFlush(S entity) { + persist(entity); + entityManager.flush(); + return entity; + } + + public List persistAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(persist(entity)); + } + return result; + } + + public List peristAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(persist(entity)); + } + entityManager.flush(); + return result; + }); + } + + public S merge(S entity) { + return entityManager.merge(entity); + } + + @Override + public S mergeAndFlush(S entity) { + S result = merge(entity); + entityManager.flush(); + return result; + } + + @Override + public List mergeAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(merge(entity)); + } + return result; + } + + @Override + public List mergeAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(merge(entity)); + } + entityManager.flush(); + return result; + }); + } + + public S update(S entity) { + session().update(entity); + return entity; + } + + @Override + public S updateAndFlush(S entity) { + update(entity); + entityManager.flush(); + return entity; + } + + @Override + public List updateAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(update(entity)); + } + return result; + } + + @Override + public List updateAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(update(entity)); + } + entityManager.flush(); + return result; + }); + } + + protected Integer getBatchSize(Session session) { + SessionFactoryImplementor sessionFactory = session.getSessionFactory().unwrap(SessionFactoryImplementor.class); + final JdbcServices jdbcServices = sessionFactory.getServiceRegistry().getService(JdbcServices.class); + if(!jdbcServices.getExtractedMetaDataSupport().supportsBatchUpdates()) { + return Integer.MIN_VALUE; + } + return session.unwrap(AbstractSharedSessionContract.class).getConfiguredJdbcBatchSize(); + } + + protected R executeBatch(Supplier callback) { + Session session = session(); + Integer jdbcBatchSize = getBatchSize(session); + Integer originalSessionBatchSize = session.getJdbcBatchSize(); + try { + if (jdbcBatchSize == null) { + session.setJdbcBatchSize(10); + } + return callback.get(); + } finally { + session.setJdbcBatchSize(originalSessionBatchSize); + } + } + + protected Session session() { + return entityManager.unwrap(Session.class); + } + + protected S unsupported() { + throw new UnsupportedOperationException("There's no such thing as a save method in JPA, so don't use this hack!"); + } +} diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/spring/base/config/SpringDataJPABaseConfiguration.java b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/base/config/SpringDataJPABaseConfiguration.java new file mode 100644 index 000000000..808c37e86 --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/base/config/SpringDataJPABaseConfiguration.java @@ -0,0 +1,134 @@ +package com.vladmihalcea.spring.base.config; + +import com.vladmihalcea.hibernate.util.logging.InlineQueryLogEntryCreator; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener; +import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import java.util.Properties; + +/** + * + * @author Vlad Mihalcea + */ +@Configuration +@EnableTransactionManagement +@EnableAspectJAutoProxy +public abstract class SpringDataJPABaseConfiguration { + + @Value("${jdbc.dataSourceClassName}") + private String dataSourceClassName; + + @Value("${jdbc.url}") + private String jdbcUrl; + + @Value("${jdbc.username}") + private String jdbcUser; + + @Value("${jdbc.password}") + private String jdbcPassword; + + @Value("${hibernate.dialect}") + private String hibernateDialect; + + @Bean + public static PropertySourcesPlaceholderConfigurer propertySources() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean(destroyMethod = "close") + public HikariDataSource actualDataSource() { + Properties driverProperties = new Properties(); + driverProperties.setProperty("url", jdbcUrl); + driverProperties.setProperty("user", jdbcUser); + driverProperties.setProperty("password", jdbcPassword); + + Properties properties = new Properties(); + properties.put("dataSourceClassName", dataSourceClassName); + properties.put("dataSourceProperties", driverProperties); + properties.setProperty("maximumPoolSize", String.valueOf(3)); + HikariConfig hikariConfig = new HikariConfig(properties); + hikariConfig.setAutoCommit(false); + return new HikariDataSource(hikariConfig); + } + + @Bean + public DataSource dataSource() { + SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener(); + loggingListener.setQueryLogEntryCreator(new InlineQueryLogEntryCreator()); + DataSource dataSource = ProxyDataSourceBuilder + .create(actualDataSource()) + .name("DATA_SOURCE_PROXY") + .listener(loggingListener) + .build(); + return dataSource; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); + entityManagerFactoryBean.setPersistenceUnitName(getClass().getSimpleName()); + entityManagerFactoryBean.setPersistenceProvider(new HibernatePersistenceProvider()); + entityManagerFactoryBean.setDataSource(dataSource()); + entityManagerFactoryBean.setPackagesToScan(packagesToScan()); + + JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter); + entityManagerFactoryBean.setJpaProperties(properties()); + return entityManagerFactoryBean; + } + + @Bean + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){ + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory); + return transactionManager; + } + + @Bean + public TransactionTemplate transactionTemplate(EntityManagerFactory entityManagerFactory) { + return new TransactionTemplate(transactionManager(entityManagerFactory)); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + protected Properties properties() { + Properties properties = new Properties(); + properties.setProperty("hibernate.dialect", hibernateDialect); + properties.setProperty("hibernate.hbm2ddl.auto", "create-drop"); + additionalProperties(properties); + return properties; + } + + protected void additionalProperties(Properties properties) { + } + + protected String[] packagesToScan() { + return new String[]{ + packageToScan() + }; + } + + protected String packageToScan() { + return "com.vladmihalcea.book.hpjp.hibernate.forum"; + } +} diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/PostRepository.java b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/PostRepository.java new file mode 100644 index 000000000..f96aeae72 --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/PostRepository.java @@ -0,0 +1,13 @@ +package com.vladmihalcea.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import com.vladmihalcea.spring.repository.domain.Post; + +/** + * @author Vlad Mihalcea + */ +@Repository +public interface PostRepository extends JpaRepository, HibernateRepository { + +} diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/SpringDataJPASaveTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/SpringDataJPASaveTest.java new file mode 100644 index 000000000..609227c6f --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/SpringDataJPASaveTest.java @@ -0,0 +1,110 @@ +package com.vladmihalcea.spring.repository; + +import com.vladmihalcea.spring.repository.config.SpringDataJPASaveConfiguration; +import com.vladmihalcea.spring.repository.domain.Post; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import com.vladmihalcea.spring.repository.domain.Post; + +import static org.junit.Assert.fail; + +/** + * @author Vlad Mihalcea + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SpringDataJPASaveConfiguration.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class SpringDataJPASaveTest { + + protected final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + @Autowired + private TransactionTemplate transactionTemplate; + + @Autowired + private PostRepository postRepository; + + @PersistenceContext + private EntityManager entityManager; + + @Test + public void testPersistAndMerge() { + String slug = "high-performance-java-persistence"; + + transactionTemplate.execute((TransactionCallback) transactionStatus -> { + postRepository.persist( + new Post() + .setId(1L) + .setTitle("High-Performance Java Persistence") + .setSlug("high-performance-java-persistence") + ); + + postRepository.persistAndFlush( + new Post() + .setId(2L) + .setTitle("Hypersistence Optimizer") + .setSlug("hypersistence-optimizer") + ); + + postRepository.peristAllAndFlush( + LongStream.range(3, 1000) + .mapToObj(i -> new Post() + .setId(i) + .setTitle(String.format("Post %d", i)) + .setSlug(String.format("post-%d", i)) + ) + .collect(Collectors.toList()) + ); + + return null; + }); + + List posts = transactionTemplate.execute(transactionStatus -> + entityManager.createQuery( + "select p " + + "from Post p " + + "where p.id < 10", Post.class) + .getResultList() + ); + + posts.forEach(post -> post.setTitle(post.getTitle() + " rocks!")); + + transactionTemplate.execute(transactionStatus -> + postRepository.updateAll(posts) + ); + } + + @Test + public void testSave() { + try { + transactionTemplate.execute((TransactionCallback) transactionStatus -> { + postRepository.save( + new Post() + .setId(1L) + .setTitle("High-Performance Java Persistence") + .setSlug("high-performance-java-persistence") + ); + return null; + }); + + fail("Should throw UnsupportedOperationException!"); + } catch (UnsupportedOperationException expected) { + LOGGER.warn("You shouldn't call the JpaRepository save method!"); + } + } +} + diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/config/SpringDataJPASaveConfiguration.java b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/config/SpringDataJPASaveConfiguration.java new file mode 100644 index 000000000..571340dc7 --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/config/SpringDataJPASaveConfiguration.java @@ -0,0 +1,34 @@ +package com.vladmihalcea.spring.repository.config; + +import com.vladmihalcea.spring.base.config.SpringDataJPABaseConfiguration; +import com.vladmihalcea.spring.repository.domain.Post; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import java.util.Properties; + +/** + * + * @author Vlad Mihalcea + */ +@ComponentScan( + basePackages = { + "com.vladmihalcea.spring.repository", + } +) +@EnableJpaRepositories("com.vladmihalcea.spring.repository") +@PropertySource({"/META-INF/jdbc-postgresql.properties"}) +public class SpringDataJPASaveConfiguration extends SpringDataJPABaseConfiguration { + + @Override + protected String packageToScan() { + return Post.class.getPackage().getName(); + } + + @Override + protected void additionalProperties(Properties properties) { + properties.put("hibernate.jdbc.batch_size", "100"); + properties.put("hibernate.order_inserts", "true"); + } +} diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/domain/Post.java b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/domain/Post.java new file mode 100644 index 000000000..a9ccb3433 --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/spring/repository/domain/Post.java @@ -0,0 +1,50 @@ +package com.vladmihalcea.spring.repository.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * @author Vlad Mihalcea + */ +@Entity +@Table( + name = "post", + uniqueConstraints = @UniqueConstraint( + name = "UK_POST_SLUG", + columnNames = "slug" + ) +) +public class Post { + + @Id + private Long id; + + private String title; + + private String slug; + + public Long getId() { + return id; + } + + public Post setId(Long id) { + this.id = id; + return this; + } + + public String getTitle() { + return title; + } + + public Post setTitle(String title) { + this.title = title; + return this; + } + + public Post setSlug(String slug) { + this.slug = slug; + return this; + } +} diff --git a/hibernate-types-55/src/test/resources/META-INF/jdbc-hsqldb.properties b/hibernate-types-55/src/test/resources/META-INF/jdbc-hsqldb.properties new file mode 100644 index 000000000..54dcea523 --- /dev/null +++ b/hibernate-types-55/src/test/resources/META-INF/jdbc-hsqldb.properties @@ -0,0 +1,6 @@ +hibernate.dialect=org.hibernate.dialect.HSQLDialect +jdbc.driverClassName=org.hsqldb.jdbc.JDBCDriver +jdbc.dataSourceClassName=org.hsqldb.jdbc.JDBCDataSource +jdbc.url=jdbc:hsqldb:mem:test +jdbc.username=sa +jdbc.password= \ No newline at end of file diff --git a/hibernate-types-55/src/test/resources/META-INF/jdbc-mysql.properties b/hibernate-types-55/src/test/resources/META-INF/jdbc-mysql.properties new file mode 100644 index 000000000..a47e83a03 --- /dev/null +++ b/hibernate-types-55/src/test/resources/META-INF/jdbc-mysql.properties @@ -0,0 +1,6 @@ +hibernate.dialect=org.hibernate.dialect.MySQL57Dialect +jdbc.dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource +jdbc.url=jdbc:mysql://localhost/high_performance_java_persistence +jdbc.database=high_performance_java_persistence +jdbc.username=mysql +jdbc.password=admin \ No newline at end of file diff --git a/hibernate-types-55/src/test/resources/META-INF/jdbc-postgresql.properties b/hibernate-types-55/src/test/resources/META-INF/jdbc-postgresql.properties new file mode 100644 index 000000000..cf95c16d5 --- /dev/null +++ b/hibernate-types-55/src/test/resources/META-INF/jdbc-postgresql.properties @@ -0,0 +1,8 @@ +hibernate.dialect=org.hibernate.dialect.PostgreSQL94Dialect +jdbc.dataSourceClassName=org.postgresql.ds.PGSimpleDataSource +jdbc.url=jdbc:postgresql://localhost:5432/high_performance_java_persistence +jdbc.host=localhost +jdbc.port=5432 +jdbc.database=high_performance_java_persistence +jdbc.username=postgres +jdbc.password=admin \ No newline at end of file diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java b/hibernate-types-60/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java new file mode 100644 index 000000000..16eef8da3 --- /dev/null +++ b/hibernate-types-60/src/main/java/com/vladmihalcea/spring/repository/HibernateRepository.java @@ -0,0 +1,55 @@ +package com.vladmihalcea.spring.repository; + +import java.util.List; + +/** + * @author Vlad Mihalcea + */ +public interface HibernateRepository { + + //Save methods will trigger an UnsupportedOperationException + + @Deprecated + S save(S entity); + + @Deprecated + List saveAll(Iterable entities); + + @Deprecated + S saveAndFlush(S entity); + + @Deprecated + List saveAllAndFlush(Iterable entities); + + //Persist methods are meant to save newly created entities + + S persist(S entity); + + S persistAndFlush(S entity); + + List persistAll(Iterable entities); + + List peristAllAndFlush(Iterable entities); + + //Merge methods are meant to propagate detached entity state changes + //if they are really needed + + S merge(S entity); + + S mergeAndFlush(S entity); + + List mergeAll(Iterable entities); + + List mergeAllAndFlush(Iterable entities); + + //Update methods are meant to force the detached entity state changes + + S update(S entity); + + S updateAndFlush(S entity); + + List updateAll(Iterable entities); + + List updateAllAndFlush(Iterable entities); + +} diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java b/hibernate-types-60/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java new file mode 100644 index 000000000..504670083 --- /dev/null +++ b/hibernate-types-60/src/main/java/com/vladmihalcea/spring/repository/HibernateRepositoryImpl.java @@ -0,0 +1,167 @@ +package com.vladmihalcea.spring.repository; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.hibernate.Session; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.AbstractSharedSessionContract; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * @author Vlad Mihalcea + */ +public class HibernateRepositoryImpl implements HibernateRepository { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public S save(S entity) { + return unsupported(); + } + + @Override + public List saveAll(Iterable entities) { + return unsupported(); + } + + @Override + public S saveAndFlush(S entity) { + return unsupported(); + } + + @Override + public List saveAllAndFlush(Iterable entities) { + return unsupported(); + } + + public S persist(S entity) { + entityManager.persist(entity); + return entity; + } + + public S persistAndFlush(S entity) { + persist(entity); + entityManager.flush(); + return entity; + } + + public List persistAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(persist(entity)); + } + return result; + } + + public List peristAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(persist(entity)); + } + entityManager.flush(); + return result; + }); + } + + public S merge(S entity) { + return entityManager.merge(entity); + } + + @Override + public S mergeAndFlush(S entity) { + S result = merge(entity); + entityManager.flush(); + return result; + } + + @Override + public List mergeAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(merge(entity)); + } + return result; + } + + @Override + public List mergeAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(merge(entity)); + } + entityManager.flush(); + return result; + }); + } + + public S update(S entity) { + session().update(entity); + return entity; + } + + @Override + public S updateAndFlush(S entity) { + update(entity); + entityManager.flush(); + return entity; + } + + @Override + public List updateAll(Iterable entities) { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(update(entity)); + } + return result; + } + + @Override + public List updateAllAndFlush(Iterable entities) { + return executeBatch(() -> { + List result = new ArrayList<>(); + for(S entity : entities) { + result.add(update(entity)); + } + entityManager.flush(); + return result; + }); + } + + protected Integer getBatchSize(Session session) { + SessionFactoryImplementor sessionFactory = session.getSessionFactory().unwrap(SessionFactoryImplementor.class); + final JdbcServices jdbcServices = sessionFactory.getServiceRegistry().getService(JdbcServices.class); + if(!jdbcServices.getExtractedMetaDataSupport().supportsBatchUpdates()) { + return Integer.MIN_VALUE; + } + return session.unwrap(AbstractSharedSessionContract.class).getConfiguredJdbcBatchSize(); + } + + protected R executeBatch(Supplier callback) { + Session session = session(); + Integer jdbcBatchSize = getBatchSize(session); + Integer originalSessionBatchSize = session.getJdbcBatchSize(); + try { + if (jdbcBatchSize == null) { + session.setJdbcBatchSize(10); + } + return callback.get(); + } finally { + session.setJdbcBatchSize(originalSessionBatchSize); + } + } + + protected Session session() { + return entityManager.unwrap(Session.class); + } + + protected S unsupported() { + throw new UnsupportedOperationException("There's no such thing as a save method in JPA, so don't use this hack!"); + } +} diff --git a/pom.xml b/pom.xml index 2fec895a6..bfb4c4e56 100644 --- a/pom.xml +++ b/pom.xml @@ -352,7 +352,7 @@ 1.7.25 1.2.3 - 4.8.1 + 4.13.1 1.8.5 1.6