Совместное использование незафиксированного объекта домена из основного потока другими потоками - PullRequest
0 голосов
/ 29 сентября 2018

У меня есть простой процесс, когда сотрудники загружаются и обрабатываются в моем приложении.У меня есть два объекта домена, 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 из основного потока с другими потоками.Как я могу обойти это?

...