Unable to fetch results with associations in spring boot 2.5.0 - java

I am trying to upgrade my spring boot project from 2.4.3 to 2.5.0. The strange thing happened to me is I am unable to fetch the results when entities are associated.
For example, I have two simple entities:
UserProfile entity:
#Entity
#Table(name = "user_profile")
public class UserProfile {
#Id
private String id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "tenantId")
private Tenant tenant;
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Tenant entity:
#Entity
public class Tenant {
#Id
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
UserProfile entity has many to one association with Tenant entity. My repository class is
public interface UserProfileRepository extends CrudRepository<UserProfile, String> {
UserProfile findByEmailAndTenantId(String email, String tenantId);
}
Now, the method findByEmailAndTenantId from the above repository always returns null.
The SQLs generated with spring boot 2.5.0 is
select
userprofil0_.id as id1_1_,
userprofil0_.email as email2_1_,
userprofil0_.first_name as first_na3_1_,
userprofil0_.last_name as last_nam4_1_,
userprofil0_.tenant_id as tenant_i5_1_
from
pr.user_profile userprofil0_
inner join
pr.tenant tenant1_
on userprofil0_.tenant_id=tenant1_.id
where
userprofil0_.email=?
and (
tenant1_.id is null
)
The SQL generated for the same entities with spring boot 2.4.3 is
select
userprofil0_.id as id1_1_,
userprofil0_.email as email2_1_,
userprofil0_.first_name as first_na3_1_,
userprofil0_.last_name as last_nam4_1_,
userprofil0_.tenant_id as tenant_i5_1_
from
pr.user_profile userprofil0_
where
userprofil0_.email=?
and (
userprofil0_.tenant_id is null
)
Is this intended behavior in Spring Boot 2.5.0? Can anyone please help me in finding the solution to this problem?

I couldn't find anything in the documentation about it, but read this What is the difference between #ManyToOne(optional=false) vs. #Column(nullable=false)
It seems that optional = false results in INNER JOIN to tenant, that combined with WHERE tenant_id IS NULL can't return any rows.
So if you really need to fetch entities with tenant_id set to null, you've got to remove optional = false from the #JoinColumn annotation.

Related

Spring boot jpa query returns no data

I have the below repository which extends JpaRepository.
public interface UserRepository extends JpaRepository<User, Long> {
// #Query(value = "SELECT * FROM users u WHERE u.email = ?1", nativeQuery = true)
User findByEmail(String email);
}
When I call the function from the below mapping no result is returned, no empty object, nothing.
#GetMapping(value = "email")
public User getByEmail(#RequestBody String email) {
return userRepository.findByEmail(email);
}
Found similar issues but with no actual answers. I have also tried native queries, as you can see the commented #Query annotation. The mapping is in a simple controller which just has a post function and a get function for all the users.
What am I doing wrong?
Edit ---
User model
package com.example.demo.User;
import javax.persistence.*;
#Entity(name="Users")
#Table(name = "users", uniqueConstraints = {
#UniqueConstraint(name = "user_email_unique", columnNames = "email")
})
public class User {
#Id
#SequenceGenerator(
name = "users_sequence",
sequenceName = "users_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "users_sequence"
)
#Column(name = "id", updatable = false) // Column options for the id
private long id;
#Column(name = "first_name", nullable = false, columnDefinition = "TEXT")
private String name;
private String lastName;
#Column(nullable = false, columnDefinition = "TEXT")
private String email;
private int age;
private int weight;
public User() {}
public User(String name, String lastName, String email, int age, int weight) {
this.name = name;
this.lastName = lastName;
this.email = email;
this.age = age;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
I have found the answer in the docs of spring boot, only POST/PUT requests have a request body.
#RequestBody annotation binds the content sent in (POST / PUT) request body with the annotated variable. Since there is no 'body' part in GET request, spring throws HttpMessageNotReadableException to indicate the same.
As a general rule, you can only use #RequestBody for the requests which can have 'body' content e.g. POST or PUT.

Hibernate JPA No join table created on ManyToMany association

I'm trying to make a many to many association with hibernate (with JPA) but my association table (between Film and Actor) is not created.
My actor class :
#Entity
public class Actor {
private Long id;
private String firstname;
private String lastname;
private int age;
public Actor(){};
public Actor(String firstname, String lastname){
this.firstname=firstname;
this.lastname=lastname;
}
#ManyToMany
#JoinTable(name="actor_films", joinColumns=#JoinColumn(name="actor_id"), inverseJoinColumns=#JoinColumn(name="film_id"))
private Set<Film> films=new HashSet<Film>();
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void addFilm(Film film){
this.films.add(film);
}
}
My Film class :
#Entity
public class Film {
private Long id;
private String title;
#ManyToMany(mappedBy = "films")
#JoinTable(name="actor_films", joinColumns=#JoinColumn(name="film_id"), inverseJoinColumns=#JoinColumn(name="actor_id"))
private Set<Actor> actors=new HashSet<Actor>();
public Film(){};
public Film(String title){
this.title=title;
};
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void addActor(Actor actor){
this.actors.add(actor);
}
}
In my main method I do something like:
// begin transaction
Film film=new Film("DBZ");
Actor actor=new Actor("Bob","A");
em.persist(actor);
em.persist(film);
film.addActor(actor);
// do commit
All tables is created in my hsql database except the association table. So if anyone can help me.
You're mixing field and accessor use for your mapping annotations. You can't do that. In any given class, mapping annotations must be either all on the getters or all on the fields. If you try to mix them, some on the getters and some on the fields, Hibernate will pick getters or fields and ignore the other (I'm not sure if its specified how Hibernate chooses if you don't specify).
Your #Id annotation in each class is on a getter, and that's making Hibernate use your getter-based annotations. Either move the #Id and #GeneratedValue annotations to the id field in each class, or define a getter (and setter) for the actor/film relationship and move the #ManyToMany and #JoinTable annotations to the new getter.
With the annotations on the fields, Hibernate will bypass your getters/setters and access the entity internal fields directly. With them on the getters, Hibernate will call your getters and setters.
Help Hibernate a bit by providing it more information regarding your join table.
In your Film entity
#ManyToMany(mappedBy = "films")
#JoinTable(name="actor_films", joinColumns=#JoinColumn(name="film_id"), inverseJoinColumns=#JoinColumn(name="actor_id"))
Do the same in your Actor entity, but reverse joinColumns and inverseJoinColumns
https://docs.jboss.org/hibernate/orm/5.0/manual/en-US/html/ch07.html#collections-bidirectional

Boolean field Hibernate QueryException: could not resolve property

I am really newbie to Hibernate and it's been like two hours trying to figure it out how to fix this issue. I am using Hibernate 4 and Postgres 9.3
Given the CatalogBase class
#MappedSuperclass
public class CatalogBase {
#Id
#Type(type = "pg-uuid")
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
protected UUID id;
}
And the derived User class
#Entity
#Table(name="erpuser")
public class User extends CatalogBase {
private String lastName;
private String name;
private String email;
private boolean isSystemAdministrator;
#Type(type="org.hibernate.type.StringClobType")
#Column(nullable = false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Column(name="lastname")
#Type(type="org.hibernate.type.StringClobType")
#NotNull(message = "es campo mandatorio")
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Column(length = 100,unique = true)
#NotNull(message = "es campo mandatorio")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#Column(name = "issystemadministrator", nullable = false)
public boolean isSystemAdministrator() {
return isSystemAdministrator;
}
public void setSystemAdministrator(boolean isSystemAdministrator) {
this.isSystemAdministrator = isSystemAdministrator;
}
}
I am trying to filter just the first result of a query using Hibernate Criteria. Like this
public boolean existsSystemAdministrator() throws NoSuchAlgorithmException{
Criteria criteria=currentSession()
.createCriteria(User.class)
.add(Restrictions.eq("isSystemAdministrator", true));
return criteria.uniqueResult() != null;
}
But I always get org.hibernate.QueryException: could not resolve property: isSystemAdministrator exception
I have changed to all lowercase since the database field is like that, but it didn't work either. From what I've read Hibernate maps with the Java property, which hasn't been the case as well.
Have tried also change the isSystemAdministrator field to Boolean instead of boolean, but it didn't work out either.
I know this must sound stupid to any Hibernate guru, if someone can come up with an answer that would save me lots of time.
Thanks in advance.
You should adhere to the JavaBeans spec (http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html). The field should be 'systemAdministrator', and the method should be 'isSystemAdministrator'.
The problem is in #Id annotation in CatalogBase class. If you change so it will work fine:
#MappedSuperclass
public class CatalogBase {
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
#Id
#Type(type = "pg-uuid")
protected UUID id;
}
You can have 2 access types in Hibernate. Property access (as you did) or field access. Hibernate will guess the access type from the position of #Id or #EmbeddedId.
As I know (I am not a Hibernate guru), it should be no difference between these two access types. But some frameworks requires to have field access. Anyway, I do not know why your implementation does not work for querying and have not found any other explanation.

org.hibernate.MappingException: Could not determine t ype for: com.yammer.dropwizard.tailor.model.CustomerModel

I'm new to hibernate and web services and creating a project for tailor system in dropwizard.
When i try to run the project through cmd as in DropWizard Sites gets:
INFO [2014-01-18 08:41:13,784] org.hibernate.annotations.common.Version: HCANN0
00001: Hibernate Commons Annotations {4.0.1.Final}
INFO [2014-01-18 08:41:13,828] org.hibernate.Version: HHH000412: Hibernate Core
{4.1.9.Final}
INFO [2014-01-18 08:41:13,847] org.hibernate.cfg.Environment: HHH000206: hibern
ate.properties not found
INFO [2014-01-18 08:41:13,850] org.hibernate.cfg.Environment: HHH000021: Byteco
de provider name : javassist
INFO [2014-01-18 08:41:14,076] com.yammer.dropwizard.hibernate.SessionFactoryFa
ctory: Entity classes: [com.yammer.dropwizard.tailor.model.CoatModel, com.yammer
.dropwizard.tailor.model.CustomerModel, com.yammer.dropwizard.tailor.model.LongS
hirtModel, com.yammer.dropwizard.tailor.model.OrderModel, com.yammer.dropwizard.
tailor.model.ShirtModel, com.yammer.dropwizard.tailor.model.TailorModel, com.yam
mer.dropwizard.tailor.model.TrouserModel]
Exception in thread "main" org.hibernate.MappingException: Could not determine t
ype for: com.yammer.dropwizard.tailor.model.CustomerModel, at table: Order, for
columns: [org.hibernate.mapping.Column(customer)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:314)
at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:292)
at org.hibernate.mapping.Property.isValid(Property.java:239)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:4
69)
at org.hibernate.mapping.RootClass.validate(RootClass.java:270)
at org.hibernate.cfg.Configuration.validate(Configuration.java:1294)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.jav
a:1742)
at com.yammer.dropwizard.hibernate.SessionFactoryFactory.buildSessionFac
tory(SessionFactoryFactory.java:77)
at com.yammer.dropwizard.hibernate.SessionFactoryFactory.build(SessionFa
ctoryFactory.java:35)
at com.yammer.dropwizard.hibernate.HibernateBundle.run(HibernateBundle.j
ava:38)
at com.yammer.dropwizard.hibernate.HibernateBundle.run(HibernateBundle.j
ava:13)
at com.yammer.dropwizard.config.Bootstrap.runWithBundles(Bootstrap.java:
64)
at com.yammer.dropwizard.cli.EnvironmentCommand.run(EnvironmentCommand.j
ava:37)
at com.yammer.dropwizard.cli.ConfiguredCommand.run(ConfiguredCommand.jav
a:58)
at com.yammer.dropwizard.cli.Cli.run(Cli.java:53)
at com.yammer.dropwizard.Service.run(Service.java:61)
at com.yammer.dropwizard.tailor.service.TailorService.main(TailorService
.java:25)
Classes:
CustomerModel class:
#NamedQueries({
#NamedQuery(
name = "com.yammer.dropwizard.tailor.model.CustomerModel.findAll",
query = "SELECT c FROM CustomerModel c"
),
#NamedQuery(
name = "com.yammer.dropwizard.tailor.model.CustomerModel.findById",
query = "SELECT c FROM CustomerModel c WHERE c.ID = :ID"
)
})
#Entity
#Table(name = "Customer")
public class CustomerModel {
#Id
#GeneratedValue
#Column(name = "c_id")
int ID;
#Column(name = "c_code")
String customerCode;
#Column(name = "c_fname")
String firstName;
#Column(name = "c_mname")
String middleName;
#Column(name = "c_lname")
String lastName;
#Column(name = "c_nic")
String NIC_Number;
#Column(name = "c_email")
String email;
#Column(name = "c_pnumber")
String number;
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getCustomerCode() {
return customerCode;
}
public void setCustomerCode(String customerCode) {
this.customerCode = customerCode;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getNIC_Number() {
return NIC_Number;
}
public void setNIC_Number(String NIC_Number) {
this.NIC_Number = NIC_Number;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}}
which other class should i list??
please help me.
More class:
Database Configuration class:
public class databaseConfiguration extends Configuration {
#Valid
#NotNull
#JsonProperty
DatabaseConfiguration dbconfigurations = new DatabaseConfiguration();
public DatabaseConfiguration getDatabaseConfiguration() {
return dbconfigurations;
}
}
.YML file
dbconfigurations:
# the name of your JDBC driver
driverClass: org.sqlite.JDBC
# the username
user:
# the password
password:
url: jdbc:sqlite:TailorDB.db
Service Class:
public class TailorService extends Service<databaseConfiguration> {
public static void main(String[] args) throws Exception {
new TailorService().run(args);
}
private final HibernateBundle<databaseConfiguration> hibernate = new HibernateBundle<databaseConfiguration>(CustomerModel.class,OrderModel.class,CoatModel.class,LongShirtModel.class,ShirtModel.class,TailorModel.class,TrouserModel.class) {
#Override
public DatabaseConfiguration getDatabaseConfiguration(databaseConfiguration configuration) {
return configuration.getDatabaseConfiguration();
}
};
#Override
public void initialize(Bootstrap<databaseConfiguration> bootstrap) {
// TODO Auto-generated method stub
bootstrap.setName("tailor");
bootstrap.addBundle(hibernate);
}
#Override
public void run(databaseConfiguration configuration, Environment environment)
throws Exception {
// TODO Auto-generated method stub
final CustomerDAO cdao = new CustomerDAO(hibernate.getSessionFactory());
final OrderDAO odao = new OrderDAO(hibernate.getSessionFactory());
environment.addResource(new TailorResource(cdao,odao));
}
}
After the first glance it seems that your sessionFactory don't know about the CustomerModel entity. Make sure it is added into the sessionFactory as a mapping file.
From the other proposed answer: "Make sure it is added into the sessionFactory as a mapping file."
This is probably exactly what Dropwizard is trying to avoid. However, they did a really bad job at their tutorial page here http://dropwizard.codahale.com/manual/hibernate/
Basically, follow that page, this error is guaranteed what you will get. Because it did not cover a very important part. Someone was too careless or too lazy to copy paste the "Person" class they use in the tutorial. Here is it from https://github.com/dropwizard/dropwizard/blob/master/dropwizard-example/src/main/java/com/example/helloworld/core/Person.java
package com.example.helloworld.core;
import javax.persistence.*;
#Entity
#Table(name = "people")
#NamedQueries({
#NamedQuery(
name = "com.example.helloworld.core.Person.findAll",
query = "SELECT p FROM Person p"
),
#NamedQuery(
name = "com.example.helloworld.core.Person.findById",
query = "SELECT p FROM Person p WHERE p.id = :id"
)
})
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "fullName", nullable = false)
private String fullName;
#Column(name = "jobTitle", nullable = false)
private String jobTitle;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getJobTitle() {
return jobTitle;
}
public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
}
Now you can see that at the top, there is the annotation specify that this class is an entity and the table name. I spent 2 hours try to make sense of this problem. What were they thinking leaving this out of the tutorial!

#Column or #Basic JPA annotations ignored in spring-data-jpa schema creation

I am utterly confused by something I expected to work just out of the box. So either I am doing something totally wrong or this is just a misunderstanding.
I am trying to have a getter/setter annotation in a JPA Entity class. I sticked to an example I found on the JPA wiki (s. http://en.wikibooks.org/wiki/Java_Persistence/Basic_Attributes#Conversion). The example looks as follows:
#Entity
public class Employee {
...
private boolean isActive;
...
#Transient
public boolean getIsActive() {
return isActive;
}
public void setIsActive(boolean isActive) {
this.isActive = isActive;
}
#Basic
private String getIsActiveValue() {
if (isActive) {
return "T";
} else {
return "F";
}
}
private void setIsActiveValue(String isActive) {
this.isActive = "T".equals(isActive);
}
}
I took the clearest and cleanest spring-data-jpa example I could find: http://spring.io/guides/gs/accessing-data-jpa/.
I checked it out from git and changed their example entity class (s. https://github.com/spring-guides/gs-accessing-data-jpa/blob/master/complete/src/main/java/hello/Customer.java) to look as follows:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
protected Customer() {}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
#Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName);
}
#Transient
private boolean isActive;
#Transient
public boolean getIsActive() {
return isActive;
}
public void setIsActive(boolean isActive) {
this.isActive = isActive;
}
#Column
private String getIsActiveValue() {
if (isActive) {
return "T";
} else {
return "F";
}
}
private void setIsActiveValue(String isActive) {
this.isActive = "T".equals(isActive);
}
}
Now nothing changes. The respective String-Field does not get created. The line from the log creating the database table still looks as follows:
17:11:10.540 [main] DEBUG o.h.tool.hbm2ddl.SchemaUpdate - create table Customer (id bigint generated by default as identity, firstName varchar(255), lastName varchar(255), primary key (id))
I have absolutely no idea on what could be the reason for this. I could find not documentation that spring-data-jpa would not allow for annotations on getters.
Any help would be very, very appreciated!
I think you simply mixed the annotations: you must either annotate the fields, or the getters, but not both. Once you decided to annotate your ID field, you must annotate all the fields (and not getters), and the opposite: if you annotated your getId() method, that you must annotate all methods.
If you want it to work with the '#Transient' annotation you should do as Andrei suggests, you could add an extra field for isActiveValue but it is most important to annotate consistently otherwise you will get unpredictable behavior.
When annotating fields versus properties (getters and setters) it will make a difference.
In your case it looks like you want to do some logic in the getter hence annotating a field will likely not have the desired result. I don't particularly like the logic but understand that there is a need to annotate a getter.
Considering the logic in your code above I would simply eliminate the transient on the field altogether and put the logic with the annotations in the getters and setters.
#Entity
#Table(name = "Customer")
public class Customer {
private static final String IS_ACTIVE = "T";
private long id;
private String firstName;
private String lastName;
private String isActive = "";
protected Customer() {}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Column(unique = true, nullable = false)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#Column(unique = true, nullable = false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Column(unique = true, nullable = false)
public String getIsActive() {
return isActive;
}
public void setIsActive(String isActive) {
this.isActive = isActive;
}
#Transient
public boolean isActive() {
return isActive.equals(IS_ACTIVE);
}
#Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName);
}
}

Categories

Resources