Related to Hibernate

Questions answered

What are the ways to map Enum types?

UserType example of mapping Enum to int is here, Enum to string is here.

Hibernate also provides the nice implementation, which is in hibernate-annotations (merged into core from v3.6.0). Sample of usage:

hibernate-mapping

<hibernate-mapping>
    <class name="Relationship" table="relationships">
        ...
        <property name="semanticType">
            <type name="org.hibernate.type.EnumType">
                <param name="enumClass">org.mycompany.dao.SemanticType</param>
            </type>
        </property>
    </class>
</hibernate-mapping>

How to initialize a lazily loaded collection?

You need to open the session and re-associate the object with a session via Session.merge(object) before accessing the lazy collection. Alternatively you can use the following example:
public void initializeLazyCollection(MyBean bean) {
    final Session session = getSessionFactory().openSession();
 
    try {
        // Associate the bean with a session:
        session.lock(bean, LockMode.READ);
 
        Hibernate.initialize(bean.getChildren());
    }
    finally {
        session.close();
    }
}

Alternatively you may use Glead (see also Using GWT with Hibernate).

How to re-associate the detached entity with session?

If your entity has no lazy associations, then Session.lock(entity, LockMode.NONE) would do the job. Otherwise do Session.merge(entity) (see What is the proper way to re-attach detached objects in Hibernate?).

What is the difference between Session.save() and Session.persist()?

Is covered here.

Why bags equals() and hashCode() behave differently from lists and maps?

When I clear the collection and re-add entities, I get ConstraintViolationException.

This can only happen when you add entities that violate DB unique constraint (not the primary key). Ideologically this unique constraint should be the entity ID, but often it is not desired as the number of columns (= properties in composite ID) grows with each level of tables hierarchy (A→B→C) and the only way out is to use synthetic (autogenerated) ID. However Hibernate can't handle this case. As from here and here the order of issued SQL statements is:
  • all entity insertions, in the same order the corresponding objects were saved using session.save()
  • all entity updates
  • all collection deletions
  • all collection element deletions, updates and insertions
  • all collection insertions
  • all entity deletions, in the same order the corresponding objects were deleted using session.delete()

and the only solution is to flush the session after collection.clear().

Alternative solution is to walk the entity tree and re-assign the IDs.

How to write a mapping for one-to-many association with composite ID?

In our simple example we will deal with two entities: the document and the image, associated with a document. The SQL schema is:
CREATE TABLE document
(
    id        INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    country        VARCHAR(255) NOT NULL,
    indexed_date    datetime NOT NULL,
    CONSTRAINT document_pk PRIMARY KEY (id)
) engine=InnoDB;
 
CREATE TABLE image
(
    document_id    INTEGER UNSIGNED NOT NULL,
    page_number    INTEGER UNSIGNED NOT NULL,
    image_data    longblob NOT NULL,
    CONSTRAINT image_pk PRIMARY KEY (document_id, page_number),
    CONSTRAINT image_fk FOREIGN KEY (document_id) REFERENCES document (id) ON DELETE cascade
) engine=InnoDB;

In our example we will not introduce the synthetic key for image table to show how to deal with composite ID, which in our case is (document_id, page_number). The mapping is:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 
<hibernate-mapping package="com.mycompany.myproject.common">
    <class name="Document" table="document">
        <id name="id">
            <generator class="native" />
        </id>
 
        <property name="country" />
        <property name="indexedDate" column="indexed_date" />
 
        <bag name="images" order-by="page_number" cascade="all-delete-orphan" lazy="true" inverse="true" fetch="select">
            <key column="document_id" />
 
            <one-to-many class="DocumentImage" />
        </bag>
    </class>
 
    <class name="DocumentImage" table="image">
        <composite-id>
            <key-many-to-one name="parentDocument" class="Document" column="document_id" />
            <key-property name="pageNumber" column="page_number" />
        </composite-id>
 
        <property name="imageData" column="image_data" />
    </class>
</hibernate-mapping>

Hibernate can only trace back the relations which are explicitly described as <many-to-one> or <key-many-to-one>, i.e. they are bidirectional. So for our example the following mapping will not work:
<class name="DocumentImage" table="image">
    <composite-id>
        <key-property name="documentId" column="document_id" />
        <key-property name="pageNumber" column="page_number" />
    </composite-id>
    ...
</class>

as Hibernate will not trace the relation back to document table by only knowing the column name document_id (see HHH-4012).

Java code example:

Document document = new Document();
 
document.setIndexedDate(new Timestamp(9876543210L));
document.setCountry("USA")
 
Collection<DocumentImage> images = new ArrayList<DocumentImage>();
 
{
    final DocumentImage image = new DocumentImage();
 
    image.setPageNumber(1);
    image.setImageData("ANdj17shOL12p".getBytes());
    image.setParentDocument(document);
 
    images.add(image);
}
 
{
    final DocumentImage image = new DocumentImage();
 
    image.setPageNumber(2);
    image.setImageData("(*&$@!9&!".getBytes());
    image.setParentDocument(document);
 
    images.add(image);
}
 
document.setImages(images);
 
Session session = getSessionFactory().openSession();
 
session.saveOrUpdate(document);
session.flush();
session.close();

If you use cascade="all-delete-orphan" for your association, you need to be careful when updating the lazily loaded collection of images, in particular, when you use the code document.setImages(images) you actually replace the Hibernate lazy collection instance with another implementation. This confuses Hibernate, who will throw the following exception:
org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.mycompany.myproject.common.Document.images
    at org.hibernate.engine.Collections.processDereferencedCollection(Collections.java:118)
    at org.hibernate.engine.Collections.processUnreachableCollection(Collections.java:62)
    at org.hibernate.event.def.AbstractFlushingEventListener.flushCollections(AbstractFlushingEventListener.java:241)
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:100)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1206)

Knowing that Hibernate will in any case initialize the collection (maybe with empty collection instance), you need to use the following code: document.getImages().addAll(images). We need to specify cascade="all" (or weaker) and to control the session orphans ourselves.

You can pass the persistent/transient Hibernate object to Session#saveOrUpdate() or to Session#persist().
  • In first case saveOrUpdate event be triggered on parent entity and all child relations, causing Hibernate to check each entity if it exists in the database (so Hibernate will issue as many select from image statements plus either update image or insert into image as there are entities in collection).
  • In second case saveOrUpdate event be triggered on parent entity and persist event will be triggered for all child entities, so only insert into image statements will be issued. Database will trigger the exception in case the child entity with the same ID already exists.

For example, assume that you have another one-to-many relation in documents (the text paragraphs), and you want to persist images and not paragraphs.
  • In case of Session#persist() the side effect is that Hibernate will initialize/load the lazy collection with paragraphs (will issue one select * from paragraph), but persist event will trigger no action for these newly loaded entities. In this case you need to set the collection which you don't want to be processed to null:
    document.setParagraphs(null);
    document.setImages(images);
     
    session.persist(document);
  • In case of Session#saveOrUpdate() the side effect is that Hibernate will check each entity if it exists in the database.

For more examples of how to use bidirectional associations with composite keys, see examples that come together with Hibernate distribution (e.g. have a loot at hibernate-distribution.tar.gz/project\testsuite\src\test\java\org\hibernate\test\legacy\Middle.hbm.xml).

What is the difference between delete v.s. delete-orphan and all-delete-orphan v.s. all,delete-orphan ?

From What is the difference between ''delete-orphan'' and ''delete''?:

Cascade delete means if parent entity is deleted, delete the related entities. delete-orphan means: if an entity is removed from a related one-to-many collection, then not only disassociate it from the current entity, but delete it (does not allow it to stay orphan).

all-delete-orphan and all,delete-orphan are synonyms and should result the same behaviour (see here).

How to set the the foreign key to NULL when the parent entity is deleted?

I think that is exactly what controlled by inverse property:
  • if inverse='false' then when collection entry is removed, the parent key is set to null (update child set parent_id = null where parent_id = ?)
  • if inverse='true' then when collection entry is removed, it is deleted by key (delete from child where id = ?)

How to expand the lifetime of Hibernate session across two transactions (e.g. two @Transactional methods) to preserve L1 cache?

By default Hibernate session is closed as soon as transaction is closed. As mentioned here you need to define interceptor that binds the Hibernate session to HTTP request and thus allows it to span across all transactions:
<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/mvc    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
 
    <mvc:interceptors>
        <bean class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
            <property name="sessionFactory" ref="hibernateSessionFactory" />
        </bean>
    </mvc:interceptors>
</beans>

The corresponding flow is displayed on figure 5.4 “Implementing application transactions with a long Session using disconnection” on p174 in chapter “Transactions, concurrency, and caching” of Hibernate In Action by Christian Bauer and Gavin King, Manning, 2005.

To Unit test it you need a helper method, that repeats the functionality of Spring DispatcherServlet:

public abstract class AbstractTest {
 
    @Autowired
    protected HandlerAdapter            handlerAdapter;
 
    @Autowired
    protected HandlerMapping            handlerMapping;
 
    protected MockHttpServletRequest    request        = new MockHttpServletRequest();
    protected MockHttpServletResponse    response    = new MockHttpServletResponse();
 
    /**
     * This function feeds the given request to Spring MVC. The code is a simplified flow from
     * {@link org.springframework.web.servlet.DispatcherServlet}.
     */
    protected void handleRequest() throws Exception {
        final HandlerExecutionChain handler = handlerMapping.getHandler(request);
        final HandlerInterceptor[] interceptors = handler.getInterceptors();
 
        if (interceptors != null) {
            for (int i = 0; i < interceptors.length; i++) {
                if (!interceptors[i].preHandle(request, response, handler.getHandler())) {
                    throw new IllegalStateException("The interceptor " + interceptors[i]
                                + " prevents the request from being processed");
                }
            }
        }
 
        handlerAdapter.handle(request, response, handler.getHandler());
    }
 
    /**
     * The same as {@link #handleRequest()} but sets a request URL beforehand.
     */
    protected void handleRequest(String requestURI) throws Exception {
        request.setRequestURI(requestURI);
        handleRequest();
    }
}

Alternatively, you can use the following code (assuming that property HibernateTemplate.alwaysUseNewSession = false):

SessionFactoryUtils.initDeferredClose(getHibernateTemplate().getSessionFactory());
 
try {
    getHibernateTemplate().doSomething();
    ...
    getHibernateTemplate().doAgain();
}
finally {
    SessionFactoryUtils.processDeferredClose(getHibernateTemplate().getSessionFactory());
}

How to avoid OutOfMemoryError caused by constantly growing Hibernate L1 cache?

You need to set autoClear property on Session instance, which will clean the session after the transaction completion (from here):
final Session session = getSessionFactory().getCurrentSession();
 
if (session instanceof SessionImplementor) {
    ((SessionImplementor) session).setAutoClear(true);
}

You may also wish to disable the 2nd level cache and analyse the heap dump (export JAVA_OPTS=-verbose:gc -XX:+PrintClassHistogram -XX:+PrintGCDetails -Xloggc:/tmp/jvm_loggc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp + Memory Analyzer). See the complete note.

The column datatype which I would like to query via native SQL is char(3) but Hibernate maps it to java.lang.Character so only first character is returned. How to change the returned type to java.lang.String?

You need to explicitly define the return type as mentioned here in your mapping file:

hibernate-mapping.xml

<sql-query name="myQuery">
    <query-param name="days" type="int" />
    <return-scalar column="count" type="int" />
    <return-scalar column="section_name" type="string" />
    <![CDATA[select count(id) as count, section_name from document where days <= :days]]>
</sql-query>

or programmatically via org.hibernate.SQLQuery#addScalar(String columnAlias, Type type).

software/hibernate.txt · Last modified: 2010/09/21 21:50 by dmitry
 
 
Recent changes RSS feed Driven by DokuWiki