spring-projects / spring-data-jpa

Simplifies the development of creating a JPA-based data access layer.
https://spring.io/projects/spring-data-jpa/
Apache License 2.0
2.98k stars 1.41k forks source link

InvalidApiUsageException: Unable to locate persister #3528

Closed eloyhere closed 3 months ago

eloyhere commented 3 months ago

Below is my code. BaseEntity.java, which is the super class of every entity.

package team.cloud.marriage.entity;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Transient;
import org.springframework.data.jpa.domain.AbstractPersistable;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
@MappedSuperclass
public class BaseEntity extends AbstractPersistable<UUID> {

    @Column(name = "ban", nullable = false)
    protected LocalDateTime ban;

    @Column(name = "edit", nullable = false)
    protected LocalDateTime edit;

    @Column(name = "spawn", nullable = false)
    protected LocalDateTime spawn;

    @Transient
    protected final TreeMap<String, Object> map = new TreeMap<>();

    public BaseEntity(){

    }

    public TreeMap<String, Object> getMap() {
        return map;
    }

    @Override
    public UUID getId() {
        return super.getId();
    }

    @Override
    public void setId(UUID id) {
        super.setId(id);
    }

    public LocalDateTime getBan() {
        return ban;
    }

    public void setBan(LocalDateTime ban) {
        this.ban = ban;
    }

    public LocalDateTime getEdit() {
        return edit;
    }

    public void setEdit(LocalDateTime edit) {
        this.edit = edit;
    }

    public LocalDateTime getSpawn() {
        return spawn;
    }

    public void setSpawn(LocalDateTime spawn) {
        this.spawn = spawn;
    }

    public boolean enable(){
        if(Objects.isNull(this.ban)){
            return false;
        }
        Clock clock = Clock.systemDefaultZone();
        LocalDateTime now = LocalDateTime.now(clock);
        return now.isAfter(this.ban);
    }
}

Consumer.java, which defines structure of table consumer.

package team.cloud.marriage.entity.consumer;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import team.cloud.marriage.entity.BaseEntity;
import team.cloud.marriage.entity.chat.Gift;
import team.cloud.marriage.entity.chat.Message;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

@Entity
@Table(name = "consumer")
public class Consumer extends BaseEntity implements UserDetails {

    @Column(name = "username", nullable = false, unique = true)
    private String username;

    @Column(name = "nickname", nullable = false)
    private String nickname;

    @Column(name = "password", nullable = false)
    private String password;

    @Column(name = "avatar", nullable = false)
    private String avatar;

    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
    @JoinTable(name = "consumer_roles",
            joinColumns = @JoinColumn(name = "consumer_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new LinkedHashSet<>();

    @JsonManagedReference
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "consumer", cascade = {CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REFRESH, CascadeType.DETACH}, orphanRemoval = true)
    private Set<Message> messages = new LinkedHashSet<>();

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    @JoinTable(name = "consumer_gifts",
            joinColumns = @JoinColumn(name = "consumer_id"),
            inverseJoinColumns = @JoinColumn(name = "gift_id"))
    private Set<Gift> gifts = new LinkedHashSet<>();

    public Set<Message> getMessages() {
        return messages;
    }

    public void setMessages(Set<Message> messages) {
        this.messages = messages;
    }

    public Set<Gift> getGifts() {
        return gifts;
    }

    public void setGifts(Set<Gift> gifts) {
        this.gifts = gifts;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return this.enable();
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.enable();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.enable();
    }

    @Override
    public boolean isEnabled() {
        return this.enable();
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.roles.stream().flatMap((role)-> role.getPermissions().stream()).distinct().toList();
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }
}

Below is my project structure.

eloyhere commented 3 months ago

Now I have fixed this problem, which is not caused by package, configuration and so on.
It's supposed to initialize entity like this:

Consumer consumer = new Consumer();
consumer.setUsername("It works!");
consumerRepository.saveAndFlush(consumer);

As we are using anonymouse objects, there must be an exception.

Consumer consumer = new Consumer(){
    {
         setUsername("Whoops! Unable to locate persister!");
    }
};
consumerRepository.saveAndFlush(consumer);