简体   繁体   中英

Problem with transaction commit in @Transactional methods using Java Spring and Hibernate and EntityManager

I'm trying to learn Spring, Hibernate with the H2 database usinge maven to build the code. Currently I have some problems how to use the @Transactional annotation correctly to automatic start the transaction and commit the transaction when eg entityManager.persist is done successful or rollback.

My test project is very simple. The POJO class is Person and contains first name, family name and email address. There is a Service class PersonSerice that is an interface that offers CRUD functions to add, change, read and delete person data. There is the PersonServiceImpl that calls the methods of the DAO class. And here the sample Code of the method PersonDAOImpl::createPerson Using

public void createPerson(Person person) {
    entityManager.getTransaction().begin();
    entityManager.persist(person);
    entityManager.getTransaction().commit();
}

Everything works as expected. There is a Hibernate SQL output

"Hibernate: call next value for hibernate_sequence Hibernate: insert into person (email, nachname, vorname, id) values (?, ?, ?, ?)"

I want to get rid of manually calling entityManager.getTransaction().commit(); So I tried to write @Transactional at the ServiceImpl method that call the DAO method

    public void createPerson(Person person) {
    entityManager.getTransaction().begin();
    entityManager.persist(person);
    entityManager.getTransaction().commit();
}

Now it does not work properly. I just get. " Hibernate: call next value for hibernate_sequence " There is something written into the database but I cannot list all entries or remove them without a manually commit. So I currently don't know what is wrong and how I can get @Transactional do the commit automatically. Here part of the entityManager content shown in Eclipse debugger:

entityManager $Proxy26 (id=33) h ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler (id=116)
containerManaged false
exceptionTranslator null jta false synchronizedWithTransaction false
target SessionImpl (id=122)
actionQueue ActionQueue (id=306)
... autoJoinTransactions true
...

I guess my main problems could be in the xml resource files so I want to show them here. Here is my Beans.xlm (./src/main/resources/Beans.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd">

   <context:component-scan base-package="maven.springhibernateh2.basic"></context:component-scan>
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName"
            value="${db.driverClassName}"></property>
        <property name="url" value="${db.url}"></property>
        <property name="username" value="${db.username}"></property>
        <property name="password" value="${db.password}"></property>
    </bean>


    <bean
        class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>database.properties</value>
            </list>
        </property>
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
    </bean>


   <!-- Definition des JpaTransactionManagers -->
   <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
      <property name="entityManagerFactory" ref="entityManagerFactory" />
   </bean>

   <!-- Acitvation of @Transactional Annotation -->
   <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />

   <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
      <property name="persistenceUnitName" value="roland.egger.maven.springhibernate" />
      <property name="dataSource" ref="dataSource" />
   </bean>

    <context:spring-configured />
    <context:annotation-config />

</beans>

One line is maybe a problem. "<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />" As I don't have aspectj dependencies in my pom. But adding them didn't change anything and I don't know what is needed to get @Transactional working as expected.

Now the other files.

Here is my persistence.xml (./src/main/resources/META-INF/persistence.xml)

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="roland.egger.maven.springhibernate" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

    <class>maven.springhibernateh2.basic.Person</class>
     <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
      <property name="hibernate.hbm2ddl.auto" value="create" />
      <property name="hibernate.show_sql" value="true" />
      <property name="hibernate.format_sql" value="true" />
    </properties>


  </persistence-unit>
</persistence>

Here my pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>roland.egger</groupId>
  <artifactId>maven.springhibernateh2.basic</artifactId>
  <version>0.0.1-SNAPSHOT</version>
   <build>
      <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.1</version>
            <configuration>
               <source>1.8</source>
               <target>1.8</target>
            </configuration>
         </plugin>
      </plugins>
   </build>
   <properties>
      <slf4j.version>1.7.30</slf4j.version>
      <spring.version>5.2.5.RELEASE</spring.version>
      <hibernate.version>5.4.15.Final</hibernate.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-core</artifactId>
         <version>${spring.version}</version>
      </dependency>
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context</artifactId>
         <version>${spring.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-orm</artifactId>
         <version>${spring.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-jdbc</artifactId>
         <version>${spring.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-aspects</artifactId>
         <version>${spring.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-tx</artifactId>
         <version>${spring.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
         <version>1.4.200</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
      <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-core</artifactId>
         <version>${hibernate.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
      <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-entitymanager</artifactId>
         <version>${hibernate.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.hibernate.javax.persistence/hibernate-jpa-2.1-api -->
      <dependency>
         <groupId>org.hibernate.javax.persistence</groupId>
         <artifactId>hibernate-jpa-2.1-api</artifactId>
         <version>1.0.2.Final</version>
      </dependency>
      <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
         <version>${slf4j.version}</version>
      </dependency>
      <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-log4j12</artifactId>
         <version>${slf4j.version}</version>
      </dependency>
      <!-- Fuer den RollingFileAppender -->
      <dependency>
         <groupId>log4j</groupId>
         <artifactId>apache-log4j-extras</artifactId>
         <version>1.1</version>
      </dependency>
   </dependencies>
</project>

Here database.properties

db.driverClassName=org.h2.Driver
db.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
db.username=sa
db.password=

Here Person.java

package maven.springhibernateh2.basic;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;


@Entity
@Table(name="person")
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id")
    private int personId;

    @Column(name = "vorname")
    private String Vorname;

    @Column(name = "nachname")
    private String Nachname;

    @Column(name = "email")
    private String Emailadresse;
    public int getPersonId() {
        return personId;
    }
    public void setPersonId(int personId) {
        this.personId = personId;
    }
    public String getVorname() {
        return Vorname;
    }
    public void setVorname(String vorname) {
        Vorname = vorname;
    }
    public String getNachname() {
        return Nachname;
    }
    public void setNachname(String nachname) {
        Nachname = nachname;
    }
    public String getEmailadresse() {
        return Emailadresse;
    }
    public void setEmailadresse(String emailadresse) {
        Emailadresse = emailadresse;
    }

    public String toString() {
        return "Person [PersonId=" + personId + ", Vorname=" + Vorname + ", Nachname=" + Nachname + ", Emailadresse=" + Emailadresse + "]";
    }
}

PersonService.java

package maven.springhibernateh2.basic;

import java.util.List;

public interface PersonService {
    public abstract void addPerson(Person person);
    public abstract Person fetchPersonById(int personId);
    public abstract void deletePersonByID(int personId);
    public abstract void updatePersonEmailByID(String newEmail, int personId);
    public abstract List<Person> getAllPersonInfo();
}

PersonServiceImpl.java

package maven.springhibernateh2.basic;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component("personService")
public class PersonServiceImpl implements PersonService {

    @Autowired
    private PersonDAO personDAO;

    public void setPersonDAO(PersonDAO personDAO) {
        this.personDAO = personDAO;
    }

    @Transactional
    public void addPerson(Person person) {
        personDAO.createPerson(person);
    }

    @Transactional
    public Person fetchPersonById(int personId) {
        return personDAO.getPersonById(personId);
    }

    @Transactional
    public void deletePersonByID(int personId) {
        personDAO.deletePersonByID(personId);
    }

    @Transactional
    public void updatePersonEmailByID(String newEmail, int personId) {
        personDAO.updatePersonEmailByID(newEmail, personId);
    }

    @Transactional
    public List<Person> getAllPersonInfo() {
        return personDAO.getAllPersonData();
    }
}

PersonDAO.java

package maven.springhibernateh2.basic;

import java.util.List;

public interface PersonDAO {
    public abstract void createPerson(Person person);
    public abstract Person getPersonById(int personId);
    public abstract void deletePersonByID(int personId);
    public abstract void updatePersonEmailByID(String newEmail, int personId);
    public abstract List<Person> getAllPersonData();

}

PersonDAOImpl.java

package maven.springhibernateh2.basic;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.springframework.stereotype.Repository;


@Repository
public class PersonDAOImpl implements PersonDAO {

    @PersistenceUnit(name = "roland.egger.maven.springhibernate")
    private EntityManagerFactory entityManagerFactory;    

    private EntityManager entityManager;

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }

    @PersistenceUnit
    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
        this.entityManager = this.entityManagerFactory.createEntityManager();
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }


    public void createPerson(Person person) {
        entityManager.persist(person);
    }

    public Person getPersonById(int personId) {
        Person person = entityManager.find(Person.class, personId);
        return person;
    }

    public void deletePersonByID(int personId) {
        Person person = getPersonById(personId);
        if (person != null) {
            //entityManager.getTransaction().begin();
            entityManager.remove(person);
            //entityManager.getTransaction().commit();
        }
    }

    public void updatePersonEmailByID(String newEmail, int personId) {
        Person person = getPersonById(personId);
        if (person != null)
        { 
            entityManager.getTransaction().begin();
            person.setEmailadresse(newEmail);
            entityManager.getTransaction().commit();
        }
    }

    public List<Person> getAllPersonData() {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Person> cq = cb.createQuery(Person.class);
        Root<Person> rootEntry = cq.from(Person.class);
        CriteriaQuery<Person> all = cq.select(rootEntry);
        TypedQuery<Person> allQuery = entityManager.createQuery(all);
        return allQuery.getResultList();
    }

}

Excuse me for posting the source code but I hope that it helps others to understand what I am doing and how the problem can be solved to get the transaction working without manually writing them into the code.

When you use @PersistenceUnit you need to create/destroy EntityManager and manually manage transactions. If you want to use spring @Transactional you need to remove entityManagerFactory which is annotated by @PersistenceUnit, and instead use @PersistenceContext on your entityManager variable as below.

@PersistenceContext
private EntityManager entityManager;

The reason is, when you use @PersistenceContext you define a container managed bean(here it is spring managed) so that you don't need to explicitly commit/rollback your transactions, on the other hand with @PersistenceUnit you specify that you want to handle the transactions.

Update:

Related with the latest error which mentions about "No EntityManager with actual transaction available for current thread":

  • You can either remove mode="aspectj" from configuration if you don't need to use aspectj and can rely on spring default AOP. After removing mode="aspectj" from Beans.xml your code should work immediately.
  • Or adjust your config to correctly implement aspectj. At least you need to add aspectj dependencies to your project pom and add "context:load-time-weaver" definition to Beans.xml. Please check spring transaction management docs for aspectj usage.

Hope this helps.

I tried to answer on Ali Gelenkers suggestion but the comments are to short and I see, that I get into another problem with the entityManager afterwards. Thank to Ali Gelenker I was informed that @PersistenceUnit in my PersonDAOImpl class for my EntityManagerFactory and its setter function causes problems and that @PersistenceContext should be used. Here the part of my new code of PersonDaoImpl

@Repository
public class PersonDAOImpl implements PersonDAO {

    private EntityManagerFactory entityManagerFactory;    

    @PersistenceContext
    private EntityManager entityManager;

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }

    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
        this.entityManager = this.entityManagerFactory.createEntityManager();
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }


    public void createPerson(Person person) {
        entityManager.persist(person);
    }
...

Now neither the setter setEntityManagerFactory nor the setter setEntityManager is called. The problem occurs in the createPerson method during calling entityManager.persist(person). The entityManager call throws the following exception:

" javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call "

Before this exception the entityManager shows the follwing content in the debugger of Eclipse.:

entityManager   $Proxy26  (id=40)   
h   SharedEntityManagerCreator$SharedEntityManagerInvocationHandler  (id=47)    
           logger   LogAdapter$Slf4jLocationAwareLog  (id=51)   
           properties   null
           proxyClassLoader Launcher$AppClassLoader  (id=55)
           synchronizedWithTransaction  true    
           targetFactory    $Proxy23  (id=62)

The complete console output is:

INFO  | 2020-05-09 22:44:44,953 |                                      |        | main | maven.springhibernateh2.basic.CRUDTest - Programmanfang... 
 INFO  | 2020-05-09 22:44:45,486 |                                      |        | main | org.hibernate.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: roland.egger.maven.springhibernate] 
 INFO  | 2020-05-09 22:44:45,532 |                                      |        | main | org.hibernate.Version - HHH000412: Hibernate ORM core version 5.4.15.Final 
 INFO  | 2020-05-09 22:44:45,657 |                                      |        | main | org.hibernate.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.0.Final} 
 INFO  | 2020-05-09 22:44:46,193 |                                      |        | main | org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.H2Dialect 
 Hibernate: 

    drop table if exists person CASCADE 
Hibernate: 

    drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: 

    create table person (
       id integer not null,
        email varchar(255),
        nachname varchar(255),
        vorname varchar(255),
        primary key (id)
    )
INFO  | 2020-05-09 22:44:46,877 |                                      |        | main | org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 
 INFO  | 2020-05-09 22:44:46,884 |                                      |        | main | org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'roland.egger.maven.springhibernate' 
 ERROR | 2020-05-09 22:44:46,987 |                                      |        | main | maven.springhibernateh2.basic.CRUDTest - javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call 
 INFO  | 2020-05-09 22:44:46,987 |                                      |        | main | maven.springhibernateh2.basic.CRUDTest - Programmende... 
 INFO  | 2020-05-09 22:44:46,988 |                                      |        | main | org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'roland.egger.maven.springhibernate' 

What is needed to make the entityManager available for the current thread?

Update: Thanks to the updated advice of Ali Gelenkers I got it working:) For my test project I've chosen the easiest solution without aspectj. Here the changed part of my Beans.xml: ...

       <!--  <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />  -->
       <tx:annotation-driven transaction-manager="transactionManager" /> 
...
       <aop:config proxy-target-class="true"/>

Now everything works fine without manual transaction calls. Thank you very much:)

Update 2 The code above is working and I'm able to run it in Eclipse and with mvn exec (mvn exec:java -Dexec.mainClass="maven.springhibernateh2.basic.CRUDTest"). Unfortunately I am not able to build an executable jar to run it. Please see: problem creating an executable jar with maven using spring 5 and hibernate 5 => BeanDefinitionParsingException I guess that the pom.xml has a problem. I would be very happy for any suggestions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM