Allows seeing all statements sent to DB via JDBC. It helps
Steps to use:
spy.properties
file to the classpathThere are many other configuration scenarios.
Note:
java.sql.Statement#addBatch
is called (with query and parameters). If we want to know how
many queries that are sent by the application, this is not what we want. To only show one query per batch,
override methods onAfterAddBatch
and onAfterAddBatch
of com.p6spy.engine.event.SimpleJdbcEventListener
.This is not the only tool. There are others, such as datasource-proxy
Needed when reproducing performance issue
alter system flush buffer_cache;
alter system flush shared_pool;
## flush disk cache, unneeded in most of the case
##echo 3 > /proc/sys/vm/drop_caches
Place breakpoints in
org.hibernate.loader.Loader#executeQueryStatement
org.hibernate.engine.jdbc.internal.StatementPreparerImpl#prepareQueryStatement
org.hibernate.jdbc.AbstractBatcher
org.hibernate.engine.jdbc.batch.internal.BatchingBatch
org.hibernate.cfg.Settings
org.hibernate.boot.spi.SessionFactoryOptions
<logger name="org.hibernate.cfg.Settings" level="DEBUG" />
CascadeType.PERSIST
in 2 different paths (probably in @OneToMany
) pointing to the same entity. Just remove such cascading from one side.5.0.8
: adding a new entity to a @OneToMany(cascade = {MERGE, PERSIST})
mapped as list
and using Hibernate 3.3.1
, 3.5.6
, 3.6.0
, 3.6.10
, 4.2.6
, 4.3.11
, or 5.0.2
(may be others).When mapped to java.sql.Clob/Blob
, they make the entity always-dirty get the getter is called!
Doesn't matter, but only use one, don't mix. Hibernate will guess the access type from the position of @Id
or @EmbeddedId
mappedBy
& @JoinColumn
mappedBy: To put in a non-owning side of a bi-directional relationship.
@JoinColumn: to put in the owning side, the side owning the foreign key.
bag
bag
is one of Hibernate's implementation for mapping collections.
When defining mapping for collections, java.util.List
is used for both bag
and list
.
From the mapping perspective, the only way to know whether we are using
a bag
or a list
is looking at the existence of @OrderColumn
(or the deprecated @IndexColumn
).
@OneToMany(cascade = ALL)
@JoinTable(name = "post_comment",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "comment_id")
)
private List<Comment> tags = new ArrayList<>(); // Bag will be used
@OrderColumn(name="comment_index")
@OneToMany(cascade = ALL)
@JoinTable(name = "post_comment",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "comment_id")
)
private List<Comment> tags = new ArrayList<>(); // List will be used
What the heck is the bag
? unordered collection, can contain duplicated elements!
Comment
has one technical ID, how come it can be duplicated? Let's model
the table comment
differently: only has columns post_id
and comment_text
. Now, the mapping can be done like following@ElementCollection
@CollectionTable(name="comment", joinColumns=@JoinColumn(name="post_id"))
@Column(name="comment_text")
private List<String> tags = new ArrayList<>(); // bag will be used
Now, elements can be duplicated!
MultipleBagFetchException
Happens when simultaneously fetch more than one bag
(either via JOIN FETCH
or FetchType.EAGER
).
But why?
Let's assume the following simple query
select PARENT.parent_id,
CHILDREN_1.children_1_id,
CHILDREN_1.children_2_id
from PARENT
left outer join CHILDREN_1 on PARENT.parent_id = CHILDREN_1.parent_id
left outer join CHILDREN_2 on PARENT.parent_id = CHILDREN_2.parent_id
PARENT has one row; CHILDREN_1 has 2 rows; CHILDREN_2 has 1 row. The query result will look like:
parent_id | children_1_id | children_1_id |
---|---|---|
p 1 | children_1 A | children_2 X |
p 1 | children_1 B | children_2 X |
What do we expect?
Recall that a bag
can contain duplicate items. So in this case, the collection of CHILDREN_2 will contain 2 identical items!
That is not what the expectation of the query
@ManyToMany
DON'T use CascadeType.REMOVE
because it will not only apply to the link table but also the other side of the association!
DON'T use PersistentBag
because it leads to "delete-then-insert" behavior.
DO use PersistentList
or PersistentSet
instead.
@...
public class Post {
...
@ManyToMany(cascade = { PERSIST, MERGE })
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
// DON'T: private List<Tag> tags = new ArrayList<>();
// DO :
private Set<Tag> tags = new HashSet<>();
...
}
@...
public class Tag {
...
@ManyToMany(mappedBy = "tags")
// DON'T: private List<Post> posts = new ArrayList<>();
// DO :
private Set<Post> posts = new HashSet<>();
}
@ManyToOne
Can be cascading
too!
@OneToOne
This is not efficient in term of DB design: The parent_id
should be used as PK of the Child
If mapping bidirectional:
@Table...
@Entity...
public class Parent {
@OneToOne(mappedBy = "parent", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private Child child;
}
@Table
@Entity
public class Child {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Parent parent;
}
When loading a Parent
, the child
will always be fetched. Reason: for a managed entity, it is required both type (class) and the ID. From the Parent
entity,
the only way to know the Child
ID is fetching it!
@MapsId
- effective unidirectional
@Table
@Entity
public class Child {
// child's ID must be also the the FK pointing to its Parent
@Id
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
private Parent parent;
}
Using @OneToMany
in the non-owning side
@Table...
@Entity...
public class Parent {
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, orphanRemoval = true)
private Set<Child> children;
// Don't publish getter/setter for children property
public Child getChild() {
return children.isEmpty() ? null : children.iterator().next();
}
public void setChild(Child child) {
children.clear();
children.add(child);
child.setParent(this);
}
}
Enable it:
hibernate.jdbc.batch_size=100
hibernate.jdbc.batch_versioned_data=true
hibernate.order_updates=true
hibernate.order_inserts=true
batch_versioned_data
: since 5.0
, it's true
by default. If it is set to false
and the entities have the optimistic locking column, they won't be batched. Reason: some databases return wrong update count in case
of error thus it's impossible to detect concurrent-update error for items within the batch (this is the case of Oracle until 11g. It was fixed
since 12c)
Since 5.2
, it's possible to override batch size per session
entityManager
.unwrap(Session.class)
.setJdbcBatchSize(10); // in this session, the batch size will be 10
Purpose: loading several uninitialized proxies if one proxy is accessed
How: by select * from ... where id in (?, ?, ...)
. See BatchingEntityLoader
, BatchingCollectionInitializer
Configure:
hibernate.default_batch_fetch_size
@BatchSize
on individual entity or collectionBehavior:
Prior to Hibernate 4.2.0
: a set of queries are pre-generated depending on the configured batch size
and use the next-smaller size. See Hibernate's ArrayHelper#getBatchSizes
.
Be aware that, extra heap memory is needed!
4.2.0
: hibernate.batch_fetch_style
can be one of:
LEGACY
(above)PADDED
, like legacy, but use next-bigger size, extra ?
will be filled with the same ID (one of IDs in the batch)DYNAMIC
: the in (...)
will be generated based on the actually number of IDs.hibernate.connection.release_mode
(org.hibernate.cfg.AvailableSettings#RELEASE_CONNECTIONS
),
AFTER_TRANSACTION
. AFTER_STATEMENT
to circumvent the issue in some EE environments
in which an EJB calling another will result a connection leak.
If don't use EE, it's best to set it to AFTER_TRANSACTION
for better performance.public class ..... implements BeanFactoryAware
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
....
LocalContainerEntityManagerFactoryBean factoryBean = ...
// JPA way of scanning entities.
// Looks for packages defined in annotation @EntityScan
String[] packageToScan = EntityScanPackages.get(this.beanFactory)
.map(bean -> bean.getPackageNames().toArray(new String[0]))
.orElse(new String[0]);
factoryBean.setPackagesToScan(packageToScan);
// Looks for Hibernate mapping files
// This is equivalent to the pattern: classpath*:resources/hbm/*.hbm.xml
ClasspathScanningPersistenceUnitPostProcessor postProcessor =
new ClasspathScanningPersistenceUnitPostProcessor("resources.hbm");
postProcessor.setMappingFileNamePattern("*.hbm.xml");
factoryBean.setPersistenceUnitPostProcessors(postProcessor);
@MappedSuperclass
public abstract class AbstractEntity {
@Transient
private boolean hasHashCodeGeneratedWhileTransient = false;
....
public boolean isPersisted() {
return getId() != null;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
if (obj instanceof AbstractEntity) {
final AbstractEntity other = (AbstractEntity) obj;
if (isPersisted() && other.isPersisted()) {
return getId().equals(other.getId());
}
}
return false;
}
@Override
public int hashCode() {
if (!isPersisted()) {
hasHashCodeGeneratedWhileTransient = true;
return super.hashCode();
}
if (hasHashCodeGeneratedWhileTransient) {
return super.hashCode();
}
return getId().hashCode();
}
}