java - 计划任务中的 Spring 事务 - 分离实体错误

标签 java spring hibernate transactions scheduled-tasks

(我知道类似的问题到处都是,但我找不到合适的解决方案。)

我有一个 Spring 计划任务,它通过 Spring Repositories/Hibernate 从数据库读取和写入,包括两个实体之间的多对多关系,需要对延迟初始化的集合进行适当的 session 管理。

但是,尽管有注释,Spring 似乎没有正确管理事务。

我做错了什么?

(我应该提到在与 @Scheduled 相同的方法上抛出一个 @Transactional 确实有效但导致整个计划任务成为一个事务,而我想要persistBannerCourse 是事务性的。)

首先是堆栈跟踪,然后是相关代码:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: edu.ucdavis.dss.dw.entities.Instructor.courses, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:572) ~[AbstractPersistentCollection.class:4.3.1.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:212) ~[AbstractPersistentCollection.class:4.3.1.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.readElementExistence(AbstractPersistentCollection.java:319) ~[AbstractPersistentCollection.class:4.3.1.Final]
at org.hibernate.collection.internal.PersistentBag.contains(PersistentBag.java:288) ~[PersistentBag.class:4.3.1.Final]
at edu.ucdavis.dss.dw.entities.Instructor.addCourse(Instructor.java:130) ~[Instructor.class:?]
at edu.ucdavis.dss.dw.entities.Course.addInstructor(Course.java:111) ~[Course.class:?]
at edu.ucdavis.dss.dw.entities.Course.addInstructor(Course.java:100) ~[Course.class:?]
at edu.ucdavis.dss.dw.tasks.BannerTasks.persistBannerCourse(BannerTasks.java:184) ~[BannerTasks.class:?]
at edu.ucdavis.dss.dw.tasks.BannerTasks.bannerImport(BannerTasks.java:80) ~[BannerTasks.class:?]
at edu.ucdavis.dss.dw.tasks.BannerTasks$$FastClassBySpringCGLIB$$d1348e2.invoke(<generated>) ~[ReflectUtils.class:?]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[MethodProxy.class:4.0.4.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:640) ~[CglibAopProxy$DynamicAdvisedInterceptor.class:4.0.4.RELEASE]
at edu.ucdavis.dss.dw.tasks.BannerTasks$$EnhancerBySpringCGLIB$$46afeb46.bannerImport(<generated>) ~[ReflectUtils.class:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0]
at java.lang.reflect.Method.invoke(Method.java:483) ~[?:1.8.0]
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65) ~[ScheduledMethodRunnable.class:4.0.4.RELEASE]
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) [DelegatingErrorHandlingRunnable.class:4.0.4.RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0]
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [?:1.8.0]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [?:1.8.0]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [?:1.8.0]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0]
at java.lang.Thread.run(Thread.java:744) [?:1.8.0]

类(class).java: 包 edu.ucdavis.dss.dw.entities;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
@Table(name = "Courses", uniqueConstraints = {
        @UniqueConstraint(name = "Courses_CRNs", columnNames = { "Crn" })
},
indexes = {
        @Index(name = "Courses_Titles", columnList = "Title")
})
public class Course implements Serializable
{
private long id;
private String crn;
private String title;
private List<Instructor> instructors = new ArrayList<Instructor>(0);
private Term term;
private Department department; /* may be null in rare cases */

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "CourseId", unique = true, nullable = false)
public long getId()
{
    return this.id;
}

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

@Basic(optional = false)
@Column(name = "Crn", nullable = false, length = 5)
public String getCrn()
{
    return this.crn;
}

public void setCrn(String crn)
{
    this.crn = crn;
}

@Basic(optional = false)
@Column(name = "Title", nullable = false, length = 30)
public String getTitle()
{
    return this.title;
}

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

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
//@LazyCollection(LazyCollectionOption.FALSE)
@JoinTable(name = "Courses_Instructors", joinColumns = { 
        @JoinColumn(name = "CourseId", nullable = false, updatable = false) }, 
        inverseJoinColumns = { @JoinColumn(name = "InstructorId", 
                nullable = false, updatable = false) })
public List<Instructor> getInstructors()
{
    return this.instructors;
}

public void setInstructors(List<Instructor> instructors)
{
    this.instructors = instructors;
}

public void addInstructor(@NotNull @Valid Instructor instructor) {
    addInstructor(instructor, true);
}

public void addInstructor(@NotNull @Valid Instructor instructor, boolean add) {
    if (instructor != null) {
        if(getInstructors().contains(instructor)) {
            getInstructors().set(getInstructors().indexOf(instructor), instructor);
        } else {
            getInstructors().add(instructor);
        }
        if(add) {
            instructor.addCourse(this, false);
        }
    }
}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TermId", nullable = false)
@NotNull
public Term getTerm() {
    return this.term;
}

public void setTerm(Term term) {
    this.term = term;
}

@ManyToOne(fetch = FetchType.LAZY, optional = true)
@JoinColumn(name = "DepartmentId", nullable = true)
public Department getDepartment()
{
    return this.department;
}

public void setDepartment(Department department)
{
    this.department = department;
}

@Override
public String toString() {
    return String.format(
            "Course[id=%d, title='%s', crn='%s', term_code='%s']",
            id, title, crn, term.getCode());
}
}

Instructor.java:

package edu.ucdavis.dss.dw.entities;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
@Table(name = "Instructors", indexes = {
        @Index(name = "Instructors_Names", columnList = "LastName, FirstName, MiddleInitial")
})
public class Instructor implements Serializable
{
private long id;
private String firstName, middleInitial, emailAddress;

@NotNull
private String lastName;

@NotNull
private String employeeId;

private List<Course> courses = new ArrayList<Course>(0);

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "InstructorId", unique = true, nullable = false)
public long getId()
{
    return this.id;
}

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

@Basic
@Column(name = "employeeId")
public String getEmployeeId()
{
    return this.employeeId;
}

public void setEmployeeId(String employeeId)
{
    this.employeeId = employeeId;
}

@Basic
@Column(name = "FirstName")
public String getFirstName()
{
    return this.firstName;
}

public void setFirstName(String firstName)
{
    this.firstName = firstName;
}

@Basic
@Column(name = "LastName")
public String getLastName()
{
    return this.lastName;
}

public void setLastName(String lastName)
{
    this.lastName = lastName;
}

@Basic
@Column(name = "MiddleInitial")
public String getMiddleInitial()
{
    return this.middleInitial;
}

public void setMiddleInitial(String middleInitial)
{
    this.middleInitial = middleInitial;
}

@Basic
public String getEmailAddress()
{
    return this.emailAddress;
}

public void setEmailAddress(String emailAddress)
{
    this.emailAddress = emailAddress;
}

@ManyToMany(fetch = FetchType.LAZY, mappedBy = "instructors")
//@LazyCollection(LazyCollectionOption.FALSE)
public List<Course> getCourses() {
    return this.courses;
}

public void setCourses(List<Course> courses) {
    this.courses = courses;
}

public void addCourse(@NotNull @Valid Course course) {
    addCourse(course, true);
}

public void addCourse(@NotNull @Valid Course course, boolean add) {
    if (course != null) {
        if(getCourses().contains(course)) {
            getCourses().set(getCourses().indexOf(course), course);
        }
        else {
            getCourses().add(course);
        }
        if (add) {
            course.addInstructor(this, false);
        }
    }
}
}

BannerTasks.java:

package edu.ucdavis.dss.dw.tasks;

import java.text.SimpleDateFormat;
import java.util.Date;
import javax.inject.Inject;
import javax.validation.ConstraintViolationException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import edu.ucdavis.dss.dw.entities.BannerCourse;
import edu.ucdavis.dss.dw.entities.BannerInstructor;
import edu.ucdavis.dss.dw.entities.Course;
import edu.ucdavis.dss.dw.entities.Department;
import edu.ucdavis.dss.dw.entities.Instructor;
import edu.ucdavis.dss.dw.entities.Term;
import edu.ucdavis.dss.dw.site.CourseManager;

@Service
public class BannerTasks {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
private static final Logger log = LogManager.getLogger();
private static int runCount = 0;

//@Inject BannerRepository bannerRepository;
@Inject CourseManager courseManager;

@Scheduled(fixedRate = 10000)
public void bannerImport() {
    runCount++;
    if(runCount == 1) {
        long startTime = new Date().getTime();
        long finishTime;

        log.info("Running Banner import at " + dateFormat.format(new Date()));


            log.info("Beginning Banner course pull ...");
            log.info("Done querying Banner, parsing rows ...");

            BannerCourse course = new BannerCourse();
            course.setTitle("Group Study");
            course.setCrn("57958");
            course.setTermCode("201301");
            course.setTermDescription("201301");
            course.setDepartmentCode("HPH");
            course.setDepartmentDescription("HPHDESC");

            BannerInstructor instructor = new BannerInstructor();
            instructor.setEmployeeId("989999999");
            instructor.setFirstName(".");
            instructor.setMiddleInitial(null);
            instructor.setLastName("The Staff");
            instructor.setEmailAddress(null);

            course.addInstructor(instructor);

            persistBannerCourse(course);

        finishTime = new Date().getTime();

        log.info("Banner import finished at " + dateFormat.format(new Date()) + ". Took " + (finishTime - startTime) / 1000 + " seconds.");
        //long finishCourses = this.courseManager.countCourses();
        //log.info("There are now " + finishCourses + " courses stored locally, difference: " + (finishCourses - startCourses) + ".");
    }
}

/* Ensures the passed in BannerCourse is persisted in the local schema */
//@Transactional(noRollbackFor=ConstraintViolationException.class)
@Transactional(propagation=Propagation.MANDATORY)
public void persistBannerCourse(BannerCourse bannerCourse) {
    // Handle the term
    Term term = this.courseManager.getTermByCode(bannerCourse.getTermCode());
    if(term == null) {
        log.info("Term is null, creating ...");
        term = new Term();
        term.setCode(bannerCourse.getTermCode());
        term.setName(bannerCourse.getTermDescription());
        this.courseManager.saveTerm(term);
    } else {
        log.info("Term is not null.");
    }

    // Handle course basics
    Course course = this.courseManager.getCourseByCrnAndTerm(bannerCourse.getCrn(), term.getId());
    if(course == null) {
        course = new Course();

        course.setCrn(bannerCourse.getCrn());
        course.setTitle(bannerCourse.getTitle());
        course.setTerm(term);

        // Handle the department
        Department department = this.courseManager.getDepartmentByCode(bannerCourse.getDepartmentCode());
        if(department == null) {
            try {
                log.info("Department is null, creating ...");
                department = new Department();
                department.setCode(bannerCourse.getDepartmentCode());
                department.setName(bannerCourse.getDepartmentDescription());
                this.courseManager.saveDepartment(department);
            } catch(ConstraintViolationException e) {
                log.info("Unable to save department locally due to validation errors:" + e.getConstraintViolations());
                department = null;
            }
        } else {
            log.info("Department is not null.");
        }

        if(department != null) {
            course.setDepartment(department);
        }

        // Handle the instructors
        for(BannerInstructor bannerInstructor : bannerCourse.getInstructors()) {
            Instructor instructor = this.courseManager.getInstructorByEmployeeId(bannerInstructor.getEmployeeId());
            if(instructor == null) {
                log.info("Instructor is null, creating ...");
                instructor = new Instructor();
                instructor.setFirstName(bannerInstructor.getFirstName());
                instructor.setLastName(bannerInstructor.getLastName());
                instructor.setMiddleInitial(bannerInstructor.getMiddleInitial());
                instructor.setEmployeeId(bannerInstructor.getEmployeeId());

                try {
                    this.courseManager.saveInstructor(instructor);
                } catch(ConstraintViolationException e) {
                    log.info("Unable to save instructor locally due to validation errors:" + e.getConstraintViolations());
                    instructor = null;
                }
            } else {
                log.info("Instructor is not null.");
            }

            if(instructor != null) {
                course.addInstructor(instructor);
            }
        }

        this.courseManager.saveCourse(course);
    } else {
        log.info(String.format("Course already exists: '%s'", course.getTitle()));
    }
}
}

CourseManager.java:

package edu.ucdavis.dss.dw.site;

import edu.ucdavis.dss.dw.entities.Course;
import edu.ucdavis.dss.dw.entities.Department;
import edu.ucdavis.dss.dw.entities.Instructor;
import edu.ucdavis.dss.dw.entities.Term;

import java.util.List;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.springframework.validation.annotation.Validated;

@Validated
public interface CourseManager
{
List<Instructor> getInstructors();

List<Course> getCourses();

List<Department> getDepartments();

void saveInstructor(@NotNull @Valid Instructor instructor);

void saveCourse(Course course);

void saveDepartment(@NotNull @Valid Department department);

void saveTerm(Term term);

Term getTermById(Long id);

Department getDepartmentByCode(String departmentCode);

Term getTermByCode(String termCode);

Instructor getInstructorByEmployeeId(String employeeId);

Course getCourseByCrnAndTerm(String crn, long termId);

long countCourses();
}

DefaultCourseManager.java:

package edu.ucdavis.dss.dw.site;

import edu.ucdavis.dss.dw.entities.Course;
import edu.ucdavis.dss.dw.entities.Department;
import edu.ucdavis.dss.dw.entities.Instructor;
import edu.ucdavis.dss.dw.entities.Term;
import edu.ucdavis.dss.dw.repositories.CourseRepository;
import edu.ucdavis.dss.dw.repositories.DepartmentRepository;
import edu.ucdavis.dss.dw.repositories.InstructorRepository;
import edu.ucdavis.dss.dw.repositories.TermRepository;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;

import java.util.ArrayList;
import java.util.List;

@Service
public class DefaultCourseManager implements CourseManager
{
@Inject InstructorRepository instructorRepository;
@Inject CourseRepository courseRepository;
@Inject DepartmentRepository departmentRepository;
@Inject TermRepository termRepository;

@Override
@Transactional
public List<Instructor> getInstructors()
{
    return this.toList(this.instructorRepository.findAll());
}

@Override
@Transactional
public List<Course> getCourses()
{
    return this.toList(this.courseRepository.findAll());
}

@Override
@Transactional
public List<Department> getDepartments()
{
    return this.toList(this.departmentRepository.findAll());
}

private <E> List<E> toList(Iterable<E> i)
{
    List<E> list = new ArrayList<>();
    i.forEach(list::add);
    return list;
}

@Override
@Transactional
public void saveInstructor(Instructor instructor)
{
    this.instructorRepository.save(instructor);
}

@Override
@Transactional
public void saveCourse(Course course)
{
    this.courseRepository.save(course);
}

@Override
@Transactional
public void saveDepartment(Department department)
{
    this.departmentRepository.save(department);
}

@Override
@Transactional
public void saveTerm(Term term)
{
    this.termRepository.save(term);
}

@Override
@Transactional
public Term getTermById(Long id)
{
    return this.termRepository.findOne(id);
}

@Override
@Transactional
public Department getDepartmentByCode(String departmentCode)
{
    return this.departmentRepository.getOneByCode(departmentCode);
}

@Override
@Transactional
public Term getTermByCode(String termCode)
{
    return this.termRepository.getOneByCode(termCode);
}

@Override
@Transactional
public Instructor getInstructorByEmployeeId(String employeeId)
{
    return this.instructorRepository.getOneByEmployeeId(employeeId);
}

@Override
@Transactional
public Course getCourseByCrnAndTerm(String crn, long termId)
{
    return this.courseRepository.getOneByCrnAndTermId(crn, termId);
}

@Override
@Transactional
public long countCourses()
{
    return this.courseRepository.count();
}
}

最佳答案

您似乎偶然发现了 Spring AOP 抽象的经典问题。 虽然您已经使用 @Transactional 注释了您的 persistBannerCourse 方法,但是您是从同一个类中调用它的。这意味着处理事务代码的代理永远不会被调用。

查看 this相关的 SO 问题。

最好的解决方案是重构您的代码并将 persistBannerCourse 移动到另一个类。 描述了另一种解决方案 here

关于java - 计划任务中的 Spring 事务 - 分离实体错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23614541/

相关文章:

java - Lucene - 验证索引的完整性

将 ManyToOne/OneToOne 关系添加到实体时抛出 javax.persistence.RollbackException

java - 数组类型参数的暴露程度相同

java - 多个 fragment 需要位置更新

java - 如何在 spring boot 到达 Controller 之前修改请求体

spring - PropertyPlaceholderConfigurer 只是不能在 tomcat 中工作,请帮忙

java - JPA 得到分离的结果

Java AMF 序列化

java - 存储访问框架 - 保存 Uri

java - (Spring MVC + Hibernate 4 + 测试 4) Autowiring DAO 返回 NULL