3

I am trying to understand why when I hit the controler for second time, My OnetoOne Mapping is getting lay initialized. Below is my code: Controller:

@RequestMapping(value="/updateOrderbyOrderid", method=RequestMethod.PUT,produces=MediaType.APPLICATION_JSON_VALUE)
public Order updateOrderbyOrderid (@RequestBody Order orderVO ) {

System.out.println(orderVO.getOrderId());
Order s1 = orderRepository.findByOrderId(orderVO.getOrderId());
if (orderVO.getCustomerId()!=null) {
    orderVO.setCustomer(customerRepository.findByCustomerId(orderVO.getCustomerId()));
}

s1 = orderRepository.saveAndFlush(orderVO);

return s1;
}

Order Entity:

@Entity
@Table(name="Ordertable", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Order {
@Id
@Column(name = "ORDER_ID")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int orderId;



@OneToOne(fetch=FetchType.LAZY,cascade=CascadeType.ALL)
@JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private Customer customer;

private transient Long customerId;


public int getOrderId() {
    return orderId;
}

public void setOrderId(int orderId) {
    this.orderId = orderId;
}

public Customer getCustomer() {
    return customer;
}

public void setCustomer(Customer customer) {
    this.customer = customer;
}


public Long getCustomerId() {
    return customerId;
}

public void setCustomerId(Long customerId) {
    this.customerId = customerId;
}

}

Customer Entity:

@Entity
@Table(name="Customer", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Customer {

@Id
@Column(name = "CUSTOMER_ID")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long customerId;

@Column(name = "CUSTOMER_NAME")
private String customer_name;


@Column(name = "CUSTOMER_address_id")
private int customer_address_id;


public Long getCustomerId() {
    return customerId;
}


public void setCustomerId(Long customerId) {
    this.customerId = customerId;
}


public String getCustomer_name() {
    return customer_name;
}


public void setCustomer_name(String customer_name) {
    this.customer_name = customer_name;
}


public int getCustomer_address_id() {
    return customer_address_id;
}


public void setCustomer_address_id(int customer_address_id) {
    this.customer_address_id = customer_address_id;
}


public Customer() {

}

}

Json structure for Controller:

{
    "orderId" :101,
    "customerId" : 2
}

Order Table for First Hit :

enter image description here

OnetoOne annotated entity screenshot for first hit. No lazy initialised bean for customerRepository.findByCustomerId(orderVO.getCustomerId()):

enter image description here

Order Table for Second Hit :

enter image description here

When I hit for second time, I get a lazy loaded bean for customerRepository.findByCustomerId(orderVO.getCustomerId()). Why is it happening:

enter image description here

My expectation is hibernate should get fully loaded bean for next time as well when I am doing orderVO.setCustomer(customerRepository.findByCustomerId(orderVO.getCustomerId())); rather than assiging the lazy initialised bean which it got from Order s1 = orderRepository.findByOrderId(orderVO.getOrderId());

One important Note, If I comment below line in controller:

Order s1 = orderRepository.findByOrderId(orderVO.getOrderId()); :

and replace it with Order s1 = null, I am not getting lazy initialised bean anymore. Was hibernate caching the same lazy initialised bean internally earlier ??

Screenshot of s1 when I didn't commented it : enter image description here

Code after commenting s1 & replacing it with null:

@RequestMapping(value="/updateOrderbyOrderid", method=RequestMethod.PUT,produces=MediaType.APPLICATION_JSON_VALUE)
public Order updateOrderbyOrderid (@RequestBody Order orderVO ) {
System.out.println(orderVO.getOrderId());
Order s1 = null;
//Order s1 = orderRepository.findByOrderId(orderVO.getOrderId());
if (orderVO.getCustomerId()!=null) {
    orderVO.setCustomer(customerRepository.findByCustomerId(orderVO.getCustomerId()));
}

s1 = orderRepository.saveAndFlush(orderVO);

return s1;
}
ASharma7
  • 726
  • 3
  • 8
  • 27
  • What is your expectation? – Prasath Apr 25 '19 at 07:22
  • My expectation is hibernate should get fully loaded bean for next time as well when I am doing orderVO.setCustomer(customerRepository.findByCustomerId(orderVO.getCustomerId())); rather than assiging the lazy initialised bean which it got from Order s1 = orderRepository.findByOrderId(orderVO.getOrderId()); – ASharma7 Apr 25 '19 at 07:24
  • Try to create a `OrderVO` class as a simple POJO (with no Hibernate annotations) containing only `customerId` and `orderId` fields and use it to collect the JSON values. Then use this values to get `Order` and `Customer` from the repositories. I think it's not a good idea to use managed entities to collect the JSON values, the managed entities must be used only for persistence. – JMSilla Jul 23 '19 at 13:17
  • @JMSilla I had tried this one as well, but got same result. You can also try this thing on your PC. – ASharma7 Jul 24 '19 at 19:07

3 Answers3

1

Spring Data JPA - findBy() method is internally implemented by EntityManager.find(). EntityManager.find() first checks the Data in cache, if no data is found in cache then hits DB to load data & puts in cache to make it managed.

If we again do EntityManager.find() (again for same primary key), it checks cache again. Now this time data is loaded from cache rather than hitting DB.

I think Hibernate is caching the Object, and when I hit second time it returns proxy object for cached item.

Below are the links I have read to understand findBy() working.

When use getOne and findOne methods Spring Data JPA

similar load and get hibernate method on spring data

What is the difference between EntityManager.find() and EntityManger.getReference()?

Now to test whether I am right or not, I used em.detach() in my below code. detach() will make managed entity --> unmanaged and also remove it from cache. Now when I execute the code, I got newly initialised bean.

@RequestMapping(value="/updateOrderbyOrderid", method=RequestMethod.PUT,produces=MediaType.APPLICATION_JSON_VALUE)
public Order updateOrderbyOrderid (@Valid @RequestBody Order orderVO ) {
    
    System.out.println(orderVO.getOrderId());
    Order s1 = orderRepository.findByOrderId(orderVO.getOrderId());
    em.detach(s1.getCustomer());
    s1= null;
    if (orderVO.getCustomerId()!=null) { 
        Customer findByCustomerId = customerRepository.findByCustomerId(orderVO.getCustomerId());
        orderVO.setCustomer(customerRepository.findByCustomerId(orderVO.getCustomerId()));
    }

    s1 = orderRepository.saveAndFlush(orderVO);
    
    return s1;
}
jmizv
  • 1,172
  • 2
  • 11
  • 28
ASharma7
  • 726
  • 3
  • 8
  • 27
0

i think you have to use fetch=FetchType.EAGER for OneToOne mapping

@OneToOne(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
@JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private Customer customer;

hope it will help.

Niravdas
  • 371
  • 2
  • 19
  • I am using spring data jpa to fetch data, not directly using hibernate entity manager. So eager will have no effect on it. See this URL : https://stackoverflow.com/q/56248867/4860775 . Also try it on your PC. – ASharma7 Jul 24 '19 at 18:59
  • 1
    1:1 is by default EAGER. – Antoniossss Jul 26 '19 at 06:20
0

Your mapping looks a lot like ManyToMany. Try this out:

// OrderTable
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "CUSTOMER_ID", nullable = false)
private Customer customer;
// constructors
public OrderTable() {}
public OrderTable(Customer customer) { this.customer = customer; }


// Customer
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "customer")
private OrderTable orderTable;
Nikhil
  • 1,126
  • 12
  • 26