使用spring-data-jpa时修改查询结果对象可能会导致UPDATE语句执行

在事务方法中使用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_atupdate_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语句对数据库进行了更新。修改后的数据如下:

image

在事务提交以后,再对结果对象进行修改后,就不会出发更新了。在test方法中修改create_at 属性,没有触发更新。

解决办法

在业务方法中尽量不要去更新结果对象,如果说确实需要通过修改属性来实现业务。那么可以clone一个新的对象出来使用。