Retorno de recurso persistido com Hibernate

Boa tarde. Gostaria de entender como resolver a seguinte questão:

No projeto que estou atuando, o Backend e Frontend estão apartados. Para persistir os dados estou utilizando o Hibernate e para controlar a conexão com o BD possuo um filtro que abre a conexão no inicio da request e encerra a mesma ao retornar a response.

Para descrever melhor minha dúvida, irei ilustrar um cenário com duas classes, sendo Usuario e Perfil onde, ao cadastrar um usuário novo ele deve ser relacionado a um perfil.

Exemplo das classes:

@Entity
public class Usuario{
   @Id
   private Long id;
   private String nome;
 
  @OneToOne
  @JoinColumn(name="id_perfil")
  private Perfil perfil;

}

@Entity
public class Perfil{
  @Id
  private Long id;
  private String nome;
}

O JSON recebido do Frontend para cadastrar usuário é:

{
	"nome":"usuario novo",
	"perfil":{
		"id":1
	}
}

O JSON retornado para o Frontend é:

{
	"nome":"usuario novo",
	"perfil":{
		"id":1
		"nome":null
	}
}

O correto seria retornar também o nome do perfil? Como eu posso fazer isso, se o commit() no BD é executado no momento da response. Ou, é correto o Frontend fazer uma nova request passando o ID do usuário criado, para que receba o objeto completo?

Pra fazer o relacionamento entre um usuário (novo ou não) a um Perfil existente vc só precisa do id deste perfil.
Mas se vc está criando um usuário novo com um perfil novo, aí sim precisam ir todos os dados do perfil pro back. Neste caso o relacionamento ficaria melhor da seguinte forma:
@OneToOne(cascade = CascadeType.ALL)

pra q automaticamente qnd salvar o Usuario ele salve junto o perfil.

Oi Rodrigo. Legal, mas minha dúvida não é esta. Os perfis já estão cadastrados no BD. Sendo assim, é uma boa prática eu enviar o objeto com todos os dados para o Frontend ou este deve fazer uma nova request para obter o objeto novo criado com todos os dados populados?

Valeu!

Aqui usamos o response pra isso, pois tem mtos dados q o servidor altera ou adiciona então em todas requests de POST ou PUT (salvar dados) o response é o objeto completo q o hibernate mesmo devolve.
*Ainda n sei c entendi

A boa prática é retornar pro client todas as informações que o usuário demandou para uma ação/funcionalidade. Ficar indo várias vezes no servidor para uma única demanda do usuário é um custo sem necessidade para o cliente.

No caso de uma listagem de dados, caso todos os dados do objeto n apareçam na listagem mas tenha uma tela com as informações completas ao clicar. Nos objetos da lista só vão os dados pertinentes a lista pra q n haja desperdício de dados trafegados. Aí ao clicar no objeto fazemos uma nova busca. Mas tudo isso é mto relativo, vc deve ponderar a quantidade de dados trafegados pela quantidade de requests e seu tempo de resposta. Com poucos dados as vezes é mais vantajoso trazer tudo, mas em alguns casos isso fica mto pesado/lento ai quebra-se em várias requests

Se esta informação faz parte da funcionalidade requisitada pelo usuário, então é bem claro que deve retornar junto, em uma única query com o banco e uma única requisição do navegador para estes dados em json.

Embora eu não faça e recomende desta forma, por ser ruim, não deveria dar grandes problemas, por estar na mesma sessão. Fale o problema que passou. Mas o ideal/mais otimizado é chamar o commit assim que terminar a sua transação com o banco.

Rodrigo e Javaflex, valeu pela ajuda. Já ficou mais claro mas ainda, apenas para fomentar mais esta troca de experiência, vou postar minha classe dao Usuario e controller Usuario:

public class UsuarioController{
    
  @PostMapping
  public ResponseEntity<Usuario> salva(@RequestBody Usuario usuario) {
     List<Aeroporto> aeroportos = new ArrayList<>();

     usuarioDao.salva(usuario);

     return new ResponseEntity<Usuario>(usuario, HttpStatus.CREATED);
  }
}


public void salva(Usuario usuario) {
            // estou utilizando esta estratégia para gerenciar a conexão no BD
	EntityManager manager = JpaUtil.getEntityManager();

	manager.persist(usuario);
}

Do jeito como está, o nome do perfil é retornado como null. Então não seria “gambiarra” antes de retornar o Usuario na response, fazer o seguinte na classe controller:

Usuario usuario = usuarioDao.salva(usuario);

Perfil perfil = perfilDao.perfil(usuario.getPertil().getId());

usuario.setPerfil(perfil);

Valeu.

Seria gambiarra sim, o perfil já deve vir do Usuario. Você não mapeou?

Sim, está mapeado, conforme eu exemplifiquei no modelo da classe Usuario no inicio do post, mas não retorna o nome do perfil. Imagino que talvez seja porque quando eu persisto, o commit no BD só será feito quando ocorrer a response. Veja como está minha classe filtro:

public class ConnectionFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        EntityManager entityManager = JpaUtil.getEntityManager();

        try {
            entityManager.getTransaction().begin();

            chain.doFilter(request, response);

            entityManager.getTransaction().commit();

        } catch (Exception e) {
            if (entityManager.isOpen()) {
                entityManager.getTransaction().rollback();
            }

            System.out.println("Erro ao gravar dados: " + e);

        } finally {
            if (entityManager.isOpen()) {
                entityManager.close();

                System.out.println("Encerrou o Entity Manager");
            }
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }
}

Alguma ideia?

Hibernate enxerga a própria sessão. Mas se por exemplo não fizer flush após salvar os dados no hibernate poderá ter problemas sim, dependendendo de como estiver programando. Pelo menos quando trabalhava com Hibernate puro o flush era sob controle, mas se sob JPA não tiver isso, o ideal, como falei antes, é chamar o commit logo após o término da transação. Nem toda requisição do usuário vai necessitar ficar abrindo transação, como consultas por exemplo. Tem que ser sob demanda e não em um evento genérico como esse filter.

No mais, mostre o que está fazendo no “salva”. Se tudo que é persistindo é retornado menos o nome do perfil, pode ter algo errado ai ou no mapeamento.

Javaflex, desculpe-me pela demora. Fiz um novo teste, agora implementando o Spring Data e ainda tenho o mesmo comportamento. Vou compartilhar minhas classes:

Utilizada para controlar os IDs das entidades Usuario e Perfil

@MappedSuperclass
public class AbstractEntity implements Serializable {

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        AbstractEntity that = (AbstractEntity) o;

        return id != null ? id.equals(that.id) : that.id == null;
    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }

    public Long getId() {
        return id;
    }

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

Classe Perfil - Existem dois criados no banco sendo: ADM e BASICO

@Entity
public class Perfil extends AbstractEntity {

    private String nome;

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }
}

Classe Usuario - Um usuário possui apenas 1 Perfil

@Entity
public class Usuario extends AbstractEntity {

    private String nome;

    @OneToOne
    private Perfil perfil;

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public Perfil getPerfil() {
        return perfil;
    }

    public void setPerfil(Perfil perfil) {
        this.perfil = perfil;
    }
}

Interface Repositorio para Usuario

public interface UsuarioRepositorio extends JpaRepository<Usuario, Long> {

}

Controller do Usuario

@RestController
@RequestMapping("/usuario")
public class UsuarioController {

    @Autowired
    private UsuarioRepositorio usuarioRepositorio;

    @PostMapping
    public ResponseEntity<Usuario> salva(@RequestBody Usuario usuario) {

        Usuario salvo = usuarioRepositorio.save(usuario);

        return new ResponseEntity<Usuario>(salvo, HttpStatus.OK);
    }
}

JSON enviado via Postman para criar um usuário

{
    "nome":"Thiago",
    "perfil":{
        "id":1
    }
}

JSON retornado no Postman

{
    "id": 1,
    "nome": "Thiago",
    "perfil": {
        "id": 1,
        "nome": null
    }
}

Como fazer para que o objeto Perfil seja retornado com os dados, ou seja, neste caso, com o Nome do perfil associado ao usuário?

Valeu.

Basicamente precisa estar mapeado. Tentando olhar seu código não achei a parte que faz a consulta no banco. O ideal é usar HQL/JPQL com fetch join para que seja executado somente 1 select com todas as colunas necessárias (de Usuario e Perfil)

Oi Javaflex. Ao utilizar o Spring Data ele já possui o CRUD definido e posso usá-lo ao estender a classe JpaRepository na interface UsuarioRepositorio.

Como você faria para obter este retorno?

Veja se @Query te atende: https://docs.spring.io/spring-data/jpa/docs/1.11.1.RELEASE/reference/html/#jpa.query-methods.at-query

Javaflex, acho que você não entendeu minha dúvida por isso, vou refazê-la de outra maneira. Eu estou gravando um registro novo no Banco de Dados, sendo Usuario (objeto novo) e um Perfil (objeto já cadastrado no BD - por isso, o Frontend só envia o id do perfil).

Como você retornaria o objeto Usuario e Perfil com todos os dados na response? Seria assim?

// grava usuario
Usuario usuario = usuarioDao.salva(usuario);

// busca o perfil através do id informado pelo frontend
Perfil perfil = perfilDao.perfil(usuario.getPertil().getId());

// adiciona o perfil localizado, agora com o nome, no usuario
usuario.setPerfil(perfil);

Valeu.

Faria uma única query, como já te indiquei, trazendo os dados do usuário junto com o perfil. Não é isso que precisa ser retornado na tela?

Só não entendo por que vai retornar dados que já estão na tela do usuário, instantes depois dele mandar salvar.