I'm using Hibernate with jpa and I'm getting LazyInizializationException trying to do a very simple thing. I know what does LazyInizializationException means but I can't understand why it comes while i'm doing everything in the most common and simple way. This is the "one" side of the relationship:
#Entity
public class User implements Serializable{
#Id #GeneratedValue
private int idUser;
private String name;
private String surname;
private String username;
#OneToMany(mappedBy="user")
private List<Device> dev;
...getters and setters...
and this is the "Many" side:
#Entity
public class Device implements Serializable {
#Id #GeneratedValue
private int idDevice;
private String brand;
private String model;
#ManyToOne
#JoinColumn(name="user_fk")
private User user;
...getters and setters...
the jUnit test that throws the exception is:
#Test
public void testLazyUserSnd() {
User u = uDao.getUser(2);
List<Device> devList = u.getDev();
Device aDevice = devList.get(0); // <--- Here the exception is thrown
aDevice.getModel();
I made the relationship as explained in the Hibernate Documentation. Any hint? Am I making some big and stupid mistakes?
While #Xavi's answer is perfectly reasonable, you may not always want to load the devices for a user. If you don't, there are 2 ways of fixing this.
Create an additional method uDao.getUserWithDevices(id) and call that when you know you need devices, otherwise call the uDao.getUser(id).
Encapsulate the test method, and therefore any production code that uses the method, in a transaction. In other words keep the session open as long as you need to.
Personally I'd use the transaction method since as it allows more flexibility and allows JPA to lazy load whenever it needs to. See also http://community.jboss.org/wiki/OpenSessionInView for more interesting information around session lifecycle.
The exception is telling you that you're trying to get some of the lazy-loaded association's elements when the session is closed. Probably you should call u.getDev() or Hibernate.initialize(u.getDev()) inside the dao's method, when the hibernate session is still open.
Or, if you're using Criteria, you could also use setFetchMode to force eager fetching.
public User getUser(String id) {
Session session = getSession();
Criteria criteriaQuery = session.createCriteria(User.class);
criteriaQuery.add(Expression.eq("id", id));
criteriaQuery.setFetchMode("dev", FetchMode.JOIN);
return criteriaQuery.uniqueResult();
}
The entity is probably detached from the session (transaction context) when you access the relation. Try to enclose your test method in a transaction.
Related
I'm currently learning Spring-Boot and Spring-Data-JPA.
I'm using a postgresql database for storing the data.
My goal is to store ingredients with a unique and custom ID (you just type it in when creating it), but when another ingredient with the same ID gets inserted, there should be some kind of error. In my understanding, this is what happens when I use the #Id annotation, hibernate also logs the correct create table statement.
This is my Ingredient class:
public class Ingredient {
#Id
#Column(name = "ingredient_id")
private String ingredient_id;
#Column(name = "name")
private String name;
#Column(name = "curr_stock")
private double curr_stock;
#Column(name = "opt_stock")
private double opt_stock;
#Column(name = "unit")
private String unit;
#Column(name = "price_per_unit")
private double price_per_unit;
#Column(name = "supplier")
private String supplier;
-- ... getters, setters, constructors (they work fine, I can insert and get the data)
}
My controller looks like this:
#RestController
#RequestMapping(path = "api/v1/ingredient")
public class IngredientController {
private final IngredientService ingredientService;
#Autowired
public IngredientController(IngredientService ingredientService) {
this.ingredientService = ingredientService;
}
#GetMapping
public List<Ingredient> getIngredients(){
return ingredientService.getIngredients();
}
#PostMapping
public void registerNewStudent(#RequestBody Ingredient ingredient) {
ingredientService.saveIngredient(ingredient);
}
}
And my service class just uses the save() method from the JpaRepository to store new ingredients.
To this point I had the feeling, that I understood the whole thing, but when sending two post-requests to my application, each one containing an ingredient with the id "1234", and then showing all ingredients with a get request, the first ingredient just got replaced by the second one and there was no error or smth. like that in between.
Sending direct sql insert statements to the database with the same values throws an error, because the primary key constraint gets violated, just as it should be. Exactly this should have happened after the second post request (in my understanding).
What did I get wrong?
Update:
From the terminal output and the answers I got below, it is now clear, that the save() method can be understood as "insert or update if primary key is already existing".
But is there a better way around this than just error-handle every time when saving a new entry by hand?
The save method will create or update the entry if the id already exists. I'd switch to auto generating the ID when inserting, instead of manually creating the IDs. That would prevent the issue you have
When saving a new ingredient, jpa will perform an update if the value contained in the “id” field is already in the table.
A nice way through which you can achieve what you want is
ingredientRepository.findById(ingredientDTO.getIngredientId()).
ifPresentOrElse( ingredientEntity-> ResponseEntity.badRequest().build(), () -> ingredientRepository.save(ingredientDTO));
You can return an error if the entity is already in the table otherwise (empty lambda), you can save the new row
This is a downside to using CrudRepository save() on an entity where the id is set by the application.
Under the hood EntityManager.persist() will only be called if the id is null otherwise EntityManager.merge() is called.
Using the EntityManager directly gives you more fine grained control and you can call the persist method in your application when required
I'm using Spring boot 2.7.0
And have the next entities in simple:
#Getter
#Setter
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
private Long version;
private String name;
}
#Getter
#Setter
#Entity
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
Account account;
private String message;
}
and jpa repositories:
#Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
}
#Repository
public interface EventRepository extends JpaRepository<Event, Long> {
Page<Event> findAllByAccount(Account account, Pageable pageable);
}
In short I call
eventRepository.findAllByAccount(accountRepository.findById(1), PageRequest.of(1,10));
Problem is every call of last code increases the version field of Account by 1. So question is why? I don't call any update or save method.
And additionally the result of this behaviour is calling of method needs
#Transactional(readonly=false)
Otherwise if I write readonly=true that throws cannot execute UPDATE in a read-only transaction
ADDED:
full code of usage:
#Transactional
public Page<Event> events(Long accountId, int page) {
return eventRepository.findByAccount(findById(accountId), PageRequest.of(page, PAGE_SIZE));
}
#GetMapping("/events")
public List<EventResponse> listEvents(#RequestParam(value = "max", defaultValue = "0") int page) {
return eventService.events(1L, page).stream().map(EventResponse::of).toList();
}
It looks like hibernate is deriving lockMode type as either of WRITE or OPTIMISTIC_FORCE_INCREMENT or PESSIMISTIC_FORCE_INCREMENT based on isolation level of your database. As per reference hibernate decides this pessimistic locking by its own based on database you use.
As per doc, if lockmode type is either of what I mentioned above, Version will get automatically incremented even if you haven't changed anything i.e. even if you haven't do any update or save.
Please check database isolation level & based on that you might get an idea about this.
Edit: as you explicitly setting lockmode as write so my answer validates that because of WRITE mode, your version got incremented automatically.
The problem should be related in the code which is using the result of the find.
If you're modifying entities under a transaction they're going to be modified at the end of the method, when Spring in this case is going to close the transaction. In this part when transaction ends, the JPA provider (for example hibernate) aligns the relative entity record into the database with the 'java entity object' by an update.
I'm sorry. After trim all my code to the posted and debug I found my mistake:
In the begin I was retrieving Account in another method by .lock(Long) method instead of .findById(Long)
lock method is below:
#Lock(LockModeType.WRITE)
#Query("from Account where id = :id")
public Optional<Account> lock(Long id);
First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?
Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.
#Entity
#Table(name = "Student")
public class Student implements serializable{
#Column(name="sid")
private int sid;
#Column(name="name")
private String name;
#Column(name="addressXml")
private String addressXml;
#Transient
private Address addressObj;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
---Not getting control here
in.defaultReadObject();
addressXml =(String)in.readObject();
addressObj =(Address) xstream.fromXML(addressXml);
}
I have a following situation in which while doing getResultList I need control in readObject so that I can convert addressXml to addressObj and set the transient variable .
List<Student> list = query.getResultList();
But i am not getting control in readyObject.
Note: Student object already persist in db and I am using Hibernate JPA implementation to retrieve Student objects.
Could you guys help me to understand what exactly i am missing here ?
There is no Java serialisation process in use when pulling an object back from the DB, unless you store an Entity as serialized. If you just want to get a handle on a FIELD when pulled back from the DB then you can make use of the JPA PostLoad event listener.
I am using Wicket, JPA+Hibernate in a project
I have the persistence class like this
#Entity
#Table(name = "document", uniqueConstraints = { #UniqueConstraint(columnNames = {
"name"}) })
public class Document
implements Serializable
{
private static final long serialVersionUID = 8496087166198616020L;
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String state;
.....
Geter && setters
Then
I have the dao implementation for crud operations
One method is
#Override
#Transactional
public void createDocument(Document aDocument)
{
entityManager.persist(aDocument);
}
My Problem is, in the wicketPage, if I use a Wicket Link and it's onclick method doing the following
document.setState("someState");
it updates the value with out calling the createDocument(document) specifically and the database is also up-to-date
But if I use AjaxLink and its onclick method
then, I have to manually update the object otherwise the value will not be updated in the database. My modification should be as follows:
#Override
#Transactional
public void createDocument(Document aDocument)
{
if(aDocument.getId()==0{
entityManager.persist(aDocument);
}
else{
entityManager.merge(aDocument);
}
Then to save the changes in the Database, I have to first update the state and call createDocument() method.
document.setState("someState");
dao.createDocument(document);
Can anyone point out why this happens?
Many thanks
Your database session must be in AutoCommit mode. When you use the Link a "classic" RequestCycle is fired which certainly triggers your OpenSessionInView filter. This way, the document object that you update is still managed by the Persistence Context, thus, the underlying JPA implementation auto commits the change to the database.
When you use the AjaxLink, you're playing with a detached entity, JPA does not manage it and hence does not autocommit it to the database. When you call your createDocument method, you merge your entity with your Persistence Context and save it back.
If you do not want the autocommit to happen, you can set the FlushMode of your entityManager to FlushMode.COMMIT