In this post, we will explore a very common Hibernate error, namely a MappingException caused by missing annotations on the Entity getters and fields.
The error
Let us check the following stack trace as an example:
java.lang.IllegalStateException: Failed to load ApplicationContext Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource
[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]
: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: bank_account, for columns:
[org.hibernate.mapping.Column(credit_transactions)]
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: bank_account, for columns: [org.hibernate.mapping.Column(credit_transactions)] Caused by: org.hibernate.MappingException: Could not determine type for: java.util.List, at table: bank_account, for columns: [org.hibernate.mapping.Column(credit_transactions)]
In the next section, we will display the faulty implementation that caused the error above. If you would like to skip to the solution, then please use the table of contents on the top of this post.
The example implementation
Now let us take a look at the code. The implementation contains two entities. The first entity is the bank account entity. The second entity is the transaction entity. Each bank account has two many-to-one relationships with the transaction table. A bank account has a list of transactions where it is credited, and a list of transactions where it its debited.
package com.nullbeans.persistence.models; import javax.persistence.*; import java.util.List; @Entity public class BankAccount { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; private int version; private String accountNumber; private boolean isActive; private List<Transaction> creditTransactions; private List<Transaction> debitTransactions; public BankAccount() { } public long getId() { return id; } public void setId(long id) { this.id = id; } @Version public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } public boolean isActive() { return isActive; } public void setActive(boolean active) { isActive = active; } @OneToMany(mappedBy = "recipientAccount") public List<Transaction> getCreditTransactions() { return creditTransactions; } public void setCreditTransactions(List<Transaction> creditTransactions) { this.creditTransactions = creditTransactions; } @OneToMany(mappedBy = "senderAccount") public List<Transaction> getDebitTransactions() { return debitTransactions; } public void setDebitTransactions(List<Transaction> debitTransactions) { this.debitTransactions = debitTransactions; } @Override public String toString() { return "BankAccount{" + "id=" + id + ", version=" + version + ", accountNumber='" + accountNumber + '\'' + ", isActive=" + isActive + '}'; } }
package com.nullbeans.persistence.models; import javax.persistence.*; @Entity public class Transaction { private long id; private int version; private String identification; private BankAccount recipientAccount; private BankAccount senderAccount; private String currency; private float amount; private boolean isExecuted; public Transaction() { } @Id @GeneratedValue(strategy= GenerationType.AUTO) public long getId() { return id; } public void setId(long id) { this.id = id; } @Version public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public String getIdentification() { return identification; } public void setIdentification(String identification) { this.identification = identification; } @ManyToOne(targetEntity = BankAccount.class) public BankAccount getRecipientAccount() { return recipientAccount; } public void setRecipientAccount(BankAccount recipientAccount) { this.recipientAccount = recipientAccount; } @ManyToOne(targetEntity = BankAccount.class) public BankAccount getSenderAccount() { return senderAccount; } public void setSenderAccount(BankAccount senderAccount) { this.senderAccount = senderAccount; } public String getCurrency() { return currency; } public void setCurrency(String currency) { this.currency = currency; } public float getAmount() { return amount; } public void setAmount(float amount) { this.amount = amount; } public boolean isExecuted() { return isExecuted; } public void setExecuted(boolean executed) { isExecuted = executed; } @Override public String toString() { return "Transaction{" + "id=" + id + ", version=" + version + ", identification='" + identification + '\'' + ", recipientAccount=" + recipientAccount + ", senderAccount=" + senderAccount + ", currency='" + currency + '\'' + ", amount=" + amount + ", isExecuted=" + isExecuted + '}'; } }
The problem cause
Sadly the information in the stacktrace is not very straightforward. It is telling us that the mapping between the Bank account and the Transaction table for the “credit_transactions” relationship is not correct. Hibernate does not understand the “credit_transactions” field.
The issue is that we have added annotations in the BankAccount entity on the fields and the getters. Notice that we added the @Id and @GeneratedValue annotations on the id field in the BankAccount entity. However, we added the @ManyToOne annotation for our relationship on the getters. If we add the @Id annotation on the getter and the @ManyToOne annotation on the field, then we will get the same error. This means that hibernate will consider only the annotations that are set on the same side as the @Id annotation. So if you add the @Id annotation on a field, then only field annotations will be considered. If the @Id annotation is set on the getter, then only annotations on the getters will be considered.
Adding the annotations on the entity fields is called “Field” access type. Adding the annotations on the entity getters is called “Property” access type. You can override JPA’s default behavior by setting the access type property of the entity.
@Entity @Access(AccessType.PROPERTY) public class BankAccount {
Solution A
There are two ways to fix such an issue. First, you will need to have the annotations on either the getters or the fields, but not both. So, our bank account class would look as follows:
@Entity public class BankAccount { private long id; ....... @Id @GeneratedValue(strategy= GenerationType.AUTO) public long getId() { return id; } public void setId(long id) { this.id = id; } @Version public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } public boolean isActive() { return isActive; } public void setActive(boolean active) { isActive = active; } @OneToMany(mappedBy = "recipientAccount") public List<Transaction> getCreditTransactions() { return creditTransactions; } ..............
Solution B
While unlikely, it may sometimes be not possible to have the annotations on just one side. For example, if your entity is inheriting from a super class. In this case, you will have to override the annotations of the super class and specifically set the access type depending on which annotations we want to be effective. Let’s say that the superclass uses “field” annotations. Then we can write getters for the fields in the super and subclass and set the access type to “Property”:
@Entity @Access(AccessType.PROPERTY) public class BankAccount extends Account{ String subclassProperty; public BankAccount() { } @Id @GeneratedValue(strategy= GenerationType.AUTO) @Override public long getId() { return id; } public String getSubclassProperty(){ return subclassProperty; }