generator-jhipster: LazyInitializationException on an entity with ManyToMany, pagination and mapstruct

Overview of the issue

Hibernate throws an exception when trying to get all Job entities after creating a Job entity.

Exception in com.jhipster.web.rest.JobResource.getAllJobs() with cause = 'NULL' and exception = 'failed to lazily initialize a collection of role: com.jhipster.domain.Job.tasks, could not initialize proxy - no Session'

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.jhipster.domain.Job.tasks, could not initialize proxy - no Session
	at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:582)
	at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:201)
	at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:561)
	at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:132)
	at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:163)
	at com.jhipster.service.mapper.JobMapperImpl.taskSetToTaskDTOSet(JobMapperImpl.java:140)
	at com.jhipster.service.mapper.JobMapperImpl.jobToJobDTO(JobMapperImpl.java:58)
	at com.jhipster.service.mapper.JobMapperImpl.jobsToJobDTOs(JobMapperImpl.java:81)
Motivation for or Use Case

This issue only appears when having an entity with a ManyToMany relationship, being paginated (pager, pagination or infinite-scroll) and dto generated with mapstruct. It’s a little specific but if you use the default JDL from JDL Studio you will have the issue.

Reproduce the error

Use the JDL below to generate entities. Create a Job entity with no Task. An exception will be thrown when displaying all Job.

Related issues

Found nothing related.

Suggest a Fix

Maybe by doing an eager load on the ManyToMany relationship like it’s done when Job is not paginated.

JHipster Version(s)

4.3.0

JHipster configuration
JHipster Version(s)
jhipster@0.0.0 /Users/Theo/Documents/perso/jhipster-issues
└── generator-jhipster@4.3.0

JHipster configuration, a .yo-rc.json file generated in the root folder
{
  "generator-jhipster": {
    "promptValues": {
      "packageName": "com.jhipster"
    },
    "jhipsterVersion": "4.3.0",
    "baseName": "jhipster",
    "packageName": "com.jhipster",
    "packageFolder": "com/jhipster",
    "serverPort": "8080",
    "authenticationType": "session",
    "hibernateCache": "ehcache",
    "clusteredHttpSession": false,
    "websocket": false,
    "databaseType": "sql",
    "devDatabaseType": "h2Disk",
    "prodDatabaseType": "mysql",
    "searchEngine": false,
    "messageBroker": false,
    "serviceDiscoveryType": false,
    "buildTool": "maven",
    "enableSocialSignIn": false,
    "rememberMeKey": "2b4523900caffa3f139b0240dc516cfd9735ba18",
    "clientFramework": "angular1",
    "useSass": false,
    "clientPackageManager": "yarn",
    "applicationType": "monolith",
    "testFrameworks": [],
    "jhiPrefix": "jhi",
    "enableTranslation": false
  }
}
Entity configuration(s) entityName.json files generated in the .jhipster directory

Job.json

{
    "fluentMethods": true,
    "relationships": [
        {
            "relationshipType": "many-to-many",
            "otherEntityRelationshipName": "job",
            "relationshipName": "task",
            "otherEntityName": "task",
            "otherEntityField": "id",
            "ownerSide": true
        }
    ],
    "fields": [
        {
            "fieldName": "jobTitle",
            "fieldType": "String"
        }
    ],
    "changelogDate": "20170420032832",
    "entityTableName": "job",
    "dto": "mapstruct",
    "pagination": "pagination",
    "service": "no"
}

Task.json

{
    "fluentMethods": true,
    "relationships": [
        {
            "relationshipType": "many-to-many",
            "relationshipName": "job",
            "otherEntityName": "job",
            "ownerSide": false,
            "otherEntityRelationshipName": "task"
        }
    ],
    "fields": [
        {
            "fieldName": "title",
            "fieldType": "String"
        }
    ],
    "changelogDate": "20170420032831",
    "entityTableName": "task",
    "dto": "mapstruct",
    "pagination": "no",
    "service": "no"
}
Browsers and Operating System

java version “1.8.0_92” Java™ SE Runtime Environment (build 1.8.0_92-b14) Java HotSpot™ 64-Bit Server VM (build 25.92-b14, mixed mode)

git version 2.10.1 (Apple Git-78)

node: v7.8.0

npm: 4.2.0

bower: 1.8.0

gulp: [23:47:04] CLI version 1.2.2 [23:47:04] Local version 3.9.1

yeoman: 1.8.5

yarn: 0.22.0

Docker version 17.03.1-ce, build c6d412e

docker-compose version 1.11.2, build dfed245

Entity configuration(s) entityName.json files generated in the .jhipster directory

JDL

entity Task {
	title String
}

entity Job {
	jobTitle String
}

relationship ManyToMany {
	Job{task} to Task{job}
}

paginate Job with pagination
dto * with mapstruct
Browsers and Operating System

Chrome 57 on OS X El Capitan

  • Checking this box is mandatory (this is just to show you read everything)

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 48 (43 by maintainers)

Commits related to this issue

Most upvoted comments

@gzsombor @jdubois what you think of showing DTO and Filter option only when Service is selected? it will atleast solve these kind of issues

The better solution is to fix the transaction handling :

  • either put the mapper code inside the service (which already has @Transactional annotation)
  • or if you insist on not using services, put @Transactional annotation on your rest methods.

Basically, you need to access your database from a code block, which is in a ‘JPA/hibernate session’

I just realized that many-to-many are currently fetched eagerly in the repository (findAllWithEagerRelationships, findOneWithEagerRelationships). I don’t think this is a good idea:

  • LAZY should always be the default
  • Eagerly fetching one level will not save you from LazyInitializationException if the graph is more than one level (A has many B, B has many C, …)

Yes I would also prefer to suppress the DTO and filtering option when service is not selected. It will reduce lot of headaches for us

Thanks & Regards, Deepu

On Mon, Dec 4, 2017 at 1:59 PM, Mathieu ABOU-AICHI <notifications@github.com

wrote:

@jdubois https://github.com/jdubois what I can do is issue the warning if the options are “incompatible” as JDL-parsing time.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/jhipster/generator-jhipster/issues/5629#issuecomment-348955424, or mute the thread https://github.com/notifications/unsubscribe-auth/ABDlF1-MheiTIQwNp5PXoBoT5aXUxp2nks5s8-zNgaJpZM4NChK8 .

My position is still the same :

  • we should not fetch the child collections in our generated endpoints. (JsonIgnore)
  • we should provide other endpoints to fetch those collections (eg. /jobs/{id}/tasks)
  • we should adapt our generated frontend to those endpoints. (eg. not displaying the list of child ids in the grid but a link to a grid of them)

The main problem for that approach would be generation since we don’t know which entities point to the entity we are generating.(we know if a one-to-many is unidir when generating the many side since it’s the owner of the relationship in this case, and not when the rel is bidir)

Other advantages of the REST link:

  • solves the N+1 select issue
  • solves the circular ref issue so we can have the link also for the non owner side of the many-to-many

For the form, it could be done as an HATEOAS-compliant links property in the response: GET /api/jobs (paginated)

[ {  
    "id": 1001,
    "jobTitle": "some job",
    "links": [ {
        "rel": "tasks",
        "href": "http://localhost:8080/api/jobs/1001/tasks"
    } ]
  },
  {  
    "id": 1003,
    "jobTitle": "some other job",
    "links": [ {
        "rel": "tasks",
        "href": "http://localhost:8080/api/jobs/1003/tasks"
    } ]
  }
]

GET /api/tasks (not paginated)

[ {  
    "id": 1002,
    "title": "some task",
    "links": [ {
        "rel": "jobs",
        "href": "http://localhost:8080/api/tasks/1002/jobs"
    } ]
} ]

GET /api/tasks/1002/jobs (paginated) where task 1002 is linked to job 1001 but not to job 1003

[ {  
    "id": 1001,
    "jobTitle": "some job",
    "links": [ {
        "rel": "tasks",
        "href": "http://localhost:8080/api/jobs/1001/tasks"
    } ]
} ]

Nobody is working on it because that doesn’t seem to interest anyone… People just have better things to do, that’s all. I would have a solution if I had time to work on this: as far as I’m concerned, MapStruct is still marked BETA, and I’m not using it personally, so this is not my priority.

If you change the line: “service all with serviceImpl except Employee, Job” by: “service all with serviceImpl”

If “Job” has service, it works correctly