Search code examples
javaspringjpaspring-transactionsspring-orm

EntityManager.persist() not working with @Transactional


I'm trying to learn how to use the @Transactional annotation in Spring (I'm not using Spring Boot). But when I swich from my - working - method using begin() and commit() ton the one with @Transactional, my entities aren't persisted. Can you help me understand what I'm doing wrong?

Without transactional/working method:

public <S extends Message> S save(S entity) {
    EntityManager em = pu.getEntityManager();
    try {
        logger.debug("Trying to save " + ((Message) entity).toString());
        em.getTransaction().begin();
        em.persist(entity);
        em.getTransaction().commit();
        logger.debug("MessageRepository.save() - after .commit()");
    }
    catch (PersistenceException e) {
        logger.error("Entity already exists");
        e.printStackTrace();
    }
    finally {
        System.out.println();
        em.close();
    }

    return entity;
}

With @Transactional/not working:

@Transactional
@Override
public <S extends Message> S save(S entity) {
    EntityManager em = pu.getEntityManager();
    em.persist(entity);
    return entity;
}

And here is my PersustanceUtil (pu) class:

package com.cypherf.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.annotation.PreDestroy;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
@Service
public class PersistenceUtil {
    private static EntityManagerFactory emf = null;

    @Bean
    public PlatformTransactionManager txManager() {
        return new JpaTransactionManager(getEntityManagerFactory());
    }

    /**
     * Creates entity manager factory as singleton instance and returns it
     *
     * @return the EntityManagerFactory
     */
    public EntityManagerFactory getEntityManagerFactory() {
        if (emf == null) {
            synchronized (EntityManagerFactory.class) {
                if (emf == null) {
                    final StandardServiceRegistry sr = new StandardServiceRegistryBuilder()
                            .configure() // Configures setting from hibernate.cfg.xml
                            .build();
                    try {
                        emf = new MetadataSources(sr).buildMetadata().buildSessionFactory();
                    }
                    catch (Exception e) {
                        StandardServiceRegistryBuilder.destroy(sr);
                        throw e;
                    }
                }
            }
        }
        return emf;
    }

    /**
     * Closes the entity manager factory
     */
    @PreDestroy
    public static void closeEntityManagerFactory() {
        System.out.println("PersistenceUtil.closeEntityManagerFactory()");
        if (emf != null) {
            System.out.println("Closing emf");
            emf.close();
        }
    }

    /**
     * Returns a new EntityManager instance
     *
     * @return the new EntityManager instance
     */
    public EntityManager getEntityManager() {
        return getEntityManagerFactory().createEntityManager();
    }
}

Main class:

package com.cypherf;
import com.cypherf.model.Message;
import com.cypherf.repository.MessageRepository;
import com.cypherf.service.MessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan
@EnableTransactionManagement
public class TestSpringApplication {
    @Autowired
    private ApplicationContext context;

    @Autowired
    private CrudRepository<Message, Long> messageRepository;

    @Autowired
    private MessageService messageService;

    final static Logger logger = LoggerFactory.getLogger(TestSpringApplication.class);

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(TestSpringApplication.class);
        TestSpringApplication app = ctx.getBean(TestSpringApplication.class);
        logger.info("Running app...");
        app.run(args);
        //TestSpringApplication app = context.getBean(TestSpringApplication.class);
        //TestSpringApplication app = new TestSpringApplication();
        //app.run(args);

    }

    public void run(String... args) {
        messageRepository.save(new Message("Message one"));
        messageRepository.save(new Message("Message two"));
        messageService.printAllMessages();

        messageRepository.save(new Message("Message two"));
        messageService.printAllMessages();

//        System.out.println("BEANS:");
//        List<String> beans = Arrays.asList(context.getBeanDefinitionNames());
//        beans.forEach(bean -> System.out.println(bean));
    }
}

Here is the output:

[...]
2419 [main] DEBUG org.hibernate.event.internal.EntityCopyObserverFactoryInitiator  - Configured EntityCopyObserver strategy: disallow
2518 [main] DEBUG org.hibernate.boot.internal.ClassLoaderAccessImpl  - Not known whether passed class name [com.cypherf.model.Message] is safe
2518 [main] DEBUG org.hibernate.boot.internal.ClassLoaderAccessImpl  - No temp ClassLoader provided; using live ClassLoader for loading potentially unsafe class : com.cypherf.model.Message
2790 [main] DEBUG org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl  - HHH000513: Unable to create the ReflectionOptimizer for [com.cypherf.model.Message]: private accessor [text]
2853 [main] DEBUG org.hibernate.orm.model.mapping.creation  - Starting post-init callbacks
2853 [main] DEBUG org.hibernate.orm.model.mapping.creation  - Starting PostInitCallbackEntry : Entity(com.cypherf.model.Message) `staticFetchableList` generator
2853 [main] DEBUG org.hibernate.orm.model.mapping.creation  - Starting PostInitCallbackEntry : Entity(com.cypherf.model.Message) `sqmMultiTableInsertStrategy` interpretation
2946 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Static SQL for entity: com.cypherf.model.Message
2946 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Version select: select id from Message where id=?
2946 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Insert (0): insert into Message (text,id) values (?,?)
2946 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Update (0): update Message set text=? where id=?
2946 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Delete (0): delete from Message where id=?
2962 [main] DEBUG org.hibernate.orm.sql.ast.create  - Created new SQL alias : m1_0
2962 [main] DEBUG org.hibernate.orm.sql.ast.create  - Registration of TableGroup [StandardTableGroup(com.cypherf.model.Message)] with identifierForTableGroup [com.cypherf.model.Message] for NavigablePath [com.cypherf.model.Message] 
2993 [main] DEBUG org.hibernate.orm.results.graph.AST  - DomainResult Graph:
 \-EntityResultImpl [com.cypherf.model.Message]
 |  \-BasicFetch [com.cypherf.model.Message.text]

2993 [main] DEBUG org.hibernate.orm.sql.ast.tree  - SQL AST Tree:
    SelectStatement {
      FromClause {
        StandardTableGroup (m1 : com.cypherf.model.Message) {
          primaryTableReference : Message as m1_0
        }
      }
    }

3040 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator  - No JtaPlatform was specified, checking resolver
3040 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator  - No JtaPlatformResolver was specified, using default [org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver]
3056 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver  - Could not resolve JtaPlatform, using default [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
3056 [main] INFO  org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator  - HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
3056 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope  - Scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration@4d27d9d] to SessionFactoryImplementor [org.hibernate.internal.SessionFactoryImpl@3a209918]
3056 [main] DEBUG org.hibernate.query.named.NamedObjectRepository  - Checking 0 named HQL queries
3056 [main] DEBUG org.hibernate.query.named.NamedObjectRepository  - Checking 0 named SQL queries
3071 [main] DEBUG org.hibernate.SQL  - 
    drop table if exists Message cascade 
Hibernate: 
    drop table if exists Message cascade 
3087 [main] INFO  org.hibernate.orm.connections.access  - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@5170bc02] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
3088 [main] DEBUG org.hibernate.SQL  - 
    drop sequence if exists Message_SEQ
Hibernate: 
    drop sequence if exists Message_SEQ
3088 [main] DEBUG org.hibernate.SQL  - 
    create sequence Message_SEQ start with 1 increment by 50
Hibernate: 
    create sequence Message_SEQ start with 1 increment by 50
3088 [main] INFO  org.hibernate.orm.connections.access  - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@4601047] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
3103 [main] DEBUG org.hibernate.SQL  - 
    create table Message (
        id bigint not null,
        text varchar(255) unique,
        primary key (id)
    )
Hibernate: 
    create table Message (
        id bigint not null,
        text varchar(255) unique,
        primary key (id)
    )
3119 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry  - Initializing SessionFactoryRegistry : org.hibernate.internal.SessionFactoryRegistry@40dd552c
3119 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry  - Registering SessionFactory: 0c3c67af-87cc-4b0a-bfd9-a5e6498e66fc (<unnamed>)
3119 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry  - Not binding SessionFactory to JNDI, no JNDI name configured
3119 [main] DEBUG org.hibernate.internal.SessionFactoryImpl  - Instantiated SessionFactory
3176 [main] DEBUG org.hibernate.stat.internal.StatisticsInitiator  - Statistics initialized [enabled=false]
3333 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Creating shared instance of singleton bean 'messageService'
3333 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Creating shared instance of singleton bean 'txManager'
3402 [main] INFO  com.cypherf.TestSpringApplication  - Running app...
3409 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Creating new transaction with name [com.cypherf.repository.MessageRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
3409 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Opened new EntityManager [SessionImpl(1809269661<open>)] for JPA transaction
3409 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
3409 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - begin
3409 [main] DEBUG com.cypherf.repository.MessageRepository  - Trying to save Message [id: 0; text:"Message one"]
3425 [main] DEBUG org.hibernate.SQL  - 
    select
        next value for Message_SEQ
Hibernate: 
    select
        next value for Message_SEQ
3425 [main] DEBUG org.hibernate.id.enhanced.SequenceStructure  - Sequence value obtained: 1
3440 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 1, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
3440 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Initiating transaction commit
3440 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Committing JPA transaction on EntityManager [SessionImpl(1809269661<open>)]
3440 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - committing
3456 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3456 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3456 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Closing JPA EntityManager [SessionImpl(1809269661<open>)] after transaction
3456 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Creating new transaction with name [com.cypherf.repository.MessageRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
3456 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Opened new EntityManager [SessionImpl(1553616699<open>)] for JPA transaction
3456 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
3456 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - begin
3456 [main] DEBUG com.cypherf.repository.MessageRepository  - Trying to save Message [id: 0; text:"Message two"]
3456 [main] DEBUG org.hibernate.SQL  - 
    select
        next value for Message_SEQ
Hibernate: 
    select
        next value for Message_SEQ
3456 [main] DEBUG org.hibernate.id.enhanced.SequenceStructure  - Sequence value obtained: 51
3456 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 2, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
3456 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Initiating transaction commit
3456 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Committing JPA transaction on EntityManager [SessionImpl(1553616699<open>)]
3456 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - committing
3456 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3456 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3456 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Closing JPA EntityManager [SessionImpl(1553616699<open>)] after transaction
3456 [main] INFO  com.cypherf.service.MessageService  - MessageService.printAllMessages()
3456 [main] DEBUG org.hibernate.orm.query.hql  - HQL : from Message
3724 [main] DEBUG org.hibernate.orm.query.sqm.ast  - SqmStatement Tree :
    -> [select]
      -> [query-spec]
        -> [select]
          -> [selection]
            -> [root] - `com.cypherf.model.Message(136820536837300)`
            <- [root] - `com.cypherf.model.Message(136820536837300)`
          <- [selection]
        <- [select]
        -> [from]
          -> [root] - `com.cypherf.model.Message(136820536837300)`
          <- [root] - `com.cypherf.model.Message(136820536837300)`
        <- [from]
      <- [query-spec]
    <- [select]

3771 [main] DEBUG org.hibernate.orm.sql.ast.create  - Created new SQL alias : m1_0
3771 [main] DEBUG org.hibernate.orm.sql.ast.create  - Registration of TableGroup [StandardTableGroup(com.cypherf.model.Message(136820536837300))] with identifierForTableGroup [com.cypherf.model.Message] for NavigablePath [com.cypherf.model.Message] 
3787 [main] DEBUG org.hibernate.orm.results.graph.AST  - DomainResult Graph:
 \-EntityResultImpl [com.cypherf.model.Message(136820536837300)]
 |  \-BasicFetch [com.cypherf.model.Message(136820536837300).text]

3787 [main] DEBUG org.hibernate.orm.sql.ast.tree  - SQL AST Tree:
    SelectStatement {
      FromClause {
        StandardTableGroup (m1 : com.cypherf.model.Message(136820536837300)) {
          primaryTableReference : Message as m1_0
        }
      }
    }

3803 [main] DEBUG org.hibernate.orm.sql.exec  - Skipping reading Query result cache data: cache-enabled = false, cache-mode = NORMAL
3819 [main] DEBUG org.hibernate.orm.results  - Initializer list
3819 [main] DEBUG org.hibernate.orm.results  -     com.cypherf.model.Message(136820536837300) -> EntityResultInitializer(com.cypherf.model.Message(136820536837300))@107577149 (SingleTableEntityPersister(com.cypherf.model.Message))
3819 [main] DEBUG org.hibernate.SQL  - 
    select
        m1_0.id,
        m1_0.text 
    from
        Message m1_0
Hibernate: 
    select
        m1_0.id,
        m1_0.text 
    from
        Message m1_0
3819 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3819 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3819 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Creating new transaction with name [com.cypherf.repository.MessageRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
3819 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Opened new EntityManager [SessionImpl(225465790<open>)] for JPA transaction
3819 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
3819 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - begin
3819 [main] DEBUG com.cypherf.repository.MessageRepository  - Trying to save Message [id: 0; text:"Message two"]
3834 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 3, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
3834 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Initiating transaction commit
3834 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Committing JPA transaction on EntityManager [SessionImpl(225465790<open>)]
3834 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl  - committing
3834 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3834 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3834 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager  - Closing JPA EntityManager [SessionImpl(225465790<open>)] after transaction
3834 [main] INFO  com.cypherf.service.MessageService  - MessageService.printAllMessages()
3834 [main] DEBUG org.hibernate.orm.sql.exec  - Skipping reading Query result cache data: cache-enabled = false, cache-mode = NORMAL
3834 [main] DEBUG org.hibernate.orm.results  - Initializer list
3834 [main] DEBUG org.hibernate.orm.results  -     com.cypherf.model.Message(136820536837300) -> EntityResultInitializer(com.cypherf.model.Message(136820536837300))@1174086484 (SingleTableEntityPersister(com.cypherf.model.Message))
3834 [main] DEBUG org.hibernate.SQL  - 
    select
        m1_0.id,
        m1_0.text 
    from
        Message m1_0
Hibernate: 
    select
        m1_0.id,
        m1_0.text 
    from
        Message m1_0
3834 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction
3834 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl  - Initiating JDBC connection release from afterTransaction

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.3/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 5s
3 actionable tasks: 2 executed, 1 up-to-date
13:29:27: Execution finished ':TestSpringApplication.main()'.

Solution

  • I finally made your code working on my local by fixing your code. :)

    Now, it's persisting the data in the database. I have used mysql database for testing.

    For testing, hibernate.cfg.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
    <hibernate-configuration>
        <session-factory>
            <property name="show_sql">true</property>
            <property name="format_sql">true</property>
            <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
            <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
            <property name="connection.url">jdbc:mysql://localhost:3306/test</property>
            <property name="connection.username">root</property>
            <property name="connection.password">Anish@123</property>
            <property name="hibernate.hbm2ddl.auto">create-drop</property>
            <property name="hibernate.show_sql">true</property>
            <mapping class="com.example.demo.Message"/>
        </session-factory>
    </hibernate-configuration>
    

    Remove @EnableTransactionManagement from the MessageRepository class, put @Transactional(transactionManager="txManager") on the save method and put @PersistenceContext on the entityManager instead of @Autowired:

    package com.example.demo;
    
    import jakarta.persistence.EntityManager;
    import jakarta.persistence.NoResultException;
    import jakarta.persistence.PersistenceContext;
    import jakarta.persistence.criteria.CriteriaBuilder;
    import jakarta.persistence.criteria.CriteriaQuery;
    import jakarta.persistence.criteria.Root;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.data.repository.CrudRepository;
    import org.springframework.lang.NonNull;
    import org.springframework.stereotype.Repository;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Optional;
    
    @Repository
    public class MessageRepository implements CrudRepository<Message, Long> {
    
        @PersistenceContext
        private EntityManager em;
    
        private final static Logger logger = LoggerFactory.getLogger(MessageRepository.class);
    
        @Transactional(transactionManager = "txManager")
        @Override
        public @NonNull <S extends Message> S save(@NonNull S message) {
            //em.getTransaction().begin();
            em.persist(message);
            //em.getTransaction().commit();
            return message;
        }
    
         ....
    }
    

    Add @EnableTransactionManagement on the PersistenceUtil and @Bean over the EntityManagerFactory so that Spring is able to find the bean and inject in EntityManager via @PersistenceContext:

    package com.example.demo;
    
    import jakarta.annotation.PreDestroy;
    import jakarta.persistence.EntityManager;
    import jakarta.persistence.EntityManagerFactory;
    import org.hibernate.boot.MetadataSources;
    import org.hibernate.boot.registry.StandardServiceRegistry;
    import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.orm.jpa.JpaTransactionManager;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    @Configuration
    @EnableTransactionManagement
    @Service
    public class PersistenceUtil {
        final static Logger logger = LoggerFactory.getLogger(PersistenceUtil.class);
    
        private static EntityManagerFactory emf = null;
    
        @Bean
        public PlatformTransactionManager txManager() {
            return new JpaTransactionManager(getEntityManagerFactory());
        }
    
        @Bean
        public EntityManagerFactory getEntityManagerFactory() {
            if (emf == null) {
                synchronized (EntityManagerFactory.class) {
                    if (emf == null) {
                        final StandardServiceRegistry sr = new StandardServiceRegistryBuilder()
                                .configure() // Configures setting from hibernate.cfg.xml
                                .build();
                        try {
                            emf = new MetadataSources(sr).buildMetadata().buildSessionFactory();
                        }
                        catch (Exception e) {
                            StandardServiceRegistryBuilder.destroy(sr);
                            throw e;
                        }
                    }
                }
            }
            return emf;
        }
    
        @PreDestroy
        public static void closeEntityManagerFactory() {
            System.out.println("PersistenceUtil.closeEntityManagerFactory()");
            if (emf != null) {
                emf.close();
            }
        }
    
        @Bean
        public EntityManager getEntityManager() {
            return getEntityManagerFactory().createEntityManager();
        }
    }
    

    TestSpringApplication:

    @Configuration
    @ComponentScan
    public class TestSpringApplication {
        @Autowired
        private ApplicationContext context;
    
        @Autowired
        private CrudRepository<Message, Long> messageRepository;
    
        final static Logger logger = LoggerFactory.getLogger(TestSpringApplication.class);
    
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(TestSpringApplication.class);
            TestSpringApplication app = ctx.getBean(TestSpringApplication.class);
            app.run(args);
        }
    
        public void run(String... args) {
            // SAVE
            logger.debug("saving message 1/one...");
            Message messageOne = messageRepository.save(new Message("Message one"));
    
            logger.debug("saving message 2/two...");
            Message messageTwo = messageRepository.save(new Message("Message two"));
            printAllMessages();
    
    
            // SAVE ALL
            logger.debug("saving message 2/two...");
            Message messageThree = new Message("Message three");
            Message messageFour = new Message("Message four");
            Message messageFive = new Message("Message five");
            Message messageSix = new Message("Message six");
            Message messageSeven = new Message("Message seven");
            Message messageEight = new Message("Message eight");
            List<Message> messages = Arrays.asList(messageThree, messageFour, messageFive, messageSix, messageSeven, messageEight);
            messageRepository.saveAll(messages);
    
            // FIND ALL
            printAllMessages();
        }
    
        private void printAllMessages() {
            logger.debug("finding all messages...");
            messageRepository.findAll().forEach(m -> {
                logger.debug("Message: " + m.toString());
            });
        }
    }
     
    

    Successful data persistence with the screenshot:

    enter image description here