JPA-在非实体类中连接两个表

Posted

技术标签:

【中文标题】JPA-在非实体类中连接两个表【英文标题】:JPA- Joining two tables in non-entity class 【发布时间】:2014-09-30 11:57:06 【问题描述】:

我是新手,尝试谷歌,但我无法解决我的查询。 请帮忙。

我正在尝试映射两个实体:我的 POJO 类 PersonC 中的 PersonA 和 Person

@Entity
class PersonA
     String sample_field;


@Entity
class Person
     String id;
     String name;


以上两个是jpa的实体。

现在我想将它们合并到一个 pojo 类中。

class PersonC

   Strind id;
   String address;

尝试了下面的代码,但是当我尝试获取地址/外键字段时它不起作用。

@SqlResultSetMapping(name="PersonC", 
classes = 
   @ConstructorResult(targetClass = PersonC.class, 
    columns = @ColumnResult(name="name")
              , @ColumnResult(name="address")
    )

我应该在哪里定义 @SqlResultSetMapping ,为上面的哪个类? ) )

【问题讨论】:

【参考方案1】:

@SqlResultSetMapping 可以放在 any 实体类中(不要注释 POJO - 它不起作用)。 JPA 2.1 版中添加了使用@ConstructorResult 映射到 POJO 类。与映射一起使用的 POJO 必须有正确的构造函数。

与预期构造函数的参数对应的所有列必须使用 ConstructorResult 注释的列元素指定,其顺序与构造函数的参数列表的顺序相同。

请参考以下查询使用示例并相应地解决您的情况。

@Entity
public class Address 
    @Id int id;  
    String street;



@SqlResultSetMapping(name="PersonDTOMapping",
    classes = 
     @ConstructorResult(targetClass = PersonDTO.class,
       columns = @ColumnResult(name="name"), @ColumnResult(name="street")
     )
)
@Entity
public class Person 
    @Id int id;
    String name;
    Address address;  
  

public class PersonDTO 
    String name;
    String street;
    public PersonDTO(String name, String street) 
        this.name = name;
        this.street = street;
    


// usage
Query query = em.createNativeQuery(
    "SELECT p.name AS name, a.street AS street FROM Person p, Address a WHERE p.address_id=a.id",
    "PersonDTOMapping");
List<PersonDTO> result = query.getResultList();

请注意,别名(AS nameAS street)必须与 @ColumnResults 中的名称匹配。 该示例针对 Ecliselink 2.5.1 进行了测试。

【讨论】:

那么将其映射到 pojo 有什么好处呢? 您能否举一个使用@EntityResult 而不是@ColumnResult 的JPA 2.0 示例? 与带有 NEW 关键字的 JPQL 非常相似。所以,有趣的部分是我们现在被困在旧的 JPA 中。如果您也将表 PAIRS_OF_SHOES 作为列表加入,您将如何映射这个有什么想法吗? 正如在下面的解决方案中指出的那样,您并不总是有一个实体类来添加注释,至少不是正确的。在调用存储过程以将结果映射到 DTO 并且您没有该 DTO 的实体版本时很常见(出于显而易见的原因)。下面的解决方案解决了这种情况。【参考方案2】:

刚刚找到了一个使用 JPQL 的更简单的解决方案。 我从@zbig 的回答中窃取了部分示例:

@Entity
public class Address 
    @Id int id;  
    String street;


@Entity
public class Person 
    @Id int id;
    String name;
    Address address;  
  

public class PersonDTO 
    String name;
    String street;
    public PersonDTO(String name, String street) 
        this.name = name;
        this.street = street;
    


List<PersonDTO> listOfPersons = em.createQuery("select new com.example.PersonDTO(p.name, a.street) " +
"from Person p, Address a " + 
"WHERE p.address.id=a.id", PersonDTO.class).getResultList();

这个解决方案的好处是你不需要使用@SqlResultSetMapping注解,它必须放在any实体类上,而不是DTO类上!这有时会让人感到困惑,因为实体类只能部分相关(例如,在连接多个表时)。

更多信息here

【讨论】:

你是英雄,我希望我可以为这个答案投票 100 次,并将你的答案打印在我的 T 恤上并一直穿着:)【参考方案3】:

这篇文章涉及 Hibernate。

@SqlResultSetMapping@NamedNativeQuery(或 @NamedQuery)放入 @Entity 的建议类定义不够优雅,显然没有遵循关注点分离原则。

更合适的解决方案是使用 @MappedSuperclass 注释,如下所示:

SingerExtended.java(类必须是abstract):

package pl.music.model.singer.extended;

import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.MappedSuperclass;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;

@MappedSuperclass
@SqlResultSetMapping( // @formatter:off
    name = "SingerExtendedMapping",
    classes = @ConstructorResult(
        targetClass = SingerExtendedDTO.class,
        columns = 
            @ColumnResult(name = "singer_id", type = Long.class),
            @ColumnResult(name = "first_name"),
            @ColumnResult(name = "last_name"),
            @ColumnResult(name = "count_albums", type = Long.class)
        
    )
)
@NamedNativeQueries(
    @NamedNativeQuery(
            name = "SingerExtendedAsc",
            query = "select"
                + " singer.singer_id,"
                + " singer.first_name,"
                + " singer.last_name,"
                + " (select count(*) from album where album.singer_id = singer.singer_id) as count_albums"
                + " from singer"
                + " group by singer.singer_id"
                + " order by last_name collate :collation asc, first_name collate :collation asc",
            resultSetMapping = "SingerExtendedMapping"
    )
) // @formatter:on
public abstract class SingerExtended 

然后是DAO类SingerExtendedDAO.java

package pl.music.model.singer.extended;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SingerExtendedDAO 

    @PersistenceContext
    EntityManager entityManager;

    @Autowired
    private String collation;

    public List<SingerExtendedDTO> getAll(Integer page, Integer count) 
        TypedQuery<SingerExtendedDTO> query = entityManager.createNamedQuery("SingerExtendedAsc", SingerExtendedDTO.class);
        query.setParameter("collation", collation);
        if ((count != null) && (count.intValue() > 0)) 
            query.setMaxResults(count.intValue());
            if ((page != null) && (page.intValue() >= 0)) 
                query.setFirstResult(count.intValue() * page.intValue());
            
        
        List<SingerExtendedDTO> singerExtendedDTOs = query.getResultList();
        return singerExtendedDTOs;
    


最后是 DTO 类 SingerExtendedDTO.java(您必须提供“完整”构造函数):

package pl.music.model.singer.extended;

public class SingerExtendedDTO 

    private Long singerId;
    private String firstName;
    private String lastName;
    private Long countAlbums;

    // IMPORTANT: this constructor must be defined !!! 
    public SingerExtendedDTO(Long singerId, String firstName, String lastName, Long countAlbums) 
        this.singerId = singerId;
        this.firstName = firstName;
        this.lastName = lastName;
        this.countAlbums = countAlbums;
    
    ... getters & setters ...

如果按照上面介绍的方式将所有这些放在一起,我们就会得到一个适当的解决方案:

一切都在一个包中, 查询声明不会污染任何无关实体, 保留关注点分离(分离查询+映射、DAO 和 DTO)。

【讨论】:

这是一个比公认的恕我直言更好的解决方案。在许多情况下,当您将存储的过程结果映射到 DTO 时,您没有实体可以放置这些注释。这很容易解决这个问题。 我收到了No query defined for that name [SingerExtendedAsc]。有什么想法吗?

以上是关于JPA-在非实体类中连接两个表的主要内容,如果未能解决你的问题,请参考以下文章

如何使用类中的 setters 方法将数据插入 JPA 实体表?

JPA实体类中的注解

如何使用 JPA 和 Hibernate 连接两个不相关的实体

JPA:查询以根据实体类中定义的外键值获取结果?

Spring Data JPA 多个实体类表联合视图查询

Spring Boot JPA Hibernate:即使实体类中存在@Id,也没有为实体错误指定标识符