I am confused with the Hibernate session get method. My understanding is that the get method always returns real object and not the proxy object (ref).
But in my program I get the proxy object even when I use the get method.
My scenario:
I have two tables product and company.
Product JPA:
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#Column(name = "prd_id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "price")
private int price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cmp_id")
private Company company;
}
Company JPA:
#Entity
#Table(name = "COMPANY")
public class Company {
#Id
#Column(name = "cmp_id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "revenue")
private int revenue;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private List<Product> products;
}
In my main method I have the following code:
Product product = (Product) session.get(Product.class, 1); // this product has cmp_id 1
System.out.println("Got product");
Company company = (Company) session.get(Company.class, 1);
System.out.println(company instanceof HibernateProxy); // this returns true
I know because of the lazy loading Hibernate has company 1 as proxy object from Product product = (Product) session.get(Product.class, 1);.
But I was expecting (Company) session.get(Company.class, 1); to return normal object.
From the log, I see hibernate does hit the database and get complete data of Company object. This makes it more confusing, if hibernate has all the data why is still returning the proxy object ?
Is my understanding incorrect ? How can ensure session get returns normal object and not a proxy object ?
Hibernate does not hit the database for every call to get.
See JavaDocs for get:If the instance is already associated with the session, return that instance. This method never returns an uninitialized instance.
So, since with session.get(Product.class) there's already an instance of Company in the session (although a proxy) so Hibernate just initializes that proxy (thus the sql you saw).
First of all session.get never returns a proxy object it returns a fully initialized object from the persistent space if it is present there otherwise it hits the database to get the object from there and give it back else returns null.
As the java doc says :
Return the persistent instance of the given entity class with the given identifier, or null if there is no such persistent instance. (If the instance is already associated with the session, return that instance. This method never returns an uninitialized instance.)
For more information you can follow the link "http://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/Session.html#get(java.lang.Class, java.io.Serializable)" .
Related
I am using spring-data-jpa to read data from db.
JPA Entity
#Entity
#Table(name = "ICM_STATUSES")
public class IcmMdStatuses implements Serializable {
#Id
#Column(name = "STATUS_INTERNAL_IDENTIFIER")
private long statusInternalIdentifier;
#Column(name = "STATUS_IDENTIFIER")
private String statusIdentifier;
#Column(name = "STATUS_NAME")
private String statusName;
#Column(name = "STATE_NAME")
private String stateName;
#Column(name = "IS_ACTIVE")
private String isActive= "Y";
#Column(name = "CREATED_BY")
private String createdBy;
#CreationTimestamp
#Column(name = "CREATE_DATE")
private Date createdDate;
#Column(name = "UPDATED_BY")
private String updatedBy;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATE_DATE")
private Date updateDate;
}
JPA Repository
public interface StatusRepository extends JpaRepository<IcmMdStatuses, Long> {
Optional<IcmMdStatuses> findByStatusIdentifier(String statusIdentifier);
}
DB Table Data
Problem:
If I try to fetch Open status record. I am getting Hibernate Proxy Object instead of real object.
If I try to fetch Reopen status record. i am getting real object.
Except for Open status, for the rest all statuses i am getting real objects.
I could able to get the real object using Hibernate.unproxy(-)
Problem
MyEntity entity = new MyEntity();
//If i use any other status except Open the below line saves pk of that particular status.
entity.setFromStatus(statusRepository.findByStatusIdentifier("Open"));//saving null in db
entity.setToStatus(statusRepository.findByStatusIdentifier("Reopen"));//saving value 93(PK of Reopen status)
myRepo.save(entity);
Please help to understand what's the actual issue, as i am experiencing this issue only for Open Status, rest all works as expected.
The problem is most probably due to open-session-in-view and the fact that you loaded some other object that refers to this Open status object. Since that other object did not load the Open status object, it created a proxy for it which is then part of the persistence context. The next time you load that object, you retrieve that proxy instead of the real object because Hibernate must maintain the object identity guarantee for a persistence context.
I'm writing a Spring application. In my application I have a rest API that updates a Shop object. The Shop object has a reference to a default currency object. My rest client sends post request with the following JSON payload.
{
currency: {id: "EUR", name: "Euro", symbol: "€"},
description : "Bla bla bla"
id : "SHOP-00006"
isActive : null
location : "the moon"
name : "Delicious Foo"
numberOfProducts : 0
profilePicture : null
}
My service looks like this:
#Transactional
public List<ShopData> updateShop(User user, ShopData shopData) {
Shop shop = shopRepository.getById(shopData.getId());
mapper.map(shopData, shop);
entityManager.merge(shop);
return getUserShops(user);
}
Where ShopData is a DTO that holds the data received from the user. And mapper.map copies the DTO to the domain object.
This method throws the following exception:
org.hibernate.HibernateException: identifier of an instance of
com.companyname.application.domain.common.Currency was altered from USD to
EUR at
org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:80)
at
org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:192)
at
org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:152)
at
org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:231)
at
org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:102)
at
org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:61)
at
org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1227)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1293) at
org.hibernate.internal.QueryImpl.list(QueryImpl.java:103) at
org.hibernate.jpa.internal.QueryImpl.list(QueryImpl.java:573) at
org.hibernate.jpa.internal.QueryImpl.getResultList(QueryImpl.java:449)
Finally the relevant domain classes are:
#Entity
#Table(name = "shop")
public class Shop extends AnEntity {
#Column(name = "name", nullable = false)
protected String name;
#Column(name = "description", nullable = false)
protected String description;
#Column(name = "location")
protected String location;
#JoinColumn(name = "owner_id", nullable = false)
#ManyToOne(fetch = FetchType.LAZY)
protected User owner;
#JoinColumn(name = "currency_id")
#ManyToOne(fetch = FetchType.EAGER)
protected Currency currency;
#Column(name = "profile_picture")
protected String profilePicture;
#Column(name = "is_active")
protected Boolean isActive;
// getters and setters
}
And
#Entity
#Table(name = "currency")
public class Currency extends AnEntity {
#Column(name = "name", length = 124)
protected String name;
#Column(name = "symbol", length = 3)
protected String symbol;
// setters and getters
}
It seems like hibernate is trying to merge my currency object even though I haven't specified any cascade value on the relation. Why does hibernate try to do that, instead of changing the currency reference to the new one? And how should this be done, if I'm making a mistake in my code?
(EDIT)
My table structure as follows:
It looks like you are changing the ID of an entity, which you should not be doing. If you are referring to your currency instance using a managed entity, you should be replacing the whole entity instead. Hibernate cannot manage to guess why the ID property of an object changed which I believe is not allowed.
Here I see 2 options:
in your mapper reload the currency entity you set in your shop instance and re-set it using a setter instead of mapping from JSON
use a different type of reference to your currency, e.g. only the ID field instead of actual object reference
If you don't want your Currency to be persisted, add a CascadeType to the declaration:
#JoinColumn(name = "currency_id")
#ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
protected Currency currency;
In your code, I see that you are reusing the shop object and hence the second call to this method will try to update the shop object created by the first call.
public List<ShopData> updateShop(User user, ShopData shopData) {
mapper.map(shopData, shop);
Create a new local Shop object and use that in your map method.
This is how I resolved this issue.
#Transactional
public List<ShopData> updateShop(User user, ShopData shopData) {
Shop updateShop = mapper.map(shopData, Shop.class);
entityManager.merge(updatedShop);
return getUserShops(user);
}
I have two tables with 'one to many' relationship. I use Jpa + Spring JpaRepository. Sometimes I have to get object from Database with internal object. Sometimes I dont't have to. Repositories always return object with internal objects.
I try to get 'Owner' from Database and I always get Set books; It's OK. But when I read fields of this internal Book , I get LazyInitializationException. How to get null instead of Exception?
#Entity
#Table(name = "owners")
#NamedEntityGraph(name = "Owner.books",
attributeNodes = #NamedAttributeNode("books"))
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "owner_id", nullable = false, unique = true)
private Long id;
#Column(name = "owner_name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
private Set<Book> books= new HashSet<>(0);
public Worker() {
}
}
#Entity
#Table(name = "books")
#NamedEntityGraph(name = "Book.owner",
attributeNodes = #NamedAttributeNode("owner"))
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id", unique = true, nullable = false)
private Long id;
#Column(name = "book_name", nullable = false, unique = true)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private Owner owner;
public Task() {
}
}
public interface BookRepository extends JpaRepository<Book,Long>{
#Query("select t from Book t")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
List<Book> findAllWithOwner();
#Query("select t from Book t where t.id = :aLong")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
Book findOneWithOwner(Long aLong);
}
You are getting LazyInitializationException because you are accessing the content of the books Set outside the context of a transaction, most likely because it's already closed. Example:
You get an Owner from the database with your DAO or Spring Data repository, in a method in your Service class:
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You try to access the Set here
return owner;
}
At this point you have an Owner object, with a books Set which is empty, and will only be populated when someone wants to access its contents. The books Set can only be populated if there is an open transaction. Unfortunately, the findOne method has opened and already closed the transaction, so there's no open transaction and you will get the infamous LazyInitializationException when you do something like owner.getBooks().size().
You have a couple of options:
Use #Transactional
As OndrejM said you need to wrap the code in a way that it all executes in the same transaction. And the easiest way to do it is using Spring's #Transactional annotation:
#Transactional
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You can access owner.getBooks() content here because the transaction is still open
return owner;
}
Use fetch = FetchType.EAGER
You have fetch = FecthType.LAZY in you #Column definition and that's why the Set is being loaded lazily (this is also the fetch type that JPA uses by default if none is specified). If you want the Set to be fully populated automatically right after you get the Owner object from the database you should define it like this:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();
If the Book entity is not very heavy and every Owner does not have a huge amount of books it's not a crime to bring all the books from that owner from the database. But you should also be aware that if you retrieve a list of Owner you are retrieving all the books from all those owners too, and that the Book entity might be loading other objects it depends on as well.
The purpose of LazyInitializationException is to to raise an error when the loaded entity has lost connection to the database but not yet loaded data which is now requested. By default, all collections inside an entity are loaded lazily, i.e. at the point when requested, usually by calling an operation on them (e.g. size() or isEmpty()).
You should wrap the code that calls the repository and then works with the entity in a single transaction, so that the entity does not loose connection to DB until the transaction is finished. If you do not do that, the repository will create a transaction on its own to load the data, and close the transaction right after. Returned entity is then without transaction and it is not possible to tell, if ots collections have some elements or not. Instead, LazyInitializationException is thrown.
I'm having an strange problem when I try to retrieve some entities from the database. The table where the entities lives just have 4 rows. When I try select all rows I get a list where the first and the last elements are loaded correct, however, the second and the third has all properties as null. Here is a print of my debug console:
The entity is simple, as you can see below:
#Entity
#Table(name = "Empresa")
public class Empresa implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID_EMPRESA")
private Integer idEmpresa;
#Basic(optional = false)
#Column(name = "NOME_EMPRESA")
#OrderColumn
private String nomeEmpresa;
#Column(name = "CNPJ")
private String cnpj;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "iDEmpresa", fetch = FetchType.LAZY)
private List<Cadastro> cadastroList;
}
If you want know how I am retrieving the entities, here is the code:
#Override
public List<T> recuperarTodos() {
Query query = entityManager.createQuery(criarQueryRecuperarTodos());
limitarQuantidadeDeRegistros(query);
return query.getResultList();
}
private String criarQueryRecuperarTodos() {
StringBuilder builder = new StringBuilder("SELECT e FROM ");
builder.append(classe.getSimpleName());
builder.append(" e");
builder.append(criarParametrosOrdenacao());
return builder.toString();
}
This is perfectly legal and expected situation. Hibernate uses dynamically generated proxies (hence javaassist objects, in the past hibernate used cglib as well) as placeholders for not fully fetched entities to allow lazy fetching. Because of this, generally speaking, you should not attempt to access attribute values directly. Using getters instead allows hibernate to issue an appropriate DB query and fill the entity. This can be a problem in some situations - for example, if the values are first requested outside the Hibernate session.
I'm trying to persist a User that has a mapping #ManyToOne with UserStatus
but when I do the code below, the hibernate throws PropertyValueException
user.setStatus(new UserStatus(1));
em.persist(user); // ou session.saveAndUpdate(user);
to work I have to do this way:
user.setStatus(em.getReference(UserStatus.class, 1));
em.persist(user); // ou session.saveAndUpdate(user);
I know the first way is possible, but what I don't know is whether I need to configure or call another method (I've already tried saveAndUpdate from Session and still the same)
Does anyone have any idea?
The error message is:
not-null property references a null or transient value
the mapping
#ManyToOne(optional = false)
#JoinColumn(name = "user_status_id", nullable = false)
public UserStatus getStatus() {
return status;
}
This error means "you are referencing a null (not persisted) object" and you have to choice: remove nullable or set #Cascade so UserStatus will per persisted when you do em.persist(user)
#ManyToOne(optional = false)
#JoinColumn(name = "user_status_id", nullable = false)
#Cascade(cascade=CascadeType.ALL)
public UserStatus getStatus() {
return status;
}
EDIT: After various test, using getReference() is the right way to proceed because new UserStatus(1) go for error and should be substituted as getReference(UserStatus.class,ID) to return a proxied instance of UserStatus. Proxied object doesn't hit on database, so SELECT is avoided and the only field setted on UserStatus proxy is the ID, necessary to resolve #ManyToOne relation!
Some useful answer: When to use EntityManager.find() vs EntityManager.getReference()What is the difference between EntityManager.find() and EntityManger.getReference()?