I'm trying to use the DomainClassConverter from Spring Data to load entities in a controller and then use these entities in a service.
The problem is that I get a LazyInitializationException when I access lazy loaded collection from my Service.
Adding Transactional annotation to the controller does not help, I guess the conversion occurs before the start of the controller transaction.
Is there a way to use this converter in this kind of use case ? can I reattach the entity to the current session someway ?
My controller:
#RestController
#RequestMapping("/api/quotation-requests/{id}/quotation")
public class QuotationResource {
#RequestMapping(value = "/lines", method = RequestMethod.POST, params="freeEntry")
#Timed
public ResponseEntity<PricingLineDTO> addFreeEntryLine(#PathVariable("id") Quotation quotation, #RequestBody PricingLineDTO pricingLineTo)
{
PricingLine pricingLine = conversionService.convert(pricingLineTo, PricingLine.class);
pricingLine = quotationService.addFreeLineToQuotation(quotation, pricingLine);
return new ResponseEntity<>(conversionService.convert(pricingLine, PricingLineDTO.class), HttpStatus.OK);
}
}
The service:
#Service
#Transactional
public class QuotationService {
public PricingLine addFreeLineToQuotation(Quotation quotation, PricingLine pricingLine) {
quotation.getPricingLines().add(pricingLine); // org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: x.y.z.Quotation.pricingLines, could not initialize proxy
}
}
And the entity
#Entity
public class Quotation {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<PricingLine> pricingLines = new ArrayList<>();
}
If it is not possible, what is my best alternative :
make the controller transactionnal, inject data repositories in it and still offer a Service API that takes Entities parameters instead of IDs.
Cons: controller become transactional, which seem to be commonly considered as a bad practice; it introduces boilerplate code.
make the Service API takes IDs as parameters, letting the controller out of the transaction.
Cons: The API become harder to read and can be error prone as every entities as referred as "Long" object, especially when a Service method needs severals entities as input. For example:
void addUserToGroup(Long userId, Long groupId)
One could easily switch parameters.
Related
In my microservice written on spring-boot I have following DTO and Entity:
#Entity
class Order {
#Id
private Long id;
#OneToMany
private List<Product> products;
// getters setters
public Product getMainProduct() {
final String someBusinessValue = "A";
return products.stream()
.filter(product -> someBusinessValue.equals(product.getSomeBusinessValue()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No product found with someBusinessValue 'A'"));
}
}
class OrderRequest {
private List<ProductDto> productDtos;
// getters setters
public ProductDto getMainProductDto() {
final String someBusinessValue = "A";
return productDtos.stream()
.filter(productDto -> someBusinessValue.equals(productDto.getSomeBusinessValue()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No productDto found with someBusinessValue 'A'"));
}
}
As seen both entity and dto contain some business logic method for taking the "main" product from the list of all product. It is needed to work with this "main" product in many parts of the code (only in service layer). I have received a comment after adding these methods:
Design-wise you made it (in Dto) and the one in DB entity tightly coupled through all layers. This method is used in services only. That means that general architecture rules for layers must apply. In this particular case, the "separation-of-concerns" rule. Essentially it means that the service layer does not need to know about the request parameter of the controller, as well as the controller shouldn't be coupled with what's returned from service layer. Otherwise a tight coupling occurs. Here's a schematical example of how it should be:
class RestController {
#Autowired
private ItemService itemService;
public CreateItemResponse createItem(CreateItemRequest request) {
CreateItemDTO createItemDTO = toCreateItemDTO(request);
ItemDTO itemDTO = itemService.createItem(createItemDTO);
return toResponse(itemDTO);
}
In fact, it is proposed to create another intermediate DTO (OrderDto), which additionally contains the main product field:
class OrderDto {
private List<ProductDto> productDtos;
private ProductDto mainProductDto;
// getters setters
}
into which the OrderRequest will be converted in the controller before passing it to the service layer, and the OrderEntity already in the service layer itself before working with it. And the method for obtaining the main product should be placed in mapper. Schematically looks like this:
---OrderRequest---> Controller(convert to OrderDto)
---OrderDto--> Service(Convert OrderEntity to OrderDto) <---OrderEntity--- JpaRepository
Actually, this leads to a lot of additional conversions within the service, some unusual work, for example, when updating an entity, now you have two intermediate dto (one from the controller for the update), the other from the repository (the entity found for the update and converted to intermediate dto in service layer) and you need to adopt the state of one into the other, and then map the result to the entity and do update.
In addition, it takes a lot of time to refactor the entire microservice.
The question is, is it a bad approach to have such methods in the entity and incoming dto, though these methods are used only in service layer, and is there another approach to refactor this?
If you need any clarification please let me now, thank you in advance!
Is there any way to check whether an entity's lazy collection attribute is initialized once we're outside the Transactional session?
For example, my Job entity has a lazy association to a list of Step, which is only sometimes initialized. In my controller, I would like to know if the steps collection has been initialized, although I'm met with a LazyInitializationException instead:
org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [com.overflow.stack.Job.steps] - no session and settings disallow loading outside the Session
at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.throwLazyInitializationException(EnhancementHelper.java:199) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.performWork(EnhancementHelper.java:89) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.loadAttribute(LazyAttributeLoadingInterceptor.java:76) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.fetchAttribute(LazyAttributeLoadingInterceptor.java:72) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.handleRead(LazyAttributeLoadingInterceptor.java:53) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at org.hibernate.bytecode.enhance.spi.interceptor.AbstractInterceptor.readObject(AbstractInterceptor.java:153) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
at com.overflow.stack.Job.$$_hibernate_read_steps(Job.java) ~[classes/:?]
at com.overflow.stack.Job.getSteps(Job.java:135) ~[classes/:?]
...
#Entity
public class Step {
...
}
#Entity
public class Job {
#OneToMany(mappedBy = "job", fetch = FetchType.LAZY)
private List<Step> steps = new ArrayList<>();
public List<Step> getSteps() {
return steps;
}
...
}
#Controller
public JobController {
public List<Step> getJobSteps(Long id) {
// Transactional service class method which fetches by id (does not initialize steps)
Job job = jobService.getJob(id);
if (Hibernate.isInitialized(job.getSteps())) { // Throws LazyInitializationException
return job.getSteps();
} else {
return List.of();
}
}
}
#Service
#Transactional
public JobService {
#Autowired JobRepository jobRepo;
public Job getJob(Long id) {
Job job = jobRepo.findById(id).get();
Hibernate.unproxy(job);
return job;
}
}
In this case, the exception is thrown when it attempts to execute job.getSteps() in the if statement.
I know that the session is closed by the time I leave the service call (the controller is not Transactional), although I'm wondering if there's any way to check if it's been initialized, or is the getSteps() method completely out of reach at that point?
Hibernate.isInitialized(job.getSteps()) shouldn't throw LazyInitializationException (LIE).
job.getSteps() returns a proxy, to have LIE you need to call a method of a proxy, for example job.getSteps().size().
job.getSteps() can throw LIE, if job is a proxy itself.
Anyway using entities as a response of REST API is a very bad idea. You will have a lot of issues with that.
Better to use JobEntity for an entity and Job for DTO. Just convert from JobEntity to Job on the service level. You can call Hibernate.isInitialized(jobEntity.getSteps()) on the service level and set an empty collection for the steps to the Job.
To unproxy entity
Hibernate.unproxy() returns a different object (not the same proxy). Also keep in mind that "If the proxy is uninitialized, it automatically triggers an initialization".
public Job getJob(Long id) {
Job job = jobRepo.findById(id).get();
return Hibernate.unproxy(job, Job.class);
}
In my project I have two domain models. A parent and a child entity. The parent references a list of child entitires. (e.g. Post and Comments) Both entities have their spring data JPA CrudRepository<Long, ModelClass> interfaces which are exposed as #RepositoryRestResource
HTTP GET and PUT operations work fine and return nice HATEOS representation of these models.
Now I need a special REST endpoint "create a new Parent that references one ore more already existing child entities". I'd like to POST the references to the children as a text/uri-list that I pass in the body of the request like this:
POST http://localhost:8080/api/v1/createNewParent
HEADER
Content-Type: text/uri-list
HTTP REQUEST BODY:
http://localhost:8080/api/v1/Child/4711
http://localhost:8080/api/v1/Child/4712
http://localhost:8080/api/v1/Child/4713
How do I implement this rest endpoint? This is what I tried so far:
#Autowired
ParentRepo parentRepo // Spring Data JPA repository for "parent" entity
#RequestMapping(value = "/createNewParent", method = RequestMethod.POST)
public #ResponseBody String createNewParentWithChildren(
#RequestBody Resources<ChildModel> childList,
PersistentEntityResourceAssembler resourceAssembler
)
{
Collection<ChildModel> childrenObjects = childList.getContent()
// Ok, this gives me the URIs I've posted
List<Link> links = proposalResource.getLinks();
// But now how to convert these URIs to domain objects???
List<ChildModel> listOfChildren = ... ???? ...
ParentModel newParnet = new ParentModel(listOfChildren)
parentRepo.save(newParent)
}
Reference / Related
https://github.com/spring-projects/spring-hateoas/issues/292
There is one related problem on a side note, that one also needs to take into account: When I want to save the parent entity, then I do not want to touch, save or alter the already existing child entity in any way. This is not that easy in JPA. Because JPA will also (try to) persist the dependant child entity. This fails with the exception:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist:
To circumvent that, you have to merge the child entity into the transactin of the JPA save() call. The only way I found to have both entities in one transaction was to create a seperate #Services which is marked as #Transactional. Seems like complete overkill and overengeneering.
Here is my code:
PollController.java // the custom REST endpoint for the PARENT entiy
#BasePathAwareController
public class PollController {
#RequestMapping(value = "/createNewPoll", method = RequestMethod.POST)
public #ResponseBody Resource createNewPoll(
#RequestBody Resource<PollModel> pollResource,
PersistentEntityResourceAssembler resourceAssembler
) throws LiquidoRestException
{
PollModel pollFromRequest = pollResource.getContent();
LawModel proposalFromRequest = pollFromRequest.getProposals().iterator().next(); // This propsal is a "detached entity". Cannot simply be saved.
//jpaContext.getEntityManagerByManagedType(PollModel.class).merge(proposal); // DOES NOT WORK IN SPRING. Must handle transaction via a seperate PollService class and #Transactional annotation there.
PollModel createdPoll;
try {
createdPoll = pollService.createPoll(proposalFromRequest, resourceAssembler);
} catch (LiquidoException e) {
log.warn("Cannot /createNewPoll: "+e.getMessage());
throw new LiquidoRestException(e.getMessage(), e);
}
PersistentEntityResource persistentEntityResource = resourceAssembler.toFullResource(createdPoll);
log.trace("<= POST /createNewPoll: created Poll "+persistentEntityResource.getLink("self").getHref());
return persistentEntityResource; // This nicely returns the HAL representation of the created poll
}
PollService.java // for transaction handling
#Service
public class PollService {
#Transactional // This should run inside a transaction (all or nothing)
public PollModel createPoll(#NotNull LawModel proposal, PersistentEntityResourceAssembler resourceAssembler) throws LiquidoException {
//===== some functional/business checks on the passed enties (must not be null etc)
//[...]
//===== create new Poll with one initial proposal
log.debug("Will create new poll. InitialProposal (id={}): {}", proposal.getId(), proposal.getTitle());
PollModel poll = new PollModel();
LawModel proposalInDB = lawRepo.findByTitle(proposal.getTitle()); // I have to lookup the proposal that I already have
Set<LawModel> linkedProposals = new HashSet<>();
linkedProposals.add(proposalInDB);
poll.setProposals(linkedProposals);
PollModel savedPoll = pollRepo.save(poll);
return savedPoll;
}
I had the same problem. The question is a bit old, but I found another solution :
In fact you have to force merge on parent but persis is called on creation. You could by-pass by saving parent with empty child list, add childs to list and saving again :
List<ChildModel> listOfChildren = ... ???? ...
ParentModel newParnet = new ParentModel()
parent = parentRepo.save(newParent)
parent.getChilds().addAll(listOfChildren)
parentRepo.save(parent)
To have access to merge, you have to code a Custom repo :
public interface PollModelRepositoryCustom {
public PollModel merge(PollModel poll);
}
and its implementation
#Repository
public class PollModelRepositoryCustomImpl implements PollModelRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public PollModel merge(PollModel poll) {
return entityManager.merge(poll);
}
}
then you can call : parentRepo.(newParent) instead of parentRepo.save(newParent)
How can one configure their JPA Entities to not fetch related entities unless a certain execution parameter is provided.
According to Spring's documentation, 4.3.9. Configuring Fetch- and LoadGraphs, you need to use the #EntityGraph annotation to specify fetch policy for queries, however this doesn't let me decide at runtime whether I want to load those entities.
I'm okay with getting the child entities in a separate query, but in order to do that I would need to configure my repository or entities to not retrieve any children. Unfortunately, I cannot seem to find any strategies on how to do this. FetchPolicy is ignored, and EntityGraph is only helpful when specifying which entities I want to eagerly retrieve.
For example, assume Account is the parent and Contact is the child, and an Account can have many Contacts.
I want to be able to do this:
if(fetchPolicy.contains("contacts")){
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}
The problem is spring-data eagerly fetches the contacts anyways.
The Account Entity class looks like this:
#Entity
#Table(name = "accounts")
public class Account
{
protected String accountId;
protected Collection<Contact> contacts;
#OneToMany
//#OneToMany(fetch=FetchType.LAZY) --> doesn't work, Spring Repositories ignore this
#JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
{
return contacts;
}
//getters & setters
}
The AccountRepository class looks like this:
public interface AccountRepository extends JpaRepository<Account, String>
{
//#EntityGraph ... <-- has type= LOAD or FETCH, but neither can help me prevent retrieval
Account findOne(String id);
}
The lazy fetch should be working properly if no methods of object resulted from the getContacts() is called.
If you prefer more manual work, and really want to have control over this (maybe more contexts depending on the use case). I would suggest you to remove contacts from the account entity, and maps the account in the contacts instead. One way to tell hibernate to ignore that field is to map it using the #Transient annotation.
#Entity
#Table(name = "accounts")
public class Account
{
protected String accountId;
protected Collection<Contact> contacts;
#Transient
public Collection<Contact> getContacts()
{
return contacts;
}
//getters & setters
}
Then in your service class, you could do something like:
public Account getAccountById(int accountId, Set<String> fetchPolicy) {
Account account = accountRepository.findOne(accountId);
if(fetchPolicy.contains("contacts")){
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}
return account;
}
Hope this is what you are looking for. Btw, the code is untested, so you should probably check again.
You can use #Transactional for that.
For that you need to fetch you account entity Lazily.
#Transactional Annotations should be placed around all operations that are inseparable.
Write method in your service layer which is accepting one flag to fetch contacts eagerly.
#Transactional
public Account getAccount(String id, boolean fetchEagerly){
Account account = accountRepository.findOne(id);
//If you want to fetch contact then send fetchEagerly as true
if(fetchEagerly){
//Here fetching contacts eagerly
Object object = account.getContacts().size();
}
}
#Transactional is a Service that can make multiple call in single transaction
without closing connection with end point.
Hope you find this useful. :)
For more details refer this link
Please find an example which runs with JPA 2.1.
Set the attribute(s) you only want to load (with attributeNodes list) :
Your entity with Entity graph annotations :
#Entity
#NamedEntityGraph(name = "accountGraph", attributeNodes = {
#NamedAttributeNode("accountId")})
#Table(name = "accounts")
public class Account {
protected String accountId;
protected Collection<Contact> contacts;
#OneToMany(fetch=FetchType.LAZY)
#JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
{
return contacts;
}
}
Your custom interface :
public interface AccountRepository extends JpaRepository<Account, String> {
#EntityGraph("accountGraph")
Account findOne(String id);
}
Only the "accountId" property will be loaded eagerly. All others properties will be loaded lazily on access.
Spring data does not ignore fetch=FetchType.Lazy.
My problem was that I was using dozer-mapping to covert my entities to graphs. Evidently dozer calls the getters and setters to map two objects, so I needed to add a custom field mapper configuration to ignore PersistentCollections...
GlobalCustomFieldMapper.java:
public class GlobalCustomFieldMapper implements CustomFieldMapper
{
public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping)
{
if (!(sourceFieldValue instanceof PersistentCollection)) {
// Allow dozer to map as normal
return;
}
if (((PersistentCollectiosourceFieldValue).wasInitialized()) {
// Allow dozer to map as normal
return false;
}
// Set destination to null, and tell dozer that the field is mapped
destination = null;
return true;
}
}
If you are trying to send the resultset of your entities to a client, I recommend you use data transfer objects(DTO) instead of the entities. You can directly create a DTO within the HQL/JPQL.
For example
"select new com.test.MyTableDto(my.id, my.name) from MyTable my"
and if you want to pass the child
"select new com.test.MyTableDto(my.id, my.name, my.child) from MyTable my"
That way you have a full control of what is being created and passed to client.
I'm using spring 3 with hibernate 3.5.4
1- I want to create an object in transaction and save it to DB ( which passes successfully ).
2- I want to update some fields in that object (same object) and updates in in DB in another transaction (and here is the problem).
The problem is, is saves the object successfully in the first transaction but it doesn't update it in DB in the second one.
here is code example:
public String entry(String str){
Bill b = create(str);
b = update(b);
b = updateAgain(b);
return "DONE";
}
#Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public Bill create(String num){
Bill bill = new Bill();
bill.setBillNumber(num);
baseDao.saveObject(bill);
return bill;
}
#Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public Bill update(Bill bill){
bill.setRetailAmount(152.0);
baseDao.saveObject(bill);
return bill;
}
NOTE: I don't want to put the #transactional annotation on method "entry".
Thanks,
The annotation will not take affect if called on a method within the same class. AOP cannot intercept that through proxy. Move your entry method outside the class.
EDIT: Spring enables the Transactional annotation via annotation-driven AOP with proxies or sub-classing. When enabled with proxies, your proxy is out of the picture in a local method call. This blog post has a good explanation with pictures.