寫在前面
所謂的動態部分更新是指:並非對數據記錄的所有字段整體更新,而是知道運行時才確定哪個或者哪些字段需要被更新。
1)Spring Data Jpa對於Entity的更新,是對數據表中Entity對應的除主鍵外的數據記錄的所有字段整體更新,
而不是僅僅更新前端傳入的字段或者那些發生了變化的字段;
2)repository.save()的邏輯是:如果不存在Entity對應的數據記錄則執行插入操作,否則則執行更新操作。同時,
在執行更新操作之前,此方法還會執行一步查詢操作。源碼如下:
@Transactional @Override public <S extends T> S save(S entity) { if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } }
3)對於字段更新時,如果使用@Query注解,通過寫原生SQL的方法,確實可以實現字段的部分更新,但是使用@Query注解無法很好地實現字段的動態部分更新。
4)使用@DynamicUpdate注解,通過在Entity實體類上添加此注解,再結合repository.save()方法進行字段更新,此方法的確具有可行性,但是仍存在一個問題:當字段值為null值時,Jpa會將null值與原值作比較,如果原值不為null,那么原值將會被覆蓋為null。
針對此問題,如何解決呢,這里提供一種方法。
對於動態部分更新,可以在@DynamicUpdate注解的基礎上,可以書寫一個Jpa工具類來避免null值對於動態部分更新的影響。
這里給出一個示例代碼:
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapperImpl; import java.beans.PropertyDescriptor; import java.util.stream.Stream; public class JpaUtil { public static void copyNotNullProperties(Object src,Object target){ BeanUtils.copyProperties(src,target,getNullPropertyNames(src)); } private static String[] getNullPropertyNames(Object object) { final BeanWrapperImpl wrapper = new BeanWrapperImpl(object); return Stream.of(wrapper.getPropertyDescriptors()) .map(PropertyDescriptor::getName) .filter(propertyName -> wrapper.getPropertyValue(propertyName) == null) .toArray(String[]::new); } }
下面根據示例代碼進行整合。
1> 數據准備
CREATE TABLE `tb_user` ( `id` int(32) NOT NULL AUTO_INCREMENT COMMENT '主鍵Id', `name` varchar(20) DEFAULT NULL COMMENT '用戶名', `age` int(10) DEFAULT NULL COMMENT '年齡', `email` varchar(255) DEFAULT NULL COMMENT '郵箱', `address` varchar(255) DEFAULT NULL COMMENT '地址', `create_time` datetime DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `tb_user` VALUES (1, '張三', 22, '123456@qq.com', '北京市', '2020-02-16 13:18:21'); INSERT INTO `tb_user` VALUES (2, '李四', 23, '243456@qq.com', '天津市', '2020-02-16 13:18:58'); INSERT INTO `tb_user` VALUES (3, '王五', 22, '123597@qq.com', '重慶市', '2020-02-16 13:18:58'); INSERT INTO `tb_user` VALUES (4, '趙六', 21, '565345@qq.com', '武漢市', '2020-02-16 13:18:58'); INSERT INTO `tb_user` VALUES (5, '錢七', 24, '375654@qq.com', '杭州市', '2020-02-16 13:18:58'); INSERT INTO `tb_user` VALUES (6, '孫八', 26, '977842@qq.com', '上海市', '2020-02-16 13:18:58'); INSERT INTO `tb_user` VALUES (7, '周九', 24, '345342@qq.com', '深圳市', '2020-02-16 13:18:58'); INSERT INTO `tb_user` VALUES (8, '鄭十', 25, '645564@qq.com', '廣州市', '2020-02-16 13:18:58');
2> 創建工程,導入依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.darren</groupId> <artifactId>springjpa-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springjpa-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3> 配置Application.yml文件
spring: datasource: url: jdbc:mysql:///springboottest?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2b8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true generate-ddl: true
4> 創建entity實體類
import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; import java.util.Date; @Entity @Data @Table(name = "tb_user") @DynamicUpdate public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private Integer age; private String email; private String address; @Column(name = "create_time") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; }
5> 創建Repository接口
import com.darren.springjpademo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> { }
6> 創建Service層接口及其實現類
6.1 創建service層接口
import com.darren.springjpademo.entity.User; import java.util.List; public interface UserService { /** * 查詢所有用戶信息 * @return */ List<User> queryList(); /** * 更新用戶信息 * @param user * @return */ String updateUser(User user); }
6.2 創建ServiceImpl實現類
import com.darren.springjpademo.repository.UserRepository; import com.darren.springjpademo.entity.User; import com.darren.springjpademo.service.UserService; import com.darren.springjpademo.uitls.JpaUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public List<User> queryList() { return userRepository.findAll(); } @Override public String updateUser(User user) { User user = new User(); user.setId(1); user.setName("張三"); user.setAddress("北京"); if(user.getId() != null) { Optional<User> originalUser = userRepository.findById(user.getId()); if (originalUser.isPresent()) { JpaUtil.copyNotNullProperties(user, originalUser.get()); } } userRepository.save(user); return user.getId()+" "+user.getName(); } }
7> 創建Controller層
import com.darren.springjpademo.entity.User; import com.darren.springjpademo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; /** * 查詢所有的用戶信息 * @return */ @GetMapping("/queryList") public List<User> queryList(){ return this.userService.queryList(); } /** * 更新用戶信息 * @param user * @return */ @PutMapping("/updateUser") public ResponseEntity<String> updateUser(@RequestBody User user){ String result = userService.updateUser(user); return ResponseEntity.ok(result); } }