У меня есть простой процесс, когда сотрудники загружаются и обрабатываются в моем приложении.У меня есть два объекта домена, Employee
и Company
.Ассоциация много к одному.
Вот мои доменные объекты
class Employee {
String id
String firstName
String lastName
String email
Company company
static mapping = {
table name: "employee"
id column: "employee_id", generator:"assigned"
version false
}
}
class Company {
String id
String name
static hasMany = [employees:Employee]
static mapping = {
table name: "company"
id column: "company_id", generator:"assigned"
autoImport false
version false
}
}
Процесс состоит в том, что User
загружает файл, содержащий Employees
, и задает новый Company
.My Service
создает Company
, а затем создает с помощью ExecutorService
10 потоков для обработки и сохранения Employees
.
My Controller
class CompanyController {
static scope = "singleton"
CompanyService companyService
def createCompany(CreateCompanyCommand createCompanyCmd){
companyService.createCompany(createCompanyCmd)
render(status: 200)
}
}
My CompanyService
отвечает за создание Company
, а затем вызывает EmployeeService
для создания Employees
.
class CompanyService {
EmployeeService employeeService
SessionFactory sessionFactory
public void createCompany(createCompanyCmd){
Company company = saveCompany(createCompanyCmd)
employeeService.createEmployees(company.id, createCompanyCmd.employeeFile)
}
public Company saveCompany(createCompanyCmd){
Company company = new Company()
company.id = createCompanyCmd.id
company.name = createCompany.name
// NOTE: flushing here
company.save(flush: true, failOnError: true)
return company
}
}
My EmployeeService
создает сотрудников через несколько потоков.
class EmployeeService {
public void createEmployees(companyId, employees){
// Find the new created company - this is on the main thread
Company company = Company.findById(companyId)
def lines = parseEmployees(employeeFile) // Method not shown, but it returns a List that contains info about each employee to create the Employee Object
ExecutorService executor = Executors.newFixedThreadPool(10)
List futures = new ArrayList()
lines.each { line ->
final def fLine = line // found this necessary to make the field actually final. Have a SO question regarding this, but don't believe it is related to my problem here.
Future future = executor.submit(new Callable() {
public def call() throws Exception {
def employee = saveEmployee(fLine, company)
return employee
}
});
futures.add(future)
}
executor.shutdown()
for(Future future : futures){
def employee = future.get()
println employee
}
}
public Employee saveEmployee(line, company) {
Employee employee = new Employee()
employee.firstName = line.firstName
employee.lastName = line.lastName
employee.email = employee.email
employee.id = line.id
employee.company = company //ERROR HERE
}
}
Это дает мне следующую ошибку:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Not-null property references a transient value - transient instance must be saved before current operation : com.example.Employee.company -> com.example.Company; nested exception is org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.example.Employee.company -> com.example.Company
Мне кажется, я понимаю проблему.Несмотря на то, что я «сбрасываю» сеанс при сохранении Company
, транзакция не была зафиксирована.Поэтому потоки, которые сохраняют объект Employee
, считают, что связанный с ним объект Company
еще не сохранен.Как я могу обойти это?Если я не делаю многопоточность, а вместо этого сохраняю сотрудников в главном потоке, все работает нормально.
Это работает:
def lines = parseEmployees(employeeFile)
lines.each { line ->
saveEmployee(line, company) // saving on the Main Thread
}
Кроме того, если я пытаюсь получить новый объект Company
из базы данных перед сохранением Employee
, он возвращает ноль.
public Employee saveEmployee(line, company) {
Company companyFromDB = Company.findById(company.id) // returns null
Employee employee = new Employee()
employee.firstName = line.firstName
employee.lastName = line.lastName
employee.email = employee.email
employee.id = line.id
employee.company = companyFromDB
}
Это имеет смысл для меня, потому что поток не использует тот же сеанс Hibernate, и чувствует, что объект Company
был очищен, но транзакция не зафиксирована, поток не увидит ее.
Поэтому я считаю, что понимаю, почему это не работает, но я не уверен, как обойти это.Я хотел бы сохранить все это в рамках одной транзакции (создание компании и сотрудников в рамках одной транзакции), но я не уверен, как разделить сеанс Hibernate из основного потока с другими потоками.Как я могу обойти это?