Hibernate-envers throwing exception when Deleting

2019-07-12 02:40发布

问题:

I would like to know if anyone else has used hibernate-envers with mysql in a Spring-boot application using the interface CrudRepository and has had trouble deleting entities with collections. I put together an example application with a test that demonstrates the exception that is generated.

The example can be cloned from https://github.com/LindesRoets/test-delete.git

You will need mysql running with a database called test_delete

CREATE DATABASE IF NOT EXISTS `test_delete` DEFAULT CHARACTER SET utf8;

Run the test

mvn test

You should see the following exception:

    2015-08-11 21:36:28.725 ERROR 3855 --- [           main] org.hibernate.AssertionFailure           : HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): java.lang.NullPointerException
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 4.926 sec <<< FAILURE! - in com.dcp.test.AuthorRepositoryTest
testAuthorCRUD(com.dcp.test.AuthorRepositoryTest)  Time elapsed: 0.163 sec  <<< ERROR!
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
    at org.hibernate.engine.internal.StatefulPersistenceContext.getLoadedCollectionOwnerOrNull(StatefulPersistenceContext.java:756)
    at org.hibernate.event.spi.AbstractCollectionEvent.getLoadedOwnerOrNull(AbstractCollectionEvent.java:75)
    at org.hibernate.event.spi.InitializeCollectionEvent.<init>(InitializeCollectionEvent.java:36)
    at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1931)
    at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:558)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:260)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:554)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:142)
    at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:294)
    at java.util.AbstractCollection.addAll(AbstractCollection.java:343)
    at org.hibernate.envers.internal.entities.mapper.relation.AbstractCollectionMapper.mapCollectionChanges(AbstractCollectionMapper.java:162)
    at org.hibernate.envers.internal.entities.mapper.relation.AbstractCollectionMapper.mapModifiedFlagsToMapFromEntity(AbstractCollectionMapper.java:212)
    at org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map(MultiPropertyMapper.java:105)
    at org.hibernate.envers.internal.synchronization.work.DelWorkUnit.generateData(DelWorkUnit.java:66)
    at org.hibernate.envers.internal.synchronization.work.AbstractAuditWorkUnit.perform(AbstractAuditWorkUnit.java:76)
    at org.hibernate.envers.internal.synchronization.AuditProcess.executeInSession(AuditProcess.java:119)

Below is all the source code as it appears in the sample application.

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>com.test</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>test-delete</name>
    <description></description>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.5.RELEASE</version>
    </parent>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>com.test.Application</start-class>
        <java.version>1.8</java.version>
        <spring-boot-version>1.2.5.RELEASE</spring-boot-version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
                <configuration>
                    <includes>
                        <include>**/*Test*.java</include>
                    </includes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>             
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.10.3</version>
            </plugin>                   
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>${spring-boot-version}</version>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.35</version>
        </dependency>            


        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
        </dependency>       
    </dependencies>

</project>

application.properties

spring.datasource.url = jdbc:mysql://localhost:3306/test_delete
spring.datasource.username =
spring.datasource.password =
# Specify the DBMS
spring.jpa.database = MYSQL
spring.datasource.driverClassName = com.mysql.jdbc.Driver

# Show or not log for each sql query
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = create

# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

#envers
spring.jpa.properties.org.hibernate.envers.global_with_modified_flag=true
spring.jpa.properties.org.hibernate.envers.store_data_at_delete=true

Application.java

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

BaseEntity.java

@MappedSuperclass
@Audited
public abstract class BaseEntity implements Serializable {

    @GeneratedValue(generator = "guid")
    @GenericGenerator(name = "guid", strategy = "guid")
    @Column(columnDefinition = "CHAR(36)")
    @Id
    protected String id;

    @Temporal(TemporalType.TIMESTAMP)
    protected Date dateCreated = new Date();

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Date getDateCreated() {
        return dateCreated;
    }

    public void setDateCreated(Date dateCreated) {
        this.dateCreated = dateCreated;
    }

    private static final long serialVersionUID = -8371628134270930829L;
}

Author.java

@Entity
@Audited
public class Author extends BaseEntity{

    private String email;

    private String name;

    @OneToMany(mappedBy = "author")
    private List<Book> books = new ArrayList<>();

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Book> getBooks() {
        return books;
    }

    public void setBooks(List<Book> books) {
        this.books = books;
    }


}

Book.java

@Entity
@Audited
public class Book extends BaseEntity {

    private String title;

    private String genre;

    @ManyToOne
    private Author author;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getGenre() {
        return genre;
    }

    public void setGenre(String genre) {
        this.genre = genre;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

}

AuthorRepository.java

@Transactional
public interface AuthorRepository extends CrudRepository<Author, String> {

}

AuthorRepositoryTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class AuthorRepositoryTest {

    @Autowired
    private AuthorRepository authorRepo;

    @Test
    public void testAuthorCRUD() {
        Author author = new Author();
        author.setName("Test Name");
        author.setEmail("test@mail.com");

        Author savedAuthor = authorRepo.save(author);

        Assert.assertNotNull(savedAuthor);

        authorRepo.delete(savedAuthor);
    }

}

If you comment out the @Audited from the entity classes Book, Author and BaseEntity the test pass just fine.

Does anyone know how to make the delete functionality work with envers?

回答1:

There are two ways that I found through trial and error to make the delete operation work as specified in the problem.

You can either specify the collection to load eagerly

@OneToMany(mappedBy = "author", fetch = FetchType.EAGER)

Or you can cascade the delete operation

@OneToMany(mappedBy = "author", cascade = CascadeType.REMOVE)

This is the minimum configuration that you will need to make the test pass as specified in the problem.