将验证移至 JPQL 查询级别

Posted

技术标签:

【中文标题】将验证移至 JPQL 查询级别【英文标题】:move validation to the JPQL query level 【发布时间】:2021-04-12 12:32:12 【问题描述】:

我正在寻找一种将验证方法从服务移动到存储库的方法

一张图片有一个图片数据。

这是方法:

// TODO .filter(pic -> pic.getPictureData().getFileName() != null)

这是我的服务

@Service
@ConditionalOnProperty(name = "picture.storage.type", havingValue = "file")
public class PictureServiceFileImpl implements PictureService 

    private static final Logger logger = LoggerFactory.getLogger(PictureServiceFileImpl.class);

    @Value("$picture.storage.path")
    private String storagePath;

    private final PictureRepository repository;

    @Autowired
    public PictureServiceFileImpl(PictureRepository repository) 
        this.repository = repository;
    

    @Override
    public Optional<String> getPictureContentTypeById(long id) 
        return repository.findById(id)
                // TODO move validation to the JPQL query level
                .filter(pic -> pic.getPictureData().getFileName() != null)
                .map(Picture::getContentType);
    

    @Override
    public Optional<byte[]> getPictureDataById(long id) 
        return repository.findById(id)
                // TODO move validation to the JPQL query level
                .filter(pic -> pic.getPictureData().getFileName() != null)
                .map(pic -> Path.of(storagePath, pic.getPictureData().getFileName()))
                .filter(Files::exists)
                .map(path -> 
                    try 
                        return Files.readAllBytes(path);
                     catch (IOException ex) 
                        logger.error("Can't open picture file", ex);
                        throw new RuntimeException(ex);
                    
                );
    

    @Override
    public PictureData createPictureData(byte[] picture) 
        String fileName = UUID.randomUUID().toString();
        try (OutputStream os = Files.newOutputStream(Path.of(storagePath, fileName))) 
            os.write(picture);
         catch (IOException ex) 
            logger.error("Can't create picture file", ex);
            throw new RuntimeException(ex);
        
        return new PictureData(fileName);
    

实体

@Entity
@Table(name = "pictures")
public class Picture 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "content_type", nullable = false)
    private String contentType;

    @OneToOne(fetch = FetchType.LAZY, cascade= CascadeType.ALL, optional = false, orphanRemoval = true)
    @JoinColumn(name="picture_data_id")
    private PictureData pictureData;

    @ManyToOne
    private Product product;

    public Picture() 
    

    public Picture(String name, String contentType, PictureData pictureData, Product product) 
        this.name = name;
        this.contentType = contentType;
        this.pictureData = pictureData;
        this.product = product;
    

    public Long getId() 
        return id;
    

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

    public String getName() 
        return name;
    

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

    public String getContentType() 
        return contentType;
    

    public void setContentType(String contentType) 
        this.contentType = contentType;
    

    public PictureData getPictureData() 
        return pictureData;
    

    public void setPictureData(PictureData pictureData) 
        this.pictureData = pictureData;
    

    public Product getProduct() 
        return product;
    

    public void setProduct(Product product) 
        this.product = product;
    


@Entity
@Table(name = "pictures_data")
public class PictureData 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Lob
    @Type(type="org.hibernate.type.BinaryType") // для правильной работы PostgreSQL
    @Column(name = "data", length = 33554430) // для правильной hibernate-валидации в mysql
    private byte[] data;

    @Column(name = "file_name")
    private String fileName;

    public PictureData() 
    

    public PictureData(byte[] data) 
        this.data = data;
    

    public PictureData(String fileName) 
        this.fileName = fileName;
    

    public Long getId() 
        return id;
    

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

    public byte[] getData() 
        return data;
    

    public void setData(byte[] data) 
        this.data = data;
    

    public String getFileName() 
        return fileName;
    

    public void setFileName(String fileName) 
        this.fileName = fileName;
    

我正在努力让查询在 JPQL 中工作。

public interface PictureRepository extends JpaRepository<Picture, Long> 

    @Query ("SELECT p FROM Picture p JOIN p.pictureData d WHERE d.data  IS NOT NULL ")
    Picture filterPictureWherePictureDataIsNotNull ();

【问题讨论】:

【参考方案1】:

既然你已经有实体级join,你可以直接使用下面的方法

public interface PictureRepository extends JpaRepository<Picture, Long> 


    @Query ("SELECT p FROM Picture p WHERE p.pictureData.data  IS NOT NULL ")
    Picture filterPictureWherePictureDataIsNotNull ();

另一个观察结果,

您的 repo 方法可能会返回图片列表而不是一张图片。因此,理想情况下,返回类型应该是

@Query ("SELECT p FROM Picture p WHERE p.pictureData.data  IS NOT NULL ")
    List<Picture> filterPictureWherePictureDataIsNotNull ();

【讨论】:

感谢您的回答。我不知道为什么,但是 p.pictureData.data 不起作用。 IntellijIdea 无法解析符号“数据” 嗯。今天一切正常。 Intellij 的一些奇怪行为 不知道是不是一定要用@Query 还是方法名够对了,剩下的就做SpringData? 是的。对于 findByFieldname、existsByFieldName 等基本方法,不需要 @query。但是对于具有逻辑的查询将需要查询注释 看起来不错!可选 findPictureByIdAndPictureData_FileNameIsNotNull(Long id);

以上是关于将验证移至 JPQL 查询级别的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式验证 JPQL(JPA 查询)

如何在 JPQL 中查找第一个

JPQL 限制查询 [重复]

是否可以将客户端验证添加到 MVC C# 的类级别验证器?

如何在 JPQL 中进行“深度”获取连接?

JPQL查询where子句使用IN,如何将参数传递给查询