java - Spring : can't persist entity annotated with @JsonIgnore

标签 java spring-data spring-data-jpa spring-data-rest

我是 Spring Boot 的初学者,无法解决问题。我有一个实体类 (Customer) 和一个 REST 存储库 (CustomerRepository)。该类包含一些我不想被 REST 存储库公开的敏感字段。因此,我使用 @JsonIgnore 注释对这些字段进行了注释,如下所示:

package br.univali.sapi.entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Customer
{
    @Id
    @GeneratedValue
    private Long id = null;

    private String login;

    private String name;

    @JsonIgnore
    private String password;

    @JsonIgnore
    private String email;

    public Customer()
    {

    }

    public Long getId()
    {
            return id;
    }

    public void setId(Long id)
    {
            this.id = id;
    }

    public String getLogin()
    {
            return login;
    }

    public void setLogin(String login)
    {
            this.login = login;
    }

    public String getName()
    {
            return name;
    }

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

    public String getPassword()
    {
            return password;
    }

    public void setPassword(String password)
    {
            this.password = password;
    }

    public String getEmail()
    {
            return email;
    }

    public void setEmail(String email)
    {
            this.email = email;
    }
}
一切正常,我的 REST API 返回了所需的结果。但是,当我向 API 发出 POST 请求以插入新实体时,我收到数据库错误: "column password can't be null", "column email can't be null"
密码和电子邮件与其他参数一起在 POST 请求中发送到服务器,但它似乎被忽略了。如果我删除 @JsonIgnore 注释,实体将正常保留。
我知道我可以使用投影来隐藏这些字段。但投影是 URL 中的可选参数。通过这种方式,有经验的用户将能够从请求 URL 中删除参数并查看这些字段。
如果我可以隐式地强制执行投影,那将解决问题,但这似乎不可能仅使用 Spring 框架来完成。我可以使用 Apache URL 重写来实现这一点,但维护会很糟糕。
任何人都知道我如何解决这个问题?
谢谢!
编辑 1:
我相信我找到了使用 DTO/投影(数据传输对象)的解决方案。您必须创建两个 DTO,一个用于显示实体,另一个用于更新实体,如下所示:
public interface CustomerViewDTO
{
    public Long getId();
    public String getLogin();
    public String getName();
}
public class CustomerUpdateDTO
{
   private String login;
   private String name;
   private String password;
   private String email;

   // Getters and Setters ommited for breviety
}
现在,在您使用 DTO 的存储库上,Spring 将发挥它的魔力:
@Transactional(readOnly = true)
public interface CustomerRepository extends JPARepository<Customer, Long>
{
   // Using derived query
   public CustomerViewDTO findByIdAsDTO(Long id);

   // Using @Query annotation
   @Query("SELECT C FROM Customer C WHERE C.id = :customerId")
   public CustomerViewDTO findByIdAsDTO(@Param("customerId") Long id);
}   
对于更新,您会在 Controller 上收到 DTO 并将其映射到您的服务上的实体,如下所示:
@RestController
public class CustomerController
{
    @Autowired
    private CustomerService customerService;

    @RequestMapping(method = "PATCH", path = "/customers/{customerId}")
    public ResponseEntity<?> updateCustomer(@PathVariable Long customerId, @RequestBody CustomerUpdateDTO customerDTO)
    {
        CustomerViewDTO updatedCustomer = customerService.updateCustomer(customerId, customerDTO);

        return ResponseEntity.ok(updatedCustomer);
    }

    @RequestMapping(method = GET, path = "/customers/{customerId}")
    public ResponseEntity<?> findCustomerById(@PathVariable Long customerId)
    {
        return ResponseEntity.ok(customerService.findByIdAsDTO(Long));
    }
}
@Service
public class CustomerService
{
    @Autowired
    private CustomerRepository customerRepository;

    // Follow this tutorial:
    // https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
    @Autowired
    private ModelMapper modelMapper; 

    @Transactional(readOnly = false)
    public CustomerViewDTO updateCustomer(Long customerId, CustomerUpdateDTO customerDTO)
    {
         Customer customerEntity = customerRepository.findById(customerId);

         // This copies all values from the DTO to the entity
         modelMapper.map(customerDTO, customerEntity); 

         // Now we have two aproaches: 
         // 1. save the entity and convert back to DTO manually using the model mapper
         // 2. save the entity and call the repository method which will convert to the DTO automatically
         // The second approach is the one I use for several reasons that
         // I won't explain here

         // Here we use save and flush to force JPA to execute the update query. This way, when we do the select the entity will come with the fields updated
         customerEntity = customerRepository.saveAndFlush(customerEntity);

         // First aproach
         return modelMapper.map(customerEntity, CustomerViewDTO.class);

         // Second approach
         return customerRepository.findByIdAsDTO(customerId);
    }

    @Transactional(readOnly = true)
    public CustomerViewDTO findByIdAsDTO(Long customerId)
    {
         return customerRepository.findByIdAsDTO(customerId);
    }
}

最佳答案

我想你在 db 级别没有空约束。基本上,当您在字段上执行 @JsonIgnore 时,您只是不传递它并且数据库无法插入该值,这很清楚。

所以我在这里看到的解决方案如下:

1) 删除 @JsonIgnore 注释以能够执行 POST 请求。

2) 使用 GET 时使用投影。您可以将 Spring Data REST 配置为 始终 使用投影,但是默认情况下 Spring Data REST 始终在单个资源上返回完整的 JSON(在您的情况下使用电子邮件和密码),这对我来说很奇怪。我在 another answer 中写了这个问题的解决方案,因为我也遇到了同样的问题。只需使用 ProjectingProcessor 并为所有投影使用一些默认名称。不要忘记使用 config.getProjectionConfiguration().addProjection 方法在您的配置中添加投影。

3) 避免第 2 步的唯一方法是在 GET 级别的单个资源上禁止 Spring Data REST 并使用自定义 Controller 。但我会采用第 2 步中的解决方案以避免样板 Controller 。

关于java - Spring : can't persist entity annotated with @JsonIgnore,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42647779/

相关文章:

java - 从事务方法开始新的事务

mysql - spring data - 原生Mysql查询?

java - CrudRepository findAll() 按列表包含子列表

javascript - 如何将选定的日期从日期选择器获取到后端的方法

java - 如何在测试中模拟 CloseableIterator?

java.lang.ClassCastException : java. lang.Integer 无法转换

java - 手动创建 SolrCrudRepository

java - Spring Boot中使用多个数据源时如何设置多个连接池?

java - 当我希望执行插入查询时使用 Spring JPA 发布请求

java - 更改 ListView