标签:java spring scheduled-tasks hibernate transactions
(我知道类似的问题已经结束了,但我找不到合适的解决方案.)
我有一个Spring计划任务,它通过Spring Repositories / Hibernate从数据库读取和写入,包括两个实体之间的多对多关系,需要对懒惰的初始化集合进行适当的会话管理.
但是,尽管有注释,Spring似乎还没有正确管理事务.
我究竟做错了什么?
(我应该提一下@Transactional和@Scheduled一样的方法,但是导致整个计划任务成为一个事务,而我希望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]
Course.java:
package 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,scheduled-tasks,hibernate,transactions 来源: https://codeday.me/bug/20190725/1530746.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。