/**
   * Gets Hibernate session.
   *
   * @param tx Cache transaction.
   * @return Session.
   */
  Session session(@Nullable GridCacheTx tx) {
    Session ses;

    if (tx != null) {
      ses = tx.meta(ATTR_SES);

      if (ses == null) {
        ses = sesFactory.openSession();

        ses.beginTransaction();

        // Store session in transaction metadata, so it can be accessed
        // for other operations on the same transaction.
        tx.addMeta(ATTR_SES, ses);

        if (log.isDebugEnabled())
          log.debug("Hibernate session open [ses=" + ses + ", tx=" + tx.xid() + "]");
      }
    } else {
      ses = sesFactory.openSession();

      ses.beginTransaction();
    }

    return ses;
  }
  /** {@inheritDoc} */
  @Override
  public void txEnd(GridCacheTx tx, boolean commit) throws GridException {
    init();

    Session ses = tx.removeMeta(ATTR_SES);

    if (ses != null) {
      Transaction hTx = ses.getTransaction();

      if (hTx != null) {
        try {
          if (commit) {
            ses.flush();

            hTx.commit();
          } else hTx.rollback();

          if (log.isDebugEnabled())
            log.debug("Transaction ended [xid=" + tx.xid() + ", commit=" + commit + ']');
        } catch (HibernateException e) {
          throw new GridException(
              "Failed to end transaction [xid=" + tx.xid() + ", commit=" + commit + ']', e);
        } finally {
          ses.close();
        }
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public void put(@Nullable GridCacheTx tx, K key, @Nullable V val) throws GridException {
    init();

    if (log.isDebugEnabled())
      log.debug("Store put [key=" + key + ", val=" + val + ", tx=" + tx + ']');

    if (val == null) {
      remove(tx, key);

      return;
    }

    Session ses = session(tx);

    try {
      GridCacheHibernateBlobStoreEntry entry =
          new GridCacheHibernateBlobStoreEntry(toBytes(key), toBytes(val));

      ses.saveOrUpdate(entry);
    } catch (HibernateException e) {
      rollback(ses, tx);

      throw new GridException(
          "Failed to put value to cache store [key=" + key + ", val" + val + "]", e);
    } finally {
      end(ses, tx);
    }
  }
  /** {@inheritDoc} */
  @SuppressWarnings({"unchecked", "RedundantTypeArguments"})
  @Override
  public V load(@Nullable GridCacheTx tx, K key) throws GridException {
    init();

    if (log.isDebugEnabled()) log.debug("Store load [key=" + key + ", tx=" + tx + ']');

    Session ses = session(tx);

    try {
      GridCacheHibernateBlobStoreEntry entry =
          (GridCacheHibernateBlobStoreEntry)
              ses.get(GridCacheHibernateBlobStoreEntry.class, toBytes(key));

      if (entry == null) return null;

      return fromBytes(entry.getValue());
    } catch (HibernateException e) {
      rollback(ses, tx);

      throw new GridException("Failed to load value from cache store with key: " + key, e);
    } finally {
      end(ses, tx);
    }
  }
  /**
   * Ends hibernate session.
   *
   * @param ses Hibernate session.
   * @param tx Cache ongoing transaction.
   */
  private void end(Session ses, GridCacheTx tx) {
    // Commit only if there is no cache transaction,
    // otherwise txEnd() will do all required work.
    if (tx == null) {
      Transaction hTx = ses.getTransaction();

      if (hTx != null && hTx.isActive()) hTx.commit();

      ses.close();
    }
  }
  /** {@inheritDoc} */
  @SuppressWarnings({"JpaQueryApiInspection", "JpaQlInspection"})
  @Override
  public void remove(@Nullable GridCacheTx tx, K key) throws GridException {
    init();

    if (log.isDebugEnabled()) log.debug("Store remove [key=" + key + ", tx=" + tx + ']');

    Session ses = session(tx);

    try {
      Object obj = ses.get(GridCacheHibernateBlobStoreEntry.class, toBytes(key));

      if (obj != null) ses.delete(obj);
    } catch (HibernateException e) {
      rollback(ses, tx);

      throw new GridException("Failed to remove value from cache store with key: " + key, e);
    } finally {
      end(ses, tx);
    }
  }