Tipos de junção JPA

Tipos de junção JPA

1. Visão geral

Neste tutorial, veremos diferentes tipos de junção suportados porJPA.

Para isso, usaremos JPQL,a query language for JPA.

2. Modelo de Dados de Amostra

Vejamos nosso modelo de dados de amostra que usaremos nos exemplos.

Em primeiro lugar, criaremos uma entidadeEmployee:

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String name;

    private int age;

    @ManyToOne
    private Department department;

    @OneToMany(mappedBy = "employee")
    private List phones;

    // getters and setters...
}

CadaEmployee será atribuído a apenas umDepartment:

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;

    @OneToMany(mappedBy = "department")
    private List employees;

    // getters and setters...
}

Por fim, cadaEmployee terá váriosPhones:

@Entity
public class Phone {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String number;

    @ManyToOne
    private Employee employee;

    // getters and setters...
}

3. Junções internas

Vamos começar com junções internas. Quando duas ou mais entidades são unidas internamente, apenas os registros que correspondem à condição de junção são coletados no resultado.

3.1. Junção interna implícita com navegação de associação com valor único

As junções internas podem serimplicit. Como o nome indica,the developer doesn’t specify implicit inner joins. Sempre que navegamos em uma associação de valor único, o JPA cria automaticamente uma associação implícita:

@Test
public void whenPathExpressionIsUsedForSingleValuedAssociation_thenCreatesImplicitInnerJoin() {
    TypedQuery query
      = entityManager.createQuery("SELECT e.department FROM Employee e", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

Aqui, a entidadeEmployee tem um relacionamento muitos para um com a entidadeDepartment. If we navigate from an Employee entity to her Department – specifying e.department –, navegaremos em uma associação de valor único. Como resultado, o JPA criará uma junção interna. Além disso, a condição de junção será derivada do mapeamento de metadados.

3.2. Junção interna explícita com associação de valor único

A seguir, vamos olhar paraexplicit junções internas, ondewe use the JOIN keyword in our JPQL query:

@Test
public void whenJoinKeywordIsUsed_thenCreatesExplicitInnerJoin() {
    TypedQuery query
      = entityManager.createQuery("SELECT d FROM Employee e JOIN e.department d", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

Nesta consulta,we specified a JOIN keyword and the associated Department entity in the FROM clause, enquanto na anterior eles não foram especificados. No entanto, além dessa diferença sintática, as consultas SQL resultantes serão muito semelhantes.

Também podemos especificar uma palavra-chave INNER opcional:

@Test
public void whenInnerJoinKeywordIsUsed_thenCreatesExplicitInnerJoin() {
    TypedQuery query
      = entityManager.createQuery("SELECT d FROM Employee e INNER JOIN e.department d", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

Portanto, como o JPA implicitamente envolverá uma junção interna, quando precisaríamos ser explícitos?

Em primeiro lugar,JPA creates an implicit inner join only when we specify a path expression. Por exemplo, quando queremos selecionar apenas osEmployees que têmDepartmente não usamos uma expressão de caminho -e.department –, devemos usar JOIN palavra-chave em nossa consulta.

Em segundo lugar, quando somos explícitos, pode ser mais fácil saber o que está acontecendo.

3.3. Junção interna explícita com associações com valor de coleção

Outro lugarwe need to be explicit is with collection-valued associations.

Se olharmos para nosso modelo de dados, oEmployee tem um relacionamento um-para-muitos comPhone. Como em um exemplo anterior, podemos tentar escrever uma consulta semelhante:

SELECT e.phones FROM Employee e

But this won’t quite work como talvez pretendíamos. Como a associação selecionada -e.phones - tem valor de coleção,we’ll get a list of Collections, instead of Phone entities:

@Test
public void whenCollectionValuedAssociationIsSpecifiedInSelect_ThenReturnsCollections() {
    TypedQuery query
      = entityManager.createQuery("SELECT e.phones FROM Employee e", Collection.class);
    List resultList = query.getResultList();

    //Assertions
}

Além disso, se quisermos filtrar entidadesPhone na cláusula WHERE, JPA não permitirá isso. Isso ocorre porquea path expression can’t continue from a collection-valued association. Então, por exemplo,e.phones.number isn’t valid.

Em vez disso, devemos criar uma junção interna explícita e criar um alias para a entidadePhone. Em seguida, podemos especificar a entidadePhone na cláusula SELECT ou WHERE:

@Test
public void whenCollectionValuedAssociationIsJoined_ThenCanSelect() {
    TypedQuery query = entityManager.
      createQuery("SELECT ph FROM Employee e JOIN e.phones ph WHERE ph LIKE '1%'", Phone.class);
    List resultList = query.getResultList();

    // Assertions...
}

4. Junção externa

Quando duas ou mais entidades são unidas externamente,the records that satisfy the join condition and also the records in the left entity are collected in the result:

@Test
public void whenLeftKeywordIsSpecified_thenCreatesOuterJoinAndIncludesNonMatched() {
    TypedQuery query = entityManager.
      createQuery("SELECT DISTINCT d FROM Department d LEFT JOIN d.employees e", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

Aqui, o resultado conteráDepartments que têmEmployees associados e também aqueles que não têm nenhum.

Isso também é chamado de junção externa esquerda. JPA doesn’t provide right joins onde também coletamos registros incompatíveis da entidade certa. Embora possamos simular junções corretas trocando entidades na cláusula FROM.

5. Associa-se à cláusula WHERE

5.1. Com uma Condição

We can list two entities in the FROM clause andthen specify the join condition in the WHERE clause.

Isso pode ser útil, especialmente quando as chaves estrangeiras de nível de banco de dados não estão no lugar:

@Test
public void whenEntitiesAreListedInFromAndMatchedInWhere_ThenCreatesJoin() {
    TypedQuery query = entityManager.
      createQuery("SELECT d FROM Employee e, Department d WHERE e.department = d", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

Aqui, estamos unindo as entidadesEmployee eDepartment, mas desta vez especificando uma condição na cláusula WHERE.

5.2. Sem uma condição (produto cartesiano)

Da mesma forma,we can list two entities in the FROM clause without specifying any join condition. Nesse caso,we’ll get a cartesian product back. Isso significa que todos os registros na primeira entidade são pareados com todos os outros registros na segunda entidade:

@Test
public void whenEntitiesAreListedInFrom_ThenCreatesCartesianProduct() {
    TypedQuery query
      = entityManager.createQuery("SELECT d FROM Employee e, Department d", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

Como podemos imaginar, esse tipo de consulta não terá um bom desempenho.

6. Várias associações

Até agora, usamos duas entidades para realizar junções, mas isso não é uma regra. We can also join multiple entities in a single JPQL query:

@Test
public void whenMultipleEntitiesAreListedWithJoin_ThenCreatesMultipleJoins() {
    TypedQuery query
      = entityManager.createQuery(
      "SELECT ph FROM Employee e
      JOIN e.department d
      JOIN e.phones ph
      WHERE d.name IS NOT NULL", Phone.class);
    List resultList = query.getResultList();

    // Assertions...
}

Aqui, estamos selecionando todos osPhones de todos osEmployees que têm umDepartment. Semelhante a outras junções internas, não estamos especificando condições, pois o JPA extrai essas informações dos metadados de mapeamento.

7. Buscar junções

Agora, vamos falar sobre fetch joins. Its primary usage is for fetching lazy-loaded associations eagerly for the current query.

Aqui, carregaremos ansiosamente a associaçãoEmployees:

@Test
public void whenFetchKeywordIsSpecified_ThenCreatesFetchJoin() {
    TypedQuery query = entityManager.
      createQuery("SELECT d FROM Department d JOIN FETCH d.employees", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

Embora essa consulta seja muito semelhante a outras consultas, há uma diferença:the Employees are eagerly loaded. Isso significa que assim que chamarmosgetResultList no teste acima, as entidadesDepartment terão seus camposemployees carregados, poupando-nos, assim, outra viagem ao banco de dados.

But be aware of the memory trade-off. Podemos ser mais eficientes porque realizamos apenas uma consulta, mas também carregamos todos osDepartments e seus funcionários na memória de uma vez.

Também podemos realizar a junção de busca externa de uma maneira semelhante às junções externas, onde coletamos registros da entidade esquerda que não correspondem à condição de junção. Além disso, ele carrega avidamente a associação especificada:

@Test
public void whenLeftAndFetchKeywordsAreSpecified_ThenCreatesOuterFetchJoin() {
    TypedQuery query = entityManager.
      createQuery("SELECT d FROM Department d LEFT JOIN FETCH d.employees", Department.class);
    List resultList = query.getResultList();

    // Assertions...
}

8. Sumário

Neste artigo, cobrimos os tipos de junção JPA.

Como sempre, você pode conferir todos os exemplos deste e de outros tutoriaisover on Github.