I am using Spring Data + Hibernate and have an entity with several #ManyToOne fields. Every field is retrieved by hibernate with separate select query.
I've already tried #Fetch(value = FetchMode.JOIN), but no use,
Example:
#Entity
public class MyEntity{
#Id
private Long id;
#JoinColumn(name = "A")
#Fetch(value = FetchMode.JOIN)
#ManyToOne
private A a;
#ManyToOne
#Fetch(value = FetchMode.JOIN)
#JoinColumn(name = "B")
private B b;
}
Hibernate will perform 3 db trips for MyEntity, A and B.
Is there a way to retrieve my entity and all of its fields with single select where A and B will be joined.?
Edit:
Entities A and B defined like this:
#Entity
public class A{
#Id
Long id;
String name;
}
Repository looks like this (Spring Data creates implementation):
public interface MyEntityRepository extends CrudRepository<MyEntity , Long> {}
And code that loads entity is pretty simple too:
myEntityRepository.findAll();
Edit 2:
I've just written QueryDSL query and for it only 1 select was executed. I'm happy with that at least.
Related
I see a lot of posts where Eager fetch performs left join of child table parent table in hibernate. But when I use springboot , hibernate fires seperate sql queries - means one select query for parent table and one select query for child table. Why is there a difference? Has there been any upgrades in springboot or is it something I am doing wrong ?
Below are the entities I am using:
Order Entity:
#Entity
#Table(name="Ordertable", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Order {
#Id
#Column(name = "ORDER_ID")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int orderId;
#Column(name = "DAT_SRC_ID")
private String dataSourceId;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private Customer customer;
}
Customer Entity:
#Entity
#Table(name="Customer", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Customer {
#Id
#Column(name = "CUSTOMER_ID")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long customerId;
#Column(name = "CUSTOMER_NAME")
private String customer_name;
#Column(name = "CUSTOMER_address_id")
private int customer_address_id;
#Column(name = "DAT_SRC_ID")
private String dataSourceId;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Order> order;
}
Controller:
#RequestMapping(value="/getByCustid/{id}", method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_VALUE)
public Customer getByCustid (#PathVariable Long id) {
Customer s1 = customerRepository.findByCustomerId(id);
return s1;
}
Repository:
public interface CustomerRepository extends JpaRepository<Customer,Long> {
public Customer findByCustomerId(Long customerId);
}
Below are the queries that are getting executed:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_,
customer0_.CUSTOMER_address_id as CUSTOMER2_0_,
customer0_.CUSTOMER_NAME as CUSTOMER3_0_,
customer0_.DAT_SRC_ID as DAT_SRC_4_0_
from
Customer customer0_
where
customer0_.CUSTOMER_ID=?
select
order0_.ORDER_CUSTOMER_ID as ORDER_CU3_5_0_,
order0_.ORDER_ID as ORDER_ID1_5_0_,
order0_.ORDER_ID as ORDER_ID1_5_1_,
order0_.ORDER_CUSTOMER_ID as ORDER_CU3_5_1_,
order0_.DAT_SRC_ID as DAT_SRC_2_5_1_
from
Ordertable order0_
where
order0_.ORDER_CUSTOMER_ID=?
EDIT : is this related to #Fetch(FetchMode.JOIN) -- Link:JPA eager fetch does not join
Hibernate: Multiple select queries made by Hibernate for Fetch mode Eager
To check whether FetchMode.JOIN works, I have added FetchMode.JOIN in entities as shown below but still no success with Join Query:
Customer Entity:
#Entity
#Table(name="Customer", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Customer {
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
#Fetch(FetchMode.JOIN)
private List<Order> order;
}
Order Entity:
#Entity
#Table(name="Ordertable", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Order {
#JsonIgnore
#ManyToOne()
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
#Fetch(FetchMode.JOIN)
private Customer customer;
}
The findByCustomerId will actually generate a query based on that method instead of using em.find. It will create something along the lines of SELECT c FROM Customer c WHERE c.customerId=:customerId. afterwards it will notice the fetch strategy and obtain the needed references. This is also explained here. The query will do exactlly what you instruct it to do.
If you want to eagerly load the reference you would need to write the query yourself along the lines of SELECT c FROM Customer c JOIN FETCH c.orders o WHERE c.customerId=:customerId, this will automatically retrieve the orders.
However the customerId is actually the primary key or identitifier for your entity and thus you should actually be using the findById or findOne method (depending on your Spring Data JPA version). This will use the EntityManager.find which should take the mapping information into account and create the appropriate query.
I have a concrete class for Employee entity. Employee is persisted by other application, I'm just using the data. I want to extend Employee to add properties that are other Entities using composition. I don't need to persist a child entity per se, but only the entities I'm trying to extend to Employee with using composition. Here is some code to help clear things up.
#Entity
#Table(name = "Legacy_Table_Name", schema = "another_owner")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "emp_id")
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String emp_id;
private String firstName;
etc...
-
public class EnhancedEmployee extends Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Transient
private SomeEntity someCompositionProperty;
#OneToMany(mappedBy = "employee", fetch = FetchType.EAGER, cascade=CascadeType.ALL)
private Collection<AnotherEntityWithItsOwnTable1> list1;
#OneToMany(mappedBy = "employee", fetch = FetchType.EAGER, cascade=CascadeType.ALL)
private Collection<AnotherEntityWithItsOwnTable2> list2;
If I make EnhancedEmployee an entity then JPA tries to create/use an EnhancedEmployee database table (right now in dev so I'm using create-drop persistence.xml property). If I remove the #Entity annotation from EnhancedEmployee, JPA complains that EnhancedEmployee is not an Entity in other classes where I want to utilize these extra properties
#ManyToMany(cascade = CascadeType.MERGE)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinTable(name = "PARTICIPATING_EMPLOYEES", joinColumns = { #JoinColumn(name = "event_id") }, inverseJoinColumns = { #JoinColumn(name = "emp_id") })
private Collection<EnhancedEmployee > participants;
All I'm trying to do is reference an Employee setter getters for these extra properties whether or not they have data persisted for these extra properties.
I realize I could probably just modify my Employee class and add someCompositionProperty and list1/2 relations to that class but doesn't that then violate https://en.wikipedia.org/wiki/Open/closed_principle. While my Employee entity class is the "same" for all my projects the source code is really part of each project's package so perhaps the open/closed doesn't apply here and I should just modify the Employee entity class
If I make EnhancedEmployee an entity then JPA tries to create/use an
EnhancedEmployee database table (right now in dev so I'm using
create-drop persistence.xml property).
You could avoid this by changing the inheritance type to SINGLE_TABLE
Also your discriminator column emp_id seems to me like a bad choice. Better change it to something like #DiscriminatorColumn(name = "TYPE") because emp_id is the primary key and and cannot repeat itself in a table. Also your EnhancedEmployee needs #DiscriminatorValue(value = "ENHANCED")
I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.
I have two tables A & B with a foreign key from B to a called FK_A. I have a not null constraint on FK_A.
I have the following two classes:
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "ID")
private long id;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name = "FK_A", nullable = true)
private Set<B> b;
//getters setters etc.
}
#Entity
#Table(name = "B")
public class B {
//attributes, getters setters etc.
}
The reads work fine but when I try to write A to the DB, I get : ORA-01400: cannot insert NULL into ("SCHEMA"."B"."FK_A")
The code I am tring to insert the entity looks something like:
#PersistenceContext(unitName = "ab")
private EntityManager em;
A a = new A();
B b = new B();
Set<B> bList = new HashSet();
bList.add(b);
a.setB(bList);
em.persist(a);
Now if I am correct shouldn't hibernate automatically populate FK_A in table B based on the id it had autogenerated. If not how can I go about setting it?
Take a look at mappedBy OneToMany.mappedBy(). JavaDoc:
The field that owns the relationship. Required unless the relationship is unidirectional.
It is a synonym for the Hibernate's keyword "inverse".
See also this question.
In addition as a variant may be your class B has property for class A declared with #ManyToOne(optional = false) annotation.
I have Hibernate Entities that look something like this (getters and setters left out):
#Entity
public class EntityA {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private EntityB parent;
}
#Entity
public class EntityB extends SuperEntity {
#OneToMany(mappedBy = "parent")
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "parent_id")
private Set<EntityA> children;
}
#MappedSuperclass
public class SuperEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long itemId;
}
When I query for EntityA it loads fine, with the parent association being replaced by a Hibernate proxy (as it is Lazy). If I want access to the parent's id I perform the following call:
EntityA entityA = queryForEntityA();
long parentId = entityA.getParent().getItemId();
As I understand that call should NOT make a roundtrip to the database, as the Id is stored in the EntityA table, and the proxy should only return that value. However, in my case this generates a SQL statement which fetches EntityB and only then returns the Id.
How can I investigate the problem? What are some likely causes of this incorrect behaviour?
As I understand that call should NOT make a roundtrip to the database, as the Id is stored in the EntityA table, and the proxy should only return that value.
Use property access type. The behavior you're experiencing is a "limitation" of field access type. Here is how Emmanuel Bernard explained it:
That is unfortunate but expected. That's one of the limitations of field level access.
Basically we have no way to know that getId() indeed only go and access the id field. So we need to load the entire object to be safe.
So change your code into:
#Entity
public class EntityA {
private EntityB parent;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
public EntityB getParent() {
return parent;
}
...
}
#MappedSuperclass
public class SuperEntity {
private long itemId;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
public long getItemId() {
return itemId;
}
...
}
Related question
Hibernate Annotations - Which is better, field or property access?
References
Proxy loaded on getId-call when using annotations on fields
proxy getId => why sql is generated !
HHH-3718 (if this issue can ever be solved)
What you say makes sense - that it would not make a DB hit since EntityA contains the parent ID. I am just not sure if the getParent() call actually loads the EntityB object regardless of whether all you're interested in is the ID. You might try marking the children collection (and any other fields) as Lazy if you want to save the DB hit.
#Entity
public class EntityB : SuperEntity {
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "parent_id")
private Set<EntityA> children;
}
As for Hibernate:
This behavior has been changed since Hibernate 5.2.12.