I use Spring Data Jpa and Hibernate is the provider.
I have a Parent class mapped as follows:
#Entity
#Table(name="parent")
public class Parent {
private List<Child> childs;
private List<AnotherChild> anotherChilds;
#OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
public List<Child> getChilds() {
return childs;
}
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
public List<AnotherChild> getAntoherChilds() {
return anotherChilds;
}
}
and child:
#Entity
#Table(name="child")
public class Child {
private Parent parent;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "column_name")
public Parent getParent() {
return patern;
}
}
#Entity
#Table(name="another_child")
public class AnotherChild {
private Parent parent;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "column_name")
public Parent getParent() {
return patern;
}
}
When I load the parent from the database, it doesn't load the list of
child immediately and
When I call parent.getChilds(), it returns
null.
Can you give some advice? Am I wrong anywhere? Thanks.
EDIT:
After some research, I realize that when I have only single child, it loaded eagerly (like it should). But when I have multiple child, it doesn't - even though it has been marked FetchType.EAGER and the other FetchType.LAZY.
Note: If I marked both as FetchType.EAGER, it'll throws MultipleBagFetchException: cannot simultaneously fetch multiple bags.
The same happened when I annotate it using #Fetch(FetchMode.JOIN)
If added Entity annotation parent.getChilds() should not come empty.it would be better as you do Entity.
#Entity
#Table(name="PARENT_TBL")
public class Parent {
//other fields
#OneToMany(mappedBy = "parent",fetch = FetchType.LAZY,cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE},orphanRemoval = true)
private List<Child> childs;
//getter setter
}
#Entity
#Table(name="CHILD_TBL")
public class Child {
//other fields
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Parent parent;
//getter setter
}
Example Get Parent Query;
public Parent getParent(long parentId) throws Exception {
session = sessionFactory.openSession();
Criteria cr = session.createCriteria(Parent.class, "parent");
cr.setFetchMode('parent.childs', FetchMode.JOIN);
cr.add( Restrictions.eq("parent.id", parentId));
Parent parent = cr.uniqueResult();
tx = session.getTransaction();
session.beginTransaction();
tx.commit();
return parent;
EAGER loading of collections means that they are fetched fully at the time their parent is fetched. So if you have Parent and it has List, all the childs are fetched from the database at the time the Parent is fetched.
LAZY on the other hand means that the contents of the List are fetched only when you try to access them. For example, by calling parent.getChilds().iterator(). Calling any access method on the List will initiate a call to the database to retrieve the elements. This is implemented by creating a Proxy around the List (or Set). So for your lazy collections, the concrete types are not ArrayList and HashSet.
Related
Consider I have 2 entity
#Entity(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#OneToMany(cascade = CascadeType.ALL)
public List<Child> children = new ArrayList<>();
}
and
#Entity
class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#ManyToOne
public Parent parent;
}
how can I create Child instance with parentId
without call findById(Long parentId) i.e.
Child createChild(parentId) {
Child child = new Child();
child.parent = //parent.findById(parentId); I don't wanna go to database
//for nothing if in this spot anyway will be parentId in database
return child;
}
I thought it can be done with quare but hql don't have
INSERT .... VALUE .., so I'm here, appreciate any help.
If it's don't have any sense due to architecture,
please explain, it's be a great help.
No need to create new object in
public List<Child> children = new ArrayList<>();
just write
#OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL)
public List<Child> children;
It is not a big problem that you will call parent.findById(parentId);
Hibernate caches some of the requests especially when used findById. You can see this answer link
The only thing to note is that you should not override findById in the repository, or if you do you should added it to the jpa cache.
EntityManager#getReference(Class<T> entityClass, Object primaryKey) is your friend here.
entityManager.getReference(Parent.class, parentId); returns an entity proxy. It can be used to improve the performance of the write operations since there will be no database call unless you access the fields of the returned entity.
I have a basic project with a many to many relationship. There are 2 classes Parent and Child, the owner of the relationship is the class Parent. When parent entities are persisted child entities are also persisted (which is the desired behavior). But at the opposite when the child entities are persisted, the parent entities are not persisted.
How do I get the parent entities persisted at the same time than the child entities? The code below give the spring-boot class that allow to reproduce the issue, after the logger.info("---> 2nd fetch all parents"); I expect to have 5 parents but I have only two.
Here are my entity classes :
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
// #JoinTable => owner of the relationship
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "parent_child",
joinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "child_id", referencedColumnName = "id"))
private Set<Child> children;
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#ManyToMany(mappedBy = "children")
private Set<Parent> parents;
// getters and setters
}
The repositories
public interface ChildRepository extends JpaRepository<Child, Long> {}
public interface ParentRepository extends JpaRepository<Parent, Integer> {}
The springboot application
#SpringBootApplication
public class Application implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
#Autowired
private ParentRepository parentRepository;
#Autowired
private ChildRepository childRepository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
#Transactional
public void run(String... strings) throws Exception {
// save a couple of parents
Child childA = new Child("Child A"); Child childB = new Child("Child B"); Child childC = new Child("Child C");
Parent parentA = new Parent("Parent A", new HashSet<>(Arrays.asList(childA, childB))); Parent parentB = new Parent("Parent B", new HashSet<>(Arrays.asList(childA, childC)));
parentRepository.saveAll(Arrays.asList(parentA, parentB));
// fetch all parents
logger.info("---> 1st fetch all parents");
for (Parent parent : parentRepository.findAll()) {
logger.info(parent.toString());
}
// save a couple of children
Parent parentD = new Parent("Parent D"); Parent parentE = new Parent("Parent E"); Parent parentF = new Parent("Parent F");
Child childD = new Child("Child D", new HashSet<Parent>(Arrays.asList(parentD, parentE))); Child childE = new Child("Child E", new HashSet<Parent>(Arrays.asList(parentE, parentF)));
childRepository.saveAll(Arrays.asList(childD, childE));
// fetch all children
logger.info("---> 1st fetch all children");
for (Child child : childRepository.findAll()) {
logger.info(child.toString());
}
// fetch all parents
logger.info("---> 2nd fetch all parents");
for (Parent parent : parentRepository.findAll()) {
logger.info(parent.toString());
}
// fetch all children
logger.info("---> 2nd fetch all children");
for (Child child : childRepository.findAll()) {
logger.info(child.toString());
}
}
}
With JPA when you want to propagated an update to a relational item you have to specify the type of propagation you want to apply.
So when you define a relation ManyToMany you can add the cascade type "PERSIST" to propagate the instruction INSERT for the entity Parent
#ManyToMany(mappedBy = "children", cascade = CascadeType.PERSIST)
private Set<Parent> parents;
Here the complete specification for Hibernate used by Springboot
In case you are using annotatons you probably have noticed the cascade attribute taking an array of CascadeType as a value. The cascade concept in JPA is very is similar to the transitive persistence and cascading of operations as described above, but with slightly different semantics and cascading types:
CascadeType.PERSIST : cascades the persist (create) operation to
associated entities persist() is called or if the entity is managed
CascadeType.MERGE : cascades the merge operation to associated
entities if merge() is called or if the entity is managed
CascadeType.REMOVE : cascades the remove operation to associated
entities if delete() is called
CascadeType.REFRESH : cascades the
refresh operation to associated entities if refresh() is called
CascadeType.DETACH : cascades the detach operation to associated
entities if detach() is called CascadeType.ALL: all of the above
I am using Spring Data Rest in my application and found some inconsistent behavior.
I got a Parent entity like
public class Parent {
#OneToMany(mappedBy = "parent", targetEntity = Child.class, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children;
}
And a Child with a parent reference
#ManyToOne
#JoinColumn(name = "PARENT_ID", referencedColumnName = "ID")
Parent parent
When I do parentRepository.save(Parent.builder().build()) I get https://hibernate.atlassian.net/browse/HHH-9940
But when I do
parentRepository.save(Parent.builder.children(Collections.emptyList()).build())
then I receive both a
#PostPersist
public void onCreate(Object entity) {
...
}
and a
#PostUpdate
public void onUpdate(Object entity) {
...
}
event that breaks my domain logic.
I am trapped for the resolution of this... Am I doing something wrong? Why storing a parent with empty list of childs send both a create and an update event for the parent object?
EDIT: I receive the same object (Parent) in both listeners. The only query appeared in logs is an INSERT, no trace of update. Is like INSERTing into db triggered an extra phantom PostUpdate
I have two entities:
parent :
public class UserGroup{
#OneToMany(fetch = FetchType.EAGER, mappedBy = "userGroup", cascade = CascadeType.ALL)
private List<User> users;
}
and child:
public class User{
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinColumn(name = "user_group_id")
private UserGroup userGroup;
}
when i am trying to save UserGroup with one User in list of users, with this method:
#Transactional
public E save(E e) {
em.persist(e);
em.flush();
em.refresh(e);
return e;
}
my parent and child is getting saved, but user_group_id in child object is null.
Is there any solution?
By considering you are giving UserGroup object along with list of User to save method: so your code should be:
em.save(userGroup);
for(User user : UserGroup.getUsers())
{
user.setuser_group_id(userGroup.getUserGroupId());
em.save(user);
}
You have a bidirectional relationship. The correct way of saving it is putting both references in the entities.
You should do:
userGroup.getUsers().add(user);
user.setUserGroup(userGroup);
entityManager.persist(userGroup);
In your parent setter method of child, do this.
public void setChildren(Collection<Child> children) {
this.children = children;
for(Child child: this.children) {
child.setParent(this)
}
}
This should solve.
I've read the documentation and thought I'd be able to do the following....
map my classes as so (which does work)
#Entity
public class ParentEntity
{
...
#OneToMany(mappedBy = "parent")
private List<ChildEntity> children;
...
}
#Entity
public class ChildEntity
{
...
#Id
#Column
private Long id;
...
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}
.. but i want to be able to insert into both tables in one go and thought this would work:
parent = new ParentEntity();
parent.setChildren(new ArrayList<ChildEntity>());
ChildEntity child = new ChildEntity();
child.setParent(parent);
parent.getChildren().add(child);
session.persist(parent);
Can anyone tell me what i'm missing?
Do i need to save the parent first, then add the child and save it again?
thanks.
You have to add #OneToMany(cascade=CascadeType.PERSIST). You can also have CascadeType.ALL which includes persist, merge, delete...
Cascading is the setting that tells hibernate what to do with collection elements when the owning entity is persisted/merged/deleted.
By default it does nothing with them. If the respective cascade type is set, it invokes the same operation for the collection elements that were invoked for the parent.