/**
   * Prepare the given Connection with the given transaction semantics.
   *
   * @param con the Connection to prepare
   * @param definition the transaction definition to apply
   * @return the previous isolation level, if any
   * @throws SQLException if thrown by JDBC methods
   * @see #resetConnectionAfterTransaction
   */
  public static Integer prepareConnectionForTransaction(
      Connection con, TransactionDefinition definition) throws SQLException {

    Assert.notNull(con, "No Connection specified");

    // Set read-only flag.
    if (definition != null && definition.isReadOnly()) {
      try {
        if (logger.isDebugEnabled()) {
          logger.debug("Setting JDBC Connection [" + con + "] read-only");
        }
        con.setReadOnly(true);
      } catch (Throwable ex) {
        // SQLException or UnsupportedOperationException
        // -> ignore, it's just a hint anyway.
        logger.debug("Could not set JDBC Connection read-only", ex);
      }
    }

    // Apply specific isolation level, if any.
    Integer previousIsolationLevel = null;
    if (definition != null
        && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
      if (logger.isDebugEnabled()) {
        logger.debug(
            "Changing isolation level of JDBC Connection ["
                + con
                + "] to "
                + definition.getIsolationLevel());
      }
      previousIsolationLevel = con.getTransactionIsolation();
      con.setTransactionIsolation(definition.getIsolationLevel());
    }

    return previousIsolationLevel;
  }
  @Override
  protected void doBegin(Object transaction, TransactionDefinition definition) {
    JpaTransactionObject txObject = (JpaTransactionObject) transaction;

    if (txObject.hasConnectionHolder()
        && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
      throw new IllegalTransactionStateException(
          "Pre-bound JDBC Connection found! JpaTransactionManager does not support "
              + "running within DataSourceTransactionManager if told to manage the DataSource itself. "
              + "It is recommended to use a single JpaTransactionManager for all transactions "
              + "on a single DataSource, no matter whether JPA or JDBC access.");
    }

    try {
      if (txObject.getEntityManagerHolder() == null
          || txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
        EntityManager newEm = createEntityManagerForTransaction();
        if (logger.isDebugEnabled()) {
          logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
        }
        txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
      }

      EntityManager em = txObject.getEntityManagerHolder().getEntityManager();

      // Delegate to JpaDialect for actual transaction begin.
      final int timeoutToUse = determineTimeout(definition);
      Object transactionData =
          getJpaDialect()
              .beginTransaction(
                  em,
                  new DelegatingTransactionDefinition(definition) {
                    @Override
                    public int getTimeout() {
                      return timeoutToUse;
                    }
                  });
      txObject.setTransactionData(transactionData);

      // Register transaction timeout.
      if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
        txObject.getEntityManagerHolder().setTimeoutInSeconds(timeoutToUse);
      }

      // Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
      if (getDataSource() != null) {
        ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
        if (conHandle != null) {
          ConnectionHolder conHolder = new ConnectionHolder(conHandle);
          if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
            conHolder.setTimeoutInSeconds(timeoutToUse);
          }
          if (logger.isDebugEnabled()) {
            logger.debug(
                "Exposing JPA transaction as JDBC transaction ["
                    + conHolder.getConnectionHandle()
                    + "]");
          }
          TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
          txObject.setConnectionHolder(conHolder);
        } else {
          if (logger.isDebugEnabled()) {
            logger.debug(
                "Not exposing JPA transaction ["
                    + em
                    + "] as JDBC transaction because JpaDialect ["
                    + getJpaDialect()
                    + "] does not support JDBC Connection retrieval");
          }
        }
      }

      // Bind the entity manager holder to the thread.
      if (txObject.isNewEntityManagerHolder()) {
        TransactionSynchronizationManager.bindResource(
            getEntityManagerFactory(), txObject.getEntityManagerHolder());
      }
      txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
    } catch (TransactionException ex) {
      closeEntityManagerAfterFailedBegin(txObject);
      throw ex;
    } catch (Exception ex) {
      closeEntityManagerAfterFailedBegin(txObject);
      throw new CannotCreateTransactionException(
          "Could not open JPA EntityManager for transaction", ex);
    }
  }
  @Override
  protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition)
      throws NotSupportedException, SystemException {

    int timeout = determineTimeout(definition);

    // Apply transaction name (if any) to WebLogic transaction.
    if (this.weblogicUserTransactionAvailable && definition.getName() != null) {
      try {
        if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) {
          /*
          weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut;
          wut.begin(definition.getName(), timeout);
          */
          this.beginWithNameAndTimeoutMethod.invoke(
              txObject.getUserTransaction(), definition.getName(), timeout);
        } else {
          /*
          weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut;
          wut.begin(definition.getName());
          */
          this.beginWithNameMethod.invoke(txObject.getUserTransaction(), definition.getName());
        }
      } catch (InvocationTargetException ex) {
        throw new TransactionSystemException(
            "WebLogic's UserTransaction.begin() method failed", ex.getTargetException());
      } catch (Exception ex) {
        throw new TransactionSystemException(
            "Could not invoke WebLogic's UserTransaction.begin() method", ex);
      }
    } else {
      // No WebLogic UserTransaction available or no transaction name specified
      // -> standard JTA begin call.
      applyTimeout(txObject, timeout);
      txObject.getUserTransaction().begin();
    }

    // Specify isolation level, if any, through corresponding WebLogic transaction property.
    if (this.weblogicTransactionManagerAvailable) {
      if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
        try {
          Transaction tx = getTransactionManager().getTransaction();
          Integer isolationLevel = definition.getIsolationLevel();
          /*
          weblogic.transaction.Transaction wtx = (weblogic.transaction.Transaction) tx;
          wtx.setProperty(ISOLATION_LEVEL_KEY, isolationLevel);
          */
          this.setPropertyMethod.invoke(tx, ISOLATION_LEVEL_KEY, isolationLevel);
        } catch (InvocationTargetException ex) {
          throw new TransactionSystemException(
              "WebLogic's Transaction.setProperty(String, Serializable) method failed",
              ex.getTargetException());
        } catch (Exception ex) {
          throw new TransactionSystemException(
              "Could not invoke WebLogic's Transaction.setProperty(String, Serializable) method",
              ex);
        }
      }
    } else {
      applyIsolationLevel(txObject, definition.getIsolationLevel());
    }
  }