Java 弹簧:can';t用@JsonIgnore注释的持久化实体

Java 弹簧:can';t用@JsonIgnore注释的持久化实体,java,spring-data,spring-data-jpa,spring-data-rest,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 j

我是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;
    }
}
public interface CustomerViewDTO
{
    public Long getId();
    public String getLogin();
    public String getName();
}
一切正常,我的restapi返回了期望的结果。但是,当我向API发出POST请求以插入新实体时,我会收到数据库错误:
“列密码不能为空”、“列电子邮件不能为空”

密码和电子邮件将与其他参数一起发送到POST请求中的服务器,但是它似乎被忽略。如果删除@JsonIgnore注释,实体将正常持久化

我知道我可以用投影来隐藏这些字段。但投影是URL中的可选参数。这样,有经验的用户就可以从请求URL中删除参数,并查看这些字段

如果我可以隐式地强制执行一个投影,这将解决问题,但仅使用Spring框架似乎不可能做到这一点。我可以使用ApacheURL重写来实现这一点,但是维护会很糟糕

有人知道我如何解决这个问题吗? 谢谢

编辑1:

我相信我找到了一个使用DTO/projections(数据传输对象)的解决方案。您必须创建两个DTO,一个用于显示实体,另一个用于更新实体,如下所示:

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;
    }
}
public interface CustomerViewDTO
{
    public Long getId();
    public String getLogin();
    public String getName();
}
现在,在存储库上使用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);
}   

我想您在db级别上没有空约束。基本上,当您对字段执行
@JsonIgnore
操作时,您只是不传递它,数据库无法插入该值,这一点非常清楚

因此,我在这里看到的解决方案如下:

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

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


3) 避免步骤2的唯一方法是禁止
Spring数据REST
级别的单个资源上获取
,并使用自定义控制器。但是我会使用步骤2中的解决方案来避免样板控制器。

也许可以帮助您。注释也很有趣。将注释添加到密码和电子邮件设置器中。即使相关字段被标记为已忽略,也应允许反序列化。或者,根据建议,仅将
@JsonIgnore
应用于getter您可以使用
@RepositoryRestResource
摘录投影
参数将投影设置为默认投影。
@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);
    }
}