问题是,有一天我们发现,如果我们在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中询问额外的信息/代码。
答案 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