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#executeQueryStatementorg.hibernate.engine.jdbc.internal.StatementPreparerImpl#prepareQueryStatementorg.hibernate.jdbc.AbstractBatcherorg.hibernate.engine.jdbc.batch.internal.BatchingBatchorg.hibernate.cfg.Settingsorg.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 & @JoinColumnmappedBy: 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.
bagbag 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!
MultipleBagFetchExceptionHappens 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
@ManyToManyDON'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<>();
}
@ManyToOneCan be cascading too!
@OneToOneThis 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();
}
}