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