All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dspace.core.HibernateDBConnection Maven / Gradle / Ivy

There is a newer version: 8.0
Show newest version
/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.core;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;

import org.dspace.authorize.ResourcePolicy;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.handle.Handle;
import org.dspace.storage.rdbms.DatabaseConfigVO;
import org.hibernate.FlushMode;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.proxy.HibernateProxyHelper;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.hibernate5.SessionFactoryUtils;

/**
 * Hibernate implementation of the DBConnection.
 * 

* NOTE: This class does NOT represent a single Hibernate database connection. Instead, it wraps * Hibernate's Session object to obtain access to a database connection in order to execute one or more * transactions. *

* Per DSpace's current Hibernate configuration ([dspace]/config/core-hibernate.xml), we use the one-session-per-thread * approach (ThreadLocalSessionContext). This means that Hibernate creates a single Session per thread (request), at the * time when getCurrentSession() is first called. *

* This Session may be reused for multiple Transactions, but if commit() is called, any objects (Entities) in * the Session become disconnected and MUST be reloaded into the Session (see reloadEntity() method below). *

* If an Error occurs, the Session itself is invalidated. No further Transactions can be run on that Session. *

* DSpace generally follows the "Session-per-request" transactional pattern described here: * https://docs.jboss.org/hibernate/orm/5.0/userguide/en-US/html/ch06.html#session-per-request * * * @author kevinvandevelde at atmire.com */ public class HibernateDBConnection implements DBConnection { @Autowired(required = true) @Qualifier("sessionFactory") private SessionFactory sessionFactory; private boolean batchModeEnabled = false; private boolean readOnlyEnabled = false; /** * Retrieves the current Session from Hibernate (per our settings, Hibernate is configured to create one Session * per thread). If Session doesn't yet exist, it is created. A Transaction is also initialized (or reinintialized) * in the Session if one doesn't exist, or was previously closed (e.g. if commit() was previously called) * @return Hibernate current Session object * @throws SQLException */ @Override public Session getSession() throws SQLException { // If we don't yet have a live transaction, start a new one // NOTE: a Session cannot be used until a Transaction is started. if (!isTransActionAlive()) { sessionFactory.getCurrentSession().beginTransaction(); configureDatabaseMode(); } // Return the current Hibernate Session object (Hibernate will create one if it doesn't yet exist) return sessionFactory.getCurrentSession(); } /** * Check if the connection has a currently active Transaction. A Transaction is active if it has not yet been * either committed or rolled back. * @return */ @Override public boolean isTransActionAlive() { Transaction transaction = getTransaction(); return transaction != null && transaction.isActive(); } /** * Retrieve the current Hibernate Transaction object from our Hibernate Session. * @return current Transaction (may be active or inactive) or null */ protected Transaction getTransaction() { return sessionFactory.getCurrentSession().getTransaction(); } /** * Check if Hibernate Session is still "alive" / open. An open Session may or may not have an open Transaction * (so isTransactionAlive() may return false even if isSessionAlive() returns true). A Session may be reused for * multiple transactions (e.g. if commit() is called, the Session remains alive while the Transaction is closed) * * @return true if Session is alive, false otherwise */ @Override public boolean isSessionAlive() { return sessionFactory.getCurrentSession() != null && sessionFactory.getCurrentSession().isOpen(); } /** * Rollback any changes applied to the current Transaction. This also closes the Transaction. A new Transaction * may be opened the next time getSession() is called. * @throws SQLException */ @Override public void rollback() throws SQLException { if (isTransActionAlive()) { getTransaction().rollback(); } } /** * Close our current Database connection. This also closes & unbinds the Hibernate Session from our thread. *

* NOTE: Because DSpace configures Hibernate to automatically create a Session per thread, a Session may still * exist after this method is called (as Hibernate may automatically create a new Session for the current thread). * However, Hibernate will automatically clean up any existing Session when the thread closes. * @throws SQLException */ @Override public void closeDBConnection() throws SQLException { if (sessionFactory.getCurrentSession() != null && sessionFactory.getCurrentSession().isOpen()) { sessionFactory.getCurrentSession().close(); } } /** * Commits any current changes cached in the Hibernate Session to the database & closes the Transaction. * To open a new Transaction, you may call getSession(). *

* WARNING: When commit() is called, while the Session is still "alive", all previously loaded objects (entities) * become disconnected from the Session. Therefore, if you continue to use the Session, you MUST reload any needed * objects (entities) using reloadEntity() method. * * @throws SQLException */ @Override public void commit() throws SQLException { if (isTransActionAlive() && !getTransaction().getStatus().isOneOf(TransactionStatus.MARKED_ROLLBACK, TransactionStatus.ROLLING_BACK)) { // Flush synchronizes the database with in-memory objects in Session (and frees up that memory) getSession().flush(); // Commit those results to the database & ends the Transaction getTransaction().commit(); } } @Override public synchronized void shutdown() { sessionFactory.close(); } @Override public String getType() { return ((SessionFactoryImplementor) sessionFactory) .getJdbcServices().getDialect().toString(); } @Override public DataSource getDataSource() { return SessionFactoryUtils.getDataSource(sessionFactory); } @Override public DatabaseConfigVO getDatabaseConfig() throws SQLException { DatabaseConfigVO databaseConfigVO = new DatabaseConfigVO(); try (Connection connection = getDataSource().getConnection()) { DatabaseMetaData metaData = connection.getMetaData(); databaseConfigVO.setDatabaseDriver(metaData.getDriverName()); databaseConfigVO.setDatabaseUrl(metaData.getURL()); databaseConfigVO.setSchema(metaData.getSchemaTerm()); databaseConfigVO.setMaxConnections(metaData.getMaxConnections()); databaseConfigVO.setUserName(metaData.getUserName()); } return databaseConfigVO; } @Override public long getCacheSize() throws SQLException { return getSession().getStatistics().getEntityCount(); } /** * Reload an entity into the Hibernate cache. This can be called after a call to commit() to re-cache an object * in the Hibernate Session (see commit()). Failing to reload objects into the cache may result in a Hibernate * throwing a "LazyInitializationException" if you attempt to use an object that has been disconnected from the * Session cache. * @param entity The DSpace object to reload * @param The class of the entity. The entity must implement the {@link ReloadableEntity} interface. * @return the newly cached object. * @throws SQLException */ @Override @SuppressWarnings("unchecked") public E reloadEntity(final E entity) throws SQLException { if (entity == null) { return null; } else if (getSession().contains(entity)) { return entity; } else { return (E) getSession().get(HibernateProxyHelper.getClassWithoutInitializingProxy(entity), entity.getID()); } } @Override public void setConnectionMode(final boolean batchOptimized, final boolean readOnlyOptimized) throws SQLException { this.batchModeEnabled = batchOptimized; this.readOnlyEnabled = readOnlyOptimized; configureDatabaseMode(); } @Override public boolean isOptimizedForBatchProcessing() { return batchModeEnabled; } private void configureDatabaseMode() throws SQLException { if (batchModeEnabled) { getSession().setHibernateFlushMode(FlushMode.ALWAYS); } else if (readOnlyEnabled) { getSession().setHibernateFlushMode(FlushMode.MANUAL); } else { getSession().setHibernateFlushMode(FlushMode.AUTO); } } /** * Evict an entity from the hibernate cache. *

* When an entity is evicted, it frees up the memory used by that entity in the cache. This is often * necessary when batch processing a large number of objects (to avoid out-of-memory exceptions). * * @param entity The entity to evict * @param The class of the entity. The entity must implement the {@link ReloadableEntity} interface. * @throws SQLException When reloading the entity from the database fails. */ @Override public void uncacheEntity(E entity) throws SQLException { if (entity != null) { if (entity instanceof DSpaceObject) { DSpaceObject dso = (DSpaceObject) entity; // The metadatavalue relation has CascadeType.ALL, so they are evicted automatically // and we don' need to uncache the values explicitly. if (Hibernate.isInitialized(dso.getHandles())) { for (Handle handle : Utils.emptyIfNull(dso.getHandles())) { uncacheEntity(handle); } } if (Hibernate.isInitialized(dso.getResourcePolicies())) { for (ResourcePolicy policy : Utils.emptyIfNull(dso.getResourcePolicies())) { uncacheEntity(policy); } } } // ITEM if (entity instanceof Item) { Item item = (Item) entity; //DO NOT uncache the submitter. This could be the current eperson. Uncaching could lead to //LazyInitializationExceptions (see DS-3648) if (Hibernate.isInitialized(item.getBundles())) { for (Bundle bundle : Utils.emptyIfNull(item.getBundles())) { uncacheEntity(bundle); } } // BUNDLE } else if (entity instanceof Bundle) { Bundle bundle = (Bundle) entity; if (Hibernate.isInitialized(bundle.getBitstreams())) { for (Bitstream bitstream : Utils.emptyIfNull(bundle.getBitstreams())) { uncacheEntity(bitstream); } } // BITSTREAM // No specific child entities to decache // COMMUNITY } else if (entity instanceof Community) { Community community = (Community) entity; // We don't uncache groups as they might still be referenced from the Context object if (Hibernate.isInitialized(community.getLogo())) { uncacheEntity(community.getLogo()); } // COLLECTION } else if (entity instanceof Collection) { Collection collection = (Collection) entity; //We don't uncache groups as they might still be referenced from the Context object if (Hibernate.isInitialized(collection.getLogo())) { uncacheEntity(collection.getLogo()); } if (Hibernate.isInitialized(collection.getTemplateItem())) { uncacheEntity(collection.getTemplateItem()); } } // Unless this object exists in the session, we won't do anything if (getSession().contains(entity)) { // If our Session has unsaved changes (dirty) and not READ-ONLY if (!readOnlyEnabled && getSession().isDirty()) { // write changes to database (don't worry if transaction fails, flushed changes will be rolled back) getSession().flush(); } // Remove object from Session getSession().evict(entity); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy