Criteria API ignore LAZY fetch - java

I have two entities Customer and Product with relation one to many with lazy strategy.
Problem is fetch not lazy, products collection always fetch all related products.
public Optional<Customer> findById(final long customerId) {
final EntityManager entityManager = sessionFactory.createEntityManager();
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Customer> criteriaQuery = criteriaBuilder.createQuery(Customer.class);
final Root<Customer> customerMetamodel = criteriaQuery.from(Customer.class);
criteriaQuery.where(criteriaBuilder.equal(customerMetamodel.get("id"), customerId));
final TypedQuery<Customer> query = entityManager.createQuery(criteriaQuery);
return Optional.ofNullable(query.getSingleResult());
}
As you can see no one mention about fetch eager, and in entity classes the same, all lazy:
Customer
#Data
#Builder
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "customer")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int age;
#OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
private List<Product> products;
}
Product
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
#ToString(exclude = "customer")
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private BigDecimal price;
#ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
}
Why lazy work as eager and how to enable real lazy mode in this case?
UPDATE_1
Integration test with H2
#Test
public void testFindById() {
Customer customer = customerRepository.create(Customer.builder().age(20).name("Denis").build());
productRepository.create(Product.builder().customer(customer).price(new BigDecimal(100)).build());
productRepository.create(Product.builder().customer(customer).price(new BigDecimal(200)).build());
productRepository.create(Product.builder().customer(customer).price(new BigDecimal(300)).build());
final Customer result = customerRepository.findById(customer.getId()).orElseThrow();
assertEquals("Denis", result.getName());
assertThrows(LazyInitializationException.class, () -> System.out.println(result.getProducts()));
}
I call .getProducts() but expect LazyInitializationException because session already closed.

I guess you are using Spring? Try disabling spring.jpa.properties.hibernate.enable_lazy_load_no_trans which is an anti-pattern anyway.

Reason is EntityManager hold open session. And load subentities on demand. In my case .getProducts().
For close session directly use entityManager.close().

Related

cascade = CascadeType.ALL doesn´t work on OneToOne relation

I have one problem with One to One relation.
I have one USER that has only one TEAM
User Entity
#Entity
#Table(name = "USER")
#Data #NoArgsConstructor #AllArgsConstructor #Builder
public class User implements Serializable {
private static final long serialVersionUID = 3233149207833106460L;
#Id
#GeneratedValue
#Column(name = "USERID")
private Long id;
...
#OneToOne(mappedBy = "user")
private Team team;
}
Team Entity
#Entity
#Table(name = "TEAM")
#Data #NoArgsConstructor #AllArgsConstructor #Builder
public class Team implements Serializable {
private static final long serialVersionUID = 3233149207833106460L;
#Id
#GeneratedValue
#Column(name = "TEAMID")
private Long id;
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USERID")
private User user;
}
This is the way I´m trying to create object.
//create Team and add to User
Team team = Team
.builder()
.value(TeamConstants.INITIAL_VALUE)
.name(userDetailsForm.getUsername()+" "+TeamConstants.TEAM)
.country(userDetailsForm.getUsername()+" "+TeamConstants.COUNTRY)
.build();
User user = User
.builder()
.username(userDetailsForm.getUsername())
.password(this.passwordEncoder.encode(userDetailsForm.getPassword()))
.email(userDetailsForm.getEmail())
.registerDate(DateUtil.getNow())
.role(userDetailsForm.getRole())
.team(team)
.build();
team.setUser(user);
this.userRepository.save(user);
And when I try to save user (and create automatically the son team), it gaves me the error
Blockquote
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.soccermanager.business.entities.user.User.team -> com.soccermanager.business.entities.team.Team;
Any idea with the solution?
This is my first question here, I hope its correct.
Thanks for your time
please interact with Team entity then user will be change follow CascadeType
or you need add
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL) private Team team; in User class.
Please try both and you can see the different. Hope this post answer can help with you
You should save your team entity before your user.
You should do as you do only if you are working in the same JPA Transaction unit. If not, it will not work and you get that exception.
Try adding #Transactional annotation on the method, with optional params like propagation = Propagation.REQUIRED and readonly=false because you are creating/updating objects here :
#Transactional(propagation = Propagation.REQUIRED, readOnly = false)
protected User saveTeamWithUser(UserDetailsForm form) {
// ...
}

Hibernate 6 perfroms update query after select query

I wrote a simple entity named "User" and another one called "Company". User entity has company inside with #ManyToOne(fetch = LAZY) mapping. When I create the session object and call get() method Hibernate executes select query and after it update, why?
User entity:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
#ToString
#Builder
#Entity
#Table(name = "users")
public class User2 {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(unique = true)
private String username;
#Embedded
#EqualsAndHashCode.Exclude
private PersonalInfo personalInfo;
#Enumerated(STRING)
#EqualsAndHashCode.Exclude
private Role role;
#Type(JsonType.class)
#EqualsAndHashCode.Exclude
private String info;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "company_id")
#EqualsAndHashCode.Exclude
#ToString.Exclude
private Company company;
}
Company entity:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#ToString
#Builder
#Entity
public class Company {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
private String name;
}
Main:
public class HibernateEntityMappingRunner {
public static void main(String[] args) {
try (SessionFactory sessionFactory = buildSessionFactory();
Session session = sessionFactory.openSession()
) {
session.beginTransaction();
User2 user2 = session.get(User2.class, 2);
session.getTransaction().commit();
}
}
}
Hibernate console log:
Hibernate:
select
u1_0.id,
u1_0.company_id,
u1_0.info,
u1_0.birth_date,
u1_0.firstname,
u1_0.lastname,
u1_0.role,
u1_0.username
from
users u1_0
where
u1_0.id=?
Hibernate:
update
users
set
company_id=?,
info=?,
birth_date=?,
firstname=?,
lastname=?,
role=?,
username=?
where
id=?
See https://hibernate.org/community/contribute/intellij-idea/#debugging for details. The problem is that some of your toString() implementations (which the IntelliJ debugger invokes) access state of uninitialized entity/collection proxies, which will cause lazy initialization and hence a select query. If you have pending flushes (insert/update/delete) which affect one of the tables that are about to be selected from, Hibernate forcefully executes pending flushes.
By changing your toString() implementations or changing how IntelliJ shows objects in the debugger, you can avoid this.

Deleting an entity with one to one relation

My two entities have one to one relation
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"), name = "library_user")
public class AppUser {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#EqualsAndHashCode.Exclude
private Long id;
// other fields
#OneToOne(mappedBy="user", cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
private PasswordResetToken token;
// getters/setters and equals/hashcode
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "password_reset_token")
public class PasswordResetToken {
private static final int EXPIRATION = 60 * 24;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// other fields
#OneToOne(targetEntity = AppUser.class, fetch = FetchType.EAGER, cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(nullable = false, name = "user_id")
private AppUser user;
// getters/setters and equals/hashcode
I tried to delete my user entity by this method
public void deleteUser(Long id) {
resetTokenRepository.deleteAllByUserId(id);
userRepository.deleteById(id);
}
PasswordResetTokenRepository class which method I called in my service method, for deleting user I used regular hibernate method deleteById(Long id)
#Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
void deleteAllByUserId(Long id);
}
But when I try to delete by this method I got this error:
not-null property references a null or transient value : kpi.diploma.ovcharenko.entity.user.PasswordResetToken.user
I read several websites how to delete one to one relation, but their advices didn't help me. For example, I tried a lot of variants of annotation cascade={CascadeType.ALL}, tried all the variants(CascadeType.REMOVE,CascadeType.PERSIST and so on), all time I got the same error. Help me pls, to understand what I do wrong.
try this:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication .

Using EntityGraph with SuperClasses and Embedded Attributes

I would like to use the EntityGraph Feature because of the known n+1 Problem. I have the following Entities structure:
#Entity
#Table(name = "customer")
public class Customer extends Person {
#Column(name = "foo")
public String foo;
#Column(name = "bar")
public String bar;
}
#Entity
#Table(name = "person")
public class Person {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "car.id")
public Car car;
#Embedded
public Key key;
}
#Entity
#Table(name = "car")
public class Car {
#Column(name = "a")
public String a;
#Column(name = "b")
public String b;
}
#Embeddable
public class Key
{
#Column(name = "key_id")
public Long keyId;
#Column(name = "key_color")
public String keyColor;
}
Now I want to use a NamedEntityGraph. As far as I understand with "#NamedEntityGraph(name = "getCustomer", includeAllAttributes=true)" it should work but it doesnt.
The NamedEntityGraph call with
em.createQuery(criteriaQuery).setHint("javax.persistence.fetchgraph", em.getEntityGraph("getCustomer")).getResultList()
returns the amount of Customers in the database but all Attributes including car and the Embedded Attribute key is always null.
Do I have to use subgraphs? I tried to declare the NamedEntityGraph on Customer class also on Person class. It makes no difference.
EDIT:
After struggling a long time with this problem, i tried to break down it to the lowest level with these two entities
#Entity
#Table(name = "publication")
#NamedEntityGraph(name = "graph.Publication.articles",
attributeNodes = #NamedAttributeNode("articles"))
public class Publication {
#Id
private String publicationId;
private String name;
private String category;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "publicationId")
private List<Article> articles;
#Entity
#Table(name = "article")
public class Article {
#Id
private String articleId;
private String title;
private String publicationId;
}
If i create a query i can see further more than one query in the postgres log.
EntityGraph<?> entityGraph = em.getEntityGraph("graph.Publication.articles");
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("javax.persistence.fetchgraph", entityGraph).getResultList();
Different queries for all publications
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_1'
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_2'
But I would only have expected one query with a join here.
Finally I found a solution for my problem. I refer to the edited part of my question.
I found this page which describes very well how to use batch query hints to improve performance.
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html?m=1
For my example I don't need the entitygraph anymore. The query should created like this
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("eclipselink.batch", "x.articles").getResultList();

Hibernate one to one criteria fetching

I am using hibernate one to one mapping between car and person table. But a person might have car and might not have a car. now while fetching the records from the person table using hibernate criteria , I want to fetch only those persons who have a car, i.e only those entries in person table for which a corresponding entry in car table exists. How this can be done using hibernate criteria/alias?
Below is the piece of code. kindly help with the criteria or alias that has to written to fetch result:
#Getter
#Setter
#Entity
#Table(name = "Person")
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Person_ID")
private Long personId;
#OneToOne(mappedBy = "personAsset", cascade = CascadeType.ALL)
private Car car;
}
#Getter
#Setter
#Entity
#Table(name = "Car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Car_ID")
private Long carId;
#OneToOne
#JoinColumn(name = "Person_ID")
private Person personAsset;
}
what you are looking for is the cascadeType orphanRemoval=true on the #OneToOne annotation.
here is your class how would look like :
#Getter
#Setter
#Entity
#Table(name = "Car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Car_ID")
private Long carId;
#OneToOne(fetch=FetchType.EAGER , cascade=CascadeType.ALL, orphanRemoval=true)
#JoinColumn(name = "Person_ID")
private Person personAsset;
}
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> person = query.from(Person.class);
Predicate predicate = cb.isNotNull(person.join("car"));
predicates.add(predicate );

Categories