Spring JPA 选择特定列

Posted

技术标签:

【中文标题】Spring JPA 选择特定列【英文标题】:Spring JPA selecting specific columns 【发布时间】:2014-03-27 06:30:47 【问题描述】:

我正在使用 Spring JPA 来执行所有数据库操作。但是我不知道如何从 Spring JPA 中的表中选择特定列?

例如:SELECT projectId, projectName FROM projects

【问题讨论】:

看到这个***.com/questions/12618489/… JPA 不寻找特定字段背后的想法是,从表格的一行中提取一列或所有列的成本(效率方面)相同。 @Desorder -- 成本并不总是相同的。对于更简单、原始的数据类型来说,这可能没什么大不了的,但我最终出现在这个页面上的原因是因为我注意到一个简单的“列表文档”查询运行缓慢。该实体有一个 BLOB 列(需要它用于文件上传/存储),我怀疑它很慢,因为它会将 BLOB 加载到内存中,即使列出文档不需要它们。 @jm0 据你所知,有多少个表有 BLOB 列? @Desorder 它只是一个表,但我正在执行“列表”功能(多行 - 列出由给定 ID 创建的所有文档)。我注意到这个问题的唯一原因是这个简单的列表查询需要几秒钟,而对其他表的更复杂的查询几乎是立即发生的。一旦我意识到,我知道随着行的增加,它会受到越来越多的影响,因为 Spring JPA 正在将每个 BLOB 加载到内存中,即使它们没有被使用。我为 Spring 数据找到了一个不错的解决方案(发布在下面),但我认为我有一个更好的纯 JPA 注释,如果它有效,我会发布 tmrw 【参考方案1】:
public static final String FIND_PROJECTS = "select ac_year_id,ac_year from tbl_au_academic_year where ac_year_id=?1";

    @Query(value = FIND_PROJECTS, nativeQuery = true)
    public  List<Object[]> findByAcYearId(Integer ac_year_id);

这对我有用

【讨论】:

【参考方案2】:

使用 Spring Data JPA 可以从数据库中选择特定列

----在DAOImpl中----

@Override
    @Transactional
    public List<Employee> getAllEmployee() throws Exception 
    LOGGER.info("Inside getAllEmployee");
    List<Employee> empList = empRepo.getNameAndCityOnly();
    return empList;
    

---- 在回购中----

public interface EmployeeRepository extends CrudRepository<Employee,Integer> 
    @Query("select e.name, e.city from Employee e" )
    List<Employee> getNameAndCityOnly();

在我的情况下它 100% 有效。 谢谢。

【讨论】:

【参考方案3】:

我特别不喜欢这种语法(它看起来有点老套......)但这是我能找到的最优雅的解决方案(它在 JPA 存储库类中使用自定义 JPQL 查询):

@Query("select new com.foo.bar.entity.Document(d.docId, d.filename) from Document d where d.filterCol = ?1")
List<Document> findDocumentsForListing(String filterValue);

当然,您只需为Document 提供一个接受docIdfilename 作为构造函数参数的构造函数。

【讨论】:

(顺便说一句,我验证了,如果“文档”被导入,你不需要提供完全限定的类名——只是这样,因为这就是我唯一的示例中的完成方式能找到) 这应该是公认的答案。它工作得很好,并且真的只选择了必要的字段。 不必要的字段也包括在内,但是值为'null',这些字段会不会占用内存? 是的,但如此之少,以至于在绝大多数情况下,试图围绕这个进行工程设计真的很荒谬——***.com/questions/2430655/… 你必须制作没有这些字段的专门的轻量级对象并让它们指向到同一张桌子?在使用 ORM 并将它们用于它们的关系时,IMO 是不受欢迎的……超优化可能更多地属于仅使用一些轻量级查询 DSL 并直接映射到 DTO 的领域,即使这样我认为不鼓励冗余 jm0 虽然它是导入的,但如果没有完全限定的类名,它对我不起作用。不过它确实编译成功了。【参考方案4】:

您可以使用@jombie 建议的答案,并且:

将接口放在实体类之外的单独文件中; 是否使用本机查询(选择取决于您的需要); 不要为此目的覆盖 findAll() 方法,而是使用您选择的名称; 记得返回一个List 参数化你的新界面(例如List&lt;SmallProject&gt;)。

【讨论】:

【参考方案5】:

在我看来这是一个很好的解决方案:

interface PersonRepository extends Repository<Person, UUID> 

    <T> Collection<T> findByLastname(String lastname, Class<T> type);

并像这样使用它

void someMethod(PersonRepository people) 

  Collection<Person> aggregates =
    people.findByLastname("Matthews", Person.class);

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);

【讨论】:

为什么不返回 List 而不是 collection?! @AbdullahKhan 因为结果可能并不总是有顺序。【参考方案6】:

使用较新的 Spring 版本可以执行以下操作:

如果不使用本机查询,可以按以下方式完成:

public interface ProjectMini 
    String getProjectId();
    String getProjectName();


public interface ProjectRepository extends JpaRepository<Project, String>  
    @Query("SELECT p FROM Project p")
    List<ProjectMini> findAllProjectsMini();

使用本机查询可以做到如下:

public interface ProjectRepository extends JpaRepository<Project, String>  
    @Query(value = "SELECT projectId, projectName FROM project", nativeQuery = true)
    List<ProjectMini> findAllProjectsMini();

详情请查看docs

【讨论】:

找不到能够从类型转换的转换器【参考方案7】:

使用原生查询:

Query query = entityManager.createNativeQuery("SELECT projectId, projectName FROM projects");
List result = query.getResultList();

【讨论】:

【参考方案8】:

在我的情况下,我只需要 json 结果,这对我有用:

public interface SchoolRepository extends JpaRepository<School,Integer> 
    @Query("select s.id, s.name from School s")
    List<Object> getSchoolIdAndName();

在控制器中:

@Autowired
private SchoolRepository schoolRepository;

@ResponseBody
@RequestMapping("getschoolidandname.do")
public List<Object> getSchool() 
    List<Object> schools = schoolRepository.getSchoolIdAndName();
    return schools;

【讨论】:

您应该将Object 替换为mpr 所述的自定义界面。完美运行【参考方案9】:

您可以在存储库接口类中应用以下代码。

entityname 表示您的数据库表名称,如项目。 List 表示 Project 是您项目中的实体类。

@Query(value="select p from ##entityName p where p.id=:projectId and p.projectName=:projectName")

List<Project> findAll(@Param("projectId") int projectId, @Param("projectName") String projectName);

【讨论】:

【参考方案10】:

可以在原生sql中指定null作为字段值。

@Query(value = "select p.id, p.uid, p.title, null as documentation, p.ptype " +
            " from projects p " +
            "where p.uid = (:uid)" +
            "  and p.ptype = 'P'", nativeQuery = true)
Project findInfoByUid(@Param("uid") String uid);

【讨论】:

【参考方案11】:

您可以像这样在Repository 类的@Query 注释中设置nativeQuery = true

public static final String FIND_PROJECTS = "SELECT projectId, projectName FROM projects";

@Query(value = FIND_PROJECTS, nativeQuery = true)
public List<Object[]> findProjects();

请注意,您必须自己进行映射。除非你真的只需要这两个值,否则像这样使用常规映射查找可能更容易:

public List<Project> findAll()

Spring 数据docs 可能也值得一看。

【讨论】:

不需要原生查询。您应该避免使用它们,因为它们会破坏 JPQL 的优势。见 Atals 的回答。 对我来说,我必须使用 value 属性名称来限定第一个属性(FIND_PROJECTS 以上)(因此,如果这是我的代码,我将不得不将其写为 @Query(value = FIND_PROJECTS, nativeQuery = true) 等.【参考方案12】:

在我的例子中,我创建了一个单独的实体类,没有不需要的字段(只有需要的字段)。

将实体映射到同一个表。 现在,当需要所有列时,我使用旧实体,当只需要一些列时,我使用 lite 实体。

例如

@Entity
@Table(name = "user")
Class User
         @Column(name = "id", unique=true, nullable=false)
         int id;
         @Column(name = "name", nullable=false)
         String name;
         @Column(name = "address", nullable=false)
         Address address;

你可以创建类似的东西:

@Entity
@Table(name = "user")
Class UserLite
         @Column(name = "id", unique=true, nullable=false)
         int id;
         @Column(name = "name", nullable=false)
         String name;

这在您知道要获取的列时有效(并且这不会改变)。

如果您需要动态决定列,则无法使用。

【讨论】:

嗨 sachin,我怀疑我是否会像你上面提到的那样创建实体。当 JPA 运行时,它将尝试使用用户名创建表。将使用哪个实体。 永远不要使用 JPA 创建表,在数据库中手动创建表,使用 JPA 将关系世界映射到对象世界。 这里为什么不能使用继承? 您将继承您试图排除的列。 @deadbug【参考方案13】:

您可以使用来自 Spring Data JPA (doc) 的投影。在您的情况下,创建界面:

interface ProjectIdAndName
    String getId();
    String getName();

并将以下方法添加到您的存储库中

List<ProjectIdAndName> findAll();

【讨论】:

这是一个干净的解决方案。它可能有锅炉模板,但接口可以是实体的内部类。让它很干净。 太棒了,记住不要在你的实体上实现接口,否则它将不起作用 这个解决方案在扩展 JpaRepository 时不起作用,有人知道解决方法吗? 我没有问题让它与 JpaRepository 一起使用,在你的存储库类中尝试这个方法签名 List findAllProjectedBy()。我还建议在包装方面让您的投影接口靠近您的 DTO。 IMO 投影接口的行为类似于 DTO。 你不能使用 findAll();因为它会与 JPARepositorys 方法发生冲突。您需要使用类似 List findAllBy();【参考方案14】:

我想最简单的方法可能是使用 Spring-Data 附带的QueryDSL。

根据你的问题,答案可以是

JPAQuery query = new JPAQuery(entityManager);
List<Tuple> result = query.from(projects).list(project.projectId, project.projectName);
for (Tuple row : result) 
 System.out.println("project ID " + row.get(project.projectId));
 System.out.println("project Name " + row.get(project.projectName)); 

实体管理器可以自动装配,您始终可以在不使用 *QL 语言的情况下使用对象和类。

正如您在链接中看到的,最后一个选择似乎对我来说更优雅,即使用 DTO 存储结果。适用于您的示例:

JPAQuery query = new JPAQuery(entityManager);
QProject project = QProject.project;
List<ProjectDTO> dtos = query.from(project).list(new QProjectDTO(project.projectId, project.projectName));

将 ProjectDTO 定义为:

class ProjectDTO 

 private long id;
 private String name;
 @QueryProjection
 public ProjectDTO(long projectId, String projectName)
   this.id = projectId;
   this.name = projectName;
 
 public String getProjectId() ... 
 public String getProjectName()....

【讨论】:

【参考方案15】:

你可以使用JPQL:

TypedQuery <Object[]> query = em.createQuery(
  "SELECT p.projectId, p.projectName FROM projects AS p", Object[].class);

List<Object[]> results = query.getResultList();

或者您可以使用本机 sql 查询。

Query query = em.createNativeQuery("sql statement");
List<Object[]> results = query.getResultList();

【讨论】:

以上是关于Spring JPA 选择特定列的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot Jpa JPQL 选择除特定列之外的列

仅从 Spring Data JPA 中的联接表(多对多)中选择特定列

Spring JPA 在嵌入的复合键上使用 @MapsId、@AttributeOverride 时插入重复的选择列

Spring Data JPA - NUMBER 列类型的空参数问题

使用 Spring Data JPA 选择一列

在所有嵌套实体中选择几个:SPRING JPA