2

I am using spring boot, spring web and spring data for the following example.

I have one entity called Person and I already populated two Persons in the database:

Person entity

@Entity
public class Person {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(unique = true, nullable = false)
private long id;
private String name;

public long getId() {
    return id;
}
public void setId(long id) {
    this.id = id;
}
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public Personne() {
}
public Personne(long id, String name) {
    this.id = id;
    this.name = name;
}}

PersonRepository

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}

PersonController

@RestController
public class PersonController {

@Autowired
private PersonRepository personRepo;

@RequestMapping(value = "/perss/{id}")
public Person getById(@PathVariable("id") long id) {
    return personRepo.xxxx(id);
}}

Use case 1:

When I replace personRepo.xxxx(id) with personRepo.getOne(id) and tap localhost:8080/perss/1 i get Could not write JSON: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) error in the browser due to the fact that getOne() method returns a proxy to Person that jackson somehow cannot convert.

Use case 2:

When I replace personRepo.xxxx(id) with personRepo.findOne(id) and tap localhost:8080/perss/1 I get the desired Person object in the correct JSON format (this one works fine).

Use case 3:

When I replace PersonController getById() method's code with the following one:

@RequestMapping(value = "/perss/{id}")
public Person getById(@PathVariable("id") long id) {
    Person p1 = personRepo.findOne(id);
    Person p2 = personRepo.getOne(id);
    return p2;
}

And tap localhost:8080/perss/1 I get the wanted Person object in the correct JSON format.

Question:

Using getOne() got me an error, but using findOne() and getOne() together gave me good result.

How does the findOne() influence the getOne()'s behavior.

EDIT

Use Case 4

When I reverse the order of p1 and p2 i get an error.

@RequestMapping(value = "/perss/{id}")
public Person getById(@PathVariable("id") long id) {
    Person p2 = personRepo.getOne(id);
    Person p1 = personRepo.findOne(id);
    return p2;
}
Mike Muske
  • 323
  • 1
  • 16
Ilyes Youssef
  • 63
  • 1
  • 7
  • check this https://stackoverflow.com/questions/32264758/why-does-getone-on-a-spring-data-repository-not-throw-an-entitynotfoundexcept – pvpkiran Jul 11 '17 at 10:30

2 Answers2

1

Try to return p1 and you probably get the same error.

@RequestMapping(value = "/perss/{id}")
public Person getById(@PathVariable("id") long id) {
    Person p1 = personRepo.findOne(id);
    Person p2 = personRepo.getOne(id);
    return p1;
}

You didn't get any, because you didn't serialized p1 which is JavassistLazyInitializer proxy. You serialized p2 instead which was already fine.

This one also will be fine:

@RequestMapping(value = "/check/{id}")
public void getById(@PathVariable("id") long id) {
    personRepo.getOne(id);
}

JSON-serialization occurs when the object converted to from POJO to JSON.

The error with serialization of beans that have lazy-init properties occurs because serialization happens before their full loading.

You can try to fix the error with findOne() doing the following options:

  1. Set the property below to your application.properties file (as exception message suggests):

    spring.jackson.serialization.fail-on-empty-beans=false

  2. Annotate entity with lazy-init properties like:

    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})

So, answering the question:

How does the findOne() influence the getOne()'s behavior.

It doesn't. And also calls to repositories doesn't invoke JSON serialization process.

J-Alex
  • 6,881
  • 10
  • 46
  • 64
1

You are correct that the order of invocation does effect the result when using both findOne() and getOne().

Short Answer: Both methods will first lookup the ID in the persistence context and return the cached value if it is present. If there is nothing found in the persistence context, they will proceed to load their preferred result and cache it. The cached value will be found by the other method the next time it runs.

  • getOne(id) will load (and cache) a proxy if id is not in the persistence context.
  • findOne(id) will load (and cache) the naked entity if id is not in the persistence context.

Long Answer: I ran into the same problem and my project uses Hibernate 5.2.4.Final. The details of what is happening involves some Hibernate code. After debugging for a while I found that both findOne() and getOne() eventually call Hibernate's DefaultLoadEventListener.onLoad() method, but they call it with different loadType arguments:

  • getOne() eventually delegates to SessionImpl.IdentifierLoadAccessImpl<T>.doGetReference() which specifies the loadType of LoadEventListener.LOAD which is eventually passed down to DefaultLoadEventListener.proxyOrLoad(). LoadEventListener.LOAD does allow for the creation of a proxy.
  • findOne() eventually delegates to SessionImpl.IdentifierLoadAccessImpl<T>.doLoad() which specifies the loadType value of LoadEventListener.GET which is eventually passed down to DefaultLoadEventListener.proxyOrLoad(). LoadEventListener.GET does not allow creation of a proxy.

Set a breakpoint in DefaultLoadEventListener.proxyOrLoad() to verify that the LoadType options argument that is passed in has different values for its allowProxyCreation field depending on whether findOne() or getOne() is calling it.

You can see that if allowProxyCreation is true and there is no proxy, then proxyOrLoad() will return the result of createProxyIfNecessary(). In the case where only getOne() is used, this will result in returning a proxy.

If it happens that findOne() was called for the same entity type and ID before getOne(), then when the getOne() call makes its way into createProxyIfNecessary() it will return early because the entity will already be found in the persistence context. In that case calling getOne() will not result in creating a proxy.

If you call getOne() before findOne() then the proxy will be created and stored in the persistence context, and findOne() will also return the proxy because it will simply retrieve the cached proxy from the persistence context.

Mike Muske
  • 323
  • 1
  • 16