0

Let's say I want to do a bulk User update in my UsersController.

In my UsersController I do:

foreach ($users as $user) {
    $userService = new UserService();
    $user->updateUser($data);
}

If there are a lot of users, it can get slower because the UserService::updateUser method just do a persist()/flush()

So I'm wondering if it could be a good idea to do something like that:

class UserService {
  public function setUseTransaction($flag)
  {
      $this->useTransaction = $flag;
      return $this;
  }

  public function updateUser($data)
  {
     // some data mapping

     $entityManager->persist($user);

     if ($this->useTransaction) {
       $entityManager->flush();
     }
   }

   public function commit()
   {
      $entityManager->flush();
   }
}

Then in my UsersController I can do:

$userService = new UserService();
$userService->setUseTransaction(true);

foreach ($users as $user) {
    $userService = new UserService();
    $user->updateUser($data);
}

$userService->commit();

What are your thought?

JohnT
  • 967
  • 2
  • 16
  • 30

2 Answers2

3

I wouldn't want to expose any transaction-management stuff above my service layer. I'd probably shove all of that stuff down into my service, and expose two public methods updateUser(userEntity) (for a one-off with an implicit flush), and updateUsers(array-of-users) (for batch updates)

Something roughly like:

class UserService {

    public function updateUser(User $user){
        $this->_updateUser();
        $this->em()->flush();        
    }

    public function updateUsers(array $users){
        foreach($users as $u) $this->_updateUser($u);
        $this->em()->flush();     
    }

    private function _updateUser(User $user){
        //do stuff to $user
        $this->em()->persist($user);
    }
}

Then, if you later decide you want to chunk your updates into groups of 100, or whatever, all your bulk-update logic is nicely factored in the service, instead of in potentially multiple places in your controller(s).

timdev
  • 61,857
  • 6
  • 82
  • 92
  • Simple and clever, I don't know why I didn't think about that before : ) – JohnT May 14 '11 at 23:16
  • If updateUser() does a flush() which is actually a commit, how do you make a transaction that is going to touch a User object and also other objects of a different class, if your updateUser() has a flush() in it ? – bobflux May 14 '11 at 23:52
  • @peufeu - you'd simply write a method in some appropriate service class to do that. – timdev May 15 '11 at 22:55
0

Wrapping it all in a transaction will definitely speed things up.

Writing the whole bulk update as a single SQL query will be hundreds of times faster though.

bobflux
  • 11,123
  • 3
  • 27
  • 27
  • I may have not be clear in my question, and I know that transaction will speed up my application, however, I'm wondering if it's a good ideas to couple such operation within my Service, it almost means that my Service is working with a database, but actually, it isn't the purpose of the Service Layer, thank you anyway – JohnT May 14 '11 at 21:41
  • @JohnT - no, your service is working with the EM, as well it should. Choices about when to flush() are probably better made in your service rather than in some controller(s). See my answer for more detail. – timdev May 14 '11 at 22:46