文档章节

Hasor JDBC 的难关,嵌套事务处理思路

哈库纳
 哈库纳
发布于 2013/12/29 14:27
字数 3739
阅读 1513
收藏 19

    本文存属提醒我自己不要忘记的事情。也是向大家展示 Hasor 对于 JDBC 方面即将的又一个重大的进步。目前该方案还在实施中。

    前段时间闲着没事分析了下 Spring JDBC ,觉得 Spring JDBC 的设计实在是太绝了,于是就拷贝了 Spring JDBC 的关键接口,然后开始了迁移工作,最后 Hasor - JDBC 问世。

    可是 Hasor JDBC 至今仍有一个重大问题没有搞定,那就是事务控制。

    虽然可以通过暴露 Connection 简单的加装一个 Aop 拦截器在配合 @Tar... 注解可以完成任务。但是我觉得我有点完美主义了。最近脑袋里一直都是 Spring 那套事务控制体系,我有种冲动在 Hasor 中重新实现这一套事务控制体系。

    简介一下 Spring 事务方面的内容,Spring 对于事务方面支持 7种事务传播属性。我用这个接口表示它们:

/**
 * 事务传播属性
 * @version : 2013-10-30
 * @author 赵永春(zyc@hasor.net)
 */
public enum TransactionBehavior {
    /**
     * 加入已有事务
     * <p><i><b>释意</b></i>:尝试加入已经存在的事务中,如果没有则开启一个新的事务。*/
    PROPAGATION_REQUIRED,
    /**
     * 独立事务
     * <p><i><b>释意</b></i>:将挂起当前存在的事务,然后开启一个独立的事务进行处理(如果存在的话)。
     * 并且开启一个全新的事务,新事务与已存在的事务之间彼此没有关系。*/
    RROPAGATION_REQUIRES_NEW,
    /**
     * 嵌套事务
     * <p><i><b>释意</b></i>:在当前事务中开启一个子事务。
     * 如果事务回滚将连同上一级事务一同回滚(当主事务提交或回滚,子事务也会提交或回滚)
     * <p><i><b>注意</b></i>:需要驱动支持保存点。*/
    PROPAGATION_NESTED,
    /**
     * 跟随环境
     * <p><i><b>释意</b></i>:如果当前没有事务存在,就以非事务方式执行;如果有,就使用当前事务。*/
    PROPAGATION_SUPPORTS,
    /**
     * 非事务方式
     * <p><i><b>释意</b></i>:如果当前没有事务存在,就以非事务方式执行;如果有,就将当前事务挂起。
     * */
    PROPAGATION_NOT_SUPPORTED,
    /**
     * 排除事务
     * <p><i><b>释意</b></i>:如果当前没有事务存在,就以非事务方式执行;如果有,就抛出异常。*/
    PROPAGATION_NEVER,
    /**
     * 强制要求事务
     * <p><i><b>释意</b></i>:如果当前没有事务存在,就抛出异常;如果有,就使用当前事务。*/
    PROPAGATION_MANDATORY,
}

    由于分析过 Spring 有关事务控制部分的代码,因此着手实现起来细节问题倒不是很难。Hasor 目前遇到的问题是结构设计上的难题。

    首先 Hasor-JDBC 是一个几乎是完全独立的项目,甚至它都不需要 Hasor-Core 的支持。这就意味着,Hasor-JDBC 是独立的。

    其次我在设计 Hasor 时候一直保持着,稳定依赖原则,包与包之间的依赖完全隔离。这也为设计 Hasor-JDBC 的结构提出了要求。

    之所以这样的缘由是这样的,首先我在设计 Hasor-JDBC 时候并不想像 Spring JDBC 那样,让 JdbcTemplate 部分和事务控制部分产生代码依赖。因此需要拆解它们。

    正因为如此 Hasor-JDBC 在 v0.0.1 版本时可以率先发布 JdbcTemplate 部分功能。而事务控制则可以交给插件体系完成。

    这样一来 Hasor 的松散设计会让 Hasor 稳定很多,万一事务控制过于复杂,开发者可以有选择的关闭这个插件,从而避免相关逻辑代码判断提高运行效率。而这一切在 Spring JDBC 中是不可能的,Spring JDBC 在两者之间有着一些代码依赖。

    为了达到这样的目的,我为 Hasor-JDBC 建立了一个 DataSourceUtils。通过它的静态方法 申请/释放 Connection 对象。这样一来 JDBC 数据库操作部分就和事务完全隔离开了。

    事务控制部分和JDBC 操作部分之间只需要通过 DataSourceUtils 上注册的 DataSourceHelper 进行耦合。

    默认情况下提供一个基于线程绑定的 DataSourceHelper 工具类,事务控制可以扩展这个类重新注册它。

------------------------------

    我先把负责实现上面 7 个事务传播属性的关键代码贴上来分享给大家,由于代码约有300行,这部分代码在本文最后奉献上,它这个类在 Hasor 中算是比较庞大的了,大家可以先看一下后面要介绍的实现原理然后在看关键代码。

   首先为了支持多数据源下的嵌套事务管理,事务管理器是只针对一个数据源的。

   其次,由于事务可以嵌套,因此需要一个“事务栈”先进后出的原则处理每一个事务请求。这是由于考虑到事务“原子性”的问题才这样设计的。

    比方说:如果连续开启了 3个事务。当递交第一个事务时,无论后面两个事务是否已经递交都需要递交。回滚也是如此。至于为什么一定要使用“事务栈”的先进后出去实现,其主要原因是事务可能位于多个 Connection 中的缘故(详见事务传播属性)。

    此外还有挂起和恢复事务,这需要与线程绑定。

    中和起来设计这个事务控制方案还是比较棘手的,不过可以借助下面这张表述事务链的图来解释。

    借助 AOP 思想,如果发生嵌套事务就为每一层事务创建一个事务状态,然后将事务状态放入“事务栈“。

    由于事务是和线程绑定的,这就可以保证事务在多线程下的调用安全,不会发生跨线程问题。

    位于事务栈中非顶端事务如果出现 commit/rollback 时,可以借助事务栈完成原子操作。

    事务状态中需要保存具体操作数据库的那个 JDBC Connection接口。

    每次创建事务状态时,如果是新申请的数据库连接,那么就设置其一个 NewConn 标志。这个标志可以用于处理嵌套事务中递交时不是将整个事务递交而是递交一个事务保存点。

    如果传播属性要求的是独立事务,那么可以将当前事务的Connection 保存起来,然后重新申请一个再次绑定到线程上。已完成传播属性要求,当这个独立事务处理完成之后,在将保存的 Connection 重新与当前线程绑定。

    如果是跟随环境的事务传播属性,则整个事务控制可以什么都不做,如果是不需要事务则可以通过判断当前连接是否为 autoCommit 来进行后续处理。

    上面是分析 Spring 事务控制时关键点的实现策略,下面是 我在 Hasor 中依照这个思想设计的事务管理器关键代码,由于是半成品。下面这段代码只能用于展示具体处理每一个不同传播属性时的细节。它还需要和整个 Hasor-JDBC 事务控制体系串起来才可以运行,现在和大家分享它们:

/**
 * 某一个数据源的事务管理器
 * 
 * <p><b><i>事务栈:</i></b>
 * <p>事务管理器允许使用不同的传播属性反复开启新的事务。所有被开启的事务在正确处置(commit,rollback)
 * 它们之前都会按照先后顺序依次压入事务管理器的“事务栈”中。一旦有事务被处理(commit,rollback)这个事务才会被从事务栈中弹出。
 * <p>倘若被弹出的事务(A)并不是栈顶的事务,那么在事务(A)被处理(commit,rollback)时会优先处理自事务(A)以后开启的其它事务。
 * 
 * @version : 2013-10-30
 * @author 赵永春(zyc@hasor.net)
 */
public abstract class AbstractPlatformTransactionManager implements TransactionManager {
    private int                           defaultTimeout = -1;
    private LinkedList<TransactionStatus> tStatusStack   = new LinkedList<TransactionStatus>();
    
    public boolean hasTransaction() {
        return !tStatusStack.isEmpty();
    }
    public boolean isTopTransaction(TransactionStatus status) {
        if (tStatusStack.isEmpty())
            return false;
        return this.tStatusStack.peek() == status;
    }
    /**开启事务*/
    public final TransactionStatus getTransaction(TransactionBehavior behavior) throws TransactionDataAccessException {
        Hasor.assertIsNotNull(behavior);
        return getTransaction(behavior, TransactionLevel.ISOLATION_DEFAULT);
    };
    public final TransactionStatus getTransaction(TransactionBehavior behavior, TransactionLevel level) throws TransactionDataAccessException {
        Hasor.assertIsNotNull(behavior);
        Hasor.assertIsNotNull(level);
        Object transaction = doGetTransaction();获取目前事务对象
        AbstractTransactionStatus defStatus = null;TODO new AbstractTransactionStatus(behavior, level, transaction);
        /*-------------------------------------------------------------
        |                      环境已经存在事务
        |
        | PROPAGATION_REQUIRED     :加入已有事务(不处理)
        | RROPAGATION_REQUIRES_NEW :独立事务(挂起当前事务,开启新事务)
        | PROPAGATION_NESTED       :嵌套事务(设置保存点)
        | PROPAGATION_SUPPORTS     :跟随环境(不处理)
        | PROPAGATION_NOT_SUPPORTED:非事务方式(仅挂起当前事务)
        | PROPAGATION_NEVER        :排除事务(异常)
        | PROPAGATION_MANDATORY    :强制要求事务(不处理)
        ===============================================================*/
        if (this.isExistingTransaction(transaction) == true) {
            /*RROPAGATION_REQUIRES_NEW:独立事务*/
            if (behavior == RROPAGATION_REQUIRES_NEW) {
                this.suspend(transaction, defStatus);/*挂起当前事务*/
                this.processBegin(transaction, defStatus);/*开启一个新的事务*/
            }
            /*PROPAGATION_NESTED:嵌套事务*/
            if (behavior == PROPAGATION_NESTED) {
                defStatus.markHeldSavepoint();/*设置保存点*/
            }
            /*PROPAGATION_NOT_SUPPORTED:非事务方式*/
            if (behavior == PROPAGATION_NOT_SUPPORTED) {
                this.suspend(transaction, defStatus);/*挂起当前事务*/
            }
            /*PROPAGATION_NEVER:排除事务*/
            if (behavior == PROPAGATION_NEVER)
                throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");
            return defStatus;
        }
        /*-------------------------------------------------------------
        |                      环境不经存在事务
        |
        | PROPAGATION_REQUIRED     :加入已有事务(开启新事务)
        | RROPAGATION_REQUIRES_NEW :独立事务(开启新事务)
        | PROPAGATION_NESTED       :嵌套事务(开启新事务)
        | PROPAGATION_SUPPORTS     :跟随环境(不处理)
        | PROPAGATION_NOT_SUPPORTED:非事务方式(不处理)
        | PROPAGATION_NEVER        :排除事务(不处理)
        | PROPAGATION_MANDATORY    :强制要求事务(异常)
        ===============================================================*/
        /*PROPAGATION_REQUIRED:加入已有事务*/
        if (behavior == PROPAGATION_REQUIRED ||
        /*RROPAGATION_REQUIRES_NEW:独立事务*/
        behavior == RROPAGATION_REQUIRES_NEW ||
        /*PROPAGATION_NESTED:嵌套事务*/
        behavior == PROPAGATION_NESTED) {
            this.processBegin(transaction, defStatus);/*开启事务*/
        }
        /*PROPAGATION_MANDATORY:强制要求事务*/
        if (behavior == PROPAGATION_MANDATORY)
            throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
        return defStatus;
    }
    /**使用一个新的连接开启一个新的事务作为当前事务。请确保在调用该方法时候当前不存在事务。*/
    private void processBegin(Object transaction, AbstractTransactionStatus defStatus) {
        try {
            doBegin(transaction, defStatus);
            this.tStatusStack.push(defStatus);/*入栈*/
        } catch (SQLException ex) {
            throw new TransactionDataAccessException("SQL Exception :", ex);
        }
    }
    /**判断当前事务对象是否已经处于事务中。该方法会用于评估事务传播属性的处理方式。*/
    protected abstract boolean isExistingTransaction(Object transaction);
    /**在当前连接上开启一个全新的事务*/
    protected abstract void doBegin(Object transaction, AbstractTransactionStatus defStatus) throws SQLException;
    
    /**递交事务*/
    public final void commit(TransactionStatus status) throws TransactionDataAccessException {
        Object transaction = doGetTransaction();获取底层维护的当前事务对象
        AbstractTransactionStatus defStatus = (AbstractTransactionStatus) status;
        /*已完毕,不需要处理*/
        if (defStatus.isCompleted())
            throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
        /*回滚情况*/
        if (defStatus.isRollbackOnly()) {
            if (Hasor.isDebugLogger())
                Hasor.logDebug("Transactional code has requested rollback");
            rollBack(defStatus);
            return;
        }
        /*-------------------------------------------------------------
        | 1.无论何种传播形式,递交事务操作都会将 isCompleted 属性置为 true。
        | 2.如果事务状态中包含一个未处理的保存点。仅递交保存点,而非递交整个事务。
        | 3.事务 isNew 只有为 true 时才真正触发递交事务操作。
        ===============================================================*/
        try {
            prepareCommit(defStatus);
            /*如果包含保存点,在递交事务时只处理保存点*/
            if (defStatus.hasSavepoint())
                defStatus.releaseHeldSavepoint();
            else if (defStatus.isNewConnection())
                doCommit(transaction, defStatus);
            
        } catch (SQLException ex) {
            rollBack(defStatus);/*递交失败,回滚*/
            throw new TransactionDataAccessException("SQL Exception :", ex);
        } finally {
            cleanupAfterCompletion(defStatus);
        }
    }
    /**递交前的预处理*/
    private void prepareCommit(AbstractTransactionStatus defStatus) {
        /*首先预处理的事务必须存在于管理器的事务栈内某一位置中,否则要处理的事务并非来源于该事务管理器。*/
        if (this.tStatusStack.contains(defStatus) == false)
            throw new IllegalTransactionStateException("This transaction is not derived from this Manager.");
        /*-------------------------------------------------------------
        | 如果预处理的事务并非位于栈顶,则进行弹栈操作。
        |--------------------------\
        | T5  ^   <-- pop-up       | 假定预处理的事务为 T4,那么:
        | T4  ^   <-- pop-up       | T5 事务会被先递交,然后是 T4
        | T3  .   <-- defStatus    | 接下来就完成了预处理。
        | T2                       |
        | T1                       |
        |--------------------------/
        |
        ===============================================================*/
        
        TransactionStatus inStackStatus = null;
        while ((inStackStatus = this.tStatusStack.peek()) != defStatus)
            this.commit(inStackStatus);
    }
    /**处理当前底层数据库连接的事务递交操作。*/
    protected abstract void doCommit(Object transaction, AbstractTransactionStatus defStatus) throws SQLException;
    
    /**回滚事务*/
    public final void rollBack(TransactionStatus status) throws TransactionDataAccessException {
        Object transaction = doGetTransaction();获取目前事务对象
        AbstractTransactionStatus defStatus = (AbstractTransactionStatus) status;
        /*已完毕,不需要处理*/
        if (defStatus.isCompleted())
            throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
        /*-------------------------------------------------------------
        | 1.无论何种传播形式,递交事务操作都会将 isCompleted 属性置为 true。
        | 2.如果事务状态中包含一个未处理的保存点。仅回滚保存点,而非回滚整个事务。
        | 3.事务 isNew 只有为 true 时才真正触发回滚事务操作。
        ===============================================================*/
        try {
            prepareRollback(defStatus);
            /*如果包含保存点,在递交事务时只处理保存点*/
            if (defStatus.hasSavepoint())
                defStatus.rollbackToHeldSavepoint();
            else if (defStatus.isNewConnection())
                doRollback(transaction, defStatus);
            
        } catch (SQLException ex) {
            throw new TransactionDataAccessException("SQL Exception :", ex);
        } finally {
            cleanupAfterCompletion(defStatus);
        }
    }
    /**回滚前的预处理*/
    private void prepareRollback(AbstractTransactionStatus defStatus) {
        /*首先预处理的事务必须存在于管理器的事务栈内某一位置中,否则要处理的事务并非来源于该事务管理器。*/
        if (this.tStatusStack.contains(defStatus) == false)
            throw new IllegalTransactionStateException("This transaction is not derived from this Manager.");
        /*-------------------------------------------------------------
        | 如果预处理的事务并非位于栈顶,则进行弹栈操作。
        |--------------------------\
        | T5  ^   <-- pop-up       | 假定预处理的事务为 T4,那么:
        | T4  ^   <-- pop-up       | T5 事务会被先回滚,然后是 T4
        | T3  .   <-- defStatus    | 接下来就完成了预处理。
        | T2                       |
        | T1                       |
        |--------------------------/
        |
        ===============================================================*/
        
        TransactionStatus inStackStatus = null;
        while ((inStackStatus = this.tStatusStack.peek()) != defStatus)
            this.rollBack(inStackStatus);
    }
    /**处理当前底层数据库连接的事务回滚操作。*/
    protected abstract void doRollback(Object transaction, AbstractTransactionStatus defStatus) throws SQLException;
    
    private static class SuspendedTransactionHolder {
        public Object transaction = null; /*挂起的底层事务对象*/
    }
    /**挂起当前事务*/
    protected final void suspend(Object transaction, AbstractTransactionStatus defStatus) {
        try {
            /*检查事务是否为栈顶事务*/
            prepareCheckStack(defStatus);
            /*创建 SuspendedTransactionHolder 对象,用于保存当前底层数据库连接以及事务对象*/
            doSuspend(transaction, defStatus);
            SuspendedTransactionHolder suspendedHolder = new SuspendedTransactionHolder();
            suspendedHolder.transaction = transaction;/*挂起的事务对象(来自于底层)*/
            defStatus.setSuspendHolder(suspendedHolder);
            
        } catch (SQLException ex) {
            throw new TransactionDataAccessException("SQL Exception :", ex);
        }
    }
    /**挂起事务,子类需要重写该方法挂起transaction事务,并同时清空底层当前数据库连接,*/
    protected void doSuspend(Object transaction, AbstractTransactionStatus defStatus) throws SQLException {
        throw new TransactionSuspensionNotSupportedException("Transaction manager [" + getClass().getName() + "] does not support transaction suspension");
    }
    /**恢复被挂起的事务,恢复挂起的事务时必须是当前事务请妥善处理当前事务之后在恢复挂起的事务*/
    protected final void resume(Object transaction, AbstractTransactionStatus defStatus) {
        if (defStatus.isCompleted() == false)
            throw new IllegalTransactionStateException("the Transaction has not completed.");
        try {
            /*检查事务是否为栈顶事务*/
            prepareCheckStack(defStatus);
            SuspendedTransactionHolder suspendedHolder = (SuspendedTransactionHolder) defStatus.getSuspendedTransactionHolder();
            doResume(suspendedHolder.transaction, defStatus);
        } catch (SQLException ex) {
            throw new TransactionDataAccessException("SQL Exception :", ex);
        }
    }
    /**恢复事务,恢复原本挂起的事务(第一个参数),并使用挂起的状态恢复当前数据库连接。*/
    protected void doResume(Object resumeTransaction, AbstractTransactionStatus defStatus) throws SQLException {
        throw new TransactionSuspensionNotSupportedException("Transaction manager [" + getClass().getName() + "] does not support transaction suspension");
    }
    
    /**检查正在处理的事务状态是否位于栈顶,否则抛出异常*/
    private void prepareCheckStack(AbstractTransactionStatus defStatus) {
        if (!this.isTopTransaction(defStatus))
            throw new IllegalTransactionStateException("the Transaction Status is not top in stack.");
    }
    /**commit,rollback。之后的清理工作,同时也负责恢复事务和操作事务堆栈。*/
    private void cleanupAfterCompletion(AbstractTransactionStatus defStatus) {
        /*清理的事务必须是位于栈顶*/
        prepareCheckStack(defStatus);
        /*标记完成*/
        defStatus.setCompleted();
        /*恢复挂起的事务*/
        if (defStatus.getSuspendedTransactionHolder() != null) {
            if (Hasor.isDebugLogger())
                Hasor.logDebug("Resuming suspended transaction after completion of inner transaction");
            resume(defStatus.getSuspendedTransactionHolder(), defStatus);
        }
    }
    /**获取当前事务管理器中存在的事务对象。*/
    protected abstract Object doGetTransaction();
}

在最后连接一下 @黄勇 的Blog,他这里有一篇文章详细介绍了 Spring 事务传播属性:http://my.oschina.net/huangyong/blog/160012

© 著作权归作者所有

共有 人打赏支持
哈库纳

哈库纳

粉丝 965
博文 84
码字总数 151810
作品 4
杭州
后端工程师
私信 提问
加载中

评论(9)

哈库纳
哈库纳

引用来自“山哥”的评论

引用来自“哈库纳”的评论

引用来自“山哥”的评论

我对嵌套事务的处理方案是: 计数。每嵌套开始一个事务计数+1,从里往外提交关闭事务计数–1,当计数为0时,真正提交事务或回滚。我已经在项目中使用了。

计数是一种办法,Spring 提供的嵌套事务里还有一种叫独立事物的东西。光用计数就不行了,还需要做一个状态对象去暂存这这个事务,然后在开启一个新事务接着从0开始计数。

我觉得事务不需要太复杂,嵌套事务和多数据源事务已经够用了。

嗯,这个意见好。 那就先不去考虑 JPA了,不然太复杂了。 本身定位是轻量化,结果还要去支持重量级的东西。有点说不过去。
山哥
山哥

引用来自“哈库纳”的评论

引用来自“山哥”的评论

我对嵌套事务的处理方案是: 计数。每嵌套开始一个事务计数+1,从里往外提交关闭事务计数–1,当计数为0时,真正提交事务或回滚。我已经在项目中使用了。

计数是一种办法,Spring 提供的嵌套事务里还有一种叫独立事物的东西。光用计数就不行了,还需要做一个状态对象去暂存这这个事务,然后在开启一个新事务接着从0开始计数。

我觉得事务不需要太复杂,嵌套事务和多数据源事务已经够用了。
哈库纳
哈库纳

引用来自“山哥”的评论

我对嵌套事务的处理方案是: 计数。每嵌套开始一个事务计数+1,从里往外提交关闭事务计数–1,当计数为0时,真正提交事务或回滚。我已经在项目中使用了。

计数是一种办法,Spring 提供的嵌套事务里还有一种叫独立事物的东西。光用计数就不行了,还需要做一个状态对象去暂存这这个事务,然后在开启一个新事务接着从0开始计数。
山哥
山哥
我对嵌套事务的处理方案是: 计数。每嵌套开始一个事务计数+1,从里往外提交关闭事务计数–1,当计数为0时,真正提交事务或回滚。我已经在项目中使用了。
哈库纳
哈库纳

引用来自“lxbzmy”的评论

事务控制还需要混合jpa,jdbc,jta。

jta 这块不是很了解,抽个空学习一下,看一看能否支持它。
lxbzmy
lxbzmy
事务控制还需要混合jpa,jdbc,jta。
哈库纳
哈库纳

引用来自“黄勇”的评论

好文一篇!作者凭借深厚的技术背景,不遗余力地帮助我们提升知识与技能,值得尊敬,值得崇拜!

哈哈,严重了严重了。只是闲得没事翻了几天 Spring 源码。
还得感谢 勇哥 那篇博文帮着介绍 spring 传播属性。所以偷偷链了一个,自己也省点力气写了 哈哈。
黄勇
黄勇
好文一篇!作者凭借深厚的技术背景,不遗余力地帮助我们提升知识与技能,值得尊敬,值得崇拜!
哈库纳
哈库纳
由于 Spring 在事务控制中预留了 JTA 方面的支持。由于我不太熟悉 JTA,在设计这套接口时。还是有一点犹豫要不要追随 Spring JDBC 的脚步。 总之挺纠结的啊。。。。
Hasor v0.0.6 发布,轻量级 Java 开发框架又添新成员

Hasor是一款开源的 Java 应用开发框架。它是围绕 Guice 为核心创建的一系列模块组合而成。使用Hasor 会加快软件软件开发效率,并降低开发成本。Hasor 的本质与Struts , Hibernate等单层框架不...

哈库纳
2013/12/21
1K
6
Hasor-0.0.8,包含(Core、Web、DB、Test) 发布

项目首页:http://www.oschina.net/p/hasor 项目地址:http://git.oschina.net/zycgit/hasor/tree/Release.Hasor-0.0.8/ 概要:Hasor-0.0.8 版本中,包含了下列部分: Hasor-Core v0.0.8 Ha......

哈库纳
2014/07/26
1K
11
Hasor-Core-0.0.7、Web-0.0.5、JDBC-0.0.2 发布

Hasor 是一款开源的 Java 应用开发框架。它是以 Guice 为核心,围绕它创建的一系列模块组合而成。使用 Hasor 会加速软件开发,并提升发效率降低开发成本。目前 Hasor 由 Hasor-Core 、 Haso...

哈库纳
2014/01/18
1K
7
Hasor 插件集及其使用 Demo

这篇是承接《轻量级 Java 开发框架 设计》系列Blog文的后续文章,本文将介绍 Hasor 内部主要插件并附带插件的功能描述和简单使用 Demo。 Hasor 使用的是“微内核 + 插件扩展”这种设计思想。...

哈库纳
2014/01/09
0
4
Hasor 2.5.1 发布,支持 JFinal 整合

Hasor 2.5.1 正式发布了。Hasor 是一款基于 Java 语言的应用程序开发框架,它的核心设计目标是提供一个简单、且必要的环境给开发者。开发者可以在此基础上,通过 Hasor 强有力的粘合机制,构...

哈库纳
2016/11/08
1K
3

没有更多内容

加载失败,请刷新页面

加载更多

【kafka】kafka集群搭建

简介 要配置kafka,首先要配置zookeeper保证集群的高可用。因此本教程包含两者的配置。 1、下载kafka:https://www.apache.org/dyn/closer.cgi?path=/kafka/2.1.0/kafka_2.11-2.1.0.tgz 2、下...

Areya
17分钟前
1
0
iOS图片拉伸之神属性:resizableImageWithCapInsets

在仿写QQ会话窗口的时候,气泡的背景图片拉伸问题。并不是所有地方都可以拉伸的,所以定义了下面的工具类中的一个方法,专门拉伸图片。 UIImageResizingModeStrech:拉伸模式,通过拉伸UIEdg...

壹峰
23分钟前
0
0
Syncfusion教程:在Xamarin.Forms中创建数据输入表单 (2)

下载Essential Studio for Xamarin最新版本 Essential Studio for Xamarin是全面的Xamarin.iOS、Xamarin.Android和Xamarin.Forms组件套包,包含最快的图表和网格。 如何将Syncfusion输入控件...

电池盒
36分钟前
0
0
Infor宣布首次公开募股

为特定行业提供商业云软件的全球领先提供商Infor今天宣布达成协议,获得科氏控股发展有限公司(KED , Koch Equity Development, LLC)以及金门资本(Golden Gate Capital)15亿美元投资。 继201...

linux-tao
47分钟前
1
0
Mysql索引总结

由于Mysql的索引是B-tree或B+tree,所以下面会先涉及到一些树的概念。 1、二叉树特性:左子树的键值小于根的键值,右子树的键值大于根的键值。 2、平衡二叉树是为了减小树的深度从而减小查找...

浮躁的码农
48分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部