├── .gitignore
├── src
├── test
│ ├── resources
│ │ ├── com
│ │ │ └── javaetmoi
│ │ │ │ └── core
│ │ │ │ └── persistence
│ │ │ │ └── hibernate
│ │ │ │ ├── TestIssue10-dataset.xml
│ │ │ │ ├── TestIssue1-dataset.xml
│ │ │ │ ├── TestIssue50-dataset.xml
│ │ │ │ ├── TestIssue2-dataset.xml
│ │ │ │ ├── HydratorImplTest-dataset.xml
│ │ │ │ └── TestIssue3-dataset.xml
│ │ ├── logback-test.xml
│ │ └── META-INF
│ │ │ └── persistence.xml
│ └── java
│ │ └── com
│ │ └── javaetmoi
│ │ └── core
│ │ └── persistence
│ │ └── hibernate
│ │ ├── joinInheritance
│ │ ├── ParentClass.java
│ │ ├── SubClass.java
│ │ ├── Data.java
│ │ └── ParentReference.java
│ │ ├── listWithEmbeddable
│ │ ├── Event.java
│ │ ├── Transfer.java
│ │ ├── SubPlan.java
│ │ └── Plan.java
│ │ ├── manyToOneList
│ │ ├── SubSystem.java
│ │ ├── BaseSystem.java
│ │ ├── Holder.java
│ │ └── System.java
│ │ ├── domain
│ │ ├── Passport.java
│ │ ├── Customer.java
│ │ ├── Foo.java
│ │ ├── Biz.java
│ │ ├── Child.java
│ │ ├── Person.java
│ │ ├── Bar.java
│ │ ├── Parent.java
│ │ ├── Country.java
│ │ ├── Project.java
│ │ ├── Address.java
│ │ └── Employee.java
│ │ ├── TestIssue50.java
│ │ ├── TestIssue10.java
│ │ ├── TestIssue1.java
│ │ ├── TestIssue2.java
│ │ ├── TestIssue3.java
│ │ ├── AbstractTest.java
│ │ ├── DBUnitLoader.java
│ │ └── HydratorImplTest.java
└── main
│ └── java
│ └── com
│ └── javaetmoi
│ └── core
│ └── persistence
│ └── hibernate
│ ├── Hydrator.java
│ ├── JpaLazyLoadingUtil.java
│ ├── LazyLoadingUtil.java
│ └── HydratorImpl.java
├── .github
└── workflows
│ ├── build.yml
│ └── release.yml
├── readme.md
├── LICENSE.txt
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse files
2 | .project
3 | .classpath
4 | .settings
5 |
6 | # Compiled classes
7 | target
8 |
9 | .DS_Store
10 |
11 | # Idea IntelliJ
12 | .idea
13 | *.iml
--------------------------------------------------------------------------------
/src/test/resources/com/javaetmoi/core/persistence/hibernate/TestIssue10-dataset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/test/resources/com/javaetmoi/core/persistence/hibernate/TestIssue1-dataset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/test/resources/com/javaetmoi/core/persistence/hibernate/TestIssue50-dataset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/test/resources/com/javaetmoi/core/persistence/hibernate/TestIssue2-dataset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/joinInheritance/ParentClass.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.joinInheritance;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.Id;
5 | import jakarta.persistence.Inheritance;
6 | import jakarta.persistence.InheritanceType;
7 |
8 | @Entity
9 | @Inheritance(strategy = InheritanceType.JOINED)
10 | public class ParentClass {
11 |
12 | @Id
13 | private Integer id;
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/listWithEmbeddable/Event.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.listWithEmbeddable;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.Id;
5 |
6 | @Entity
7 | public class Event {
8 | @Id
9 | private Integer id;
10 | private String name;
11 |
12 | public Integer getId() {
13 | return id;
14 | }
15 |
16 | public void setId(Integer id) {
17 | this.id = id;
18 | }
19 |
20 | public String getName() {
21 | return name;
22 | }
23 |
24 | public void setName(String name) {
25 | this.name = name;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss.SSS} | %5p | %-200m | %C.%M\(%F:%L\)%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/joinInheritance/SubClass.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.joinInheritance;
2 |
3 | import java.util.List;
4 |
5 | import jakarta.persistence.Entity;
6 | import jakarta.persistence.JoinColumn;
7 | import jakarta.persistence.OneToMany;
8 |
9 | @Entity
10 | public class SubClass extends ParentClass {
11 |
12 | @OneToMany
13 | @JoinColumn(name = "DATA_ID")
14 | List datas;
15 |
16 |
17 | public List getDatas() {
18 | return datas;
19 | }
20 |
21 | public void setDatas(List datas) {
22 | this.datas = datas;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/joinInheritance/Data.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.joinInheritance;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.Id;
5 |
6 | @Entity
7 | public class Data {
8 |
9 | @Id
10 | private Integer id;
11 |
12 | private String name;
13 |
14 | public Integer getId() {
15 | return id;
16 | }
17 |
18 | public void setId(Integer id) {
19 | this.id = id;
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | public void setName(String name) {
27 | this.name = name;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/joinInheritance/ParentReference.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.joinInheritance;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.Id;
5 | import jakarta.persistence.OneToOne;
6 |
7 | // Couple
8 | @Entity
9 | public class ParentReference {
10 | @Id
11 | private Integer id;
12 |
13 | @OneToOne
14 | private ParentClass parent;
15 |
16 | public ParentClass getParent() {
17 | return parent;
18 | }
19 |
20 | public void setParent(ParentClass parent) {
21 | this.parent = parent;
22 | }
23 |
24 | public Integer getId() {
25 | return id;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/manyToOneList/SubSystem.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.manyToOneList;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.JoinColumn;
5 | import jakarta.persistence.ManyToOne;
6 |
7 | import org.hibernate.annotations.Cascade;
8 | import org.hibernate.annotations.CascadeType;
9 |
10 | @Entity
11 | public class SubSystem extends BaseSystem {
12 |
13 | @ManyToOne
14 | @Cascade(CascadeType.ALL)
15 | @JoinColumn(name = "SYSTEM_KEY_FK", referencedColumnName = "SYSTEM_KEY_PK")
16 | private System parent;
17 |
18 | public System getParent() {
19 | return parent;
20 | }
21 |
22 | public void setParent(System parent) {
23 | this.parent = parent;
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Passport.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.domain;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.Id;
5 | import jakarta.persistence.OneToOne;
6 | import java.io.Serializable;
7 |
8 | @Entity
9 | public class Passport implements Serializable {
10 |
11 | @Id
12 | private Integer id;
13 |
14 | @OneToOne(mappedBy = "passport")
15 | private Customer owner;
16 |
17 | public Integer getId() {
18 | return id;
19 | }
20 |
21 | public void setId(Integer id) {
22 | this.id = id;
23 | }
24 |
25 | public Customer getOwner() {
26 | return owner;
27 | }
28 |
29 | public void setOwner(Customer owner) {
30 | this.owner = owner;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Customer.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.domain;
2 |
3 | import jakarta.persistence.*;
4 | import java.io.Serializable;
5 |
6 | @Entity
7 | public class Customer implements Serializable {
8 |
9 | @Id
10 | private Integer id;
11 |
12 | @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
13 | @JoinColumn(name = "passport_fk")
14 | private Passport passport;
15 |
16 | public Integer getId() {
17 | return id;
18 | }
19 |
20 | public void setId(Integer id) {
21 | this.id = id;
22 | }
23 |
24 | public Passport getPassport() {
25 | return passport;
26 | }
27 |
28 | public void setPassport(Passport passport) {
29 | this.passport = passport;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/test/resources/com/javaetmoi/core/persistence/hibernate/HydratorImplTest-dataset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/listWithEmbeddable/Transfer.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.listWithEmbeddable;
2 |
3 | import jakarta.persistence.Embeddable;
4 | import jakarta.persistence.JoinColumn;
5 | import jakarta.persistence.ManyToOne;
6 |
7 | import org.hibernate.annotations.Cascade;
8 | import org.hibernate.annotations.CascadeType;
9 |
10 | @Embeddable
11 | public class Transfer {
12 |
13 | private String name;
14 | @ManyToOne
15 | @JoinColumn(name = "SUBPLAN")
16 | @Cascade(CascadeType.ALL)
17 | private SubPlan subPlan;
18 |
19 | public SubPlan getSubPlan() {
20 | return subPlan;
21 | }
22 |
23 | public void setSubPlan(SubPlan subPlan) {
24 | this.subPlan = subPlan;
25 | }
26 |
27 | public String getName() {
28 | return name;
29 | }
30 |
31 | public void setName(String name) {
32 | this.name = name;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/TestIssue50.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import com.javaetmoi.core.persistence.hibernate.joinInheritance.ParentReference;
6 | import com.javaetmoi.core.persistence.hibernate.joinInheritance.SubClass;
7 |
8 |
9 | import static org.junit.jupiter.api.Assertions.*;
10 |
11 |
12 | class TestIssue50 extends AbstractTest {
13 |
14 | @Test
15 | void should_lazyload_subclass_with_join_inheritance() {
16 | var dbContainer = findDeepHydratedEntity(ParentReference.class, 1);
17 |
18 | assertNotNull(dbContainer);
19 | assertEquals(Integer.valueOf(1), dbContainer.getId());
20 | assertInstanceOf(SubClass.class, dbContainer.getParent());
21 | assertEquals(1, ((SubClass) dbContainer.getParent()).getDatas().size());
22 | assertEquals("data", ((SubClass) dbContainer.getParent()).getDatas().get(0).getName());
23 | }
24 | }
--------------------------------------------------------------------------------
/src/test/resources/com/javaetmoi/core/persistence/hibernate/TestIssue3-dataset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
18 |
19 |
20 |
22 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/manyToOneList/BaseSystem.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.manyToOneList;
2 |
3 | import jakarta.persistence.GeneratedValue;
4 | import jakarta.persistence.GenerationType;
5 | import jakarta.persistence.Id;
6 | import jakarta.persistence.MappedSuperclass;
7 |
8 | @MappedSuperclass
9 | public abstract class BaseSystem {
10 | @Id
11 | @GeneratedValue(strategy = GenerationType.AUTO)
12 | private Integer id;
13 | private String systemNumber;
14 | private String name;
15 |
16 | public Integer getId() {
17 | return id;
18 | }
19 |
20 | public void setId(Integer id) {
21 | this.id = id;
22 | }
23 |
24 | public String getSystemNumber() {
25 | return systemNumber;
26 | }
27 |
28 | public void setSystemNumber(String systemNumber) {
29 | this.systemNumber = systemNumber;
30 | }
31 |
32 | public String getName() {
33 | return name;
34 | }
35 |
36 | public void setName(String name) {
37 | this.name = name;
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/TestIssue10.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate;
2 |
3 | import com.javaetmoi.core.persistence.hibernate.domain.Customer;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.junit.jupiter.api.Assertions.assertEquals;
7 | import static org.junit.jupiter.api.Assertions.assertNotNull;
8 |
9 | /**
10 | * Unit test for issue 10: Test reference back to parent does not cause infinite recursion.
11 | *
12 | * @see Issue 10
13 | */
14 | class TestIssue10 extends AbstractTest {
15 | @Test
16 | void oneToOneBidirectionalRelationship() {
17 | var dbCustomer = findDeepHydratedEntity(Customer.class, 1);
18 |
19 | assertEquals(1, dbCustomer.getId());
20 | assertNotNull(dbCustomer.getPassport());
21 | assertEquals(1, dbCustomer.getPassport().getId());
22 | assertEquals(1, dbCustomer.getPassport().getOwner().getId());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/manyToOneList/Holder.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.manyToOneList;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.GeneratedValue;
5 | import jakarta.persistence.GenerationType;
6 | import jakarta.persistence.Id;
7 | import jakarta.persistence.JoinColumn;
8 | import jakarta.persistence.ManyToOne;
9 |
10 | import org.hibernate.annotations.Fetch;
11 | import org.hibernate.annotations.FetchMode;
12 |
13 | @Entity
14 | public class Holder {
15 | @Id
16 | @GeneratedValue(strategy = GenerationType.AUTO)
17 | private Integer id;
18 | @ManyToOne
19 | @JoinColumn(name = "system")
20 | @Fetch(FetchMode.JOIN)
21 | private System system;
22 |
23 | public Integer getId() {
24 | return id;
25 | }
26 |
27 | public void setId(Integer id) {
28 | this.id = id;
29 | }
30 |
31 | public System getSystem() {
32 | return system;
33 | }
34 |
35 | public void setSystem(System system) {
36 | this.system = system;
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/listWithEmbeddable/SubPlan.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.listWithEmbeddable;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import jakarta.persistence.Entity;
7 | import jakarta.persistence.Id;
8 | import jakarta.persistence.ManyToMany;
9 |
10 | import org.hibernate.annotations.Cascade;
11 | import org.hibernate.annotations.CascadeType;
12 | import org.hibernate.annotations.Fetch;
13 | import org.hibernate.annotations.FetchMode;
14 |
15 | @Entity
16 | public class SubPlan {
17 | @Id
18 | private Integer id;
19 |
20 | @ManyToMany
21 | @Fetch(FetchMode.SUBSELECT)
22 | @Cascade(value = { CascadeType.SAVE_UPDATE, CascadeType.DELETE })
23 | private List events = new ArrayList();
24 |
25 | public Integer getId() {
26 | return id;
27 | }
28 |
29 | public void setId(Integer id) {
30 | this.id = id;
31 | }
32 |
33 | public List getEvents() {
34 | return events;
35 | }
36 |
37 | public void setEvents(List events) {
38 | this.events = events;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/manyToOneList/System.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.manyToOneList;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import jakarta.persistence.AttributeOverride;
7 | import jakarta.persistence.AttributeOverrides;
8 | import jakarta.persistence.Column;
9 | import jakarta.persistence.Entity;
10 | import jakarta.persistence.OneToMany;
11 | import jakarta.persistence.OrderBy;
12 |
13 | import org.hibernate.annotations.Cascade;
14 | import org.hibernate.annotations.CascadeType;
15 |
16 | @Entity
17 | @AttributeOverrides(value = { @AttributeOverride(name = "id", column = @Column(name = "SYSTEM_KEY_PK")) })
18 | public class System extends BaseSystem {
19 | @OneToMany(mappedBy = "parent", orphanRemoval = true)
20 | @Cascade(CascadeType.ALL)
21 | @OrderBy(value = "systemNumber asc")
22 | private List subSystems = new ArrayList();
23 |
24 | public List getSubSystems() {
25 | return subSystems;
26 | }
27 |
28 | public void setSubSystems(List subSystems) {
29 | this.subSystems = subSystems;
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 | name: Hibernate ${{ matrix.mapper }} Build
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | mapper: [ last, hibernate-6.1, hibernate-6.2, hibernate-6.3, hibernate-6.4, hibernate-6.5 ]
19 | steps:
20 | - uses: actions/checkout@v3
21 | - name: Set up JDK 17
22 | uses: actions/setup-java@v3
23 | with:
24 | distribution: 'temurin'
25 | java-version: '17'
26 | - name: Cache Maven packages
27 | uses: actions/cache@v3
28 | with:
29 | path: ~/.m2/repository
30 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
31 | restore-keys: ${{ runner.os }}-m2
32 | - name: Build with Maven
33 | run: mvn --batch-mode --update-snapshots verify -P ${{ matrix.mapper }}
34 |
--------------------------------------------------------------------------------
/src/test/resources/META-INF/persistence.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Foo.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import jakarta.persistence.Embedded;
17 | import jakarta.persistence.Entity;
18 | import jakarta.persistence.Id;
19 |
20 | @Entity
21 | public class Foo {
22 |
23 | @Id
24 | private Integer id;
25 |
26 | @Embedded
27 | public Bar bar = new Bar();
28 |
29 | public Integer getId() {
30 | return id;
31 | }
32 |
33 | public Bar getBar() {
34 | return bar;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Biz.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import jakarta.persistence.Entity;
17 | import jakarta.persistence.Id;
18 |
19 | @Entity
20 | public class Biz {
21 |
22 | @Id
23 | private Integer id;
24 |
25 | private String name;
26 |
27 | public String getName() {
28 | return name;
29 | }
30 |
31 | public void setName(String name) {
32 | this.name = name;
33 | }
34 |
35 | public Integer getId() {
36 | return id;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/listWithEmbeddable/Plan.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate.listWithEmbeddable;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import jakarta.persistence.CollectionTable;
7 | import jakarta.persistence.ElementCollection;
8 | import jakarta.persistence.Entity;
9 | import jakarta.persistence.Id;
10 | import jakarta.persistence.JoinColumn;
11 | import jakarta.persistence.OrderColumn;
12 |
13 | import org.hibernate.annotations.Cascade;
14 | import org.hibernate.annotations.CascadeType;
15 | import org.hibernate.annotations.Fetch;
16 | import org.hibernate.annotations.FetchMode;
17 |
18 |
19 |
20 | @Entity
21 | public class Plan {
22 | @Id
23 | private Integer id;
24 | @ElementCollection
25 | @Fetch(FetchMode.SELECT)
26 | @OrderColumn(name = "elementOrder")
27 | @Cascade(CascadeType.ALL)
28 | @CollectionTable(name = "TRANSFER", joinColumns = @JoinColumn(name = "PLAN_ID"))
29 | private List transfers = new ArrayList();
30 |
31 | public Integer getId() {
32 | return id;
33 | }
34 |
35 | public void setId(Integer id) {
36 | this.id = id;
37 | }
38 |
39 | public List getTransfers() {
40 | return transfers;
41 | }
42 |
43 | public void setTransfers(List transfers) {
44 | this.transfers = transfers;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Child.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import jakarta.persistence.Entity;
17 | import jakarta.persistence.JoinColumn;
18 | import jakarta.persistence.ManyToOne;
19 | import jakarta.persistence.Table;
20 |
21 | import org.hibernate.annotations.Cascade;
22 | import org.hibernate.annotations.CascadeType;
23 |
24 | @Entity
25 | @Table(name = "Child")
26 | public class Child extends Person {
27 |
28 | @ManyToOne
29 | @Cascade(CascadeType.ALL)
30 | @JoinColumn(name = "parentKey_FK", referencedColumnName = "parentKey")
31 | private Parent parent;
32 |
33 | public Parent getParent() {
34 | return parent;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Person.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import jakarta.persistence.GeneratedValue;
17 | import jakarta.persistence.GenerationType;
18 | import jakarta.persistence.Id;
19 | import jakarta.persistence.MappedSuperclass;
20 |
21 | @MappedSuperclass
22 | public abstract class Person {
23 |
24 | @Id
25 | @GeneratedValue(strategy = GenerationType.AUTO)
26 | private Long id;
27 | private String number;
28 | private String name;
29 |
30 | public Long getId() {
31 | return id;
32 | }
33 |
34 | public String getNumber() {
35 | return number;
36 | }
37 |
38 | public String getName() {
39 | return name;
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Bar.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import jakarta.persistence.Embeddable;
20 | import jakarta.persistence.JoinColumn;
21 | import jakarta.persistence.OneToMany;
22 |
23 | import org.hibernate.annotations.Cascade;
24 | import org.hibernate.annotations.CascadeType;
25 | import org.hibernate.annotations.Fetch;
26 | import org.hibernate.annotations.FetchMode;
27 |
28 | @Embeddable
29 | public class Bar {
30 |
31 | @OneToMany
32 | @Fetch(FetchMode.SELECT)
33 | @Cascade(CascadeType.ALL)
34 | @JoinColumn(name = "FOO_ID")
35 | private List bizs = new ArrayList();
36 |
37 | public List getBizs() {
38 | return bizs;
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/TestIssue1.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate;
15 |
16 | import com.javaetmoi.core.persistence.hibernate.domain.Foo;
17 | import org.junit.jupiter.api.Test;
18 |
19 | import static org.junit.jupiter.api.Assertions.assertEquals;
20 | import static org.junit.jupiter.api.Assertions.assertNotNull;
21 |
22 | /**
23 | * Unit test for issue 1.
24 | *
25 | * @see Issue 1
26 | */
27 | class TestIssue1 extends AbstractTest {
28 | @Test
29 | void nestedListInEmbeddable() {
30 | var dbFoo = findDeepHydratedEntity(Foo.class, 1);
31 |
32 | assertNotNull(dbFoo.getBar());
33 | assertNotNull(dbFoo.getBar().getBizs());
34 | assertEquals(2, dbFoo.getBar().getBizs().size(), "Fix the LazyInitializationException");
35 | assertNotNull(dbFoo.getBar().getBizs().get(0));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/TestIssue2.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate;
15 |
16 | import com.javaetmoi.core.persistence.hibernate.domain.Parent;
17 | import org.junit.jupiter.api.Test;
18 |
19 | import static org.junit.jupiter.api.Assertions.assertEquals;
20 | import static org.junit.jupiter.api.Assertions.assertNotNull;
21 |
22 | /**
23 | * Unit test for issue 2.
24 | *
25 | * @see Issue 2
26 | */
27 | class TestIssue2 extends AbstractTest {
28 | @Test
29 | void nestedListUsingMappedSuperclass() {
30 | var dbParent = findDeepHydratedEntity(Parent.class, 1);
31 |
32 | assertEquals(Long.valueOf(1), dbParent.getId());
33 | assertEquals("Parent 1", dbParent.getName());
34 | assertEquals(2, dbParent.getChildren().size());
35 | assertEquals("Child 10", dbParent.getChildren().get(0).getName());
36 | assertEquals("Parent 1", dbParent.getChildren().get(0).getParent().getName());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Parent.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import jakarta.persistence.AttributeOverride;
20 | import jakarta.persistence.AttributeOverrides;
21 | import jakarta.persistence.Column;
22 | import jakarta.persistence.Entity;
23 | import jakarta.persistence.OneToMany;
24 | import jakarta.persistence.OrderBy;
25 | import jakarta.persistence.Table;
26 |
27 | import org.hibernate.annotations.Cascade;
28 | import org.hibernate.annotations.CascadeType;
29 |
30 | @Entity
31 | @Table(name = "Parent")
32 | @AttributeOverrides(value = { @AttributeOverride(name = "id", column = @Column(name = "parentKey")) })
33 | public class Parent extends Person {
34 |
35 | @OneToMany(mappedBy = "parent", orphanRemoval = true)
36 | @Cascade(CascadeType.ALL)
37 | @OrderBy(value = "number asc")
38 | private List children = new ArrayList();
39 |
40 | public List getChildren() {
41 | return children;
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Country.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import jakarta.persistence.Entity;
17 | import jakarta.persistence.Id;
18 |
19 | import org.apache.commons.lang3.builder.EqualsBuilder;
20 | import org.apache.commons.lang3.builder.HashCodeBuilder;
21 | import org.apache.commons.lang3.builder.ToStringBuilder;
22 |
23 | @Entity
24 | public class Country {
25 |
26 | @Id
27 | private Integer id;
28 |
29 | private String name;
30 |
31 | public Country() {
32 |
33 | }
34 |
35 | public Country(Integer id, String name) {
36 | this.id = id;
37 | this.name = name;
38 | }
39 |
40 | public Integer getId() {
41 | return id;
42 | }
43 |
44 | public void setId(Integer id) {
45 | this.id = id;
46 | }
47 |
48 | public String getName() {
49 | return name;
50 | }
51 |
52 | public void setName(String name) {
53 | this.name = name;
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return new HashCodeBuilder(17, 37).append(id).toHashCode();
59 | }
60 |
61 | @Override
62 | public boolean equals(Object obj) {
63 | if (this == obj) {
64 | return true;
65 | }
66 | if (obj == null) {
67 | return false;
68 | }
69 | if (!Country.class.isAssignableFrom(obj.getClass())) {
70 | return false;
71 | }
72 |
73 | Country other = (Country) obj;
74 |
75 | return new EqualsBuilder().append(id, other.id).append(this.name, other.name).isEquals();
76 | }
77 |
78 | @Override
79 | public String toString() {
80 | return new ToStringBuilder(this).append(id).append(name).build();
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This workflow does a Maven Release then publishes the artefacts into Maven Central.
2 | #
3 | # Sources :
4 | # - http://bjansen.github.io/java/2021/02/03/deploying-to-maven-central-using-github-actions.html
5 | # - https://blog.frankel.ch/github-actions-maven-releases/
6 | # - https://stackoverflow.com/questions/58254332/maven-release-from-github-actions
7 | # - https://gist.github.com/faph/20331648cdc0b492eba0f4d83f69eaa5
8 |
9 | name: Maven Release
10 |
11 | on:
12 | workflow_dispatch:
13 | inputs:
14 | releaseVersion:
15 | description: "Default version to use when preparing a release."
16 | required: true
17 | default: "X.Y.Z"
18 | developmentVersion:
19 | description: "Default version to use for new local working copy."
20 | required: true
21 | default: "X.Y.Z-SNAPSHOT"
22 |
23 | jobs:
24 | release:
25 | name: Release on Sonatype OSS
26 | runs-on: ubuntu-latest
27 |
28 | steps:
29 | - name: Checkout project
30 | uses: actions/checkout@v3
31 | with:
32 | ref: ${{ github.ref }}
33 |
34 | - name: Set up JDK 17
35 | uses: actions/setup-java@v3
36 | with:
37 | distribution: 'temurin'
38 | java-version: '17'
39 |
40 | - name: Set up Apache Maven Central
41 | uses: actions/setup-java@v3
42 | with: # running setup-java again overwrites the settings.xml
43 | java-version: '17'
44 | distribution: 'temurin'
45 | server-id: central
46 | server-username: OSSRH_USERNAME
47 | server-password: OSSRH_PASSWORD
48 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
49 | gpg-passphrase: MAVEN_GPG_PASSPHRASE
50 |
51 | - name: Configure Git User
52 | run: |
53 | git config --global committer.email "noreply@github.com"
54 | git config --global committer.name "GitHub"
55 | git config --global author.email "${GITHUB_ACTOR}@users.noreply.github.com"
56 | git config --global author.name "${GITHUB_ACTOR}"
57 |
58 | - name: Maven Release then publish artefacts to the Apache Maven Central
59 | run: mvn -B release:prepare release:perform -DreleaseVersion=${{ github.event.inputs.releaseVersion }} -DdevelopmentVersion=${{ github.event.inputs.developmentVersion }} -Dusername=$GITHUB_ACTOR -Dpassword=$GITHUB_TOKEN
60 | env:
61 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
62 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
63 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
64 | GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
65 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Project.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import java.util.ArrayList;
17 | import java.util.Collection;
18 |
19 | import jakarta.persistence.Entity;
20 | import jakarta.persistence.Id;
21 | import jakarta.persistence.ManyToMany;
22 |
23 | import org.apache.commons.lang3.builder.EqualsBuilder;
24 | import org.apache.commons.lang3.builder.HashCodeBuilder;
25 | import org.apache.commons.lang3.builder.ToStringBuilder;
26 |
27 | @Entity
28 | public class Project {
29 |
30 | @Id
31 | private Integer id;
32 |
33 | private String name;
34 |
35 | @ManyToMany(mappedBy = "projects")
36 | private Collection members = new ArrayList();
37 |
38 | public Project() {
39 |
40 | }
41 |
42 | public Project(Integer id, String name) {
43 | this.id = id;
44 | this.name = name;
45 | }
46 |
47 | public Integer getId() {
48 | return id;
49 | }
50 |
51 | public void setId(Integer id) {
52 | this.id = id;
53 | }
54 |
55 | public String getName() {
56 | return name;
57 | }
58 |
59 | public void setName(String name) {
60 | this.name = name;
61 | }
62 |
63 | public Collection getMembers() {
64 | return members;
65 | }
66 |
67 | public void setMembers(Collection members) {
68 | this.members = members;
69 | }
70 |
71 | @Override
72 | public int hashCode() {
73 | return new HashCodeBuilder(17, 37).append(id).toHashCode();
74 | }
75 |
76 | @Override
77 | public boolean equals(Object obj) {
78 | if (this == obj) {
79 | return true;
80 | }
81 | if (obj == null) {
82 | return false;
83 | }
84 | if (!Project.class.isAssignableFrom(obj.getClass())) {
85 | return false;
86 | }
87 |
88 | Project other = (Project) obj;
89 |
90 | // Do not compare the members properties in order to avoid recursive equals stack call on
91 | // bi-directional relationship
92 | return new EqualsBuilder().append(id, other.id).append(this.name, other.name).isEquals();
93 | }
94 |
95 | @Override
96 | public String toString() {
97 | return new ToStringBuilder(this).append(id).append(name).build();
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/javaetmoi/core/persistence/hibernate/Hydrator.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate;
2 |
3 | import jakarta.persistence.EntityManager;
4 | import jakarta.persistence.EntityManagerFactory;
5 | import org.hibernate.LazyInitializationException;
6 | import org.hibernate.Session;
7 | import org.hibernate.SessionFactory;
8 |
9 | import java.util.Collection;
10 |
11 | /**
12 | * Hydrate Hibernate/JPA entities.
13 | */
14 | public interface Hydrator {
15 | /**
16 | * Factory for {@link EntityManager} or {@link Session}.
17 | *
18 | * @param entityManager
19 | * {@link EntityManager} of an open {@link EntityManagerFactory}.
20 | * @return new instance.
21 | */
22 | public static Hydrator hydrator(EntityManager entityManager) {
23 | return hydrator(entityManager.getEntityManagerFactory());
24 | }
25 |
26 | /**
27 | * Factory for {@link EntityManagerFactory} or {@link SessionFactory}.
28 | *
29 | * @param entityManagerFactory
30 | * Open {@link EntityManagerFactory}.
31 | * @return new instance.
32 | */
33 | public static Hydrator hydrator(EntityManagerFactory entityManagerFactory) {
34 | return new HydratorImpl(entityManagerFactory);
35 | }
36 |
37 | /**
38 | * Exclude the attribute of the entity from hydration.
39 | * Just supports attributes that are JPA entities or collections.
40 | * {@link jakarta.persistence.Embeddable}s are not supported.
41 | *
42 | * @param entityClass
43 | * Entity class.
44 | * @param attribute
45 | * Attribute name.
46 | * @throws IllegalArgumentException if then entity class is not a JPA entity or the attribute does not exist.
47 | * @return new instance with the exclusion.
48 | */
49 | public Hydrator withExclude(Class> entityClass, String attribute);
50 |
51 | /**
52 | * Populate a lazy-initialized object graph by recursion.
53 | *
54 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
55 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
56 | * Attention: This method has to be called from an open persistent context / Hibernate session.
57 | *
58 | *
59 | * @param entities
60 | * A {@link Collection} of attached Hibernate entities to load.
61 | * @return the {@link Collection} of Hibernate entities fully loaded.
62 | * Similar to the entities input parameter.
63 | * Useful when calling this method in a return statement.
64 | */
65 | public , E> C deepHydrateCollection(C entities);
66 |
67 | /**
68 | * Populate a lazy-initialized object graph by recursion.
69 | *
70 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
71 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
72 | * Attention: This method has to be called from an open persistent context / Hibernate session.
73 | *
74 | *
75 | * @param entity
76 | * An attached Hibernate entity to load.
77 | * @return the Hibernate entity fully loaded.
78 | * Similar to the entity input parameter.
79 | * Useful when calling this method in a return statement.
80 | */
81 | public E deepHydrate(E entity);
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/javaetmoi/core/persistence/hibernate/JpaLazyLoadingUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate;
15 |
16 | import java.util.Collection;
17 |
18 | import jakarta.persistence.EntityManager;
19 |
20 | import jakarta.persistence.EntityManagerFactory;
21 | import org.hibernate.LazyInitializationException;
22 |
23 | import static com.javaetmoi.core.persistence.hibernate.Hydrator.hydrator;
24 |
25 | /**
26 | * Set of helper methods that fetch a complete entity graph.
27 | *
28 | * Provides a lazy way to resolve all Hibernate proxies.
29 | *
30 | *
31 | * @author Antoine Rey
32 | * @deprecated Use {@link Hydrator#hydrator(EntityManagerFactory)} or {@link LazyLoadingUtil} instead.
33 | */
34 | @Deprecated(forRemoval = true)
35 | public final class JpaLazyLoadingUtil {
36 | /**
37 | * No-arg constructor.
38 | */
39 | private JpaLazyLoadingUtil() {
40 | // Private visibility because utility class
41 | }
42 |
43 | /**
44 | * Populate a lazy-initialized object graph by recursion.
45 | *
46 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
47 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
48 | * Attention: This method has to be called from an open persistent context / Hibernate session.
49 | *
50 | *
51 | * @param entityManager
52 | * Open {@link EntityManager}.
53 | * @param entities
54 | * A {@link Collection} of attached Hibernate entities to load.
55 | * @return the {@link Collection} of Hibernate entities fully loaded.
56 | * Similar to the entities input parameter.
57 | * Useful when calling this method in a return statement.
58 | */
59 | public static , E> C deepHydrate(EntityManager entityManager, C entities) {
60 | return hydrator(entityManager).deepHydrateCollection(entities);
61 | }
62 |
63 | /**
64 | * Populate a lazy-initialized object graph by recursion.
65 | *
66 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
67 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
68 | * Attention: This method has to be called from an open persistent context / Hibernate session.
69 | *
70 | *
71 | * @param entityManager
72 | * Open {@link EntityManager}.
73 | * @param entity
74 | * An attached Hibernate entity to load.
75 | * @return the Hibernate entity fully loaded.
76 | * Similar to the entity input parameter.
77 | * Useful when calling this method in a return statement.
78 | */
79 | public static E deepHydrate(EntityManager entityManager, E entity) {
80 | return hydrator(entityManager).deepHydrate(entity);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Address.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import jakarta.persistence.Entity;
17 | import jakarta.persistence.FetchType;
18 | import jakarta.persistence.Id;
19 | import jakarta.persistence.ManyToOne;
20 | import jakarta.persistence.OneToOne;
21 |
22 | import org.apache.commons.lang3.builder.EqualsBuilder;
23 | import org.apache.commons.lang3.builder.HashCodeBuilder;
24 | import org.apache.commons.lang3.builder.ToStringBuilder;
25 |
26 | @Entity
27 | public class Address {
28 |
29 | @Id
30 | private Integer id;
31 |
32 | private String type;
33 |
34 | private String city;
35 |
36 | @ManyToOne(fetch = FetchType.LAZY)
37 | private Employee employee;
38 |
39 | @ManyToOne
40 | private Country country;
41 |
42 | public Address() {
43 |
44 | }
45 |
46 | public Address(Integer id, String type, String city, Employee employee) {
47 | this(id, type, city, employee, null);
48 | }
49 |
50 | public Address(Integer id, String type, String city, Employee employee, Country country) {
51 | this.id = id;
52 | this.type = type;
53 | this.city = city;
54 | this.employee = employee;
55 | this.country = country;
56 | }
57 |
58 | public Integer getId() {
59 | return id;
60 | }
61 |
62 | public void setId(Integer id) {
63 | this.id = id;
64 | }
65 |
66 | public String getType() {
67 | return type;
68 | }
69 |
70 | public void setType(String type) {
71 | this.type = type;
72 | }
73 |
74 | public String getCity() {
75 | return city;
76 | }
77 |
78 | public void setCity(String city) {
79 | this.city = city;
80 | }
81 |
82 | public Employee getEmployee() {
83 | return employee;
84 | }
85 |
86 | public void setEmployee(Employee employee) {
87 | this.employee = employee;
88 | }
89 |
90 | public Country getCountry() {
91 | return country;
92 | }
93 |
94 | public void setCountry(Country country) {
95 | this.country = country;
96 | }
97 |
98 | @Override
99 | public int hashCode() {
100 | return new HashCodeBuilder(17, 37).append(id).toHashCode();
101 | }
102 |
103 | @Override
104 | public boolean equals(Object obj) {
105 | if (this == obj) {
106 | return true;
107 | }
108 | if (obj == null) {
109 | return false;
110 | }
111 | if (!Address.class.isAssignableFrom(obj.getClass())) {
112 | return false;
113 | }
114 |
115 | Address other = (Address) obj;
116 |
117 | // Do not compare the employees properties in order to avoid recursive equals stack call on
118 | // bi-directional relationship
119 | return new EqualsBuilder().append(id, other.id).append(this.city, other.city).append(
120 | this.country, other.country).append(this.type, other.type).isEquals();
121 | }
122 |
123 | @Override
124 | public String toString() {
125 | return new ToStringBuilder(this).append(id).append(city).append(country).append(type).build();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/TestIssue3.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate;
15 |
16 | import com.javaetmoi.core.persistence.hibernate.listWithEmbeddable.Plan;
17 | import com.javaetmoi.core.persistence.hibernate.manyToOneList.Holder;
18 | import com.javaetmoi.core.persistence.hibernate.manyToOneList.System;
19 | import jakarta.persistence.EntityManager;
20 | import org.junit.jupiter.api.Test;
21 |
22 | import java.util.List;
23 |
24 | import static org.junit.jupiter.api.Assertions.*;
25 |
26 | /**
27 | * Unit test for issue 3.
28 | *
29 | * @see Issue 3
30 | */
31 | class TestIssue3 extends AbstractTest {
32 | @Test
33 | void listWithEmbeddableClass() {
34 | var dbPlan = findDeepHydratedEntity(Plan.class, 1);
35 |
36 | assertEquals(1, dbPlan.getId());
37 | assertEquals(1, dbPlan.getTransfers().size());
38 | assertEquals(2, dbPlan.getTransfers().get(0).getSubPlan()
39 | .getEvents().size());
40 | }
41 |
42 | @Test
43 | void listWithMappedEntity() {
44 | var dbHolder = findDeepHydratedEntity(Holder.class, 1);
45 |
46 | assertEquals(1, dbHolder.getId());
47 | assertNotNull(dbHolder.getSystem());
48 | assertEquals(1, dbHolder.getSystem().getId());
49 | assertNotNull(dbHolder.getSystem().getSubSystems());
50 | assertEquals(2, dbHolder.getSystem().getSubSystems().size());
51 | }
52 |
53 | @Test
54 | void listWithMappedEntityWithAdditionalSpecificCriteria() {
55 | var dbSystems = doInJPA(entityManager ->
56 | hydrator.deepHydrateCollection(selectAllSystemsOrderedByNumber(entityManager)));
57 |
58 | assertEquals(2, dbSystems.size());
59 | assertEquals(1, dbSystems.get(0).getId());
60 | assertNotNull(dbSystems.get(0).getSubSystems());
61 | assertEquals(2, dbSystems.get(0).getSubSystems().size());
62 | }
63 |
64 | @Test
65 | void retrieveEntityWhenAlreadyInsSessionOnAccountOfSave() {
66 | var dbSystem = doInJPA(entityManager -> {
67 | var holder = entityManager.find(Holder.class, 1L);
68 | var system = holder.getSystem();
69 | system.setName("system1A");
70 | system.setSystemNumber("1A");
71 | var subSystem1 = system.getSubSystems().get(0);
72 | subSystem1.setName("subsystem1A");
73 | subSystem1.setSystemNumber("1-1A");
74 | var subSystem2 = system.getSubSystems().get(1);
75 | subSystem2.setName("subsystem21");
76 | subSystem2.setSystemNumber("1-21");
77 | entityManager.persist(subSystem1);
78 | entityManager.persist(subSystem2);
79 | entityManager.persist(system);
80 | entityManager.persist(holder);
81 |
82 | selectAllSystemsOrderedByNumber(entityManager);
83 | return hydrator.deepHydrate(system);
84 | });
85 |
86 | assertEquals(1, dbSystem.getId());
87 | assertNotNull(dbSystem.getSubSystems());
88 | assertEquals(2, dbSystem.getSubSystems().size());
89 | assertEquals(1, dbSystem.getSubSystems().get(0).getId());
90 | assertEquals(2, dbSystem.getSubSystems().get(1).getId());
91 | }
92 |
93 | private List selectAllSystemsOrderedByNumber(EntityManager entityManager) {
94 | var builder = entityManager.getCriteriaBuilder();
95 | var query = builder.createQuery(System.class);
96 | var from = query.from(System.class);
97 | query.orderBy(builder.asc(from.get("systemNumber")));
98 | var select = query.select(from);
99 | return entityManager.createQuery(select).getResultList();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/domain/Employee.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate.domain;
15 |
16 | import java.util.*;
17 |
18 | import jakarta.persistence.Basic;
19 | import jakarta.persistence.CascadeType;
20 | import jakarta.persistence.Entity;
21 | import jakarta.persistence.FetchType;
22 | import jakarta.persistence.Id;
23 | import jakarta.persistence.ManyToMany;
24 | import jakarta.persistence.MapKey;
25 | import jakarta.persistence.OneToMany;
26 |
27 | import org.apache.commons.lang3.builder.EqualsBuilder;
28 | import org.apache.commons.lang3.builder.HashCodeBuilder;
29 | import org.apache.commons.lang3.builder.ToStringBuilder;
30 |
31 | @Entity
32 | public class Employee {
33 |
34 | @Id
35 | private Integer id;
36 |
37 | private String name;
38 |
39 | @ManyToMany(cascade = CascadeType.ALL)
40 | private List projects = new ArrayList();
41 |
42 | @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL)
43 | @MapKey(name = "type")
44 | private Map addresses = new HashMap();
45 |
46 | @Basic(fetch = FetchType.LAZY)
47 | private String job;
48 |
49 | public Employee() {
50 |
51 | }
52 |
53 | public Employee(Integer id, String name, String job) {
54 | this.id = id;
55 | this.name = name;
56 | this.job = job;
57 | }
58 |
59 | public Integer getId() {
60 | return id;
61 | }
62 |
63 | public void setId(Integer id) {
64 | this.id = id;
65 | }
66 |
67 | public String getName() {
68 | return name;
69 | }
70 |
71 | public void setName(String name) {
72 | this.name = name;
73 | }
74 |
75 | public List getProjects() {
76 | return projects;
77 | }
78 |
79 | public void setProjects(List projects) {
80 | this.projects = projects;
81 | }
82 |
83 | public Map getAddresses() {
84 | return addresses;
85 | }
86 |
87 | public void setAddresses(Map adresses) {
88 | this.addresses = adresses;
89 | }
90 |
91 | public String getJob() {
92 | return job;
93 | }
94 |
95 | public void setJob(String job) {
96 | this.job = job;
97 | }
98 |
99 | @Override
100 | public int hashCode() {
101 | return new HashCodeBuilder(17, 37).append(id).toHashCode();
102 | }
103 |
104 | @Override
105 | public boolean equals(Object obj) {
106 | if (this == obj) {
107 | return true;
108 | }
109 | if (obj == null) {
110 | return false;
111 | }
112 | if (!Employee.class.isAssignableFrom(obj.getClass())) {
113 | return false;
114 | }
115 |
116 | Employee other = (Employee) obj;
117 |
118 | // PersistentBag does not respect the collection API so we compare arrays
119 | return new EqualsBuilder().append(id, other.id).append(this.job, other.job).append(
120 | this.name, other.name).append(this.addresses, other.addresses).append(
121 | this.projects.toArray(), other.projects.toArray()).isEquals();
122 | }
123 |
124 | @Override
125 | public String toString() {
126 | return new ToStringBuilder(this).append(id).append(job).append(name).append(addresses).append(
127 | projects).build();
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/AbstractTest.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate;
2 |
3 | import jakarta.persistence.EntityManager;
4 | import jakarta.persistence.EntityManagerFactory;
5 | import org.hibernate.SessionFactory;
6 | import org.hibernate.proxy.HibernateProxy;
7 | import org.hibernate.stat.Statistics;
8 | import org.hibernate.testing.transaction.TransactionUtil;
9 | import org.hibernate.testing.transaction.TransactionUtil.JPATransactionFunction;
10 | import org.junit.jupiter.api.BeforeEach;
11 |
12 | import java.util.List;
13 | import java.util.function.Consumer;
14 |
15 | import static com.javaetmoi.core.persistence.hibernate.Hydrator.hydrator;
16 | import static jakarta.persistence.Persistence.createEntityManagerFactory;
17 | import static java.util.Arrays.stream;
18 | import static java.util.stream.Collectors.toList;
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | public class AbstractTest {
22 | private final EntityManagerFactory entityManagerFactory =
23 | createEntityManagerFactory("hibernate-hydrate");
24 | protected final Hydrator hydrator = hydrator(entityManagerFactory);
25 | private final SessionFactory sessionFactory =
26 | entityManagerFactory.unwrap(SessionFactory.class);
27 | private final DBUnitLoader dbUnitLoader =
28 | new DBUnitLoader((String) entityManagerFactory.getProperties().get("hibernate.connection.url"));
29 |
30 | @BeforeEach
31 | void setUpDatabase() {
32 | dbUnitLoader.loadDatabase(getClass());
33 |
34 | // Reset Hibernate statistics.
35 | statistics().clear();
36 | }
37 |
38 | //
39 | // Interface.
40 | //
41 |
42 | protected Statistics statistics() {
43 | return sessionFactory.getStatistics();
44 | }
45 |
46 | protected E findEntity(Class entityClass, long entityId) {
47 | return doInJPA(entityManager ->
48 | entityManager.find(entityClass, entityId));
49 | }
50 |
51 | protected E findDeepHydratedEntity(Class entityClass, long entityId) {
52 | return doInJPA(entityManager ->
53 | hydrator.deepHydrate(entityManager.find(entityClass, entityId)));
54 | }
55 |
56 | protected List findDeepHydratedEntities(Class entityClass, long... entityIds) {
57 | return doInJPA(entityManager -> {
58 | var entities = stream(entityIds)
59 | .mapToObj(id -> entityManager.find(entityClass, id))
60 | .collect(toList());
61 | return hydrator.deepHydrateCollection(entities);
62 | });
63 | }
64 |
65 | protected E findDeepHydratedEntityReference(Class entityClass, long entityId) {
66 | return doInJPA(entityManager -> {
67 | var reference = entityManager.getReference(entityClass, entityId);
68 | assertProxy(entityClass, reference);
69 | return hydrator.deepHydrate(reference);
70 | });
71 | }
72 |
73 | protected List findDeepHydratedEntityReferences(Class entityClass, long... entityIds) {
74 | return doInJPA(entityManager -> {
75 | var references = stream(entityIds)
76 | .mapToObj(id -> entityManager.getReference(entityClass, id))
77 | .collect(toList());
78 | references.forEach(entity -> assertProxy(entityClass, entity));
79 | return hydrator.deepHydrateCollection(references);
80 | });
81 | }
82 |
83 | /**
84 | * Ensure that we got a proxy.
85 | */
86 | private void assertProxy(Class entityClass, E reference) {
87 | assertThat(reference)
88 | .isInstanceOf(HibernateProxy.class)
89 | .extracting(Object::getClass).isNotEqualTo(entityClass);
90 | }
91 |
92 | protected void doInJPAVoid(Consumer action) {
93 | doInJPA(entityManager -> {
94 | action.accept(entityManager);
95 | return null;
96 | });
97 | }
98 |
99 | protected R doInJPA(JPATransactionFunction action) {
100 | return TransactionUtil.doInJPA(() -> entityManagerFactory, action);
101 | }
102 | }
--------------------------------------------------------------------------------
/src/main/java/com/javaetmoi/core/persistence/hibernate/LazyLoadingUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate;
15 |
16 | import jakarta.persistence.EntityManager;
17 | import jakarta.persistence.EntityManagerFactory;
18 | import org.hibernate.LazyInitializationException;
19 | import org.hibernate.Session;
20 | import org.hibernate.SessionFactory;
21 |
22 | import java.util.Collection;
23 |
24 | import static com.javaetmoi.core.persistence.hibernate.Hydrator.hydrator;
25 |
26 | /**
27 | * Set of helper methods that fetch a complete entity graph.
28 | *
29 | * Provides a lazy way to resolve all Hibernate proxies.
30 | *
31 | *
32 | * @author Antoine Rey
33 | */
34 | public final class LazyLoadingUtil {
35 | /**
36 | * No-arg constructor.
37 | */
38 | private LazyLoadingUtil() {
39 | // Private visibility because utility class.
40 | }
41 |
42 | /**
43 | * Populate a lazy-initialized object graph by recursion.
44 | *
45 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
46 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
47 | * Attention: This method has to be called from an open persistent context / Hibernate session.
48 | *
49 | *
50 | * @param entityManager
51 | * Open {@link EntityManager} or {@link Session}.
52 | * @param entities
53 | * A {@link Collection} of attached Hibernate entities to load.
54 | * @return the {@link Collection} of Hibernate entities fully loaded.
55 | * Similar to the entities input parameter.
56 | * Useful when calling this method in a return statement.
57 | */
58 | public static , E> C deepHydrate(EntityManager entityManager, C entities) {
59 | return hydrator(entityManager).deepHydrateCollection(entities);
60 | }
61 |
62 | /**
63 | * Populate a lazy-initialized object graph by recursion.
64 | *
65 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
66 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
67 | * Attention: This method has to be called from an open persistent context / Hibernate session.
68 | *
69 | *
70 | * @param entityManagerFactory
71 | * {@link EntityManagerFactory} or {@link SessionFactory}.
72 | * @param entities
73 | * A {@link Collection} of attached Hibernate entities to load.
74 | * @return the {@link Collection} of Hibernate entities fully loaded.
75 | * Similar to the entities input parameter.
76 | * Useful when calling this method in a return statement.
77 | */
78 | public static , E> C deepHydrate(EntityManagerFactory entityManagerFactory, C entities) {
79 | return hydrator(entityManagerFactory).deepHydrateCollection(entities);
80 | }
81 |
82 | /**
83 | * Populate a lazy-initialized object graph by recursion.
84 | *
85 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
86 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
87 | * Attention: This method has to be called from an open persistent context / Hibernate session.
88 | *
89 | *
90 | * @param entityManager
91 | * Open {@link EntityManager} or {@link Session}.
92 | * @param entity
93 | * An attached Hibernate entity to load.
94 | * @return the Hibernate entity fully loaded.
95 | * Similar to the entity input parameter.
96 | * Useful when calling this method in a return statement.
97 | */
98 | public static E deepHydrate(EntityManager entityManager, E entity) {
99 | return hydrator(entityManager).deepHydrate(entity);
100 | }
101 |
102 | /**
103 | * Populate a lazy-initialized object graph by recursion.
104 | *
105 | * This method deeply navigates into a graph of entities in order to resolve uninitialized Hibernate proxies.
106 | * The goal is to avoid any {@link LazyInitializationException} once entities are detached.
107 | * Attention: This method has to be called from an open persistent context / Hibernate session.
108 | *
109 | *
110 | * @param entityManagerFactory
111 | * {@link EntityManagerFactory} or {@link SessionFactory}.
112 | * @param entity
113 | * An attached Hibernate entity to load.
114 | * @return the Hibernate entity fully loaded.
115 | * Similar to the entity input parameter.
116 | * Useful when calling this method in a return statement.
117 | */
118 | public static E deepHydrate(EntityManagerFactory entityManagerFactory, E entity) {
119 | return hydrator(entityManagerFactory).deepHydrate(entity);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/test/java/com/javaetmoi/core/persistence/hibernate/DBUnitLoader.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.javaetmoi.core.persistence.hibernate;
15 |
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 | import java.sql.SQLException;
19 |
20 | import javax.sql.DataSource;
21 |
22 | import org.dbunit.DatabaseUnitException;
23 | import org.dbunit.database.DatabaseDataSourceConnection;
24 | import org.dbunit.database.IDatabaseConnection;
25 | import org.dbunit.dataset.DataSetException;
26 | import org.dbunit.dataset.IDataSet;
27 | import org.dbunit.dataset.NoSuchTableException;
28 | import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
29 | import org.dbunit.ext.h2.H2DataTypeFactory;
30 | import org.dbunit.operation.DatabaseOperation;
31 | import org.h2.jdbcx.JdbcDataSource;
32 |
33 | import static java.util.Arrays.stream;
34 | import static org.apache.commons.lang3.ArrayUtils.isEmpty;
35 | import static org.dbunit.database.DatabaseConfig.PROPERTY_DATATYPE_FACTORY;
36 | import static org.dbunit.operation.DatabaseOperation.DELETE_ALL;
37 | import static org.dbunit.operation.DatabaseOperation.INSERT;
38 |
39 | /**
40 | * Allows to easily insert and cleanup test data into a database.
41 | *
42 | * @author Antoine Rey
43 | * @author Markus Heiden
44 | */
45 | public class DBUnitLoader {
46 |
47 | private final DataSource dataSource;
48 |
49 | DBUnitLoader(String databaseUrl) {
50 | var jdbcDataSource = new JdbcDataSource();
51 | jdbcDataSource.setURL(databaseUrl);
52 | this.dataSource = jdbcDataSource;
53 | }
54 |
55 | /**
56 | * Load the database from a data set for the given class.
57 | * If the class is named {@code com.example.MyTest}, DBUnitLoad loads your DBUnit data set
58 | * from the file {@code MyTest-dataset.xml} in the package {@code com.example}.
59 | *
60 | * @param testClass
61 | * Test class.
62 | */
63 | public void loadDatabase(Class> testClass) {
64 | loadDatabase(testClass, testClass.getSimpleName() + "-dataset.xml");
65 | }
66 |
67 | /**
68 | * Load the database from the given data sets.
69 | *
70 | * @param dataSetLocations
71 | * Data set locations.
72 | * Absolute: {@code "com/example/MyTest-dataset.xml"}.
73 | */
74 | public void loadDatabase(String... dataSetLocations) {
75 | loadDatabase(null, dataSetLocations);
76 | }
77 |
78 | /**
79 | * Load the database from the given data sets.
80 | *
81 | * @param testClass
82 | * Test class used as anchor.
83 | * @param dataSetLocations
84 | * Data set locations.
85 | * If the test class is not {@code null}, relative to the test class: {@code "MyTest-dataset.xml"}.
86 | * If the test class is {@code null}, absolute: {@code "com/example/MyTest-dataset.xml"}.
87 | */
88 | public void loadDatabase(Class> testClass, String... dataSetLocations) {
89 | if (isEmpty(dataSetLocations)) {
90 | throw new IllegalArgumentException("Data set locations are mandatory");
91 | }
92 |
93 | var dataSets = stream(dataSetLocations)
94 | .map(dataSetLocation -> buildDataSet(testClass, dataSetLocation))
95 | .toArray(IDataSet[]::new);
96 |
97 | var connection = connection();
98 | executeAll(connection, DELETE_ALL, dataSets);
99 | executeAll(connection, INSERT, dataSets);
100 | }
101 |
102 | private IDataSet buildDataSet(Class> testClass, String dataSetLocation) {
103 | try (var dataSet = getResourceAsStream(testClass, dataSetLocation)) {
104 | if (dataSet == null) {
105 | throw new IllegalArgumentException("No data set file located at " + dataSetLocation);
106 | }
107 |
108 | return new FlatXmlDataSetBuilder()
109 | .setColumnSensing(true)
110 | .build(dataSet);
111 | } catch (DataSetException | IOException e) {
112 | throw new RuntimeException("Error while reading data set " + dataSetLocation, e);
113 | }
114 | }
115 |
116 | private InputStream getResourceAsStream(Class> testClass, String dataSetLocation) {
117 | return testClass != null ?
118 | testClass.getResourceAsStream(dataSetLocation) :
119 | getClass().getClassLoader().getResourceAsStream(dataSetLocation);
120 | }
121 |
122 | private IDatabaseConnection connection() {
123 | try {
124 | var connection = new DatabaseDataSourceConnection(dataSource);
125 | var config = connection.getConfig();
126 | config.setProperty(PROPERTY_DATATYPE_FACTORY, new H2DataTypeFactory());
127 | return connection;
128 | } catch (SQLException e) {
129 | throw new RuntimeException("Error while getting the JDBC data source", e);
130 | }
131 | }
132 |
133 | public void executeAll(IDatabaseConnection connection, DatabaseOperation operation, IDataSet... dataSets) {
134 | try (var jdbcConnection = dataSource.getConnection(); var statement = jdbcConnection.createStatement()) {
135 | // language=H2
136 | statement.execute("SET REFERENTIAL_INTEGRITY FALSE");
137 | for (var dataSet : dataSets) {
138 | operation.execute(connection, dataSet);
139 | }
140 | // language=H2
141 | statement.execute("SET REFERENTIAL_INTEGRITY TRUE");
142 | } catch (NoSuchTableException e) {
143 | // Ignore missing tables.
144 | // Delete all: A not existing table needs not be dropped.
145 | // Insert: When creating a table it should not exist.
146 | } catch (DatabaseUnitException | SQLException e) {
147 | throw new RuntimeException("Error while applying a data set", e);
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Hibernate Hydrate #
2 |
3 | [](https://github.com/arey/hibernate-hydrate/actions/workflows/build.yml)
4 | [](https://maven-badges.herokuapp.com/maven-central/com.javaetmoi.core/javaetmoi-hibernate6-hydrate)
5 |
6 | The primary goal of the [Hibernate Hydrate](https://github.com/arey/hibernate-hydrate) project is to populate a graph of persistent entities and thus avoid the famous [LazyInitializationException](http://docs.jboss.org/hibernate/orm/3.6/javadocs/org/hibernate/LazyInitializationException.html).
7 |
8 | ## Features ##
9 |
10 | * Utility class to populate a lazy-initialized object graph by recursivity
11 | * Supports JPA with Hibernate as provider
12 | * Supports Hibernate 3.x to 6.x (with different artefactId)
13 |
14 | ## Getting Help ##
15 |
16 | This readme file as well as the [wiki](https://github.com/arey/hibernate-hydrate/wiki) are the best places to start learning about Hibernate Hydrate.
17 | There are also unit tests available to look at.
18 |
19 | The [wiki](https://github.com/arey/hibernate-hydrate/wiki) contains links to basic project information such as source code, jenkins build, javadocs, issue tracking, etc.
20 |
21 | A French article titled *Say goodbye to LazyInitializationException* : http://javaetmoi.com/2012/03/hibernate-dites-adieu-aux-lazy-initialization-exception/
22 |
23 | ## Quick Start ##
24 |
25 | ### Dependency ###
26 |
27 | Download the jar though Maven:
28 |
29 | ```xml
30 |
31 |
32 | com.javaetmoi.core
33 | javaetmoi-hibernate6-hydrate
34 | 6.3.4
35 |
36 |
37 |
38 |
39 | com.javaetmoi.core
40 | javaetmoi-hibernate5-hydrate
41 | 5.2.4
42 |
43 |
44 |
45 |
46 | com.javaetmoi.core
47 | javaetmoi-hibernate5-hydrate
48 | 2.3
49 |
50 |
51 |
52 |
53 | com.javaetmoi.core
54 | javaetmoi-hibernate4-hydrate
55 | 2.2
56 |
57 |
58 |
59 |
60 | com.javaetmoi.core
61 | javaetmoi-hibernate3-hydrate
62 | 2.2
63 |
64 | ```
65 |
66 | Please note that we are not able to support Hibernate versions 6.0 up to 6.1.5 due to bugs in them.
67 |
68 | Hibernate Hydrate artefacts are available from [Maven Central](https://repo1.maven.org/maven2/com/javaetmoi/core/javaetmoi-hibernate6-hydrate/)
69 |
70 | [](https://maven-badges.herokuapp.com/maven-central/com.javaetmoi.core/javaetmoi-hibernate6-hydrate)
71 |
72 | ### Usage ###
73 |
74 | First get a `Hydrator` instance. There are four possible ways:
75 | * `Hydrator.hydrator(entityManagerFactory)`
76 | * `Hydrator.hydrator(entityManager)`
77 | * `Hydrator.hydrator(sessionFactory)`
78 | * `Hydrator.hydrator(session)`
79 |
80 | To this `Hydrator` instance you can pass **attached** entities that need to be fully initialized.
81 | Afterward you can detach these entities and access all of their transitive attributes
82 | without getting problems with lazy loading. That is, there will be no `LazyInitializationException`.
83 |
84 | A simple example of a service method that returns a fully initialized entity
85 | except for its `mySubEntities` attribute:
86 |
87 | ```java
88 | import jakarta.persistence.EntityManager;
89 | import jakarta.transaction.Transactional;
90 | import com.javaetmoi.core.persistence.hibernate.Hydrator;
91 | import static com.javaetmoi.core.persistence.hibernate.Hydrator.hydrator;
92 |
93 | public class MyEntityService {
94 | private final EntityManager entityManager;
95 | private final Hydrator hydrator;
96 |
97 | public MyEntityService(EntityManager entityManager) {
98 | this.entityManager = entityManager;
99 | this.hydrator = hydrator(entityManager).withExclude(MyEntity.class, "mySubEntities");
100 | }
101 |
102 | @Transactional
103 | public MyEntity myEntity(long id) {
104 | var myEntity = entityManager.find(MyEntity.class, id);
105 | var myHydratedEntity = hydrator.deepHydrate(myEntity);
106 | return myHydrateEntity;
107 | }
108 | }
109 | ```
110 |
111 | ## Contributing to Hibernate Hydrate ##
112 |
113 | * GitHub is for social coding platform: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/).
114 | If you want to contribute code this way, please reference a GitHub ticket as well covering the specific issue you are addressing.
115 | * Each major version of Hibernate has it own git branch:
116 | * Hibernate 6.2 on the master
117 | * Hibernate 5 on the hibernate5 branch
118 | * Hibernate 4 on the hibernate4 branch
119 | * Hibernate 3 on the hibernate3 branch
120 |
121 | ### Development environment installation ###
122 |
123 | Download the code with git:
124 |
125 | ``git clone git://github.com/arey/hibernate-hydrate.git``
126 |
127 | Compile the code with maven:
128 |
129 | ``mvn clean install``
130 |
131 | If you're using an IDE that supports Maven-based projects (IntelliJ Idea, Netbeans or m2Eclipse), you can import the project directly from its POM.
132 | Otherwise, generate IDE metadata with the related IDE maven plugin:
133 |
134 | ``mvn eclipse:clean eclipse:eclipse``
135 |
136 | ## Release
137 |
138 | This project artefact is published to Maven Central.
139 | The Maven Release Plugin is used to release the project with Maven.
140 | The [release.yml](https://github.com/arey/hibernate-hydrate/actions/workflows/release.yml) GitHub Actions workflow automates the process.
141 |
142 |
143 | ## Credits ##
144 |
145 | * Uses [Maven](http://maven.apache.org/) as a build tool
146 | * Uses [GitHub Actions](https://github.com/features/actions) for continuous integration builds whenever code is pushed into GitHub
147 | * [Izaak (John) Alpert](https://github.com/karlhungus) and [Marc Cobery](https://github.com/mcobery) and [Markus Heiden](https://github.com/markusheiden) for their pull requests
148 |
149 |
150 | ## Build Status ##
151 |
152 | GitHub Actions: [](https://github.com/arey/hibernate-hydrate/actions/workflows/build.yml)
153 |
--------------------------------------------------------------------------------
/src/main/java/com/javaetmoi/core/persistence/hibernate/HydratorImpl.java:
--------------------------------------------------------------------------------
1 | package com.javaetmoi.core.persistence.hibernate;
2 |
3 | import java.util.Collection;
4 | import java.util.HashSet;
5 | import java.util.Map;
6 | import java.util.Set;
7 |
8 | import org.hibernate.Hibernate;
9 | import org.hibernate.engine.spi.SessionFactoryImplementor;
10 | import org.hibernate.internal.util.collections.IdentitySet;
11 | import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
12 | import org.hibernate.metamodel.mapping.EntityValuedModelPart;
13 | import org.hibernate.metamodel.mapping.ModelPart;
14 | import org.hibernate.metamodel.mapping.PluralAttributeMapping;
15 | import org.hibernate.metamodel.model.domain.NavigableRole;
16 | import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
17 |
18 | import jakarta.persistence.EntityManagerFactory;
19 |
20 | /**
21 | * Default implementation of {@link Hydrator}.
22 | */
23 | class HydratorImpl implements Hydrator {
24 | /**
25 | * Mapping metamodel.
26 | */
27 | private final MappingMetamodelImplementor mappingMetamodel;
28 |
29 | /**
30 | * Excludes from hydration.
31 | */
32 | private final Set excludes;
33 |
34 | /**
35 | * Convenience constructor.
36 | */
37 | HydratorImpl(EntityManagerFactory entityManagerFactory) {
38 | this(entityManagerFactory.unwrap(SessionFactoryImplementor.class).getMappingMetamodel(), Set.of());
39 | }
40 |
41 | /**
42 | * Base constructor.
43 | */
44 | HydratorImpl(MappingMetamodelImplementor mappingMetamodel, Set excludes) {
45 | this.mappingMetamodel = mappingMetamodel;
46 | this.excludes = Set.copyOf(excludes);
47 | }
48 |
49 | @Override
50 | public Hydrator withExclude(Class> entityClass, String attribute) {
51 | var entityDescriptor = mappingMetamodel.getEntityDescriptor(entityClass);
52 | var attributeMapping = entityDescriptor.findAttributeMapping(attribute);
53 | if (attributeMapping == null) {
54 | throw new IllegalArgumentException(String.format(
55 | "The attribute %s does not exist at the entity %s.", attribute, entityDescriptor.getEntityName()));
56 | }
57 |
58 | var newExcludes = new HashSet<>(this.excludes);
59 | newExcludes.add(attributeMapping.getNavigableRole());
60 |
61 | return new HydratorImpl(mappingMetamodel, newExcludes);
62 | }
63 |
64 | @Override
65 | public , E> C deepHydrateCollection(C entities) {
66 | // Reduce resizes for big collections.
67 | int capacity = Math.max(entities.size(), 32);
68 | var recursiveGuard = new IdentitySet<>(capacity);
69 | entities.forEach(entity ->
70 | deepInflateInitialEntity(entity, recursiveGuard));
71 | return entities;
72 | }
73 |
74 | @Override
75 | public E deepHydrate(E entity) {
76 | var recursiveGuard = new IdentitySet<>();
77 | deepInflateInitialEntity(entity, recursiveGuard);
78 | return entity;
79 | }
80 |
81 | /**
82 | * Populate a lazy-initialized object graph by recursion. Recursion entry point.
83 | *
84 | * @param entity
85 | * The entity. May be {@code null}.
86 | * @param recursiveGuard
87 | * A guard to avoid endless recursion.
88 | */
89 | private void deepInflateInitialEntity(Object entity, IdentitySet