Hibernate 5.2.17 not rolling back failed transactions - java

I'm currently in the process of updating a large project from Hibernate 5.1 to Hibernate 5.2.17 and I'm running into an issue that I'm struggling to resolve.
We have a suite of tests that are testing our DAOs using the H2 in-memory database, but some tests have started to fail on the updated version of Hibernate.
Some of the tests attempt to delete a null entity from the persistence context and expect the operation to fail with an IllegalArgumentException. With the new version of Hibernate the exception is still thrown as expected, but the transaction is no longer being rolled back and is being left active, and is consequently causing subsequent tests to fail because there's already an active transaction. Stack trace included below:
java.lang.AssertionError: Transaction is still active when it should have been rolled back.
at org.junit.Assert.fail(Assert.java:88)
at hibernatetest.persistence.HibernateTestDAOTest.testDeleteDetachedEntity(HibernateTestDAOTest.java:50)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
While investigating I noticed a similar difference in behaviour when attempting to delete a detached entity as well. I have been able to recreate the behaviour in a small, standalone project that can be found here. The project also includes configuration in the pom.xml (commented out) for running against Hibernate 5.0.10, where the tests pass with no issue and the failed transaction is correctly rolled-back.
While I haven't been able to recreate the error deleting a null entity, I have managed to recreate it with a detached entity, and I'm hoping the answer to why this is happening will help guide me to why it's also failing with null in the real code.
Are we doing something wrong here, or is this an issue with Hibernate itself?
Code also included below:
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>HibernateTest</groupId>
<artifactId>HibernateTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.version>3.7.0</maven.compiler.version>
<!-- Uncomment this property to run as Hibernate 5.0.10 -->
<!-- <hibernate.core.version>5.0.10.Final</hibernate.core.version> -->
<!-- Uncomment this property to run as Hibernate 5.2.17 -->
<hibernate.core.version>5.2.17.Final</hibernate.core.version>
<junit.version>4.12</junit.version>
<h2.version>1.4.197</h2.version>
<javaee.api.version>7.0</javaee.api.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.core.version}</version>
</dependency>
<!-- Uncomment these dependencies to run using Hibernate 5.0.10 -->
<!--
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>${hibernate.core.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.core.version}</version>
<scope>test</scope>
</dependency>
-->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>${javaee.api.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
HibernateTest.java (entity class):
package hibernatetest.persistence;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Type;
#Entity
#Table(name = "hibernate_test")
public class HibernateTest {
#Id
#Column(name = "id")
#Type(type = "uuid-char")
private UUID id;
public HibernateTest(final UUID id) {
this.id = id;
}
}
HibernateTestDAO.java
package hibernatetest.persistence;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class HibernateTestDAO {
#PersistenceContext(unitName = "hibernate-test")
private EntityManager entityManager;
public void delete(final HibernateTest entity) {
entityManager.remove(entity);
}
}
EntityManagerRule.java (JUnit Rule to provide the entity manager for the tests):
package hibernatetest.persistence;
import java.lang.reflect.Field;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.rules.ExternalResource;
public class EntityManagerRule extends ExternalResource {
private EntityManagerFactory emFactory;
private EntityManager em;
#Override
protected void before() {
emFactory = Persistence.createEntityManagerFactory("hibernate-test");
em = emFactory.createEntityManager();
}
#Override
protected void after() {
if (em != null) {
em.close();
}
if (emFactory != null) {
emFactory.close();
}
}
public HibernateTestDAO initDAO() {
final HibernateTestDAO dao = new HibernateTestDAO();
try {
injectEntityManager(dao);
} catch (Exception e) {
e.printStackTrace();
}
return dao;
}
public EntityManager getEntityManager() {
return em;
}
public void persist(final Object entity) {
final EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
em.persist(entity);
} catch (Exception e) {
transaction.rollback();
throw e;
}
transaction.commit();
}
private void injectEntityManager(final HibernateTestDAO dao) throws Exception {
final Field emField = dao.getClass().getDeclaredField("entityManager");
emField.setAccessible(true);
emField.set(dao, em);
}
}
HibernateTestDAOTest.java:
package hibernatetest.persistence;
import static org.junit.Assert.fail;
import java.util.UUID;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class HibernateTestDAOTest {
#Rule
public EntityManagerRule rule = new EntityManagerRule();
private HibernateTestDAO dao;
#Before
public void setup() {
dao = rule.initDAO();
}
#Test
public void testDeleteNullEntity() {
HibernateTest entity = null;
try {
dao.delete(entity);
} catch (IllegalArgumentException e) {
if (rule.getEntityManager().getTransaction().isActive()) {
fail("Transaction is still active when it should have been rolled back.");
}
}
}
#Test
public void testDeleteDetachedEntity() {
HibernateTest entity = new HibernateTest(UUID.randomUUID());
rule.persist(entity);
rule.getEntityManager().detach(entity);
try {
dao.delete(entity);
} catch (IllegalArgumentException e) {
if (rule.getEntityManager().getTransaction().isActive()) {
fail("Transaction is still active when it should have been rolled back.");
}
}
}
}
persistence.xml from src/test/resources/META-INF:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="hibernate-test" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>hibernatetest.persistence.HibernateTest</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.url"
value="jdbc:h2:mem:test;INIT=create schema if not exists test\;runscript from 'classpath:/populate.sql';DB_CLOSE_DELAY=-1;"/>
<property name="javax.persistence.validation.mode" value="none"/>
</properties>
</persistence-unit>
</persistence>
populate.sql from src/test/resources:
CREATE TABLE IF NOT EXISTS hibernate_test (
id UUID NOT NULL
);

Nothing in the spec states that when a call to EntityManager#remove fails that the persistence provider should rollback the existing transaction, that just makes no sense.
If you look at all the examples in the Hibernate test suite, you'll notice this behavior:
EntityManager entityManager = getOrCreateEntityManager();
try {
entityManager.getTransaction().begin();
// do something
entityManager.getTransaction().commit();
}
catch ( Exception e ) {
if ( entityManager != null && entityManager.getTransaction.isActive() ) {
entityManager.getTransaction().rollback();
}
throw e;
}
finally {
if ( entityManager != null ) {
entityManager.close();
}
}
If your tests worked previously and no longer do in the same way, I'm not sure I'd necesssarily say that is a bug as the code which you've supplied above does not conform to what I have shown here with properly handling the rollback in user code unless you have spring or some other framework at play which you haven't illustrated.
But if you feel there is a regression between 5.1 and 5.2, you're welcomed to open a JIRA and report it with your reproducable test use case and we can do further investigation.
One key point to remember is that 5.2.x introduced the merging of the JPA artifact hibernate-entitymanager into hibernate-core proper, so there could be a regression here with that but its extremely unlikely.

Related

Hibernate v5->v6 upgrade - poor SELECT performance

I've upgraded an application using Hibernate v5 to v6, but after doing this a query has got very slow - >10x slower.
Take the following simple application that persists 500,000 MyEntitys to a new in-memory database, retrieves them and prints performance metrics. It can be run with either Hibernate v5 or v6, as per the commented out section in pom.xml:
MyApplication.java:
package com.me;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.Properties;
import java.util.stream.IntStream;
import org.h2.Driver;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.schema.Action;
public class MyApplication {
public static void main(final String[] args) {
Instant start = Instant.now();
final Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.connection.url", "jdbc:h2:mem:");
jpaProperties.put("jakarta.persistence.jdbc.driver", Driver.class.getName());
jpaProperties.put("jakarta.persistence.schema-generation.database.action", Action.CREATE);
try (Session session = new Configuration().addAnnotatedClass(MyEntity.class).addProperties(jpaProperties)
.buildSessionFactory().openSession()) {
session.beginTransaction();
IntStream.range(0, 500000).mapToObj(i -> new MyEntity()).forEach(session::persist);
printTiming(start, "Setup / Publish");
start = Instant.now();
session.createQuery("FROM MyEntity", MyEntity.class).getResultList();
printTiming(start, "Get");
}
}
private static void printTiming(final Instant startTime, final String label) {
System.out.println(MessageFormat.format("{0} took {1}", label, Duration.between(startTime, Instant.now())));
}
}
MyEntity.java:
package com.me;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
}
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">
<groupId>com.me</groupId>
<modelVersion>4.0.0</modelVersion>
<artifactId>hibernate-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.5.Final</version>
</dependency>
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core-jakarta</artifactId>
<version>5.6.14.Final</version>
</dependency>-->
</dependencies>
</project>
hibernate-core 6.1.5:
Setup / Publish took PT2.6288547S
Get took PT35.0881315S
hibernate-core-jakarta 5.6.14.Final:
Setup / Publish took PT3.486003S
Get took PT2.3955987S
I've profiled it I can see Hibernate 6 is spending ~90% of the time in org.hibernate.sql.results.spi.ListResultsConsumer.withDuplicationCheck() - some kind of results post-processing.
As I mentioned in comments that is HBN issue introduced in HHH-15133 and somehow addressed in upcoming releases:
HHH-15719
HHH-15479
Possible workarounds at the current moment are:
ask query to return stream instead of list:
try (Stream<MyEntity> stream = session.createQuery("FROM MyEntity", MyEntity.class).getResultStream()) {
return stream.collect(Collectors.toList());
}
select tuples instead of entities (stream option seems to be more convenient):
List<Object[]> tuples = session.createQuery("select e.id, e FROM MyEntity e", Object[].class).getResultList();

JPA Causing java.lang.NullPointerException in Spring Boot

I'm trying to run some unit tests in my Spring Boot application and getting the following errors:
I can't tell if this is because it can't connect to my database in Heroku (ClearDB) or if I have some annotation wrong within my code.
I have the environment variables being loaded into my application-dev.properties file at runtime, all that information can be seen at the bottom.
Error
java.lang.NullPointerException
at com.algoq.algoq.services.AlgorithmService.getSubscribers(AlgorithmService.java:26)
at com.algoq.algoq.ExampleTest.subscriberListNull(ExampleTest.java:42)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Test Class
package com.algoq.algoq;
import com.algoq.algoq.models.Subscriber;
import com.algoq.algoq.respositories.SubscriberRepository;
import com.algoq.algoq.services.AlgorithmService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat;
#RunWith(SpringRunner.class)
#TestConfiguration
#SpringBootTest(classes = {
AlgoQApplication.class,
})
public class ExampleTest extends AlgoQApplicationTests {
#Autowired
private AlgorithmService aService;
#MockBean
private SubscriberRepository employeeRepository;
#Bean
public AlgorithmService aService() {
return new AlgorithmService();
}
#Test
public void subscriberListNull() throws Exception {
ArrayList<Subscriber> subs = aService.getSubscribers();
assertThat(subs).isEmpty();
}
}
Service Class
package com.algoq.algoq.services;
import com.algoq.algoq.models.Subscriber;
import com.algoq.algoq.respositories.SubscriberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
#Service
public class AlgorithmService {
#Autowired
private SubscriberRepository subRep;
/**
* Gets a list of subscribers to return to the API
* #return
*/
public ArrayList<Subscriber> getSubscribers() {
ArrayList<Subscriber> subscribers = new ArrayList<>();
subRep.findAll()
.forEach(subscribers::add);
return subscribers;
}
/**
* Adds a new subscriber to the database
* #param sub
* #return
*/
public void addSubscriber(Subscriber sub) {
subRep.save(sub);
}
/**
* Finds a single user id
* #param email
* #return
*/
public List<Subscriber> getSubscriber(String email) {
return subRep.findByEmailAddress(email);
}
}
POM
http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
<groupId>com.algoQ</groupId>
<artifactId>algo-q</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>algo-q</name>
<description>An algorithm a day keeps the brain up to date</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.9</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.12</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<!--<dependency>-->
<!--<groupId>com.h2database</groupId>-->
<!--<artifactId>h2</artifactId>-->
<!--<version>1.4.196</version>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>1.5.2.RELEASE</version>
</dependency>
<!--<dependency>-->
<!--<groupId>com.h2database</groupId>-->
<!--<artifactId>h2</artifactId>-->
<!--<version>1.4.194</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.pygments</groupId>
<artifactId>pygments</artifactId>
<version>1.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
App Properties
server.port = 5600
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=${MAIL_USER}
spring.mail.password=${MAIL_PASS}
#mail properties
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML5
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url = ${MYSQL_HOST}
spring.datasource.username = ${MYSQL_USERNAME}
spring.datasource.password = ${MYSQL_PASSWORD}
spring.datasource.tomcat.max-active=20
spring.datasource.maxIdle=2
spring.datasource.tomcat.remove-abandoned=true
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
You are using #MockBean which, underneath, uses Mockito.mock to create a mock of your bean. The default behavior of a mock is to return default values and in this case it will return null as the default value. Hence it will break on the forEach
What you need to do is tell your mock what to do on a specific method call. In your case you might want to return an empty list. In your test method you would need to add something along these lines.
#Test
public void subscriberListNull() throws Exception {
Mockito.when(employeeService.findAll)).thenReturn(new ArrayList());
ArrayList<Subscriber> subs = aService.getSubscribers();
assertThat(subs).isEmpty();
}
This is also inline with the default for Spring Data JPA as that will never return null from a collection returning method. If there are no results you will get an empty collection.

Transaction rollback in JPA never works

I have a piece of example code:
public class JpaTest {
private EntityManagerFactory emf;
private void setUp() throws Exception {
emf = Persistence.createEntityManagerFactory("testPU");
}
private void tearDown() {
emf.close();
}
private void save() {
EntityManager em = null;
EntityTransaction tx = null;
try {
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
em.persist(new Event("First event", new Date()));
em.persist(new Event("A follow up event", new Date()));
throw new RuntimeException();
} catch (Exception e) {
if (tx != null) tx.rollback();
} finally {
if (em != null) em.close();
}
}
public static void main(String[] args) throws Exception {
JpaTest test = new JpaTest();
test.setUp();
test.save();
test.tearDown();
}
}
The database is MySQL.
The code persists Event entity into the database and than throws an Exception. I expect tx.rollback() to delete the changes made into the database, but this command never works and the data remains in the table:
The question is why tx.rollback() fails and how to delete changes made in the database if transaction throws an Exception?
UPDATED:
persistence.xml:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="testPU">
<class>exampleForTestingJpa.Event</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url"
value="url here..."/>
<property name="javax.persistence.jdbc.user" value="username here..."/>
<property name="javax.persistence.jdbc.password" value="password here..."/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.connection.autocommit" value="false"/>
</properties>
</persistence-unit>
</persistence>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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>groupId</groupId>
<artifactId>example</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.9.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
</dependencies>
</project>
Add this to your persistence.xml
<property name="hibernate.connection.autocommit" value="false"/>
Maybe autocommit is enabled ? MySQL manual states that autocommit is enabled by default.
If thats the problem, you surely will not be the first who stumbled over that ;-)
As other users said, it is probably a auto-commit issue since according to the MySQL documentation :
In InnoDB ...
By default, MySQL starts the session for each new connection with
autocommit enabled, so MySQL does a commit after each SQL statement if
that statement did not return an error. If a statement returns an
error, the commit or rollback behavior depends on the error. See
Section 14.21.4, “InnoDB Error Handling”.
Besides, you should not store the Transaction object in a variable.
At each time you want to invoke a Transaction method, get the Transaction object from the EntityManager.
So replace :
tx = em.getTransaction();
tx.begin();
by :
em.getTransaction().begin();
And replace tx.rollback(); by em.getTransaction().rollback();
The Transaction object stored in the EntityManager may be serialized and so have a new reference during transaction processing.
For example, look at the serialization method of AbstractEntityManagerImpl:
public class org.hibernate.jpa.spi.AbstractEntityManagerImpl{
...
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
tx = new TransactionImpl( this );
}
...
}
The problem with not rolling transaction back was caused by MyISAM. With InnoDB rollback works fine.

org.hibernate.ResourceClosedException

i am getting this error when I want to run the following code:
package HIndexSaar.HIndex;
public class AppHibernate {
public static void main(String[] args){
HibernateManager mng = new HibernateManager();
mng.addPerson("H H", "Uni Saarland");
mng.addPerson("Bernd Finkbeiner", "Uni Saarland");
mng.addUniversity("Saarland University");
}
}
My HibernateManager class:
package HIndexSaar.HIndex;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
public class HibernateManager {
private static SessionFactory ourSessionFactory;
private static ServiceRegistry serviceRegistry;
public HibernateManager(){
try {
Configuration configuration = new Configuration();
configuration.configure();
serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
ourSessionFactory = configuration.buildSessionFactory(serviceRegistry);
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
/**
* adds a person to the database.
* #param name: the name of the person
* #param affiliation: the university of the person
* #return the created ID
*/
public Integer addPerson(String name, String affiliation){
Transaction tx = null;
Integer personID = null;
try (Session session = ourSessionFactory.openSession()) {
tx = session.beginTransaction();
Person p = new Person(name, affiliation);
personID = (Integer) session.save(p);
tx.commit();
} catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
}
return personID;
}
/**
* adds a university to the database.
* #param name: the name of the university
* #return the id of the newly created university
* */
public Integer addUniversity(String name){
Transaction trans = null;
Integer uniID = null;
try (Session session = ourSessionFactory.openSession()) {
trans = session.beginTransaction();
University uni = new University(name);
uniID = (Integer) session.save(uni);
trans.commit();
} catch (HibernateException e) {
if (trans != null) {
trans.rollback();
}
e.printStackTrace();
}
return uniID;
}
/**
* adds a publication to the database.
* #param name: the name of the publication
* #param author: the author of the publication
* #return the generated ID
*/
public Integer addPublication(String name, String author){
Transaction trans = null;
Integer pubID = null;
try (Session session = ourSessionFactory.openSession()) {
trans = session.beginTransaction();
Publication p = new Publication(name, author);
pubID = (Integer) session.save(p);
trans.commit();
} catch (HibernateException e) {
if (trans != null) {
trans.rollback();
}
e.printStackTrace();
}
return pubID;
}
}
And my hibernate.cfg.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory name="HIndex Session">
<!-- Database connection settings -->
<property name="connection.driver:class">org.postgreSQL.Driver</property>
<property name="connection.url">jdbc:postgresql://localhost/HIndex</property>
<property name="hibernate.connection.username">index_user</property>
<property name="hibernate.connection.password">password</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL Dialect -->
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQL82Dialect</property>
<!-- Assume test is the database name -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<!-- Names the annotated entity class -->
<mapping class="HIndexSaar.HIndex.Person"/>
<mapping class="HIndexSaar.HIndex.University"/>
<mapping class="HIndexSaar.HIndex.Publication"/>
</session-factory>
</hibernate-configuration>
And the 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>HIndexSaar</groupId>
<artifactId>HIndex</artifactId>
<version>Version 0.2</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<name>HIndex</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<!-- jsoup HTML parser library # http://jsoup.org/ -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1207</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.0.7.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.5.6-Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.0-RC1</version>
</dependency>
</dependencies>
</project>
The error is shown at the "tx.rollback();" line:
catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
}
I do not use multiple threads anywhere, so what am I missing?
From the docs for ResourceClosedException:
Indicates an attempt was made to use a closed resource (Session,
SessionFactory, etc).
Now, when using "try with resources", the documentation says:
Note: A try-with-resources statement can have catch and finally blocks
just like an ordinary try statement. In a try-with-resources
statement, any catch or finally block is run after the resources
declared have been closed.
So by the time you call rollback() the Session will already have been closed.
The simplest solution here would be to move your existing catch block to an inner try/catch block around the transaction-management code, e.g.:
try (Session session = ourSessionFactory.openSession()) {
try {
tx = session.beginTransaction();
Person p = new Person(name, affiliation);
personID = (Integer) session.save(p);
tx.commit();
}
catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
}
}
This ensures that the Session only gets closed once you've (at least) requested rollback.

Problems with Maven when changing code?

I am trying to run a little Maven test program using NetBeans. Now the NetBeans-given code works perfectly fine with Maven. Compiling, running, all good. But when I change anything, like adding an FXML-File or adding JPA persistence, I then get errors when starting the program. Compiling seems to work, but I can't start the program. I expect the error to be somewhere in the configuration of Maven, but I can't find it. The code by itself should work just fine.
Ok, to show you guys what exactly I mean, here is the code. I just add a JPA-persistence to a MySQL-Database. After adding the program doesn't run anymore.
App.java:
package com.myproject.mavenproject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello new World!" );
EntityManagerFactory emf = Persistence.createEntityManagerFactory( "GlassesPU" );
try {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
Hund hund = new Hund();
hund.setFormerName("Albert");
hund.setNewName("Einstein");
em.persist(hund);
//em.persist( entity1 );
//em.merge( entity2 );
//em.find( MyEntity.class, id );
tx.commit();
} catch( RuntimeException ex ) {
if( tx != null && tx.isActive() ) tx.rollback();
throw ex;
} finally {
em.close();
}
} finally {
emf.close();
}
}
}
Hund.java:
package com.myproject.mavenproject;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class Hund implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String formerName;
private String newName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFormerName() {
return formerName;
}
public void setFormerName(String formerName) {
this.formerName = formerName;
}
public String getNewName() {
return newName;
}
public void setNewName(String newName) {
this.newName = newName;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Hund)) {
return false;
}
Hund other = (Hund) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.myproject.mavenproject.Hund[ id=" + id + " ]";
}
}
persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="com.myproject_MavenProject_jar_1.0-SNAPSHOTPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.myproject.mavenproject.Hund</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/glasses?zeroDateTimeBehavior=convertToNull"/>
<property name="javax.persistence.jdbc.password" value="mypassword"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>
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>com.myproject</groupId>
<artifactId>MavenProject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>MavenProject</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
<version>2.5.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Error message:
cd /home/myuser/NetBeansProjects/MavenProject; JAVA_HOME=/usr/lib/jvm/jdk1.7.0_51 /home/myuser/netbeans-7.4/java/maven/bin/mvn "-Dexec.args=-classpath %classpath com.myproject.mavenproject.App" -Dexec.executable=/usr/lib/jvm/jdk1.7.0_51/bin/java org.codehaus.mojo:exec-maven-plugin:1.2.1:exec
Running NetBeans Compile On Save execution. Phase execution is skipped and output directories of dependency projects (with Compile on Save turned on) will be used instead of their jar artifacts.
Scanning for projects...
------------------------------------------------------------------------
Building MavenProject 1.0-SNAPSHOT
------------------------------------------------------------------------
--- exec-maven-plugin:1.2.1:exec (default-cli) # MavenProject ---
Hello new World!
Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named GlassesPU
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:85)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
at com.myproject.mavenproject.App.main(App.java:18)
------------------------------------------------------------------------
BUILD FAILURE
------------------------------------------------------------------------
Total time: 0.891s
Finished at: Mon Feb 03 21:35:25 CET 2014
Final Memory: 5M/111M
------------------------------------------------------------------------
Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.2.1:exec (default-cli) on project MavenProject: Command execution failed. Process exited with an error: 1 (Exit value: 1) -> [Help 1]
To see the full stack trace of the errors, re-run Maven with the -e switch.
Re-run Maven using the -X switch to enable full debug logging.
For more information about the errors and possible solutions, please read the following articles:
[Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
Anyone has an idea what the problem could be?
There is no problem with Maven. It's only reporting the error. It says that it can't find a persistence provider called "GlassesPU".
Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named GlassesPU
Your persistence.xml has another name:
<persistence-unit name="com.myproject_MavenProject_jar_1.0-SNAPSHOTPU" ... >
Try changing it to "GlassesPU":
<persistence-unit name="GlassesPU" ...>
If Maven complains again, it will probably be due to something else.

Categories

Resources