7
respostas

Relacionamento entre entidades

Estou desenvolvendo um projeto e gostaria de uma ajuda. Preciso criar uma rota onde informo o ID do usuário e o sistema busca todas as tarefas relacionadas àquele usuário. No meu banco de dados, existe uma coluna que possui uma chave estrangeira com o ID do usuário, então só gostaria de buscar na coluna 'userid' todos os IDs que são iguais ao que informei.

Gostaria de saber como posso desenvolver uma rota para listar todas as tarefas que possuem o ID igual ao que é informado, de acordo com o que já existe no meu sistema. A parte do usuário já desenvolvi, incluindo a entidade, rota de criação, etc.

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

@Entity
@Table(name = "tasks")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String id;
    private String title;
    private String description;
    @Enumerated(EnumType.STRING)
    private EnumTaskStatus taskStatus;
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    @ManyToOne
    @JoinColumn(name = "userid")
    @JsonIgnore
    private User userid;

    public Task(RequestDataCreateTaskDto dados, User user) {
        this.title = dados.title();
        this.description = dados.description();
        this.taskStatus = EnumTaskStatus.pending;
        this.createdAt = LocalDateTime.now();
        this.userid = user;
    }
}
@Service
public class ListAllTaskFromUser {
    @Autowired
    TaskRepository taskRepository;
    @Autowired
    UserRepository userRepository;
    public List<Task> listAll(String id){

        return null;
    }
}
@RestController
@RequestMapping("/tasks")
public class TasksController {
   
    @Autowired
    CreateTaskService createTaskService;
    @PostMapping()
    @Transactional
    public ResponseEntity create(@RequestBody @Valid RequestDataCreateTaskDto dados, UriComponentsBuilder uriBuilder) {
        var newTask = createTaskService.execute(dados);
        var uri = uriBuilder.path("/customer/{id}").buildAndExpand(newTask.getId()).toUri();
        return ResponseEntity.created(uri).body(new ResponseDataCreateTaskDto(newTask));
    }

    @Autowired
    ListAllTaskFromUser listAllTaskFromUser;
    @GetMapping("/listTasks")
    public ResponseEntity listAllById(@RequestHeader("id") String id) {
        List<Task> tasks = listAllTaskFromUser.listAll(id);
        if (tasks.isEmpty()) {
            throw new IllegalArgumentException("User not found with id: " + id);
        }
        return ResponseEntity.ok().body(tasks);
    }
}
public interface TaskRepository extends JpaRepository<Task, String> {
    Optional<List<Task>> findByUserid(String id);

}
7 respostas

Oi João,

Esse código altual não está conseguindo recuperar a lista de tasks? Tentar fazer um ajuste no método do repositório:

List<Task> findAllByUserid(String id)

Isso já deve resolver. O "findAll" vai indicar que é uma lista, assim deve funcionar. Eu tenho um projeto parecido no meu Github também

Não Funciona, gera um assim que rodo o projeto

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2024-04-27T13:01:50.414-03:00 ERROR 13584 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'createTaskService': Unsatisfied dependency expressed through field 'taskRepository': Error creating bean with name 'taskRepository' defined in com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Reason: Failed to create query for method public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Can't compare test expression of type [User] with element of type [basicType@5(java.lang.String,12)] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:776) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:756) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:497) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1414) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:518) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.19.jar:6.0.19] com.project.toDoListApiRestSemSpringSecurity.ToDoListApiRestSemSpringSecurityApplication.main(ToDoListApiRestSemSpringSecurityApplication.java:10) ~[classes/:na] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:578) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.11.jar:3.1.11] Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'taskRepository' defined in com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Reason: Failed to create query for method public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Can't compare test expression of type [User] with element of type [basicType@5(java.lang.String,12)]

Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Reason: Failed to create query for method public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Can't compare test expression of type [User] with element of type [basicType@5(java.lang.String,12)] at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:115) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:99) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:88) ~[spring-data-commons-3.1.11.jar:3.1.11] at java.base/java.util.Optional.map(Optional.java:260) ~[na:na] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.(QueryExecutorMethodInterceptor.java:88) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:357) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:279) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.util.Lazy.getNullable(Lazy.java:245) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.util.Lazy.get(Lazy.java:114) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:285) ~[spring-data-commons-3.1.11.jar:3.1.11] at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) ~[spring-data-jpa-3.1.11.jar:3.1.11] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1815) ~[spring-beans-6.0.19.jar:6.0.19] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1764) ~[spring-beans-6.0.19.jar:6.0.19] ... 33 common frames omitted Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Can't compare test expression of type [User] with element of type [basicType@5(java.lang.String,12)] at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.(PartTreeJpaQuery.java:107) ~[spring-data-jpa-3.1.11.jar:3.1.11] at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:124) ~[spring-data-jpa-3.1.11.jar:3.1.11] at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:258) ~[spring-data-jpa-3.1.11.jar:3.1.11] at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:95) ~[spring-data-jpa-3.1.11.jar:3.1.11] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:111) ~[spring-data-commons-3.1.11.jar:3.1.11] ... 45 common frames omitted Caused by: java.lang.IllegalArgumentException: Can't compare test expression of type [User] with element of type [basicType@5(java.lang.String,12)] at org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.assertComparable(SqmCriteriaNodeBuilder.java:2109) ~[hibernate-core-6.2.24.Final.jar:6.2.24.Final] at org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.equal(SqmCriteriaNodeBuilder.java:2120) ~[hibernate-core-6.2.24.Final.jar:6.2.24.Final] at org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.equal(SqmCriteriaNodeBuilder.java:183) ~[hibernate-core-6.2.24.Final.jar:6.2.24.Final] at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:312) ~[spring-data-jpa-3.1.11.jar:3.1.11] at org.s

Não sei se é importante mais na entidade task estou usando o User como tipo do atributo userid, o User é uma outra entidade

Usar a entidade não tem problema, o Spring vai entender que ele deve usar a chave primária da entidade user e pelo print da tabela dá pra ver que ele está fazendo isso mesmo, o valor de userid é o valor do id da entidade User. Na consulta que tá sendo criada a JPA está comparando a String id com a entidade User em vez de comparar a String id com User.id.

Tente findAllByUseridId(String id)

Se isso também não funcionar tenta usar um JPQL só pra ver no que dá... "SELECT t FROM Task t WHERE t.userid.id = :id"

O nome do seu atributo User como "userid" me confundiu kkk

Caramba, verdade! Deu certo do primeiro jeito que você falou. Ele estava tentando comparar uma entidade com uma string, kkkkk. Como você chegou a essa conclusão? Alguma coisa no erro estava falando sobre isso? Consegui me dar uma breve explicação de como funciona essa parte do nome usada nos repositórios? Porque antes não estava funcionando, mas daí só adicionei o ID no final e funcionou. Como funciona? Ele pega o nome da coluna da tabela ou da entidade do código? Eu sei que quando pegamos um método já existente, tipo findAllBy, e adicionamos findAllByTitle, ele busca por um título específico que eu forneço, porque o próprio ORM já faz automaticamente a query em cima do que ele já conhece. Porém, no exemplo findAllByUseridId, não entendia a diferença entre findAllByUserid.

Queria saber também se geralmente, quando se trata de relacionamento entre tabelas, é mais comum usar realmente a entidade ali como tipo ou apenas um campo com ID do usuário? Por exemplo, só mudei o campo userid.

@Entity
@Table(name = "tasks")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String id;
    
    private String title;
    
    private String description;

    @ManyToOne()
    @JoinColumn(name = "userid")
    @JsonIgnore
    private User userid;

    }

Ou

@Entity
@Table(name = "tasks")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String id;
    
    private String title;
    
    private String description;

    @ManyToOne()
    @JoinColumn(name = "userid")
    @JsonIgnore
    private String userid;

    }

A sua implementação atual está perfeita, é uma boa prática usar a própria entidade User como atributo, isso é fazer bom uso da orientação a objetos, normalmente só fazemos referência ao ID de uma entidade em vez de ela própria em aplicações de microsserviços, onde as entidades não coexistem na mesma base de código, por exemplo:

Temos 2 microsserviços: User e Product. Cada um é uma aplicação separada e com seu próprio banco de dados isolado, mas eles se comunicam entre si. Como a entidade User não existe dentro do app Product, Product precisa fazer referência ao User de alguma maneira... Nesse caso faria sentido usar um atributo que representa apenas a chave primária da entidade User, no caso, algo como String user_id.

Eu entendi o problema pelo caused by:

Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Reason: Failed to create query for method public abstract java.util.List com.project.toDoListApiRestSemSpringSecurity.modules.tasks.TaskRepository.findAllByUserid(java.lang.String); Can't compare test expression of type [User] with element of type [basicType@5(java.lang.String,12)] at

Principalmente nesse trecho aqui: Can't compare test expression of type [User] with element of type [basicType@5(java.lang.String,12)] at. Dá pra ver que ele tá dizendo que não foi possível comprar um tipo User com um tipo String.

O Spring Data JPA se orienta por Objeto, ele estava comparando a String que você passava com um objeto User porque ele se orienta pelos nomes dos atributos da sua entidade Task. O nome do seu atributo User é "userid", quando chamamos "userid" não estamos referenciando o ID de User, estamos referenciando o objeto User. Na method query findAllByUserid nós estamos falando para o Spring algo como "encontre todos as Tasks pelo atributo USER", pois "userid" é literalmente o nome do atributo User. Para acessar o ID de fato do User precisamos especifica-lo, por isso a method query findAllByUseridId. Para cada trecho que começa com uma letra maiúscula ele vai considerar como um atributo. Se o seu User fosse uma classe que tivesse um atributo do tipo Endereço e o objeto endereço tivesse um atributo String rua, você poderia criar uma query para trazer todas as Tasks de todos os usuários que moram numa rua específica, ficaria assim: findAllByUseridEnderecoRua(String nomeDaRua)

O nome do atributo do tipo User como "userid" pode gerar essa confusão na nossa cabeça, eu mesmo no primeiro momento me confundi também e acabei sugerindo uma query incorreta kkkk Aqui vai uma sugestão de melhoria pro seu código:

@Entity
@Table(name = "tasks")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String id;
    
    private String title;
    
    private String description;

    @ManyToOne()
    @JoinColumn(name = "user_id")
    @JsonIgnore
    private User user;

    }

Assim, lá na tabela vai ficar bem claro que esse é o ID e no código back-end vai ficar claro o que é o User e o que é o ID do User. Feita essa alteração, não esqueça de atualizar o método repository para findAllByUserId.

Se te ajudou marca aí. Se quiser me adicione lá no Linkedin que gente pode se ajudar :)