Reputation: 13
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 e.g. 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.
Upvotes: 1
Views: 10508
Reputation: 13
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.
Upvotes: 0
Reputation: 241
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":
Hope this helps.
Upvotes: 5