Spring存储库还保存了我不想保存的对象

时间:2018-03-08 09:45:55

标签: java spring spring-mvc jpa spring-boot

问题是,有一天我们发现,如果我们在spring boot存储库中保存一个对象,那么在同一方法中更改的另一个对象也会更新并保存在数据库中。

好奇心很大,可以找出为什么会发生这种情况。我使用Spring Initializr和一些模板代码创建了示例项目,以显示实际情况(尽量保持依赖项的数量尽可能低)。

使用Spring启动版1.5.11(SNAPSHOT)和项目具有以下依赖关系:

dependencies {
  compile('org.springframework.boot:spring-boot-starter-data-jpa')
  compile('org.springframework.boot:spring-boot-starter-web')
  compile('org.mariadb.jdbc:mariadb-java-client:2.1.0')
  testCompile('org.springframework.boot:spring-boot-starter-test')
}

现在到了这一点:

Project有两个实体Pet

@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Pet.class)
public class Pet {
  @Id
  @GeneratedValue
  private long id;
  private String type;

  public Pet() {}
  public String getType() { return type; }
  public void setType(String type) { this.type = type; }
}

User

@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = User.class)
public class User {
  @Id
  @GeneratedValue
  private long id;
  private String name;

  public User() {}
  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
}

两个实体也都有存储库Pet

@Repository
public interface PetRepository extends CrudRepository<Pet, Long> {
  Pet findPetById(Long id);
}

User

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
  User findUserById(Long id);
}

一个简单的服务,其中魔法实际发生(我预先保存了一个Pet和一个User对象,具有不同的名称和类型)

@Service
public class UserService {
  @Autowired
  UserRepository userRepository;
  @Autowired
  PetRepository petRepository;

  public User changeUserAndPet() {
    User user = userRepository.findUserById(1L);
    Pet pet = petRepository.findPetById(1L);

    user.setName("Kevin");
    pet.setType("Cow");
    userRepository.save(user);
    return user;
  }

}

在调用userRepository.save(user);之后,Pet对象也在数据库中使用新类型的&#39; Cow&#39;进行更新。如果我只保存User对象,为什么会发生这种情况呢?这是打算这样的吗?

还有一个简单的控制器和简单的测试端点来调用服务方法,这很可能对问题不重要,但为了完整起见,我仍然会在这里添加它。

@RestController
public class UserController {

  @Autowired
  UserService userService;

  @RequestMapping(value = "/test", method = RequestMethod.GET)
  public User changeUserAndPet() {
    return userService.changeUserAndPet();
  }

}

感谢任何解释/提示,并随时在github中询问额外的信息/代码。

2 个答案:

答案 0 :(得分:6)

Spring Data存储库是JPA EntityManager的包装器。加载实体时,您将获得实例,但该对象的副本存储在EntityManager内。当您的事务提交时,EntityManager会迭代所有托管实体,并将它们与返回到代码的版本进行比较。如果您对版本进行了任何更改,JPA会计算应在数据库中执行哪些更新以反映您的更改。

除非您非常了解JPA,否则预测调用何时传播到数据库可能会很棘手,因为flush()是在内部调用的。例如,每次执行查询时,JPA都会执行每次查询刷新,因为任何挂起的插入都必须发送到数据库,否则查询将无法找到它们。

如果您使用@Transactional方法定义了一个事务,那么即使用户未保存,pet也会更新。如果没有事务,则对save的调用必须触发EntityManager将更新传播到数据库。对我来说,为什么会这样,这有点神秘。我知道Spring在调用Controller之前在EntityManager内创建OpenEntityManagerInViewInterceptor,但由于事务不是显式的,因此必须隐式创建它,并且可能存在多个事务。

我总是鼓励开发人员在Spring中使用显式事务,并在适当时使用readonly对其进行限定。

答案 1 :(得分:4)

这是JPA和EntityManager的工作原理。如果通过存储库查找实体,它将作为管理实体附加到EntityManager。当EntityManager执行刷新时,将对您对该对象所做的任何更改进行拾取。实际上,在您的情况下,您甚至不需要在存储库中调用save方法。

您可以找到有关JPA实体生命周期的更多信息,例如在这里:https://dzone.com/articles/jpa-entity-lifecycle