在事务方法中使用spring-data-jpa
进行查询,返回的结果对象对于Hiberante
来说是处于持久状态(persistent)状态的。
如果修改结果对象的属性,可能会导致在事务提交的时候执行UPDATE语句,要注意这个问题。
演示
实体
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@Column
private String avatar;
@Column
private LocalDateTime createAt;
@Column
private LocalDateTime updateAt;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public LocalDateTime getCreateAt() {
return createAt;
}
public void setCreateAt(LocalDateTime createAt) {
this.createAt = createAt;
}
public LocalDateTime getUpdateAt() {
return updateAt;
}
public void setUpdateAt(LocalDateTime updateAt) {
this.updateAt = updateAt;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", avatar=" + avatar + ", createAt=" + createAt + ", updateAt="
+ updateAt + "]";
}
}
表结构
jpa自动生成的
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`avatar` varchar(255) DEFAULT NULL,
`create_at` datetime(6) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`update_at` datetime(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
业务方法
import java.time.LocalDateTime;
import javax.annotation.Resource;
import javax.transaction.Transactional;
import org.springframework.stereotype.Service;
import io.springboot.demo.entity.User;
import io.springboot.demo.repository.UserRepository;
@Service
public class UserService {
@Resource
private UserRepository userRepository;
@Transactional
public User queryAndUpdate (Integer userId) {
User user = this.userRepository.findById(userId).orElse(null);
if (user != null) {
// 修改用户的`updateAt`字段
user.setUpdateAt(LocalDateTime.now());
}
return user;
}
}
初始化一条数据
INSERT INTO `demo`.`user`(`id`, `avatar`, `create_at`, `name`, `update_at`) VALUES (1, 'avatar', NULL, 'foo', NULL);
注意,create_at
和 update_at
都是 null
测试方法
import java.time.LocalDateTime;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import io.springboot.demo.DemoApplication;
import io.springboot.demo.entity.User;
import io.springboot.demo.service.UserService;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class DemoApplicationTest {
@Resource
private UserService userService;
@Test
public void test () {
User user = this.userService.queryAndUpdate(1);
user.setCreateAt(LocalDateTime.now());
System.out.println(user);
}
}
控制台输出(核心日志):
2021-06-28 17:01:03.301 DEBUG 18156 --- [ main] org.hibernate.SQL :
select
user0_.id as id1_0_0_,
user0_.avatar as avatar2_0_0_,
user0_.create_at as create_a3_0_0_,
user0_.name as name4_0_0_,
user0_.update_at as update_a5_0_0_
from
user user0_
where
user0_.id=?
2021-06-28 17:01:03.373 DEBUG 18156 --- [ main] org.hibernate.SQL :
update
user
set
avatar=?,
create_at=?,
name=?,
update_at=?
where
id=?
2021-06-28 17:01:03.495 DEBUG 18156 --- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1505791217<open>)] after transaction
User [id=1, name=foo, avatar=avatar, createAt=2021-06-28T17:01:03.495, updateAt=2021-06-28T17:01:03.352]
可以看到最后执行了一条SQL语句对数据库进行了更新。修改后的数据如下:
在事务提交以后,再对结果对象进行修改后,就不会出发更新了。在test
方法中修改create_at
属性,没有触发更新。
解决办法
在业务方法中尽量不要去更新结果对象,如果说确实需要通过修改属性来实现业务。那么可以clone
一个新的对象出来使用。