Skip to content
Advertisement

Error accessing field by reflection for persistent property [model.Credentials#email] on @EmbeddedID

I’ve been experiencing problems implementing the Authorities on my spring boot application, and after digging a little I realised that maybe the association between my Credentials and Authorities tables, was wrong.

In fact it come to my attention that Spring allowed every type of user, (regardless their authority) to access ever method, even the ones I though have been secured. At that point, I implemented a .findAll() method to actually see if there was some kind of problem on the association, and indeed there was.

Let me show first the MySQL tables:

CREATE TABLE credentials (
  credential_id bigint UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  email varchar(50) NOT NULL UNIQUE,
  password varchar(255) NOT NULL,
  enabled BOOLEAN NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


CREATE TABLE authorities (
  email varchar(50) NOT NULL,
  authority varchar(50) NOT NULL,
  PRIMARY KEY (email, authority),
  CONSTRAINT fk_authorities_credentials FOREIGN KEY(email) REFERENCES credentials(email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Before jumping onto the associations in the entities, it’s worth noting that the authorities entity has and embedded key reflecting the design of the table. So the email column its a foreign key and a primary at the same time:

@Embeddable
public class AuthoritiesKey implements Serializable {
  @OneToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "email", nullable = false
          ,referencedColumnName = "email")
  private Credentials credentials;

  @JoinColumn(name = "authority", nullable = false)
  private String authority;

//getters setters omitted
}

Credentials class:

@Entity
@Table(name = "credentials")
public class Credentials implements Serializable {

  @OneToOne(cascade = CascadeType.ALL,
          fetch = FetchType.LAZY,
          mappedBy = "ak.credentials")
  private Authorities authorities;

//other fields and getters/setters omitted
}

Removing the bilateral association (thus leaving it just on the AuthoritiesKey class), hibernate successfully returns all the credentials (without their authorities of course), and all the authorities (this time preceded by the credentials).

I cannot wrap my head around it.

The complete meaningful stack trace i get when I leave the bilateral association is the following:

Error accessing field [private java.lang.String com.server.model.Credentials.email] by reflection for persistent property [com.servermodel.Credentials#email] : 64; 
nested exception is org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.String com.server.model.Credentials.email] by reflection for persistent property [com.server.model.Credentials#email] : 64

Where 64 is the id of the (last, but get called first) row in the Credential table.

=== UPDATE ===

public class CredentialsService implements UserDetailsService {

  @Autowired
  private CredentialsRepository cr;

   @Override
  public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    Credentials user = cr.findByEmail(email);
    if (user == null){
      throw new UsernameNotFoundException(email);
    }
    return new UserPrincipal(user);
  }
}

Advertisement

Answer

It looks like you can not use @OneToOne here due to the hibernate bug. (See HHH-3824) It’s reproduced even with Hibernate 5.4.28.Final

As workaround, I would suggest you to correct your mapping in this way:

@Entity
@Table(name = "authorities")
public class Authorities {
   
   @EmbeddedId
   private AuthoritiesKey pk;

   // ...
}

@Embeddable
public class AuthoritiesKey implements Serializable {

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "email", referencedColumnName = "email", nullable = false)
  private Credentials credentials;

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

  public AuthoritiesKey(Credentials credentials, String authority) {
    this.credentials = credentials;
    this.authority = authority;
  }

  public AuthoritiesKey() {
  }

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

    AuthoritiesKey pk = (AuthoritiesKey) o;
    return Objects.equals( credentials, pk.credentials ) &&
            Objects.equals( authority, pk.authority );
  }

  @Override
  public int hashCode() {
    return Objects.hash( credentials, authority );
  }
}

@Entity
@Table(name = "credentials")
public class Credentials implements Serializable {
   
   @Id
   @Column(name = "credential_id")
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;

   @OneToMany(cascade = CascadeType.ALL,
        fetch = FetchType.LAZY,
        mappedBy = "pk.credentials")
   private List<Authorities> authorities;

   @NaturalId
   @Column(name = "email")
   private String email;

   @Transient
   public Authorities getAuthority()
   {
      return this.authorities != null && this.authorities.size() > 0
          ? this.authorities.get(0) : null;
   }
 
   // ...
}
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement